diff --git a/pylock.toml b/pylock.toml index bb25e5c3..8f72465c 100644 --- a/pylock.toml +++ b/pylock.toml @@ -3,8 +3,7 @@ lock-version = "1.0" requires-python = "<4.0,>=3.10.0" environments = [ - "python_version ~= \"3.12\"", - "python_full_version >= \"3.10.0\" and python_version < \"3.12\"", + "python_full_version >= \"3.10.0\" and python_version < \"4.0\" and os_name == \"posix\" and platform_machine == \"x86_64\" and platform_system == \"Linux\" and sys_platform == \"linux\"", ] extras = ["all", "audio", "dev", "openai", "perf", "recommended", "vision"] dependency-groups = ["default"] @@ -16,37 +15,13 @@ name = "torch" version = "2.9.0+cpu" requires-python = ">=3.10" wheels = [ - {name = "torch-2.9.0+cpu-cp314-cp314-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl",hashes = {sha256 = "44aadb735774d4a99525d2ec29126b23016c44a07b02ce6c237dfa61a223dd52"}}, {name = "torch-2.9.0+cpu-cp314-cp314-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl",hashes = {sha256 = "b355e07b7f0c369cb031adfcbff5c37a609abcea091b918a39886412afd2e07d"}}, - {name = "torch-2.9.0+cpu-cp314-cp314-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp314-cp314-win_amd64.whl",hashes = {sha256 = "c2698999361d73c2d25d7cc8a787130188d49b183abb18b554228daa102e1594"}}, - {name = "torch-2.9.0+cpu-cp314-cp314t-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "fa0d1373d04b30ff8f12d542135d292f1a1ddb7c0d852a3d487a320360e5dab9"}}, {name = "torch-2.9.0+cpu-cp314-cp314t-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "2f49bb57a5fe0dc7f8e73ea9e5d36ebda2ea25b8a714a788f0fc2fc47d20a830"}}, - {name = "torch-2.9.0+cpu-cp314-cp314t-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "3a60d1ecf27a9cce839b3aa665b26f0af1b1007b9c9f1e7f597f6b7bdf107617"}}, - {name = "torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "d8e2ab7f86010330bdcc39c8b2c795590cc75e37df4823cdaee2c98d6e3ff4a3"}}, - {name = "torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "a3e859039c985d8e3ea60d7a54ca7e97ea2ae15e31beced4f3260128a161bb01"}}, - {name = "torch-2.9.0+cpu-cp313-cp313-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl",hashes = {sha256 = "be4438d8dad7f0d5a5e54f0feef8a893446894ec87f102bb1d82dcc4518542e4"}}, {name = "torch-2.9.0+cpu-cp313-cp313-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl",hashes = {sha256 = "6c9b217584400963d5b4daddb3711ec7a3778eab211e18654fba076cce3b8682"}}, - {name = "torch-2.9.0+cpu-cp313-cp313-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-win_amd64.whl",hashes = {sha256 = "728372e3f58c5826445f677746e5311c1935c1a7c59599f73a49ded850e038e8"}}, - {name = "torch-2.9.0+cpu-cp313-cp313-win_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313-win_arm64.whl",hashes = {sha256 = "95e56c26f919fbb98f16e7a0b87af494b893f9da9a65a020f17a01c13e520a81"}}, - {name = "torch-2.9.0+cpu-cp313-cp313t-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "6c777160288b08555820781ae0f3a2c67a59bd24b065e88ca1ec20e2f9dc8ac7"}}, {name = "torch-2.9.0+cpu-cp313-cp313t-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "528fd338311f31c9fb18038cafd00e6eae0bf5ad5577521701acb62510753d18"}}, - {name = "torch-2.9.0+cpu-cp313-cp313t-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "d572863990e7d2762b547735ef589f6350d9eb4e441d38753a1c33636698cf4c"}}, - {name = "torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "259548471194ab63d7ea273873053a6e3cc23530c1510f01e9d7ad259187bbd0"}}, - {name = "torch-2.9.0-cp313-none-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl",hashes = {sha256 = "e24836d968b54ef4dfb05594001a61958711ac9224026291e4e3f92f83a6fd7f"}}, - {name = "torch-2.9.0+cpu-cp312-cp312-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl",hashes = {sha256 = "3a651434ae1248b0568c12b5f9e3acc8942eb28378d9d04a79302938b68c6f24"}}, {name = "torch-2.9.0+cpu-cp312-cp312-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl",hashes = {sha256 = "28f6eb31b08180a5c5e98d5bc14eef6909c9f5a1dbff9632c3e02a8773449349"}}, - {name = "torch-2.9.0+cpu-cp312-cp312-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-win_amd64.whl",hashes = {sha256 = "e438061b87ec7dd6018fca9f975219889aa0a3f6cdc3ea10dd0ae2bc7f1c47ce"}}, - {name = "torch-2.9.0+cpu-cp312-cp312-win_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp312-cp312-win_arm64.whl",hashes = {sha256 = "eb13ff1c34e338d722e76a4fd83b8d282782505bd1b99af4b3c32da66eba6eb4"}}, - {name = "torch-2.9.0-cp312-none-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl",hashes = {sha256 = "4de0ed8cbc457a506dbca40376e206a29efee10756a00f1f3404bf67ad737d04"}}, - {name = "torch-2.9.0+cpu-cp311-cp311-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl",hashes = {sha256 = "da77341ccaba31762d9238b0942c165c4582a26818f3045b052b39cebdd7ad9d"}}, {name = "torch-2.9.0+cpu-cp311-cp311-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl",hashes = {sha256 = "add3e93ecc1eeaa6853f6a973ce60ffb3cb14ed2e80f5055e139b09385dce0a7"}}, - {name = "torch-2.9.0+cpu-cp311-cp311-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp311-cp311-win_amd64.whl",hashes = {sha256 = "389e1e0b8083fd355f7caf5ba82356b5e01c318998bd575dbf2285a0d8137089"}}, - {name = "torch-2.9.0+cpu-cp311-cp311-win_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp311-cp311-win_arm64.whl",hashes = {sha256 = "5ce3d01aef91dc078fbb121814e556d55bc886d303efaf42c4fe67e411f5f9ad"}}, - {name = "torch-2.9.0-cp311-none-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp311-none-macosx_11_0_arm64.whl",hashes = {sha256 = "aa4483602586cc9a35d1cf33771a9977f05f642b9161518a289e36548a0b77c2"}}, - {name = "torch-2.9.0+cpu-cp310-cp310-manylinux_2_28_aarch64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl",hashes = {sha256 = "b224792ea567b52c7f1ce1d789567f6920e06fd3b339fa1e1b05948845f783ad"}}, {name = "torch-2.9.0+cpu-cp310-cp310-manylinux_2_28_x86_64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl",hashes = {sha256 = "bd2a257e670ede9fc01c6d76dccdc473040913b8e9328169bf177dbdc38e2484"}}, - {name = "torch-2.9.0+cpu-cp310-cp310-win_amd64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0%2Bcpu-cp310-cp310-win_amd64.whl",hashes = {sha256 = "96f3f7aa4eb9e7fc5af8a722eaf1e5e32e3039dbafe817178d7b90a8566be32d"}}, - {name = "torch-2.9.0-cp310-none-macosx_11_0_arm64.whl",url = "https://download.pytorch.org/whl/cpu/torch-2.9.0-cp310-none-macosx_11_0_arm64.whl",hashes = {sha256 = "59484193b01299bf669520505a72b29d59a0028ae4c6d95f492938f186592208"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras" @@ -66,18 +41,10 @@ name = "torchcodec" version = "0.8.0" requires-python = ">=3.8" wheels = [ - {name = "torchcodec-0.8.0-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/9b/1c/40fd9358e5dd958775b8d0a01c962a022884810f441ac28229ed0e811599/torchcodec-0.8.0-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "1f3309252d035c888e6ae4518f5aca24f1c38f163124792d8a29a6872bf457f2"}}, {name = "torchcodec-0.8.0-cp313-cp313-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/27/81/2e8f8657aed983f20f9ce842b19016d4aff05dd608ac0def94e013602814/torchcodec-0.8.0-cp313-cp313-manylinux_2_28_x86_64.whl",hashes = {sha256 = "253cc3c7a17c7be26abfcf2470e8eab3803ff3108f70be060a7efdcb49d917bc"}}, - {name = "torchcodec-0.8.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/09/1f/b09f028822991241eb1a31931749d034aee2c654d00f1930f4cecce595bc/torchcodec-0.8.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "c69285cb393c3b36c7bcc4e59e304076ea22b350ff6adca4a2a09b5f3f81f15c"}}, - {name = "torchcodec-0.8.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/17/ae/8b1d69e653894243fa66e2fec511cf203107dd146d161c9f095893c13bbc/torchcodec-0.8.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "af82d1fac3667335e089dc958b5e8eef5458e37d65cb3a94ebf81f45f00f7805"}}, {name = "torchcodec-0.8.0-cp312-cp312-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f6/fd/eec92c82545038a90ffd24e3626bb3a85f7d51577b04819c1c753d380a9b/torchcodec-0.8.0-cp312-cp312-manylinux_2_28_x86_64.whl",hashes = {sha256 = "2ec2e874dfb6fbf9bbeb792bea56317529636e78db175f56aad1e4efd6e12502"}}, - {name = "torchcodec-0.8.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/fe/09/ce7436151a3825f27c00263d722b0cf093609921da6cf24b0fa8133cc415/torchcodec-0.8.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "318da9af9179d156be0a84296e909d51e4cd758598eaaea08c828790c80bf977"}}, - {name = "torchcodec-0.8.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4d/27/33cec4b4cf23832244989553b729c761d1a0f09294ff6beb30424527e07a/torchcodec-0.8.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "36e261367eee07db787a191dd0cd73a08df426c49730466b497cb12390e5d514"}}, {name = "torchcodec-0.8.0-cp311-cp311-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b5/0b/19a8ae47b5b89b815e104941e9becef197328fab51caec8591eee69f9bd4/torchcodec-0.8.0-cp311-cp311-manylinux_2_28_x86_64.whl",hashes = {sha256 = "fed2e085cd12d6d87c05d3a24085ddacb8b786d3005b7dff35c29683c8bda21d"}}, - {name = "torchcodec-0.8.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/1f/a7/304deb5c8004eb80a68929cb919246912e2fb52349444f6182aa3e498478/torchcodec-0.8.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "aeea99b2518d3ac1fbafcb84eb22d202faeac8b61581d14d3fdd14357ac4f560"}}, - {name = "torchcodec-0.8.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/73/2c/136b9fa9e5c5f2c48a3b0872cbd4b751a8a4f25265d9f549af9708ad603a/torchcodec-0.8.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "952dbb809dc5c9520e1674cd4815b7e6838006737bd159b90c012f36e91ca3df"}}, {name = "torchcodec-0.8.0-cp310-cp310-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/e5/7a/04796762482d1efbbe1f9bb1b31de2c974059adb3d18cec565450f306db2/torchcodec-0.8.0-cp310-cp310-manylinux_2_28_x86_64.whl",hashes = {sha256 = "5899f1b54bb6a4a50178a354575e27d94e8f6074da6e1ebb1fed2008ad9e781c"}}, - {name = "torchcodec-0.8.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/0b/df/0a588fb2f2a8ffe1b77bae941fe96686b54739e1f2d00c5310a6e6ede0eb/torchcodec-0.8.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "c77a9f5ac9e658e9a731ea48ae05ab7e768a77d1bb0ab7d04871b7290e8b1ace"}}, ] marker = "\"all\" in extras or \"audio\" in extras or \"dev\" in extras" @@ -108,55 +75,13 @@ version = "0.12.0" requires-python = ">=3.9" sdist = {name = "tiktoken-0.12.0.tar.gz", url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hashes = {sha256 = "b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}} wheels = [ - {name = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}}, - {name = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}}, - {name = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl",hashes = {sha256 = "285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}}, {name = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl",hashes = {sha256 = "d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}}, - {name = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}}, - {name = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}}, - {name = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}}, - {name = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}}, - {name = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}}, - {name = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}}, {name = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}}, - {name = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}}, - {name = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}}, - {name = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}}, - {name = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}}, - {name = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}}, - {name = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl",hashes = {sha256 = "01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}}, {name = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl",hashes = {sha256 = "4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}}, - {name = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}}, - {name = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}}, - {name = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}}, - {name = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}}, - {name = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}}, - {name = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}}, {name = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}}, - {name = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}}, - {name = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}}, - {name = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}}, - {name = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}}, - {name = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}}, - {name = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl",hashes = {sha256 = "65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}}, {name = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl",hashes = {sha256 = "edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}}, - {name = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}}, - {name = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}}, - {name = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}}, - {name = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl",hashes = {sha256 = "6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}}, - {name = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}}, - {name = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl",hashes = {sha256 = "f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}}, {name = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl",hashes = {sha256 = "47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}}, - {name = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}}, - {name = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}}, - {name = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}}, - {name = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/89/b3/2cb7c17b6c4cf8ca983204255d3f1d95eda7213e247e6947a0ee2c747a2c/tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl",hashes = {sha256 = "3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}}, - {name = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/27/0f/df139f1df5f6167194ee5ab24634582ba9a1b62c6b996472b0277ec80f66/tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}}, - {name = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ef/5d/26a691f28ab220d5edc09b9b787399b130f24327ef824de15e5d85ef21aa/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl",hashes = {sha256 = "cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}}, {name = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/94/443fab3d4e5ebecac895712abd3849b8da93b7b7dec61c7db5c9c7ebe40c/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl",hashes = {sha256 = "6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}}, - {name = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/54/35/388f941251b2521c70dd4c5958e598ea6d2c88e28445d2fb8189eecc1dfc/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}}, - {name = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f8/00/c6681c7f833dd410576183715a530437a9873fa910265817081f65f9105f/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}}, - {name = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/5f/d2/82e795a6a9bafa034bf26a58e68fe9a89eeaaa610d51dbeb22106ba04f0a/tiktoken-0.12.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}}, ] marker = "\"all\" in extras or \"dev\" in extras or \"openai\" in extras or \"recommended\" in extras" @@ -212,67 +137,18 @@ dependencies = [ [[packages]] name = "numpy" -version = "2.3.4" -requires-python = ">=3.11" -sdist = {name = "numpy-2.3.4.tar.gz", url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hashes = {sha256 = "a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}} -wheels = [ - {name = "numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl",hashes = {sha256 = "81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e"}}, - {name = "numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff"}}, - {name = "numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl",hashes = {sha256 = "62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f"}}, - {name = "numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl",hashes = {sha256 = "9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b"}}, - {name = "numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7"}}, - {name = "numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2"}}, - {name = "numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52"}}, - {name = "numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26"}}, - {name = "numpy-2.3.4-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl",hashes = {sha256 = "e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc"}}, - {name = "numpy-2.3.4-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl",hashes = {sha256 = "3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9"}}, - {name = "numpy-2.3.4-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl",hashes = {sha256 = "6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868"}}, - {name = "numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl",hashes = {sha256 = "22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec"}}, - {name = "numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3"}}, - {name = "numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl",hashes = {sha256 = "0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365"}}, - {name = "numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl",hashes = {sha256 = "8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252"}}, - {name = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e"}}, - {name = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0"}}, - {name = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0"}}, - {name = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f"}}, - {name = "numpy-2.3.4-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl",hashes = {sha256 = "863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d"}}, - {name = "numpy-2.3.4-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6"}}, - {name = "numpy-2.3.4-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29"}}, - {name = "numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966"}}, - {name = "numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3"}}, - {name = "numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl",hashes = {sha256 = "3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197"}}, - {name = "numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl",hashes = {sha256 = "043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e"}}, - {name = "numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7"}}, - {name = "numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953"}}, - {name = "numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37"}}, - {name = "numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd"}}, - {name = "numpy-2.3.4-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl",hashes = {sha256 = "e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646"}}, - {name = "numpy-2.3.4-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl",hashes = {sha256 = "56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d"}}, - {name = "numpy-2.3.4-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl",hashes = {sha256 = "a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc"}}, - {name = "numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879"}}, - {name = "numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562"}}, - {name = "numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl",hashes = {sha256 = "d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a"}}, - {name = "numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl",hashes = {sha256 = "84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6"}}, - {name = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7"}}, - {name = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0"}}, - {name = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f"}}, - {name = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64"}}, - {name = "numpy-2.3.4-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl",hashes = {sha256 = "fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb"}}, - {name = "numpy-2.3.4-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c"}}, - {name = "numpy-2.3.4-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40"}}, - {name = "numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11"}}, - {name = "numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9"}}, - {name = "numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl",hashes = {sha256 = "f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667"}}, - {name = "numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl",hashes = {sha256 = "8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef"}}, - {name = "numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e"}}, - {name = "numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a"}}, - {name = "numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16"}}, - {name = "numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786"}}, - {name = "numpy-2.3.4-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl",hashes = {sha256 = "a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc"}}, - {name = "numpy-2.3.4-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl",hashes = {sha256 = "985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32"}}, - {name = "numpy-2.3.4-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl",hashes = {sha256 = "4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db"}}, -] -marker = "python_version ~= \"3.12\"" +version = "2.2.6" +requires-python = ">=3.10" +sdist = {name = "numpy-2.2.6.tar.gz", url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hashes = {sha256 = "e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}} +wheels = [ + {name = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}}, + {name = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}}, + {name = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}}, + {name = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}}, + {name = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}}, + {name = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}}, +] +marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" [packages.tool.pdm] dependencies = [] @@ -283,62 +159,12 @@ version = "6.0.3" requires-python = ">=3.8" sdist = {name = "pyyaml-6.0.3.tar.gz", url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hashes = {sha256 = "d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}} wheels = [ - {name = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}}, - {name = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}}, - {name = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}}, - {name = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}}, {name = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}}, - {name = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}}, - {name = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}}, - {name = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl",hashes = {sha256 = "4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}}, - {name = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl",hashes = {sha256 = "93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}}, {name = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}}, - {name = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}}, - {name = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}}, - {name = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}}, - {name = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}}, - {name = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}}, {name = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}}, - {name = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}}, - {name = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}}, - {name = "pyyaml-6.0.3-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl",hashes = {sha256 = "d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}}, - {name = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl",hashes = {sha256 = "79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}}, - {name = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl",hashes = {sha256 = "5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}}, - {name = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}}, - {name = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}}, - {name = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}}, - {name = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}}, {name = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}}, - {name = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}}, - {name = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}}, - {name = "pyyaml-6.0.3-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl",hashes = {sha256 = "96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}}, - {name = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl",hashes = {sha256 = "5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}}, - {name = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl",hashes = {sha256 = "64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}}, - {name = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl",hashes = {sha256 = "44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}}, - {name = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}}, - {name = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}}, - {name = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}}, {name = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}}, - {name = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}}, - {name = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}}, - {name = "pyyaml-6.0.3-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl",hashes = {sha256 = "8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}}, - {name = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl",hashes = {sha256 = "9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}}, - {name = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl",hashes = {sha256 = "214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}}, - {name = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}}, - {name = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}}, - {name = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}}, {name = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}}, - {name = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}}, - {name = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}}, - {name = "pyyaml-6.0.3-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl",hashes = {sha256 = "28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}}, - {name = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl",hashes = {sha256 = "bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -443,18 +269,18 @@ dependencies = [ [[packages]] name = "mdformat-footnote" -version = "0.1.2" -requires-python = ">=3.10" -sdist = {name = "mdformat_footnote-0.1.2.tar.gz", url = "https://files.pythonhosted.org/packages/27/25/08e1feac080165330278f31f605f28a963ef51864aee8306dd2039b55a61/mdformat_footnote-0.1.2.tar.gz", hashes = {sha256 = "d8e6a7fece0f902f0c7dfbd009c093182b9b523527ae48f57f57506d79ccb4ec"}} +version = "0.1.1" +requires-python = ">=3.7" +sdist = {name = "mdformat_footnote-0.1.1.tar.gz", url = "https://files.pythonhosted.org/packages/f6/90/7f4b2729af8d691a57518e8202e90c3a638714437b5e753662982f744cb5/mdformat_footnote-0.1.1.tar.gz", hashes = {sha256 = "3b85c4c84119f15f0b651df89c99a4f6f119fc46dca6b33f7edf4f09655d1126"}} wheels = [ - {name = "mdformat_footnote-0.1.2-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/99/47/07544df46d55284cc1862acd49dc30f984e4e5cb1630becbf216692f30b6/mdformat_footnote-0.1.2-py3-none-any.whl",hashes = {sha256 = "fe23888fc8ddb68c080bae9787a65bb1e51253f5de2bea2633feffdb095e59cd"}}, + {name = "mdformat_footnote-0.1.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/93/aa/4736dc47867c60236a9992f2f779e85e5331e65e648434d6362c3ed2dae0/mdformat_footnote-0.1.1-py3-none-any.whl",hashes = {sha256 = "30063aaa0f74c36257c2e80fa0cf00d7c71a5277f27e98109e8765ae8678a95b"}}, ] marker = "\"dev\" in extras" [packages.tool.pdm] dependencies = [ - "mdformat>=0.7.0", - "mdit-py-plugins>=0.4.0", + "mdformat<0.8.0,>=0.7.8", + "mdit-py-plugins", ] [[packages]] @@ -513,31 +339,11 @@ version = "1.15.0" requires-python = ">=3.9" sdist = {name = "mypy-1.15.0.tar.gz", url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hashes = {sha256 = "404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}} wheels = [ - {name = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}}, - {name = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}}, - {name = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}}, {name = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}}, - {name = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}}, - {name = "mypy-1.15.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}}, - {name = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}}, - {name = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}}, - {name = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}}, {name = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}}, - {name = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}}, - {name = "mypy-1.15.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}}, - {name = "mypy-1.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl",hashes = {sha256 = "5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}}, - {name = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}}, - {name = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}}, - {name = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}}, {name = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}}, - {name = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}}, - {name = "mypy-1.15.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}}, - {name = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}}, - {name = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}}, - {name = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}}, {name = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}}, - {name = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}}, - {name = "mypy-1.15.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}}, + {name = "mypy-1.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl",hashes = {sha256 = "5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}}, ] marker = "\"dev\" in extras" @@ -569,18 +375,18 @@ dependencies = [ [[packages]] name = "pydantic" -version = "2.12.4" +version = "2.12.2" requires-python = ">=3.9" -sdist = {name = "pydantic-2.12.4.tar.gz", url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hashes = {sha256 = "0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}} +sdist = {name = "pydantic-2.12.2.tar.gz", url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", hashes = {sha256 = "7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd"}} wheels = [ - {name = "pydantic-2.12.4-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl",hashes = {sha256 = "92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}}, + {name = "pydantic-2.12.2-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl",hashes = {sha256 = "25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae"}}, ] marker = "\"default\" in dependency_groups" [packages.tool.pdm] dependencies = [ "annotated-types>=0.6.0", - "pydantic-core==2.41.5", + "pydantic-core==2.41.4", "typing-extensions>=4.14.1", "typing-inspection>=0.4.2", ] @@ -722,23 +528,7 @@ version = "0.11.13" requires-python = ">=3.7" sdist = {name = "ruff-0.11.13.tar.gz", url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hashes = {sha256 = "26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514"}} wheels = [ - {name = "ruff-0.11.13-py3-none-linux_armv6l.whl",url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl",hashes = {sha256 = "4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46"}}, - {name = "ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl",hashes = {sha256 = "aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48"}}, - {name = "ruff-0.11.13-py3-none-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl",hashes = {sha256 = "53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b"}}, - {name = "ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a"}}, - {name = "ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc"}}, - {name = "ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629"}}, - {name = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl",url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl",hashes = {sha256 = "d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933"}}, - {name = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165"}}, - {name = "ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71"}}, {name = "ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9"}}, - {name = "ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl",hashes = {sha256 = "4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc"}}, - {name = "ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl",hashes = {sha256 = "d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7"}}, - {name = "ruff-0.11.13-py3-none-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl",hashes = {sha256 = "26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432"}}, - {name = "ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl",hashes = {sha256 = "51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492"}}, - {name = "ruff-0.11.13-py3-none-win32.whl",url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl",hashes = {sha256 = "96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250"}}, - {name = "ruff-0.11.13-py3-none-win_amd64.whl",url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl",hashes = {sha256 = "29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3"}}, - {name = "ruff-0.11.13-py3-none-win_arm64.whl",url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl",hashes = {sha256 = "b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b"}}, ] marker = "\"dev\" in extras" @@ -747,66 +537,21 @@ dependencies = [] [[packages]] name = "scipy" -version = "1.16.3" -requires-python = ">=3.11" -sdist = {name = "scipy-1.16.3.tar.gz", url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hashes = {sha256 = "01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb"}} -wheels = [ - {name = "scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl",url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl",hashes = {sha256 = "875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6"}}, - {name = "scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl",hashes = {sha256 = "bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657"}}, - {name = "scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl",hashes = {sha256 = "f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26"}}, - {name = "scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl",hashes = {sha256 = "7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc"}}, - {name = "scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22"}}, - {name = "scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc"}}, - {name = "scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0"}}, - {name = "scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800"}}, - {name = "scipy-1.16.3-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl",hashes = {sha256 = "e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d"}}, - {name = "scipy-1.16.3-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl",hashes = {sha256 = "6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f"}}, - {name = "scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl",hashes = {sha256 = "e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c"}}, - {name = "scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl",hashes = {sha256 = "9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40"}}, - {name = "scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl",hashes = {sha256 = "3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d"}}, - {name = "scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl",hashes = {sha256 = "f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa"}}, - {name = "scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8"}}, - {name = "scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353"}}, - {name = "scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146"}}, - {name = "scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d"}}, - {name = "scipy-1.16.3-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7"}}, - {name = "scipy-1.16.3-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562"}}, - {name = "scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl",hashes = {sha256 = "d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c"}}, - {name = "scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d"}}, - {name = "scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl",hashes = {sha256 = "5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9"}}, - {name = "scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl",hashes = {sha256 = "b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4"}}, - {name = "scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959"}}, - {name = "scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88"}}, - {name = "scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234"}}, - {name = "scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d"}}, - {name = "scipy-1.16.3-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl",hashes = {sha256 = "062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304"}}, - {name = "scipy-1.16.3-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl",hashes = {sha256 = "50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2"}}, - {name = "scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl",url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl",hashes = {sha256 = "fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b"}}, - {name = "scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl",hashes = {sha256 = "8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079"}}, - {name = "scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl",hashes = {sha256 = "03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a"}}, - {name = "scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl",hashes = {sha256 = "57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119"}}, - {name = "scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c"}}, - {name = "scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e"}}, - {name = "scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135"}}, - {name = "scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6"}}, - {name = "scipy-1.16.3-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc"}}, - {name = "scipy-1.16.3-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a"}}, - {name = "scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl",url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl",hashes = {sha256 = "81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6"}}, - {name = "scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl",hashes = {sha256 = "c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07"}}, - {name = "scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl",hashes = {sha256 = "2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9"}}, - {name = "scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl",hashes = {sha256 = "3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686"}}, - {name = "scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203"}}, - {name = "scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1"}}, - {name = "scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe"}}, - {name = "scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70"}}, - {name = "scipy-1.16.3-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl",hashes = {sha256 = "56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc"}}, - {name = "scipy-1.16.3-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl",hashes = {sha256 = "a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2"}}, -] -marker = "python_version ~= \"3.12\"" +version = "1.15.3" +requires-python = ">=3.10" +sdist = {name = "scipy-1.15.3.tar.gz", url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hashes = {sha256 = "eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf"}} +wheels = [ + {name = "scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7"}}, + {name = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb"}}, + {name = "scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49"}}, + {name = "scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982"}}, + {name = "scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40"}}, +] +marker = "\"dev\" in extras" [packages.tool.pdm] dependencies = [ - "numpy<2.6,>=1.25.2", + "numpy<2.5,>=1.23.5", ] [[packages]] @@ -817,7 +562,7 @@ sdist = {name = "setuptools-80.9.0.tar.gz", url = "https://files.pythonhosted.or wheels = [ {name = "setuptools-80.9.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl",hashes = {sha256 = "062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}}, ] -marker = "\"default\" in dependency_groups or \"dev\" in extras" +marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras" [packages.tool.pdm] dependencies = [] @@ -907,46 +652,14 @@ dependencies = [ [[packages]] name = "uvloop" -version = "0.22.1" -requires-python = ">=3.8.1" -sdist = {name = "uvloop-0.22.1.tar.gz", url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hashes = {sha256 = "6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f"}} -wheels = [ - {name = "uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142"}}, - {name = "uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74"}}, - {name = "uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35"}}, - {name = "uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25"}}, - {name = "uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6"}}, - {name = "uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079"}}, - {name = "uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289"}}, - {name = "uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3"}}, - {name = "uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c"}}, - {name = "uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21"}}, - {name = "uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88"}}, - {name = "uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e"}}, - {name = "uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705"}}, - {name = "uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8"}}, - {name = "uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d"}}, - {name = "uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e"}}, - {name = "uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e"}}, - {name = "uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad"}}, - {name = "uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42"}}, - {name = "uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6"}}, - {name = "uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370"}}, - {name = "uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4"}}, - {name = "uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2"}}, - {name = "uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0"}}, - {name = "uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9"}}, - {name = "uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77"}}, - {name = "uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21"}}, - {name = "uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702"}}, - {name = "uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733"}}, - {name = "uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473"}}, - {name = "uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c"}}, - {name = "uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792"}}, - {name = "uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86"}}, - {name = "uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd"}}, - {name = "uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2"}}, - {name = "uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec"}}, +version = "0.21.0" +requires-python = ">=3.8.0" +sdist = {name = "uvloop-0.21.0.tar.gz", url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hashes = {sha256 = "3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"}} +wheels = [ + {name = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"}}, + {name = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"}}, + {name = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"}}, + {name = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"dev\" in extras or \"perf\" in extras or \"recommended\" in extras" @@ -955,100 +668,28 @@ dependencies = [] [[packages]] name = "pillow" -version = "12.0.0" -requires-python = ">=3.10" -sdist = {name = "pillow-12.0.0.tar.gz", url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hashes = {sha256 = "87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"}} -wheels = [ - {name = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl",url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl",hashes = {sha256 = "beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"}}, - {name = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl",url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl",hashes = {sha256 = "d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"}}, - {name = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl",url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl",hashes = {sha256 = "3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"}}, - {name = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl",hashes = {sha256 = "e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"}}, - {name = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"}}, - {name = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"}}, - {name = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"}}, - {name = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"}}, - {name = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"}}, - {name = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"}}, - {name = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"}}, - {name = "pillow-12.0.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl",hashes = {sha256 = "1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"}}, - {name = "pillow-12.0.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"}}, - {name = "pillow-12.0.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"}}, - {name = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl",hashes = {sha256 = "1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"}}, - {name = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"}}, - {name = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"}}, - {name = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"}}, - {name = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"}}, - {name = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"}}, - {name = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"}}, - {name = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"}}, - {name = "pillow-12.0.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl",hashes = {sha256 = "3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"}}, - {name = "pillow-12.0.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"}}, - {name = "pillow-12.0.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"}}, - {name = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl",url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl",hashes = {sha256 = "0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"}}, - {name = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl",url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl",hashes = {sha256 = "a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"}}, - {name = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl",url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl",hashes = {sha256 = "1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"}}, - {name = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"}}, - {name = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"}}, - {name = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"}}, - {name = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"}}, - {name = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"}}, - {name = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"}}, - {name = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"}}, - {name = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"}}, - {name = "pillow-12.0.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl",hashes = {sha256 = "4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"}}, - {name = "pillow-12.0.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"}}, - {name = "pillow-12.0.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"}}, - {name = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"}}, - {name = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"}}, - {name = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"}}, - {name = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"}}, - {name = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"}}, - {name = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"}}, - {name = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"}}, - {name = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"}}, - {name = "pillow-12.0.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl",hashes = {sha256 = "4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"}}, - {name = "pillow-12.0.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"}}, - {name = "pillow-12.0.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"}}, - {name = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"}}, - {name = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"}}, - {name = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"}}, - {name = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"}}, - {name = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"}}, - {name = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"}}, - {name = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"}}, - {name = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"}}, - {name = "pillow-12.0.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl",hashes = {sha256 = "dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"}}, - {name = "pillow-12.0.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"}}, - {name = "pillow-12.0.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"}}, - {name = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl",url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl",hashes = {sha256 = "0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"}}, - {name = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"}}, - {name = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"}}, - {name = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"}}, - {name = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"}}, - {name = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"}}, - {name = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"}}, - {name = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"}}, - {name = "pillow-12.0.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl",hashes = {sha256 = "27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"}}, - {name = "pillow-12.0.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"}}, - {name = "pillow-12.0.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"}}, - {name = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl",hashes = {sha256 = "b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"}}, - {name = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/08/26e68b6b5da219c2a2cb7b563af008b53bb8e6b6fcb3fa40715fcdb2523a/pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl",hashes = {sha256 = "3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}}, - {name = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/cb/e9/4e58fb097fb74c7b4758a680aacd558810a417d1edaa7000142976ef9d2f/pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}}, - {name = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/4b/e0/1fa492aa9f77b3bc6d471c468e62bfea1823056bf7e5e4f1914d7ab2565e/pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"}}, - {name = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/c1/09/4de7cd03e33734ccd0c876f0251401f1314e819cbfd89a0fcb6e77927cc6/pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"}}, - {name = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/2e/69/0688e7c1390666592876d9d474f5e135abb4acb39dcb583c4dc5490f1aff/pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"}}, - {name = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/ed/1c/880921e98f525b9b44ce747ad1ea8f73fd7e992bafe3ca5e5644bf433dea/pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"}}, - {name = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/28/03/96f718331b19b355610ef4ebdbbde3557c726513030665071fd025745671/pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"}}, - {name = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/3a/a0/6a193b3f0cc9437b122978d2c5cbce59510ccf9a5b48825096ed7472da2f/pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"}}, - {name = "pillow-12.0.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/a7/c4/043192375eaa4463254e8e61f0e2ec9a846b983929a8d0a7122e0a6d6fff/pillow-12.0.0-cp310-cp310-win32.whl",hashes = {sha256 = "bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"}}, - {name = "pillow-12.0.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/92/c6/c2f2fc7e56301c21827e689bb8b0b465f1b52878b57471a070678c0c33cd/pillow-12.0.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"}}, - {name = "pillow-12.0.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"}}, +version = "11.3.0" +requires-python = ">=3.9" +sdist = {name = "pillow-11.3.0.tar.gz", url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hashes = {sha256 = "3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}} +wheels = [ + {name = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}}, + {name = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}}, + {name = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}}, + {name = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}}, + {name = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}}, + {name = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}}, + {name = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}}, + {name = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}}, + {name = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}}, + {name = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}}, + {name = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}}, + {name = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}}, + {name = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}}, + {name = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}}, + {name = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}}, + {name = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}}, + {name = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}}, + {name = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}}, ] marker = "\"all\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -1070,11 +711,11 @@ dependencies = [] [[packages]] name = "faker" -version = "37.12.0" +version = "37.11.0" requires-python = ">=3.9" -sdist = {name = "faker-37.12.0.tar.gz", url = "https://files.pythonhosted.org/packages/3d/84/e95acaa848b855e15c83331d0401ee5f84b2f60889255c2e055cb4fb6bdf/faker-37.12.0.tar.gz", hashes = {sha256 = "7505e59a7e02fa9010f06c3e1e92f8250d4cfbb30632296140c2d6dbef09b0fa"}} +sdist = {name = "faker-37.11.0.tar.gz", url = "https://files.pythonhosted.org/packages/c9/4b/ca43f6bbcef63deb8ac01201af306388670a172587169aab3b192f7490f0/faker-37.11.0.tar.gz", hashes = {sha256 = "22969803849ba0618be8eee2dd01d0d9e2cd3b75e6ff1a291fa9abcdb34da5e6"}} wheels = [ - {name = "faker-37.12.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl",hashes = {sha256 = "afe7ccc038da92f2fbae30d8e16d19d91e92e242f8401ce9caf44de892bab4c4"}}, + {name = "faker-37.11.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/a3/46/8f4097b55e43af39e8e71e1f7aec59ff7398bca54d975c30889bc844719d/faker-37.11.0-py3-none-any.whl",hashes = {sha256 = "1508d2da94dfd1e0087b36f386126d84f8583b3de19ac18e392a2831a6676c57"}}, ] marker = "\"default\" in dependency_groups" @@ -1106,59 +747,12 @@ version = "1.1.2" requires-python = ">=3.9" sdist = {name = "msgpack-1.1.2.tar.gz", url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hashes = {sha256 = "3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e"}} wheels = [ - {name = "msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00"}}, - {name = "msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939"}}, - {name = "msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e"}}, {name = "msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931"}}, - {name = "msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014"}}, - {name = "msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2"}}, - {name = "msgpack-1.1.2-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl",hashes = {sha256 = "80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717"}}, - {name = "msgpack-1.1.2-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl",hashes = {sha256 = "9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b"}}, - {name = "msgpack-1.1.2-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl",hashes = {sha256 = "59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af"}}, - {name = "msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a"}}, - {name = "msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b"}}, - {name = "msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245"}}, {name = "msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90"}}, - {name = "msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20"}}, - {name = "msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27"}}, - {name = "msgpack-1.1.2-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl",hashes = {sha256 = "1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b"}}, - {name = "msgpack-1.1.2-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff"}}, - {name = "msgpack-1.1.2-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46"}}, - {name = "msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf"}}, - {name = "msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7"}}, - {name = "msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999"}}, {name = "msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e"}}, - {name = "msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162"}}, - {name = "msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794"}}, - {name = "msgpack-1.1.2-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl",hashes = {sha256 = "a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c"}}, - {name = "msgpack-1.1.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9"}}, - {name = "msgpack-1.1.2-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl",hashes = {sha256 = "e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84"}}, - {name = "msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa"}}, - {name = "msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb"}}, - {name = "msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f"}}, {name = "msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42"}}, - {name = "msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9"}}, - {name = "msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620"}}, - {name = "msgpack-1.1.2-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl",hashes = {sha256 = "1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029"}}, - {name = "msgpack-1.1.2-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl",hashes = {sha256 = "1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b"}}, - {name = "msgpack-1.1.2-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl",hashes = {sha256 = "be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69"}}, - {name = "msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c"}}, - {name = "msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0"}}, - {name = "msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296"}}, {name = "msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef"}}, - {name = "msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c"}}, - {name = "msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e"}}, - {name = "msgpack-1.1.2-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl",hashes = {sha256 = "602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e"}}, - {name = "msgpack-1.1.2-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl",hashes = {sha256 = "d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68"}}, - {name = "msgpack-1.1.2-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl",hashes = {sha256 = "86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406"}}, - {name = "msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/a2/3b68a9e769db68668b25c6108444a35f9bd163bb848c0650d516761a59c0/msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2"}}, - {name = "msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/5b/e1/2b720cc341325c00be44e1ed59e7cfeae2678329fbf5aa68f5bda57fe728/msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87"}}, - {name = "msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/71/e5/c2241de64bfceac456b140737812a2ab310b10538a7b34a1d393b748e095/msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251"}}, {name = "msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b7/09/2a06956383c0fdebaef5aa9246e2356776f12ea6f2a44bd1368abf0e46c4/msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a"}}, - {name = "msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/0e/74/2957703f0e1ef20637d6aead4fbb314330c26f39aa046b348c7edcf6ca6b/msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f"}}, - {name = "msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/a5/09/3bfc12aa90f77b37322fc33e7a8a7c29ba7c8edeadfa27664451801b9860/msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f"}}, - {name = "msgpack-1.1.2-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/4b/4f/05fcebd3b4977cb3d840f7ef6b77c51f8582086de5e642f3fefee35c86fc/msgpack-1.1.2-cp310-cp310-win32.whl",hashes = {sha256 = "e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9"}}, - {name = "msgpack-1.1.2-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d0/3e/b4547e3a34210956382eed1c85935fff7e0f9b98be3106b3745d7dec9c5e/msgpack-1.1.2-cp310-cp310-win_amd64.whl",hashes = {sha256 = "db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"dev\" in extras or \"perf\" in extras or \"recommended\" in extras" @@ -1171,34 +765,10 @@ version = "0.19.0" requires-python = ">=3.9" sdist = {name = "msgspec-0.19.0.tar.gz", url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hashes = {sha256 = "604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e"}} wheels = [ - {name = "msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86"}}, - {name = "msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314"}}, - {name = "msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e"}}, {name = "msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5"}}, - {name = "msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9"}}, - {name = "msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327"}}, - {name = "msgspec-0.19.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f"}}, - {name = "msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f"}}, - {name = "msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2"}}, - {name = "msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12"}}, {name = "msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc"}}, - {name = "msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c"}}, - {name = "msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537"}}, - {name = "msgspec-0.19.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0"}}, - {name = "msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e"}}, - {name = "msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551"}}, - {name = "msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7"}}, {name = "msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011"}}, - {name = "msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063"}}, - {name = "msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716"}}, - {name = "msgspec-0.19.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c"}}, - {name = "msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/13/40/817282b42f58399762267b30deb8ac011d8db373f8da0c212c85fbe62b8f/msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259"}}, - {name = "msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/92/99/bd7ed738c00f223a8119928661167a89124140792af18af513e6519b0d54/msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36"}}, - {name = "msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/e5/27/322badde18eb234e36d4a14122b89edd4e2973cdbc3da61ca7edf40a1ccd/msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947"}}, {name = "msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/c6/65/080509c5774a1592b2779d902a70b5fe008532759927e011f068145a16cb/msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909"}}, - {name = "msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/6f/2e/1c23c6b4ca6f4285c30a39def1054e2bee281389e4b681b5e3711bd5a8c9/msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a"}}, - {name = "msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/83/fe/95f9654518879f3359d1e76bc41189113aa9102452170ab7c9a9a4ee52f6/msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633"}}, - {name = "msgspec-0.19.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/79/f6/71ca7e87a1fb34dfe5efea8156c9ef59dd55613aeda2ca562f122cd22012/msgspec-0.19.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90"}}, ] marker = "\"all\" in extras or \"dev\" in extras or \"perf\" in extras or \"recommended\" in extras" @@ -1207,102 +777,45 @@ dependencies = [] [[packages]] name = "orjson" -version = "3.11.4" +version = "3.11.3" requires-python = ">=3.9" -sdist = {name = "orjson-3.11.4.tar.gz", url = "https://files.pythonhosted.org/packages/c6/fe/ed708782d6709cc60eb4c2d8a361a440661f74134675c72990f2c48c785f/orjson-3.11.4.tar.gz", hashes = {sha256 = "39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d"}} -wheels = [ - {name = "orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/25/e3/54ff63c093cc1697e758e4fceb53164dd2661a7d1bcd522260ba09f54533/orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "42d43a1f552be1a112af0b21c10a5f553983c2a0938d2bbb8ecd8bc9fb572803"}}, - {name = "orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ac/7d/e2d1076ed2e8e0ae9badca65bf7ef22710f93887b29eaa37f09850604e09/orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl",hashes = {sha256 = "26a20f3fbc6c7ff2cb8e89c4c5897762c9d88cf37330c6a117312365d6781d54"}}, - {name = "orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/9f/37/ca2eb40b90621faddfa9517dfe96e25f5ae4d8057a7c0cdd613c17e07b2c/orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "6e3f20be9048941c7ffa8fc523ccbd17f82e24df1549d1d1fe9317712d19938e"}}, - {name = "orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/c7/62/1021ed35a1f2bad9040f05fa4cc4f9893410df0ba3eaa323ccf899b1c90a/orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "aac364c758dc87a52e68e349924d7e4ded348dedff553889e4d9f22f74785316"}}, - {name = "orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/e8/3f/f84d966ec2a6fd5f73b1a707e7cd876813422ae4bf9f0145c55c9c6a0f57/orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "d5c54a6d76e3d741dcc3f2707f8eeb9ba2a791d3adbf18f900219b62942803b1"}}, - {name = "orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/32/78/4fa0aeca65ee82bbabb49e055bd03fa4edea33f7c080c5c7b9601661ef72/orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "f28485bdca8617b79d44627f5fb04336897041dfd9fa66d383a49d09d86798bc"}}, - {name = "orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/c1/9d/0c102e26e7fde40c4c98470796d050a2ec1953897e2c8ab0cb95b0759fa2/orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "bfc2a484cad3585e4ba61985a6062a4c2ed5c7925db6d39f1fa267c9d166487f"}}, - {name = "orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/df/ac/2de7188705b4cdfaf0b6c97d2f7849c17d2003232f6e70df98602173f788/orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "e34dbd508cb91c54f9c9788923daca129fe5b55c5b4eebe713bf5ed3791280cf"}}, - {name = "orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/e0/52/847fcd1a98407154e944feeb12e3b4d487a0e264c40191fb44d1269cbaa1/orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b13c478fa413d4b4ee606ec8e11c3b2e52683a640b006bb586b3041c2ca5f606"}}, - {name = "orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/c1/ae/21d208f58bdb847dd4d0d9407e2929862561841baa22bdab7aea10ca088e/orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "724ca721ecc8a831b319dcd72cfa370cc380db0bf94537f08f7edd0a7d4e1780"}}, - {name = "orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/8d/55/0789d6de386c8366059db098a628e2ad8798069e94409b0d8935934cbcb9/orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl",hashes = {sha256 = "977c393f2e44845ce1b540e19a786e9643221b3323dae190668a98672d43fb23"}}, - {name = "orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/cc/1d/7ff81ea23310e086c17b41d78a72270d9de04481e6113dbe2ac19118f7fb/orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1e539e382cf46edec157ad66b0b0872a90d829a6b71f17cb633d6c160a223155"}}, - {name = "orjson-3.11.4-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/77/92/25b886252c50ed64be68c937b562b2f2333b45afe72d53d719e46a565a50/orjson-3.11.4-cp314-cp314-win32.whl",hashes = {sha256 = "d63076d625babab9db5e7836118bdfa086e60f37d8a174194ae720161eb12394"}}, - {name = "orjson-3.11.4-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/63/b8/718eecf0bb7e9d64e4956afaafd23db9f04c776d445f59fe94f54bdae8f0/orjson-3.11.4-cp314-cp314-win_amd64.whl",hashes = {sha256 = "0a54d6635fa3aaa438ae32e8570b9f0de36f3f6562c308d2a2a452e8b0592db1"}}, - {name = "orjson-3.11.4-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/1a/bf/def5e25d4d8bfce296a9a7c8248109bf58622c21618b590678f945a2c59c/orjson-3.11.4-cp314-cp314-win_arm64.whl",hashes = {sha256 = "78b999999039db3cf58f6d230f524f04f75f129ba3d1ca2ed121f8657e575d3d"}}, - {name = "orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/23/15/c52aa7112006b0f3d6180386c3a46ae057f932ab3425bc6f6ac50431cca1/orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534"}}, - {name = "orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ec/38/05340734c33b933fd114f161f25a04e651b0c7c33ab95e9416ade5cb44b8/orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl",hashes = {sha256 = "afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff"}}, - {name = "orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/55/b9/ae8d34899ff0c012039b5a7cb96a389b2476e917733294e498586b45472d/orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad"}}, - {name = "orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/33/aa/6346dd5073730451bee3681d901e3c337e7ec17342fb79659ec9794fc023/orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5"}}, - {name = "orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/39/e4/8eea51598f66a6c853c380979912d17ec510e8e66b280d968602e680b942/orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a"}}, - {name = "orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/9a/47/cb8c654fa9adcc60e99580e17c32b9e633290e6239a99efa6b885aba9dbc/orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436"}}, - {name = "orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/43/92/04b8cc5c2b729f3437ee013ce14a60ab3d3001465d95c184758f19362f23/orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9"}}, - {name = "orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/aa/fd/d0733fcb9086b8be4ebcfcda2d0312865d17d0d9884378b7cffb29d0763f/orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73"}}, - {name = "orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c2/d7/3c5514e806837c210492d72ae30ccf050ce3f940f45bf085bab272699ef4/orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0"}}, - {name = "orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/9c/dd/ba9d32a53207babf65bd510ac4d0faaa818bd0df9a9c6f472fe7c254f2e3/orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196"}}, - {name = "orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/8e/f9/f68ad68f4af7c7bde57cd514eaa2c785e500477a8bc8f834838eb696a685/orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a"}}, - {name = "orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b6/d2/7f847761d0c26818395b3d6b21fb6bc2305d94612a35b0a30eae65a22728/orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6"}}, - {name = "orjson-3.11.4-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/9f/37/acd14b12dc62db9a0e1d12386271b8661faae270b22492580d5258808975/orjson-3.11.4-cp313-cp313-win32.whl",hashes = {sha256 = "6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839"}}, - {name = "orjson-3.11.4-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c0/a9/967be009ddf0a1fffd7a67de9c36656b28c763659ef91352acc02cbe364c/orjson-3.11.4-cp313-cp313-win_amd64.whl",hashes = {sha256 = "09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a"}}, - {name = "orjson-3.11.4-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/cb/db/399abd6950fbd94ce125cb8cd1a968def95174792e127b0642781e040ed4/orjson-3.11.4-cp313-cp313-win_arm64.whl",hashes = {sha256 = "a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de"}}, - {name = "orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/63/51/6b556192a04595b93e277a9ff71cd0cc06c21a7df98bcce5963fa0f5e36f/orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50"}}, - {name = "orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1c/2c/2602392ddf2601d538ff11848b98621cd465d1a1ceb9db9e8043181f2f7b/orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl",hashes = {sha256 = "e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853"}}, - {name = "orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/4e/47/bf85dcf95f7a3a12bf223394a4f849430acd82633848d52def09fa3f46ad/orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938"}}, - {name = "orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/b4/4d/a0cb31007f3ab6f1fd2a1b17057c7c349bc2baf8921a85c0180cc7be8011/orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415"}}, - {name = "orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/f7/ef/2811def7ce3d8576b19e3929fff8f8f0d44bc5eb2e0fdecb2e6e6cc6c720/orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44"}}, - {name = "orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/00/d4/9aee9e54f1809cec8ed5abd9bc31e8a9631d19460e3b8470145d25140106/orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2"}}, - {name = "orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/db/ea/67bfdb5465d5679e8ae8d68c11753aaf4f47e3e7264bad66dc2f2249e643/orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708"}}, - {name = "orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/01/7e/62517dddcfce6d53a39543cd74d0dccfcbdf53967017c58af68822100272/orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210"}}, - {name = "orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/18/ae/40516739f99ab4c7ec3aaa5cc242d341fcb03a45d89edeeaabc5f69cb2cf/orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241"}}, - {name = "orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/82/18/ff5734365623a8916e3a4037fcef1cd1782bfc14cf0992afe7940c5320bf/orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b"}}, - {name = "orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/e1/43/96436041f0a0c8c8deca6a05ebeaf529bf1de04839f93ac5e7c479807aec/orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c"}}, - {name = "orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/1b/48/78302d98423ed8780479a1e682b9aecb869e8404545d999d34fa486e573e/orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9"}}, - {name = "orjson-3.11.4-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/4a/7b/ad613fdcdaa812f075ec0875143c3d37f8654457d2af17703905425981bf/orjson-3.11.4-cp312-cp312-win32.whl",hashes = {sha256 = "b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa"}}, - {name = "orjson-3.11.4-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b9/3c/9cf47c3ff5f39b8350fb21ba65d789b6a1129d4cbb3033ba36c8a9023520/orjson-3.11.4-cp312-cp312-win_amd64.whl",hashes = {sha256 = "c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140"}}, - {name = "orjson-3.11.4-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/c6/3b/e2425f61e5825dc5b08c2a5a2b3af387eaaca22a12b9c8c01504f8614c36/orjson-3.11.4-cp312-cp312-win_arm64.whl",hashes = {sha256 = "d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e"}}, - {name = "orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/63/1d/1ea6005fffb56715fd48f632611e163d1604e8316a5bad2288bee9a1c9eb/orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39"}}, - {name = "orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl",url = "https://files.pythonhosted.org/packages/37/d7/ffed10c7da677f2a9da307d491b9eb1d0125b0307019c4ad3d665fd31f4f/orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl",hashes = {sha256 = "5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d"}}, - {name = "orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/a2/96/3e4d10a18866d1368f73c8c44b7fe37cc8a15c32f2a7620be3877d4c55a3/orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175"}}, - {name = "orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/eb/1f/465f66e93f434f968dd74d5b623eb62c657bdba2332f5a8be9f118bb74c7/orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040"}}, - {name = "orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/28/43/d1e94837543321c119dff277ae8e348562fe8c0fafbb648ef7cb0c67e521/orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63"}}, - {name = "orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/bf/04/93303776c8890e422a5847dd012b4853cdd88206b8bbd3edc292c90102d1/orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9"}}, - {name = "orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/1e/ef/75519d039e5ae6b0f34d0336854d55544ba903e21bf56c83adc51cd8bf82/orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a"}}, - {name = "orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b5/18/bf8581eaae0b941b44efe14fee7b7862c3382fbc9a0842132cfc7cf5ecf4/orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be"}}, - {name = "orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c4/35/a6d582766d351f87fc0a22ad740a641b0a8e6fc47515e8614d2e4790ae10/orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7"}}, - {name = "orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/76/b3/5a4801803ab2e2e2d703bce1a56540d9f99a9143fbec7bf63d225044fef8/orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549"}}, - {name = "orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/80/55/a8f682f64833e3a649f620eafefee175cbfeb9854fc5b710b90c3bca45df/orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905"}}, - {name = "orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ad/e4/c132fa0c67afbb3eb88274fa98df9ac1f631a675e7877037c611805a4413/orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907"}}, - {name = "orjson-3.11.4-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/54/06/dc3491489efd651fef99c5908e13951abd1aead1257c67f16135f95ce209/orjson-3.11.4-cp311-cp311-win32.whl",hashes = {sha256 = "87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c"}}, - {name = "orjson-3.11.4-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/79/b7/5e5e8d77bd4ea02a6ac54c42c818afb01dd31961be8a574eb79f1d2cfb1e/orjson-3.11.4-cp311-cp311-win_amd64.whl",hashes = {sha256 = "e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a"}}, - {name = "orjson-3.11.4-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/0f/dc/9484127cc1aa213be398ed735f5f270eedcb0c0977303a6f6ddc46b60204/orjson-3.11.4-cp311-cp311-win_arm64.whl",hashes = {sha256 = "6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045"}}, - {name = "orjson-3.11.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/e0/30/5aed63d5af1c8b02fbd2a8d83e2a6c8455e30504c50dbf08c8b51403d873/orjson-3.11.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "e3aa2118a3ece0d25489cbe48498de8a5d580e42e8d9979f65bf47900a15aba1"}}, - {name = "orjson-3.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/44/1f/da46563c08bef33c41fd63c660abcd2184b4d2b950c8686317d03b9f5f0c/orjson-3.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "a69ab657a4e6733133a3dca82768f2f8b884043714e8d2b9ba9f52b6efef5c44"}}, - {name = "orjson-3.11.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/02/bd/b551a05d0090eab0bf8008a13a14edc0f3c3e0236aa6f5b697760dd2817b/orjson-3.11.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "3740bffd9816fc0326ddc406098a3a8f387e42223f5f455f2a02a9f834ead80c"}}, - {name = "orjson-3.11.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/87/6c/9ddd5e609f443b2548c5e7df3c44d0e86df2c68587a0e20c50018cdec535/orjson-3.11.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "65fd2f5730b1bf7f350c6dc896173d3460d235c4be007af73986d7cd9a2acd23"}}, - {name = "orjson-3.11.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/95/f2/9f04f2874c625a9fb60f6918c33542320661255323c272e66f7dcce14df2/orjson-3.11.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "9fdc3ae730541086158d549c97852e2eea6820665d4faf0f41bf99df41bc11ea"}}, - {name = "orjson-3.11.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/d2/c2/c7302afcbdfe8a891baae0e2cee091583a30e6fa613e8bdf33b0e9c8a8c7/orjson-3.11.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "e10b4d65901da88845516ce9f7f9736f9638d19a1d483b3883dc0182e6e5edba"}}, - {name = "orjson-3.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/c6/3a/b31c8f0182a3e27f48e703f46e61bb769666cd0dac4700a73912d07a1417/orjson-3.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "fb6a03a678085f64b97f9d4a9ae69376ce91a3a9e9b56a82b1580d8e1d501aff"}}, - {name = "orjson-3.11.4-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/29/d0/fd9ab96841b090d281c46df566b7f97bc6c8cd9aff3f3ebe99755895c406/orjson-3.11.4-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "2c82e4f0b1c712477317434761fbc28b044c838b6b1240d895607441412371ac"}}, - {name = "orjson-3.11.4-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/d6/ce/36eb0f15978bb88e33a3480e1a3fb891caa0f189ba61ce7713e0ccdadabf/orjson-3.11.4-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "d58c166a18f44cc9e2bad03a327dc2d1a3d2e85b847133cfbafd6bfc6719bd79"}}, - {name = "orjson-3.11.4-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/85/11/e8af3161a288f5c6a00c188fc729c7ba193b0cbc07309a1a29c004347c30/orjson-3.11.4-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "94f206766bf1ea30e1382e4890f763bd1eefddc580e08fec1ccdc20ddd95c827"}}, - {name = "orjson-3.11.4-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ea/96/209d52db0cf1e10ed48d8c194841e383e23c2ced5a2ee766649fe0e32d02/orjson-3.11.4-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "41bf25fb39a34cf8edb4398818523277ee7096689db352036a9e8437f2f3ee6b"}}, - {name = "orjson-3.11.4-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/ef/0e/526db1395ccb74c3d59ac1660b9a325017096dc5643086b38f27662b4add/orjson-3.11.4-cp310-cp310-win32.whl",hashes = {sha256 = "fa9627eba4e82f99ca6d29bc967f09aba446ee2b5a1ea728949ede73d313f5d3"}}, - {name = "orjson-3.11.4-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/e6/69/18a778c9de3702b19880e73c9866b91cc85f904b885d816ba1ab318b223c/orjson-3.11.4-cp310-cp310-win_amd64.whl",hashes = {sha256 = "23ef7abc7fca96632d8174ac115e668c1e931b8fe4dde586e92a500bf1914dcc"}}, +sdist = {name = "orjson-3.11.3.tar.gz", url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hashes = {sha256 = "1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}} +wheels = [ + {name = "orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl",url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl",hashes = {sha256 = "d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229"}}, + {name = "orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2"}}, + {name = "orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667"}}, + {name = "orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2"}}, + {name = "orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/44/1d/ca2230fd55edbd87b58a43a19032d63a4b180389a97520cc62c535b726f9/orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf"}}, ] marker = "\"all\" in extras or \"dev\" in extras or \"perf\" in extras or \"recommended\" in extras" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "pandas-stubs" +version = "2.3.2.250926" +requires-python = ">=3.10" +sdist = {name = "pandas_stubs-2.3.2.250926.tar.gz", url = "https://files.pythonhosted.org/packages/1b/3b/32be58a125db39d0b5f62cc93795f32b5bb2915bd5c4a46f0e35171985e2/pandas_stubs-2.3.2.250926.tar.gz", hashes = {sha256 = "c64b9932760ceefb96a3222b953e6a251321a9832a28548be6506df473a66406"}} +wheels = [ + {name = "pandas_stubs-2.3.2.250926-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/40/96/1e4a035eaf4dce9610aac6e43026d0c6baa05773daf6d21e635a4fe19e21/pandas_stubs-2.3.2.250926-py3-none-any.whl",hashes = {sha256 = "81121818453dcfe00f45c852f4dceee043640b813830f6e7bd084a4ef7ff7270"}}, +] +marker = "\"dev\" in extras" + +[packages.tool.pdm] +dependencies = [ + "numpy>=1.23.5", + "types-pytz>=2022.1.1", +] + [[packages]] name = "protobuf" -version = "6.33.0" +version = "6.32.1" requires-python = ">=3.9" -sdist = {name = "protobuf-6.33.0.tar.gz", url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hashes = {sha256 = "140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954"}} +sdist = {name = "protobuf-6.32.1.tar.gz", url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hashes = {sha256 = "ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d"}} wheels = [ - {name = "protobuf-6.33.0-cp310-abi3-win32.whl",url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl",hashes = {sha256 = "d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035"}}, - {name = "protobuf-6.33.0-cp310-abi3-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl",hashes = {sha256 = "9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee"}}, - {name = "protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl",hashes = {sha256 = "905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455"}}, - {name = "protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl",hashes = {sha256 = "e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90"}}, - {name = "protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl",hashes = {sha256 = "e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298"}}, - {name = "protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl",hashes = {sha256 = "35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef"}}, - {name = "protobuf-6.33.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl",hashes = {sha256 = "25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995"}}, + {name = "protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl",hashes = {sha256 = "b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710"}}, + {name = "protobuf-6.32.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl",hashes = {sha256 = "2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346"}}, ] marker = "\"default\" in dependency_groups" @@ -1350,6 +863,19 @@ dependencies = [ "setuptools>=70.1.0", ] +[[packages]] +name = "tabulate" +version = "0.9.0" +requires-python = ">=3.7" +sdist = {name = "tabulate-0.9.0.tar.gz", url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hashes = {sha256 = "0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}} +wheels = [ + {name = "tabulate-0.9.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl",hashes = {sha256 = "024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "transformers" version = "4.57.1" @@ -1423,109 +949,19 @@ dependencies = [ [[packages]] name = "pydantic-core" -version = "2.41.5" +version = "2.41.4" requires-python = ">=3.9" -sdist = {name = "pydantic_core-2.41.5.tar.gz", url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hashes = {sha256 = "08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}} -wheels = [ - {name = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl",hashes = {sha256 = "3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl",hashes = {sha256 = "0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl",hashes = {sha256 = "63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl",hashes = {sha256 = "e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl",hashes = {sha256 = "aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl",hashes = {sha256 = "8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}}, - {name = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl",hashes = {sha256 = "e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl",hashes = {sha256 = "8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl",hashes = {sha256 = "a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl",hashes = {sha256 = "239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl",hashes = {sha256 = "2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl",hashes = {sha256 = "b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}}, - {name = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl",hashes = {sha256 = "941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl",hashes = {sha256 = "01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl",hashes = {sha256 = "6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl",hashes = {sha256 = "915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl",hashes = {sha256 = "650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl",hashes = {sha256 = "79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}}, - {name = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl",hashes = {sha256 = "3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl",hashes = {sha256 = "f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl",hashes = {sha256 = "c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl",hashes = {sha256 = "482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl",hashes = {sha256 = "bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl",hashes = {sha256 = "b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl",hashes = {sha256 = "1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}}, - {name = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl",hashes = {sha256 = "1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl",hashes = {sha256 = "a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl",hashes = {sha256 = "4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl",hashes = {sha256 = "34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl",hashes = {sha256 = "c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl",hashes = {sha256 = "2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl",hashes = {sha256 = "76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}}, - {name = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl",hashes = {sha256 = "4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl",hashes = {sha256 = "b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl",hashes = {sha256 = "03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl",hashes = {sha256 = "a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl",hashes = {sha256 = "f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}}, - {name = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl",hashes = {sha256 = "56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl",hashes = {sha256 = "77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl",hashes = {sha256 = "29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl",hashes = {sha256 = "d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl",hashes = {sha256 = "df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl",hashes = {sha256 = "1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}}, - {name = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl",hashes = {sha256 = "62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl",hashes = {sha256 = "b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl",hashes = {sha256 = "33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl",url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl",hashes = {sha256 = "c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl",hashes = {sha256 = "242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}}, - {name = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl",hashes = {sha256 = "d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}}, +sdist = {name = "pydantic_core-2.41.4.tar.gz", url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hashes = {sha256 = "70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}} +wheels = [ + {name = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}}, + {name = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}}, + {name = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}}, + {name = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}}, + {name = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}}, + {name = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}}, + {name = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}}, + {name = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}}, + {name = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}}, ] marker = "\"default\" in dependency_groups" @@ -1535,14 +971,19 @@ dependencies = [ ] [[packages]] -name = "packaging" -version = "25.0" +name = "tomli" +version = "2.3.0" requires-python = ">=3.8" -sdist = {name = "packaging-25.0.tar.gz", url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hashes = {sha256 = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}} +sdist = {name = "tomli-2.3.0.tar.gz", url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hashes = {sha256 = "64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}} wheels = [ - {name = "packaging-25.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl",hashes = {sha256 = "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}}, + {name = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}}, + {name = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}}, + {name = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}}, + {name = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}}, + {name = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}}, + {name = "tomli-2.3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl",hashes = {sha256 = "e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}}, ] -marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" +marker = "python_version < \"3.11\" and \"dev\" in extras" [packages.tool.pdm] dependencies = [] @@ -1560,13 +1001,26 @@ marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in [packages.tool.pdm] dependencies = [] +[[packages]] +name = "packaging" +version = "25.0" +requires-python = ">=3.8" +sdist = {name = "packaging-25.0.tar.gz", url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hashes = {sha256 = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}} +wheels = [ + {name = "packaging-25.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl",hashes = {sha256 = "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}}, +] +marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "huggingface-hub" -version = "0.36.0" +version = "0.35.3" requires-python = ">=3.8.0" -sdist = {name = "huggingface_hub-0.36.0.tar.gz", url = "https://files.pythonhosted.org/packages/98/63/4910c5fa9128fdadf6a9c5ac138e8b1b6cee4ca44bf7915bbfbce4e355ee/huggingface_hub-0.36.0.tar.gz", hashes = {sha256 = "47b3f0e2539c39bf5cde015d63b72ec49baff67b6931c3d97f3f84532e2b8d25"}} +sdist = {name = "huggingface_hub-0.35.3.tar.gz", url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", hashes = {sha256 = "350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a"}} wheels = [ - {name = "huggingface_hub-0.36.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl",hashes = {sha256 = "7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d"}}, + {name = "huggingface_hub-0.35.3-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl",hashes = {sha256 = "0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -1582,19 +1036,6 @@ dependencies = [ "hf-xet<2.0.0,>=1.1.3; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"", ] -[[packages]] -name = "colorama" -version = "0.4.6" -requires-python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -sdist = {name = "colorama-0.4.6.tar.gz", url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hashes = {sha256 = "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}} -wheels = [ - {name = "colorama-0.4.6-py2.py3-none-any.whl",url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",hashes = {sha256 = "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}}, -] -marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" - -[packages.tool.pdm] -dependencies = [] - [[packages]] name = "markdown-it-py" version = "3.0.0" @@ -1610,21 +1051,6 @@ dependencies = [ "mdurl~=0.1", ] -[[packages]] -name = "mdit-py-plugins" -version = "0.5.0" -requires-python = ">=3.10" -sdist = {name = "mdit_py_plugins-0.5.0.tar.gz", url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hashes = {sha256 = "f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"}} -wheels = [ - {name = "mdit_py_plugins-0.5.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl",hashes = {sha256 = "07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"}}, -] -marker = "\"dev\" in extras" - -[packages.tool.pdm] -dependencies = [ - "markdown-it-py<5.0.0,>=2.0.0", -] - [[packages]] name = "pluggy" version = "1.6.0" @@ -1710,93 +1136,31 @@ marker = "\"dev\" in extras" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "backports-asyncio-runner" +version = "1.2.0" +requires-python = "<3.11,>=3.8" +sdist = {name = "backports_asyncio_runner-1.2.0.tar.gz", url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hashes = {sha256 = "a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}} +wheels = [ + {name = "backports_asyncio_runner-1.2.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl",hashes = {sha256 = "0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}}, +] +marker = "python_version < \"3.11\" and \"dev\" in extras" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "charset-normalizer" version = "3.4.4" requires-python = ">=3.7" sdist = {name = "charset_normalizer-3.4.4.tar.gz", url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hashes = {sha256 = "94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}} wheels = [ - {name = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}}, {name = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl",hashes = {sha256 = "2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl",hashes = {sha256 = "f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl",hashes = {sha256 = "8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}}, - {name = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl",hashes = {sha256 = "de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}}, {name = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl",hashes = {sha256 = "c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl",hashes = {sha256 = "9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl",hashes = {sha256 = "b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}}, - {name = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl",hashes = {sha256 = "542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}}, {name = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl",hashes = {sha256 = "d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl",hashes = {sha256 = "5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl",hashes = {sha256 = "a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}}, - {name = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl",hashes = {sha256 = "376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}}, - {name = "charset_normalizer-3.4.4-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl",hashes = {sha256 = "7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}}, {name = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl",hashes = {sha256 = "0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl",hashes = {sha256 = "eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl",hashes = {sha256 = "5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}}, - {name = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl",hashes = {sha256 = "65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}}, {name = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl",hashes = {sha256 = "faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl",hashes = {sha256 = "f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl",hashes = {sha256 = "a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}}, - {name = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl",hashes = {sha256 = "cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}}, + {name = "charset_normalizer-3.4.4-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl",hashes = {sha256 = "7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"openai\" in extras or \"recommended\" in extras or \"vision\" in extras" @@ -1857,112 +1221,16 @@ dependencies = [] [[packages]] name = "aiohttp" -version = "3.13.2" +version = "3.13.0" requires-python = ">=3.9" -sdist = {name = "aiohttp-3.13.2.tar.gz", url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hashes = {sha256 = "40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca"}} -wheels = [ - {name = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b"}}, - {name = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61"}}, - {name = "aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4"}}, - {name = "aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b"}}, - {name = "aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694"}}, - {name = "aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906"}}, - {name = "aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9"}}, - {name = "aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011"}}, - {name = "aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6"}}, - {name = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213"}}, - {name = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49"}}, - {name = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae"}}, - {name = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl",hashes = {sha256 = "ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa"}}, - {name = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4"}}, - {name = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a"}}, - {name = "aiohttp-3.13.2-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl",hashes = {sha256 = "f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940"}}, - {name = "aiohttp-3.13.2-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl",hashes = {sha256 = "fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl",hashes = {sha256 = "d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734"}}, - {name = "aiohttp-3.13.2-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f"}}, - {name = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be"}}, - {name = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742"}}, - {name = "aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293"}}, - {name = "aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811"}}, - {name = "aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a"}}, - {name = "aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4"}}, - {name = "aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a"}}, - {name = "aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e"}}, - {name = "aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb"}}, - {name = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded"}}, - {name = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b"}}, - {name = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8"}}, - {name = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl",hashes = {sha256 = "550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04"}}, - {name = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476"}}, - {name = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23"}}, - {name = "aiohttp-3.13.2-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl",hashes = {sha256 = "0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254"}}, - {name = "aiohttp-3.13.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a"}}, - {name = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b"}}, - {name = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc"}}, - {name = "aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7"}}, - {name = "aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb"}}, - {name = "aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3"}}, - {name = "aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f"}}, - {name = "aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6"}}, - {name = "aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e"}}, - {name = "aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7"}}, - {name = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d"}}, - {name = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b"}}, - {name = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8"}}, - {name = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl",hashes = {sha256 = "9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16"}}, - {name = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169"}}, - {name = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248"}}, - {name = "aiohttp-3.13.2-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl",hashes = {sha256 = "27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e"}}, - {name = "aiohttp-3.13.2-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl",hashes = {sha256 = "8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45"}}, - {name = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0"}}, - {name = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb"}}, - {name = "aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9"}}, - {name = "aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613"}}, - {name = "aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead"}}, - {name = "aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780"}}, - {name = "aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a"}}, - {name = "aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592"}}, - {name = "aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab"}}, - {name = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30"}}, - {name = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40"}}, - {name = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948"}}, - {name = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl",hashes = {sha256 = "2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf"}}, - {name = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782"}}, - {name = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8"}}, - {name = "aiohttp-3.13.2-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl",hashes = {sha256 = "868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec"}}, - {name = "aiohttp-3.13.2-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl",hashes = {sha256 = "7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c"}}, - {name = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/6d/34/939730e66b716b76046dedfe0842995842fa906ccc4964bba414ff69e429/aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155"}}, - {name = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/fd/cf/dcbdf2df7f6ca72b0bb4c0b4509701f2d8942cf54e29ca197389c214c07f/aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c"}}, - {name = "aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/9d/87/71c8867e0a1d0882dcbc94af767784c3cb381c1c4db0943ab4aae4fed65e/aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636"}}, - {name = "aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/38/0f/46c24e8dae237295eaadd113edd56dee96ef6462adf19b88592d44891dc5/aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da"}}, - {name = "aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/eb/c6/4cdfb4440d0e28483681a48f69841fa5e39366347d66ef808cbdadddb20e/aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725"}}, - {name = "aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/84/37/8708cf678628216fb678ab327a4e1711c576d6673998f4f43e86e9ae90dd/aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5"}}, - {name = "aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/e6/2e/3ebfe12fdcb9b5f66e8a0a42dffcd7636844c8a018f261efb2419f68220b/aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3"}}, - {name = "aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/a1/4f/ca2ef819488cbb41844c6cf92ca6dd15b9441e6207c58e5ae0e0fc8d70ad/aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802"}}, - {name = "aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/f8/fe/1fe2e1179a0d91ce09c99069684aab619bf2ccde9b20bd6ca44f8837203e/aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a"}}, - {name = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5a/2b/f3781899b81c45d7cbc7140cddb8a3481c195e7cbff8e36374759d2ab5a5/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204"}}, - {name = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/72/27/c37e85cd3ece6f6c772e549bd5a253d0c122557b25855fb274224811e4f2/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22"}}, - {name = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/66/20/3af1ab663151bd3780b123e907761cdb86ec2c4e44b2d9b195ebc91fbe37/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d"}}, - {name = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/95/eb/ae5cab15efa365e13d56b31b0d085a62600298bf398a7986f8388f73b598/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl",hashes = {sha256 = "b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f"}}, - {name = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/e9/2d/1683e8d67ec72d911397fe4e575688d2a9b8f6a6e03c8fdc9f3fd3d4c03f/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f"}}, - {name = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/99/a2/ffe8e0e1c57c5e542d47ffa1fcf95ef2b3ea573bf7c4d2ee877252431efc/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6"}}, - {name = "aiohttp-3.13.2-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/0d/42/d511aff5c3a2b06c09d7d214f508a4ad8ac7799817f7c3d23e7336b5e896/aiohttp-3.13.2-cp310-cp310-win32.whl",hashes = {sha256 = "e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251"}}, - {name = "aiohttp-3.13.2-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8b/ea/1c2eb7098b5bad4532994f2b7a8228d27674035c9b3234fe02c37469ef14/aiohttp-3.13.2-cp310-cp310-win_amd64.whl",hashes = {sha256 = "364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514"}}, +sdist = {name = "aiohttp-3.13.0.tar.gz", url = "https://files.pythonhosted.org/packages/62/f1/8515650ac3121a9e55c7b217c60e7fae3e0134b5acfe65691781b5356929/aiohttp-3.13.0.tar.gz", hashes = {sha256 = "378dbc57dd8cf341ce243f13fa1fa5394d68e2e02c15cd5f28eae35a70ec7f67"}} +wheels = [ + {name = "aiohttp-3.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/57/e8/66e3c32841fc0e26a09539c377aa0f3bbf6deac1957ac5182cf276c5719c/aiohttp-3.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d812838c109757a11354a161c95708ae4199c4fd4d82b90959b20914c1d097f6"}}, + {name = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/88/e2/86050aaa3bd7021b115cdfc88477b754e8cf93ef0079867840eee22d3c34/aiohttp-3.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "019dbef24fe28ce2301419dd63a2b97250d9760ca63ee2976c2da2e3f182f82e"}}, + {name = "aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b"}}, + {name = "aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8"}}, + {name = "aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3"}}, + {name = "aiohttp-3.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f9/ca/135c21e85ffeff66b80ecd8a647ca104f2e5a91c37dc86649244ddbf87ab/aiohttp-3.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "72714919ed9b90f030f761c20670e529c4af96c31bd000917dd0c9afd1afb731"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -1984,133 +1252,14 @@ version = "6.7.0" requires-python = ">=3.9" sdist = {name = "multidict-6.7.0.tar.gz", url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hashes = {sha256 = "c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}} wheels = [ - {name = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}}, - {name = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}}, - {name = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}}, - {name = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}}, - {name = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}}, - {name = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}}, - {name = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}}, - {name = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}}, {name = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}}, - {name = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}}, - {name = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}}, - {name = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl",hashes = {sha256 = "fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}}, - {name = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}}, - {name = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}}, - {name = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}}, - {name = "multidict-6.7.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl",hashes = {sha256 = "fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}}, - {name = "multidict-6.7.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}}, - {name = "multidict-6.7.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}}, - {name = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}}, - {name = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}}, - {name = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}}, - {name = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}}, - {name = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}}, - {name = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}}, - {name = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}}, - {name = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}}, {name = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}}, - {name = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}}, - {name = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}}, - {name = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl",hashes = {sha256 = "a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}}, - {name = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}}, - {name = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}}, - {name = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}}, - {name = "multidict-6.7.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl",hashes = {sha256 = "b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}}, - {name = "multidict-6.7.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}}, - {name = "multidict-6.7.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}}, - {name = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}}, - {name = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}}, - {name = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}}, - {name = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}}, - {name = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}}, - {name = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}}, - {name = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}}, - {name = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}}, {name = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}}, - {name = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}}, - {name = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}}, - {name = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}}, - {name = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}}, - {name = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}}, - {name = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}}, - {name = "multidict-6.7.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl",hashes = {sha256 = "a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}}, - {name = "multidict-6.7.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}}, - {name = "multidict-6.7.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}}, - {name = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}}, - {name = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}}, - {name = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}}, - {name = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}}, - {name = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}}, - {name = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}}, - {name = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}}, - {name = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}}, {name = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}}, - {name = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}}, - {name = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}}, - {name = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl",hashes = {sha256 = "2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}}, - {name = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}}, - {name = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl",hashes = {sha256 = "e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}}, - {name = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}}, - {name = "multidict-6.7.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl",hashes = {sha256 = "19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}}, - {name = "multidict-6.7.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}}, - {name = "multidict-6.7.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}}, - {name = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}}, - {name = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}}, - {name = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}}, - {name = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}}, - {name = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}}, - {name = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}}, - {name = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}}, - {name = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}}, {name = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}}, - {name = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}}, - {name = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}}, - {name = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}}, - {name = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}}, - {name = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}}, - {name = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}}, - {name = "multidict-6.7.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl",hashes = {sha256 = "dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}}, - {name = "multidict-6.7.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}}, - {name = "multidict-6.7.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}}, - {name = "multidict-6.7.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl",hashes = {sha256 = "394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}}, - {name = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}}, - {name = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}}, - {name = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}}, - {name = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}}, - {name = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}}, - {name = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}}, - {name = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}}, - {name = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}}, {name = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}}, - {name = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}}, - {name = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}}, - {name = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}}, - {name = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}}, - {name = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}}, - {name = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}}, - {name = "multidict-6.7.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl",hashes = {sha256 = "a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}}, - {name = "multidict-6.7.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}}, - {name = "multidict-6.7.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}}, - {name = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/a9/63/7bdd4adc330abcca54c85728db2327130e49e52e8c3ce685cec44e0f2e9f/multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}}, - {name = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/3f/bb/b6c35ff175ed1a3142222b78455ee31be71a8396ed3ab5280fbe3ebe4e85/multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}}, - {name = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e0/1f/064c77877c5fa6df6d346e68075c0f6998547afe952d6471b4c5f6a7345d/multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}}, - {name = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/04/7a/bf6aa92065dd47f287690000b3d7d332edfccb2277634cadf6a810463c6a/multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}}, - {name = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/94/39/297a8de920f76eda343e4ce05f3b489f0ab3f9504f2576dfb37b7c08ca08/multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}}, - {name = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/39/3a/d0eee2898cfd9d654aea6cb8c4addc2f9756e9a7e09391cfe55541f917f7/multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}}, - {name = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/05/48/3b328851193c7a4240815b71eea165b49248867bbb6153a0aee227a0bb47/multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}}, - {name = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/b1/ca/0706a98c8d126a89245413225ca4a3fefc8435014de309cf8b30acb68841/multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}}, {name = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/5e/4f/9c7992f245554d8b173f6f0a048ad24b3e645d883f096857ec2c0822b8bd/multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}}, - {name = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/31/79/26a85991ae67efd1c0b1fc2e0c275b8a6aceeb155a68861f63f87a798f16/multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}}, - {name = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/14/1e/75fa96394478930b79d0302eaf9a6c69f34005a1a5251ac8b9c336486ec9/multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}}, - {name = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/b2/5e/085544cb9f9c4ad2b5d97467c15f856df8d9bac410cffd5c43991a5d878b/multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}}, - {name = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b9/c3/e9d9e2f20c9474e7a8fcef28f863c5cbd29bb5adce6b70cebe8bdad0039d/multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}}, - {name = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/b5/3f/df171b6efa3239ae33b97b887e42671cd1d94d460614bfb2c30ffdab3b95/multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}}, - {name = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/3c/2f/9b5564888c4e14b9af64c54acf149263721a283aaf4aa0ae89b091d5d8c1/multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}}, - {name = "multidict-6.7.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/6c/3a/0bd6ca0f7d96d790542d591c8c3354c1e1b6bfd2024d4d92dc3d87485ec7/multidict-6.7.0-cp310-cp310-win32.whl",hashes = {sha256 = "afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}}, - {name = "multidict-6.7.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/00/35/f6a637ea2c75f0d3b7c7d41b1189189acff0d9deeb8b8f35536bb30f5e33/multidict-6.7.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}}, - {name = "multidict-6.7.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/e7/b8/f7bf8329b39893d02d9d95cf610c75885d12fc0f402b1c894e1c8e01c916/multidict-6.7.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}}, + {name = "multidict-6.7.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl",hashes = {sha256 = "394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -2119,6 +1268,19 @@ dependencies = [ "typing-extensions>=4.1.0; python_version < \"3.11\"", ] +[[packages]] +name = "async-timeout" +version = "5.0.1" +requires-python = ">=3.8" +sdist = {name = "async_timeout-5.0.1.tar.gz", url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hashes = {sha256 = "d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}} +wheels = [ + {name = "async_timeout-5.0.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl",hashes = {sha256 = "39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}}, +] +marker = "\"default\" in dependency_groups and python_version < \"3.11\" or \"all\" in extras and python_version < \"3.11\" or \"audio\" in extras and python_version < \"3.11\" or \"dev\" in extras and python_version < \"3.11\" or \"vision\" in extras and python_version < \"3.11\"" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "h2" version = "4.3.0" @@ -2137,31 +1299,11 @@ dependencies = [ [[packages]] name = "hf-xet" -version = "1.2.0" +version = "1.1.10" requires-python = ">=3.8" -sdist = {name = "hf_xet-1.2.0.tar.gz", url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hashes = {sha256 = "a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f"}} -wheels = [ - {name = "hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl",hashes = {sha256 = "10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e"}}, - {name = "hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8"}}, - {name = "hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0"}}, - {name = "hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090"}}, - {name = "hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a"}}, - {name = "hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f"}}, - {name = "hf_xet-1.2.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl",hashes = {sha256 = "ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832"}}, - {name = "hf_xet-1.2.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382"}}, - {name = "hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl",hashes = {sha256 = "46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848"}}, - {name = "hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl",hashes = {sha256 = "27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4"}}, - {name = "hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd"}}, - {name = "hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl",hashes = {sha256 = "d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c"}}, - {name = "hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl",hashes = {sha256 = "4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737"}}, - {name = "hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865"}}, - {name = "hf_xet-1.2.0-cp37-abi3-win_amd64.whl",url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl",hashes = {sha256 = "e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69"}}, +sdist = {name = "hf_xet-1.1.10.tar.gz", url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hashes = {sha256 = "408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97"}} +wheels = [ + {name = "hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435"}}, ] marker = "\"default\" in dependency_groups and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\") or \"all\" in extras and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\") or \"audio\" in extras and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\") or \"dev\" in extras and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\") or \"vision\" in extras and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\")" @@ -2207,111 +1349,35 @@ marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in [packages.tool.pdm] dependencies = [] +[[packages]] +name = "mdit-py-plugins" +version = "0.5.0" +requires-python = ">=3.10" +sdist = {name = "mdit_py_plugins-0.5.0.tar.gz", url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hashes = {sha256 = "f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"}} +wheels = [ + {name = "mdit_py_plugins-0.5.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl",hashes = {sha256 = "07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"}}, +] +marker = "\"dev\" in extras" + +[packages.tool.pdm] +dependencies = [ + "markdown-it-py<5.0.0,>=2.0.0", +] + [[packages]] name = "regex" -version = "2025.11.3" +version = "2025.9.18" requires-python = ">=3.9" -sdist = {name = "regex-2025.11.3.tar.gz", url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hashes = {sha256 = "1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01"}} -wheels = [ - {name = "regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/31/e9/f6e13de7e0983837f7b6d238ad9458800a874bf37c264f7923e63409944c/regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6"}}, - {name = "regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/a3/5c/261f4a262f1fa65141c1b74b255988bd2fa020cc599e53b080667d591cfc/regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4"}}, - {name = "regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/8e/57/f14eeb7f072b0e9a5a090d1712741fd8f214ec193dba773cf5410108bb7d/regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73"}}, - {name = "regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/3c/6b/1d650c45e99a9b327586739d926a1cd4e94666b1bd4af90428b36af66dc7/regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f"}}, - {name = "regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/99/ee/d66dcbc6b628ce4e3f7f0cbbb84603aa2fc0ffc878babc857726b8aab2e9/regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d"}}, - {name = "regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/bf/2d/f238229f1caba7ac87a6c4153d79947fb0261415827ae0f77c304260c7d3/regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be"}}, - {name = "regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/bd/3d/22a4eaba214a917c80e04f6025d26143690f0419511e0116508e24b11c9b/regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db"}}, - {name = "regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/84/b1/03188f634a409353a84b5ef49754b97dbcc0c0f6fd6c8ede505a8960a0a4/regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62"}}, - {name = "regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/99/6a/27d072f7fbf6fadd59c64d210305e1ff865cc3b78b526fd147db768c553b/regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f"}}, - {name = "regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/9a/70/1b3878f648e0b6abe023172dacb02157e685564853cc363d9961bcccde4e/regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02"}}, - {name = "regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/dd/d5/68e25559b526b8baab8e66839304ede68ff6727237a47727d240006bd0ff/regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed"}}, - {name = "regex-2025.11.3-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/fc/df/43971264857140a350910d4e33df725e8c94dd9dee8d2e4729fa0d63d49e/regex-2025.11.3-cp314-cp314-win32.whl",hashes = {sha256 = "795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4"}}, - {name = "regex-2025.11.3-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/01/6f/9711b57dc6894a55faf80a4c1b5aa4f8649805cb9c7aef46f7d27e2b9206/regex-2025.11.3-cp314-cp314-win_amd64.whl",hashes = {sha256 = "9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad"}}, - {name = "regex-2025.11.3-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f1/7e/f6eaa207d4377481f5e1775cdeb5a443b5a59b392d0065f3417d31d80f87/regex-2025.11.3-cp314-cp314-win_arm64.whl",hashes = {sha256 = "dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f"}}, - {name = "regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/c3/06/49b198550ee0f5e4184271cee87ba4dfd9692c91ec55289e6282f0f86ccf/regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc"}}, - {name = "regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/ce/bf/abdafade008f0b1c9da10d934034cb670432d6cf6cbe38bbb53a1cfd6cf8/regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49"}}, - {name = "regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f9/ef/0c357bb8edbd2ad8e273fcb9e1761bc37b8acbc6e1be050bebd6475f19c1/regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536"}}, - {name = "regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/79/06/edbb67257596649b8fb088d6aeacbcb248ac195714b18a65e018bf4c0b50/regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95"}}, - {name = "regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f4/d9/ad4deccfce0ea336296bd087f1a191543bb99ee1c53093dcd4c64d951d00/regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009"}}, - {name = "regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/13/75/a55a4724c56ef13e3e04acaab29df26582f6978c000ac9cd6810ad1f341f/regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9"}}, - {name = "regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/67/1e/a1657ee15bd9116f70d4a530c736983eed997b361e20ecd8f5ca3759d5c5/regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d"}}, - {name = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b8/6f/f7516dde5506a588a561d296b2d0044839de06035bb486b326065b4c101e/regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6"}}, - {name = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d9/dd/3d10b9e170cc16fb34cb2cef91513cf3df65f440b3366030631b2984a264/regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154"}}, - {name = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/f5/8e/935e6beff1695aa9085ff83195daccd72acc82c81793df480f34569330de/regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267"}}, - {name = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/92/12/10650181a040978b2f5720a6a74d44f841371a3d984c2083fc1752e4acf6/regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379"}}, - {name = "regex-2025.11.3-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/67/90/8f37138181c9a7690e7e4cb388debbd389342db3c7381d636d2875940752/regex-2025.11.3-cp314-cp314t-win32.whl",hashes = {sha256 = "4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38"}}, - {name = "regex-2025.11.3-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8f/cd/867f5ec442d56beb56f5f854f40abcfc75e11d10b11fdb1869dd39c63aaf/regex-2025.11.3-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de"}}, - {name = "regex-2025.11.3-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/20/31/32c0c4610cbc070362bf1d2e4ea86d1ea29014d400a6d6c2486fcfd57766/regex-2025.11.3-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801"}}, - {name = "regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/e1/a7/dda24ebd49da46a197436ad96378f17df30ceb40e52e859fc42cac45b850/regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "c1e448051717a334891f2b9a620fe36776ebf3dd8ec46a0b877c8ae69575feb4"}}, - {name = "regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/19/22/af2dc751aacf88089836aa088a1a11c4f21a04707eb1b0478e8e8fb32847/regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "9b5aca4d5dfd7fbfbfbdaf44850fcc7709a01146a797536a8f84952e940cca76"}}, - {name = "regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a3/88/1a3ea5672f4b0a84802ee9891b86743438e7c04eb0b8f8c4e16a42375327/regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "04d2765516395cf7dda331a244a3282c0f5ae96075f728629287dfa6f76ba70a"}}, - {name = "regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/fb/8c/f5987895bf42b8ddeea1b315c9fedcfe07cadee28b9c98cf50d00adcb14d/regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5d9903ca42bfeec4cebedba8022a7c97ad2aab22e09573ce9976ba01b65e4361"}}, - {name = "regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/99/2a/6591ebeede78203fa77ee46a1c36649e02df9eaa77a033d1ccdf2fcd5d4e/regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "639431bdc89d6429f6721625e8129413980ccd62e9d3f496be618a41d205f160"}}, - {name = "regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/94/d6/be32a87cf28cf8ed064ff281cfbd49aefd90242a83e4b08b5a86b38e8eb4/regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "f117efad42068f9715677c8523ed2be1518116d1c49b1dd17987716695181efe"}}, - {name = "regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/62/11/9bcef2d1445665b180ac7f230406ad80671f0fc2a6ffb93493b5dd8cd64c/regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4aecb6f461316adf9f1f0f6a4a1a3d79e045f9b71ec76055a791affa3b285850"}}, - {name = "regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/e5/a7/da0dc273d57f560399aa16d8a68ae7f9b57679476fc7ace46501d455fe84/regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "3b3a5f320136873cc5561098dfab677eea139521cb9a9e8db98b7e64aef44cbc"}}, - {name = "regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/da/4b/732a0c5a9736a0b8d6d720d4945a2f1e6f38f87f48f3173559f53e8d5d82/regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "75fa6f0056e7efb1f42a1c34e58be24072cb9e61a601340cc1196ae92326a4f9"}}, - {name = "regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/0c/f5/a2a03df27dc4c2d0c769220f5110ba8c4084b0bfa9ab0f9b4fcfa3d2b0fc/regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "dbe6095001465294f13f1adcd3311e50dd84e5a71525f20a10bd16689c61ce0b"}}, - {name = "regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d6/09/e1cd5bee3841c7f6eb37d95ca91cdee7100b8f88b81e41c2ef426910891a/regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "454d9b4ae7881afbc25015b8627c16d88a597479b9dea82b8c6e7e2e07240dc7"}}, - {name = "regex-2025.11.3-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/eb/51/702f5ea74e2a9c13d855a6a85b7f80c30f9e72a95493260193c07f3f8d74/regex-2025.11.3-cp313-cp313-win32.whl",hashes = {sha256 = "28ba4d69171fc6e9896337d4fc63a43660002b7da53fc15ac992abcf3410917c"}}, - {name = "regex-2025.11.3-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8b/00/6e29bb314e271a743170e53649db0fdb8e8ff0b64b4f425f5602f4eb9014/regex-2025.11.3-cp313-cp313-win_amd64.whl",hashes = {sha256 = "bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5"}}, - {name = "regex-2025.11.3-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/25/f1/b156ff9f2ec9ac441710764dda95e4edaf5f36aca48246d1eea3f1fd96ec/regex-2025.11.3-cp313-cp313-win_arm64.whl",hashes = {sha256 = "2292cd5a90dab247f9abe892ac584cb24f0f54680c73fcb4a7493c66c2bf2467"}}, - {name = "regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/20/28/fd0c63357caefe5680b8ea052131acbd7f456893b69cc2a90cc3e0dc90d4/regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "1eb1ebf6822b756c723e09f5186473d93236c06c579d2cc0671a722d2ab14281"}}, - {name = "regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/df/ec/7014c15626ab46b902b3bcc4b28a7bae46d8f281fc7ea9c95e22fcaaa917/regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "1e00ec2970aab10dc5db34af535f21fcf32b4a31d99e34963419636e2f85ae39"}}, - {name = "regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/23/ab/3b952ff7239f20d05f1f99e9e20188513905f218c81d52fb5e78d2bf7634/regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "a4cb042b615245d5ff9b3794f56be4138b5adc35a4166014d31d1814744148c7"}}, - {name = "regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/21/7e/3dc2749fc684f455f162dcafb8a187b559e2614f3826877d3844a131f37b/regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "44f264d4bf02f3176467d90b294d59bf1db9fe53c141ff772f27a8b456b2a9ed"}}, - {name = "regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/1b/0b/d529a85ab349c6a25d1ca783235b6e3eedf187247eab536797021f7126c6/regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "7be0277469bf3bd7a34a9c57c1b6a724532a0d235cd0dc4e7f4316f982c28b19"}}, - {name = "regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/7d/18/2d868155f8c9e3e9d8f9e10c64e9a9f496bb8f7e037a88a8bed26b435af6/regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "0d31e08426ff4b5b650f68839f5af51a92a5b51abd8554a60c2fbc7c71f25d0b"}}, - {name = "regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/2d/71/9d72ff0f354fa783fe2ba913c8734c3b433b86406117a8db4ea2bf1c7a2f/regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "e43586ce5bd28f9f285a6e729466841368c4a0353f6fd08d4ce4630843d3648a"}}, - {name = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/e7/19/ce4bf7f5575c97f82b6e804ffb5c4e940c62609ab2a0d9538d47a7fdf7d4/regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "0f9397d561a4c16829d4e6ff75202c1c08b68a3bdbfe29dbfcdb31c9830907c6"}}, - {name = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/03/86/fd1063a176ffb7b2315f9a1b08d17b18118b28d9df163132615b835a26ee/regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "dd16e78eb18ffdb25ee33a0682d17912e8cc8a770e885aeee95020046128f1ce"}}, - {name = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/12/43/103fb2e9811205e7386366501bc866a164a0430c79dd59eac886a2822950/regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl",hashes = {sha256 = "ffcca5b9efe948ba0661e9df0fa50d2bc4b097c70b9810212d6b62f05d83b2dd"}}, - {name = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/7d/22/e392e53f3869b75804762c7c848bd2dd2abf2b70fb0e526f58724638bd35/regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c56b4d162ca2b43318ac671c65bd4d563e841a694ac70e1a976ac38fcf4ca1d2"}}, - {name = "regex-2025.11.3-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/4f/f9/8bd6b656592f925b6845fcbb4d57603a3ac2fb2373344ffa1ed70aa6820a/regex-2025.11.3-cp313-cp313t-win32.whl",hashes = {sha256 = "9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a"}}, - {name = "regex-2025.11.3-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/e5/87/0e7d603467775ff65cd2aeabf1b5b50cc1c3708556a8b849a2fa4dd1542b/regex-2025.11.3-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c"}}, - {name = "regex-2025.11.3-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/8d/d0/2afc6f8e94e2b64bfb738a7c2b6387ac1699f09f032d363ed9447fd2bb57/regex-2025.11.3-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e"}}, - {name = "regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/e8/74/18f04cb53e58e3fb107439699bd8375cf5a835eec81084e0bddbd122e4c2/regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41"}}, - {name = "regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/78/3f/37fcdd0d2b1e78909108a876580485ea37c91e1acf66d3bb8e736348f441/regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "22b29dda7e1f7062a52359fca6e58e548e28c6686f205e780b02ad8ef710de36"}}, - {name = "regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bf/26/0a575f58eb23b7ebd67a45fccbc02ac030b737b896b7e7a909ffe43ffd6a/regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "3a91e4a29938bc1a082cc28fdea44be420bf2bebe2665343029723892eb073e1"}}, - {name = "regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ea/98/6a8dff667d1af907150432cf5abc05a17ccd32c72a3615410d5365ac167a/regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "08b884f4226602ad40c5d55f52bf91a9df30f513864e0054bad40c0e9cf1afb7"}}, - {name = "regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/64/15/92c1db4fa4e12733dd5a526c2dd2b6edcbfe13257e135fc0f6c57f34c173/regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "3e0b11b2b2433d1c39c7c7a30e3f3d0aeeea44c2a8d0bae28f6b95f639927a69"}}, - {name = "regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/f9/e7/3ad7da8cdee1ce66c7cd37ab5ab05c463a86ffeb52b1a25fe7bd9293b36c/regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "87eb52a81ef58c7ba4d45c3ca74e12aa4b4e77816f72ca25258a85b3ea96cb48"}}, - {name = "regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/84/bd/9ce9f629fcb714ffc2c3faf62b6766ecb7a585e1e885eb699bcf130a5209/regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a12ab1f5c29b4e93db518f5e3872116b7e9b1646c9f9f426f777b50d44a09e8c"}}, - {name = "regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/7c/0f/8dc2e4349d8e877283e6edd6c12bdcebc20f03744e86f197ab6e4492bf08/regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "7521684c8c7c4f6e88e35ec89680ee1aa8358d3f09d27dfbdf62c446f5d4c695"}}, - {name = "regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f9/73/cff02702960bc185164d5619c0c62a2f598a6abff6695d391b096237d4ab/regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "7fe6e5440584e94cc4b3f5f4d98a25e29ca12dccf8873679a635638349831b98"}}, - {name = "regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/61/83/0e8d1ae71e15bc1dc36231c90b46ee35f9d52fab2e226b0e039e7ea9c10a/regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "8e026094aa12b43f4fd74576714e987803a315c76edb6b098b9809db5de58f74"}}, - {name = "regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/f5/70a5cdd781dcfaa12556f2955bf170cd603cb1c96a1827479f8faea2df97/regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "435bbad13e57eb5606a68443af62bed3556de2f46deb9f7d4237bc2f1c9fb3a0"}}, - {name = "regex-2025.11.3-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/59/9b/7c29be7903c318488983e7d97abcf8ebd3830e4c956c4c540005fcfb0462/regex-2025.11.3-cp312-cp312-win32.whl",hashes = {sha256 = "3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204"}}, - {name = "regex-2025.11.3-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/1a/67/3b92df89f179d7c367be654ab5626ae311cb28f7d5c237b6bb976cd5fbbb/regex-2025.11.3-cp312-cp312-win_amd64.whl",hashes = {sha256 = "e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9"}}, - {name = "regex-2025.11.3-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/d7/55/85ba4c066fe5094d35b249c3ce8df0ba623cfd35afb22d6764f23a52a1c5/regex-2025.11.3-cp312-cp312-win_arm64.whl",hashes = {sha256 = "64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26"}}, - {name = "regex-2025.11.3-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/f7/90/4fb5056e5f03a7048abd2b11f598d464f0c167de4f2a51aa868c376b8c70/regex-2025.11.3-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "eadade04221641516fa25139273505a1c19f9bf97589a05bc4cfcd8b4a618031"}}, - {name = "regex-2025.11.3-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/85/23/63e481293fac8b069d84fba0299b6666df720d875110efd0338406b5d360/regex-2025.11.3-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "feff9e54ec0dd3833d659257f5c3f5322a12eee58ffa360984b716f8b92983f4"}}, - {name = "regex-2025.11.3-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2b/9d/b101d0262ea293a0066b4522dfb722eb6a8785a8c3e084396a5f2c431a46/regex-2025.11.3-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "3b30bc921d50365775c09a7ed446359e5c0179e9e2512beec4a60cbcef6ddd50"}}, - {name = "regex-2025.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/0c/64/79241c8209d5b7e00577ec9dca35cd493cc6be35b7d147eda367d6179f6d/regex-2025.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "f99be08cfead2020c7ca6e396c13543baea32343b7a9a5780c462e323bd8872f"}}, - {name = "regex-2025.11.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/3d/e2/23cd5d3573901ce8f9757c92ca4db4d09600b865919b6d3e7f69f03b1afd/regex-2025.11.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "6dd329a1b61c0ee95ba95385fb0c07ea0d3fe1a21e1349fa2bec272636217118"}}, - {name = "regex-2025.11.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/2a/4c/aecf31beeaa416d0ae4ecb852148d38db35391aac19c687b5d56aedf3a8b/regex-2025.11.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "4c5238d32f3c5269d9e87be0cf096437b7622b6920f5eac4fd202468aaeb34d2"}}, - {name = "regex-2025.11.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/61/22/b8cb00df7d2b5e0875f60628594d44dba283e951b1ae17c12f99e332cc0a/regex-2025.11.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "10483eefbfb0adb18ee9474498c9a32fcf4e594fbca0543bb94c48bac6183e2e"}}, - {name = "regex-2025.11.3-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/02/a8/c4b20330a5cdc7a8eb265f9ce593f389a6a88a0c5f280cf4d978f33966bc/regex-2025.11.3-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "78c2d02bb6e1da0720eedc0bad578049cad3f71050ef8cd065ecc87691bed2b0"}}, - {name = "regex-2025.11.3-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b4/4c/ae3e52988ae74af4b04d2af32fee4e8077f26e51b62ec2d12d246876bea2/regex-2025.11.3-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "e6b49cd2aad93a1790ce9cffb18964f6d3a4b0b3dbdbd5de094b65296fce6e58"}}, - {name = "regex-2025.11.3-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/06/d1/a8b9cf45874eda14b2e275157ce3b304c87e10fb38d9fc26a6e14eb18227/regex-2025.11.3-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "885b26aa3ee56433b630502dc3d36ba78d186a00cc535d3806e6bfd9ed3c70ab"}}, - {name = "regex-2025.11.3-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ea/fe/1830eb0236be93d9b145e0bd8ab499f31602fe0999b1f19e99955aa8fe20/regex-2025.11.3-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ddd76a9f58e6a00f8772e72cff8ebcff78e022be95edf018766707c730593e1e"}}, - {name = "regex-2025.11.3-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/66/47/dc2577c1f95f188c1e13e2e69d8825a5ac582ac709942f8a03af42ed6e93/regex-2025.11.3-cp311-cp311-win32.whl",hashes = {sha256 = "3e816cc9aac1cd3cc9a4ec4d860f06d40f994b5c7b4d03b93345f44e08cc68bf"}}, - {name = "regex-2025.11.3-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/50/1e/15f08b2f82a9bbb510621ec9042547b54d11e83cb620643ebb54e4eb7d71/regex-2025.11.3-cp311-cp311-win_amd64.whl",hashes = {sha256 = "087511f5c8b7dfbe3a03f5d5ad0c2a33861b1fc387f21f6f60825a44865a385a"}}, - {name = "regex-2025.11.3-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f4/fc/6500eb39f5f76c5e47a398df82e6b535a5e345f839581012a418b16f9cc3/regex-2025.11.3-cp311-cp311-win_arm64.whl",hashes = {sha256 = "1ff0d190c7f68ae7769cd0313fe45820ba07ffebfddfaa89cc1eb70827ba0ddc"}}, - {name = "regex-2025.11.3-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/8a/d6/d788d52da01280a30a3f6268aef2aa71043bff359c618fea4c5b536654d5/regex-2025.11.3-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "2b441a4ae2c8049106e8b39973bfbddfb25a179dda2bdb99b0eeb60c40a6a3af"}}, - {name = "regex-2025.11.3-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/69/39/abec3bd688ec9bbea3562de0fd764ff802976185f5ff22807bf0a2697992/regex-2025.11.3-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "2fa2eed3f76677777345d2f81ee89f5de2f5745910e805f7af7386a920fa7313"}}, - {name = "regex-2025.11.3-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/39/b3/9a231475d5653e60002508f41205c61684bb2ffbf2401351ae2186897fc4/regex-2025.11.3-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "d8b4a27eebd684319bdf473d39f1d79eed36bf2cd34bd4465cdb4618d82b3d56"}}, - {name = "regex-2025.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/c3/c5/1929a0491bd5ac2d1539a866768b88965fa8c405f3e16a8cef84313098d6/regex-2025.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5cf77eac15bd264986c4a2c63353212c095b40f3affb2bc6b4ef80c4776c1a28"}}, - {name = "regex-2025.11.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ce/fd/16aa16cf5d497ef727ec966f74164fbe75d6516d3d58ac9aa989bc9cdaad/regex-2025.11.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "b7f9ee819f94c6abfa56ec7b1dbab586f41ebbdc0a57e6524bd5e7f487a878c7"}}, - {name = "regex-2025.11.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/e6/49/3294b988855a221cb6565189edf5dc43239957427df2d81d4a6b15244f64/regex-2025.11.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "838441333bc90b829406d4a03cb4b8bf7656231b84358628b0406d803931ef32"}}, - {name = "regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/14/62/b56d29e70b03666193369bdbdedfdc23946dbe9f81dd78ce262c74d988ab/regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "cfe6d3f0c9e3b7e8c0c694b24d25e677776f5ca26dce46fd6b0489f9c8339391"}}, - {name = "regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/fc/e4c31d061eced63fbf1ce9d853975f912c61a7d406ea14eda2dd355f48e7/regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "2ab815eb8a96379a27c3b6157fcb127c8f59c36f043c1678110cea492868f1d5"}}, - {name = "regex-2025.11.3-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b2/bb/5e30c7394bcf63f0537121c23e796be67b55a8847c3956ae6068f4c70702/regex-2025.11.3-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "728a9d2d173a65b62bdc380b7932dd8e74ed4295279a8fe1021204ce210803e7"}}, - {name = "regex-2025.11.3-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c5/c4/fce773710af81b0cb37cb4ff0947e75d5d17dee304b93d940b87a67fc2f4/regex-2025.11.3-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "509dc827f89c15c66a0c216331260d777dd6c81e9a4e4f830e662b0bb296c313"}}, - {name = "regex-2025.11.3-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/7b/5e/9466a7ec4b8ec282077095c6eb50a12a389d2e036581134d4919e8ca518c/regex-2025.11.3-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "849202cd789e5f3cf5dcc7822c34b502181b4824a65ff20ce82da5524e45e8e9"}}, - {name = "regex-2025.11.3-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/95/18/82980a60e8ed1594eb3c89eb814fb276ef51b9af7caeab1340bfd8564af6/regex-2025.11.3-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "b6f78f98741dcc89607c16b1e9426ee46ce4bf31ac5e6b0d40e81c89f3481ea5"}}, - {name = "regex-2025.11.3-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/03/cc/90ab0fdbe6dce064a42015433f9152710139fb04a8b81b4fb57a1cb63ffa/regex-2025.11.3-cp310-cp310-win32.whl",hashes = {sha256 = "149eb0bba95231fb4f6d37c8f760ec9fa6fabf65bab555e128dde5f2475193ec"}}, - {name = "regex-2025.11.3-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/34/9d/e9e8493a85f3b1ddc4a5014465f5c2b78c3ea1cbf238dcfde78956378041/regex-2025.11.3-cp310-cp310-win_amd64.whl",hashes = {sha256 = "ee3a83ce492074c35a74cc76cf8235d49e77b757193a5365ff86e3f2f93db9fd"}}, - {name = "regex-2025.11.3-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/15/c4/b54b24f553966564506dbf873a3e080aef47b356a3b39b5d5aba992b50db/regex-2025.11.3-cp310-cp310-win_arm64.whl",hashes = {sha256 = "38af559ad934a7b35147716655d4a2f79fcef2d695ddfe06a06ba40ae631fa7e"}}, +sdist = {name = "regex-2025.9.18.tar.gz", url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hashes = {sha256 = "c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"}} +wheels = [ + {name = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"}}, + {name = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"}}, + {name = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"}}, + {name = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"}}, + {name = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"}}, + {name = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"}}, + {name = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"}}, + {name = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"dev\" in extras or \"openai\" in extras or \"recommended\" in extras" @@ -2324,20 +1390,7 @@ version = "0.22.1" requires-python = ">=3.9" sdist = {name = "tokenizers-0.22.1.tar.gz", url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", hashes = {sha256 = "61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9"}} wheels = [ - {name = "tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl",hashes = {sha256 = "59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73"}}, - {name = "tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl",hashes = {sha256 = "8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc"}}, - {name = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a"}}, - {name = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7"}}, - {name = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21"}}, - {name = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214"}}, - {name = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f"}}, {name = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4"}}, - {name = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879"}}, - {name = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl",hashes = {sha256 = "331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446"}}, - {name = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl",hashes = {sha256 = "607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a"}}, - {name = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390"}}, - {name = "tokenizers-0.22.1-cp39-abi3-win32.whl",url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl",hashes = {sha256 = "b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82"}}, - {name = "tokenizers-0.22.1-cp39-abi3-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl",hashes = {sha256 = "65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138"}}, ] marker = "\"default\" in dependency_groups" @@ -2363,11 +1416,11 @@ dependencies = [ [[packages]] name = "virtualenv" -version = "20.35.4" +version = "20.35.3" requires-python = ">=3.8" -sdist = {name = "virtualenv-20.35.4.tar.gz", url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hashes = {sha256 = "643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c"}} +sdist = {name = "virtualenv-20.35.3.tar.gz", url = "https://files.pythonhosted.org/packages/a4/d5/b0ccd381d55c8f45d46f77df6ae59fbc23d19e901e2d523395598e5f4c93/virtualenv-20.35.3.tar.gz", hashes = {sha256 = "4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44"}} wheels = [ - {name = "virtualenv-20.35.4-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl",hashes = {sha256 = "c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b"}}, + {name = "virtualenv-20.35.3-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl",hashes = {sha256 = "63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a"}}, ] marker = "\"dev\" in extras" @@ -2411,119 +1464,14 @@ version = "1.22.0" requires-python = ">=3.9" sdist = {name = "yarl-1.22.0.tar.gz", url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hashes = {sha256 = "bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}} wheels = [ - {name = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}}, - {name = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}}, - {name = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}}, - {name = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}}, - {name = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}}, - {name = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}}, - {name = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}}, {name = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}}, - {name = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}}, - {name = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}}, - {name = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}}, - {name = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}}, - {name = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}}, - {name = "yarl-1.22.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl",hashes = {sha256 = "6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}}, - {name = "yarl-1.22.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}}, - {name = "yarl-1.22.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}}, - {name = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}}, - {name = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}}, - {name = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}}, - {name = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}}, - {name = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}}, - {name = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}}, - {name = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}}, {name = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}}, - {name = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}}, - {name = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}}, - {name = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}}, - {name = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}}, - {name = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}}, - {name = "yarl-1.22.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl",hashes = {sha256 = "8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}}, - {name = "yarl-1.22.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}}, - {name = "yarl-1.22.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}}, - {name = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}}, - {name = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}}, - {name = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}}, - {name = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}}, - {name = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}}, - {name = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}}, - {name = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}}, {name = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}}, - {name = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}}, - {name = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}}, - {name = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}}, - {name = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}}, - {name = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}}, - {name = "yarl-1.22.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl",hashes = {sha256 = "d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}}, - {name = "yarl-1.22.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}}, - {name = "yarl-1.22.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}}, - {name = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}}, - {name = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}}, - {name = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}}, - {name = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}}, - {name = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}}, - {name = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}}, - {name = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}}, {name = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}}, - {name = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}}, - {name = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}}, - {name = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}}, - {name = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl",hashes = {sha256 = "5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}}, - {name = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}}, - {name = "yarl-1.22.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl",hashes = {sha256 = "1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}}, - {name = "yarl-1.22.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}}, - {name = "yarl-1.22.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}}, - {name = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}}, - {name = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}}, - {name = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}}, - {name = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}}, - {name = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}}, - {name = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}}, - {name = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}}, {name = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}}, - {name = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}}, - {name = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}}, - {name = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}}, - {name = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}}, - {name = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}}, - {name = "yarl-1.22.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl",hashes = {sha256 = "70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}}, - {name = "yarl-1.22.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}}, - {name = "yarl-1.22.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}}, - {name = "yarl-1.22.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl",hashes = {sha256 = "1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}}, - {name = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}}, - {name = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}}, - {name = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}}, - {name = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}}, - {name = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}}, - {name = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}}, - {name = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}}, {name = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}}, - {name = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}}, - {name = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}}, - {name = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}}, - {name = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}}, - {name = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}}, - {name = "yarl-1.22.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl",hashes = {sha256 = "a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}}, - {name = "yarl-1.22.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}}, - {name = "yarl-1.22.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}}, - {name = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}}, - {name = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}}, - {name = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}}, - {name = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}}, - {name = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}}, - {name = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}}, - {name = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}}, {name = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}}, - {name = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}}, - {name = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}}, - {name = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}}, - {name = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}}, - {name = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}}, - {name = "yarl-1.22.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl",hashes = {sha256 = "595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}}, - {name = "yarl-1.22.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}}, - {name = "yarl-1.22.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}}, + {name = "yarl-1.22.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl",hashes = {sha256 = "1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -2540,112 +1488,14 @@ version = "0.4.1" requires-python = ">=3.9" sdist = {name = "propcache-0.4.1.tar.gz", url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hashes = {sha256 = "f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}} wheels = [ - {name = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}}, - {name = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}}, - {name = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}}, - {name = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}}, - {name = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}}, - {name = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}}, {name = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}}, - {name = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}}, - {name = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}}, - {name = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}}, - {name = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}}, - {name = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}}, - {name = "propcache-0.4.1-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl",hashes = {sha256 = "ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}}, - {name = "propcache-0.4.1-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl",hashes = {sha256 = "5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}}, - {name = "propcache-0.4.1-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl",hashes = {sha256 = "74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}}, - {name = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}}, - {name = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}}, - {name = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}}, - {name = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}}, - {name = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}}, - {name = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}}, {name = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}}, - {name = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}}, - {name = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}}, - {name = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}}, - {name = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}}, - {name = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}}, - {name = "propcache-0.4.1-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl",hashes = {sha256 = "05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}}, - {name = "propcache-0.4.1-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}}, - {name = "propcache-0.4.1-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}}, - {name = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}}, - {name = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}}, - {name = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}}, - {name = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}}, - {name = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}}, - {name = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}}, {name = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}}, - {name = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}}, - {name = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}}, - {name = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}}, - {name = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}}, - {name = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}}, - {name = "propcache-0.4.1-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl",hashes = {sha256 = "bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}}, - {name = "propcache-0.4.1-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl",hashes = {sha256 = "381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}}, - {name = "propcache-0.4.1-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl",hashes = {sha256 = "8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}}, - {name = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}}, - {name = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}}, - {name = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}}, - {name = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}}, - {name = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}}, - {name = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}}, {name = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}}, - {name = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}}, - {name = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}}, - {name = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}}, - {name = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl",hashes = {sha256 = "e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}}, - {name = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}}, - {name = "propcache-0.4.1-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl",hashes = {sha256 = "2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}}, - {name = "propcache-0.4.1-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}}, - {name = "propcache-0.4.1-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}}, - {name = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}}, - {name = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}}, - {name = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}}, - {name = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}}, - {name = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}}, - {name = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}}, {name = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}}, - {name = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}}, - {name = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}}, - {name = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}}, - {name = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}}, - {name = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}}, - {name = "propcache-0.4.1-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl",hashes = {sha256 = "671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}}, - {name = "propcache-0.4.1-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl",hashes = {sha256 = "cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}}, - {name = "propcache-0.4.1-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl",hashes = {sha256 = "204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}}, - {name = "propcache-0.4.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl",hashes = {sha256 = "af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}}, - {name = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}}, - {name = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}}, - {name = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}}, - {name = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}}, - {name = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}}, - {name = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}}, {name = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}}, - {name = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}}, - {name = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}}, - {name = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}}, - {name = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}}, - {name = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}}, - {name = "propcache-0.4.1-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl",hashes = {sha256 = "f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}}, - {name = "propcache-0.4.1-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl",hashes = {sha256 = "364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}}, - {name = "propcache-0.4.1-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl",hashes = {sha256 = "e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}}, - {name = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}}, - {name = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}}, - {name = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}}, - {name = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}}, - {name = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}}, - {name = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}}, {name = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}}, - {name = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}}, - {name = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}}, - {name = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}}, - {name = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}}, - {name = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}}, - {name = "propcache-0.4.1-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl",hashes = {sha256 = "a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}}, - {name = "propcache-0.4.1-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl",hashes = {sha256 = "1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}}, - {name = "propcache-0.4.1-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl",hashes = {sha256 = "d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}}, + {name = "propcache-0.4.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl",hashes = {sha256 = "af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -2680,18 +1530,16 @@ dependencies = [] [[packages]] name = "aiologic" -version = "0.15.0" +version = "0.14.0" requires-python = ">=3.8" -sdist = {name = "aiologic-0.15.0.tar.gz", url = "https://files.pythonhosted.org/packages/85/ae/93a6dcdcffc15c890fc3d899e62f3a16eefb507553c6e531f26cbe5799da/aiologic-0.15.0.tar.gz", hashes = {sha256 = "2aa851d2298588f9bfa543a425773e8c3e07069f96c09ccc7106c0dd899fe5fd"}} +sdist = {name = "aiologic-0.14.0.tar.gz", url = "https://files.pythonhosted.org/packages/7e/2d/e893dcfa041dab1d045abfc8898239747cde19881796640861609138d360/aiologic-0.14.0.tar.gz", hashes = {sha256 = "c87925fa2bfe9ae292859e1094eb8fb6d456c8202a16405b0a44134803c8a791"}} wheels = [ - {name = "aiologic-0.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/21/bd/1666bf801f84e6f18872653a5884a7d15d7dbcae307899f5603b11d1438b/aiologic-0.15.0-py3-none-any.whl",hashes = {sha256 = "35f9f906bd80424b39c475b3b7f8dd853ee279a176b73b9bc290a41feb1b0db7"}}, + {name = "aiologic-0.14.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/4d/1f/f797b684fb4e11a5066ab464b460b5cfdbaedea9c4a3d0f0afc8e894ada0/aiologic-0.14.0-py3-none-any.whl",hashes = {sha256 = "cc59d39dc1d5e2575b4a6b5faf678b551fb0f910c7cb42e4c5f5689ffedcce78"}}, ] marker = "\"default\" in dependency_groups" [packages.tool.pdm] dependencies = [ - "sniffio>=1.3.0", - "typing-extensions>=4.5.0; python_version < \"3.13\"", "wrapt>=1.16.0", ] @@ -2717,119 +1565,14 @@ version = "1.8.0" requires-python = ">=3.9" sdist = {name = "frozenlist-1.8.0.tar.gz", url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hashes = {sha256 = "3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}} wheels = [ - {name = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}}, - {name = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}}, - {name = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}}, {name = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}}, - {name = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}}, - {name = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}}, - {name = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}}, - {name = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}}, - {name = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}}, - {name = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}}, - {name = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}}, - {name = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}}, - {name = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}}, - {name = "frozenlist-1.8.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl",hashes = {sha256 = "bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}}, - {name = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}}, - {name = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}}, {name = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl",hashes = {sha256 = "342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}}, - {name = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}}, - {name = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}}, - {name = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}}, - {name = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}}, {name = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}}, - {name = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}}, - {name = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}}, - {name = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}}, - {name = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}}, - {name = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}}, - {name = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}}, - {name = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}}, - {name = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}}, - {name = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}}, - {name = "frozenlist-1.8.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl",hashes = {sha256 = "8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}}, - {name = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}}, - {name = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}}, {name = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl",hashes = {sha256 = "032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl",hashes = {sha256 = "0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}}, - {name = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}}, - {name = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}}, - {name = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}}, - {name = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}}, {name = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}}, - {name = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}}, - {name = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}}, - {name = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}}, - {name = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}}, - {name = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}}, - {name = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}}, - {name = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}}, - {name = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}}, - {name = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}}, - {name = "frozenlist-1.8.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl",hashes = {sha256 = "433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}}, - {name = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}}, - {name = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}}, - {name = "frozenlist-1.8.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl",hashes = {sha256 = "0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}}, - {name = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}}, - {name = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}}, - {name = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}}, {name = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}}, - {name = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}}, - {name = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}}, - {name = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}}, - {name = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}}, - {name = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}}, - {name = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}}, - {name = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}}, - {name = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}}, - {name = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}}, - {name = "frozenlist-1.8.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl",hashes = {sha256 = "27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}}, - {name = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}}, - {name = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}}, - {name = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}}, - {name = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}}, - {name = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}}, {name = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}}, - {name = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}}, - {name = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl",hashes = {sha256 = "c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}}, - {name = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}}, - {name = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}}, - {name = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}}, - {name = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}}, - {name = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}}, - {name = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}}, - {name = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}}, - {name = "frozenlist-1.8.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl",hashes = {sha256 = "adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}}, - {name = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}}, - {name = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}}, + {name = "frozenlist-1.8.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl",hashes = {sha256 = "0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -2931,109 +1674,54 @@ marker = "\"dev\" in extras" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "colorama" +version = "0.4.6" +requires-python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +sdist = {name = "colorama-0.4.6.tar.gz", url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hashes = {sha256 = "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}} +wheels = [ + {name = "colorama-0.4.6-py2.py3-none-any.whl",url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",hashes = {sha256 = "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}}, +] +marker = "\"dev\" in extras" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "coverage" -version = "7.11.0" +version = "7.11.1" requires-python = ">=3.10" -sdist = {name = "coverage-7.11.0.tar.gz", url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hashes = {sha256 = "167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}} -wheels = [ - {name = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}}, - {name = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}}, - {name = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}}, - {name = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}}, - {name = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}}, - {name = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}}, - {name = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}}, - {name = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl",hashes = {sha256 = "5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}}, - {name = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl",hashes = {sha256 = "f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}}, - {name = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}}, - {name = "coverage-7.11.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl",hashes = {sha256 = "bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}}, - {name = "coverage-7.11.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}}, - {name = "coverage-7.11.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}}, - {name = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}}, - {name = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}}, - {name = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}}, - {name = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}}, - {name = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}}, - {name = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}}, - {name = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}}, - {name = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl",hashes = {sha256 = "ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}}, - {name = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}}, - {name = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}}, - {name = "coverage-7.11.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl",hashes = {sha256 = "4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}}, - {name = "coverage-7.11.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}}, - {name = "coverage-7.11.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}}, - {name = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}}, - {name = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}}, - {name = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}}, - {name = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}}, - {name = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}}, - {name = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}}, - {name = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}}, - {name = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}}, - {name = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl",hashes = {sha256 = "8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}}, - {name = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}}, - {name = "coverage-7.11.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl",hashes = {sha256 = "695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}}, - {name = "coverage-7.11.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}}, - {name = "coverage-7.11.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}}, - {name = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}}, - {name = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}}, - {name = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}}, - {name = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}}, - {name = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}}, - {name = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}}, - {name = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}}, - {name = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl",hashes = {sha256 = "d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}}, - {name = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}}, - {name = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}}, - {name = "coverage-7.11.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl",hashes = {sha256 = "cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}}, - {name = "coverage-7.11.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}}, - {name = "coverage-7.11.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}}, - {name = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}}, - {name = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}}, - {name = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}}, - {name = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}}, - {name = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}}, - {name = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}}, - {name = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}}, - {name = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}}, - {name = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl",hashes = {sha256 = "dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}}, - {name = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}}, - {name = "coverage-7.11.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl",hashes = {sha256 = "037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}}, - {name = "coverage-7.11.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}}, - {name = "coverage-7.11.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}}, - {name = "coverage-7.11.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl",hashes = {sha256 = "4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}}, - {name = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}}, - {name = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}}, - {name = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}}, - {name = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}}, - {name = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}}, - {name = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}}, - {name = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}}, - {name = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}}, - {name = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl",hashes = {sha256 = "258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}}, - {name = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}}, - {name = "coverage-7.11.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl",hashes = {sha256 = "fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}}, - {name = "coverage-7.11.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}}, - {name = "coverage-7.11.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}}, - {name = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/12/95/c49df0aceb5507a80b9fe5172d3d39bf23f05be40c23c8d77d556df96cec/coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}}, - {name = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/dc/c6/7bb46ce01ed634fff1d7bb53a54049f539971862cc388b304ff3c51b4f66/coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}}, - {name = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/94/b2/75d9d8fbf2900268aca5de29cd0a0fe671b0f69ef88be16767cc3c828b85/coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}}, - {name = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/ac/acaa984c18f440170525a8743eb4b6c960ace2dbad80dc22056a437fc3c6/coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}}, - {name = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d8/0d/938d0bff76dfa4a6b228c3fc4b3e1c0e2ad4aa6200c141fcda2bd1170227/coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}}, - {name = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/38/54/8f5f5e84bfa268df98f46b2cb396b1009734cfb1e5d6adb663d284893b32/coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}}, - {name = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/68/30/8ba337c2877fe3f2e1af0ed7ff4be0c0c4aca44d6f4007040f3ca2255e99/coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}}, - {name = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/cc/fb/c6f1d6d9a665536b7dde2333346f0cc41dc6a60bd1ffc10cd5c33e7eb000/coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}}, - {name = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/be/38/1b532319af5f991fa153c20373291dc65c2bf532af7dbcffdeef745c8f79/coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl",hashes = {sha256 = "7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}}, - {name = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/67/3d/f39331c60ef6050d2a861dc1b514fa78f85f792820b68e8c04196ad733d6/coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}}, - {name = "coverage-7.11.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/4b/55/cb7c9df9d0495036ce582a8a2958d50c23cd73f84a23284bc23bd4711a6f/coverage-7.11.0-cp310-cp310-win32.whl",hashes = {sha256 = "765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}}, - {name = "coverage-7.11.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/68/a8/b79cb275fa7bd0208767f89d57a1b5f6ba830813875738599741b97c2e04/coverage-7.11.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}}, +sdist = {name = "coverage-7.11.1.tar.gz", url = "https://files.pythonhosted.org/packages/89/12/3e2d2ec71796e0913178478e693a06af6a3bc9f7f9cb899bf85a426d8370/coverage-7.11.1.tar.gz", hashes = {sha256 = "b4b3a072559578129a9e863082a2972a2abd8975bc0e2ec57da96afcd6580a8a"}} +wheels = [ + {name = "coverage-7.11.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/1d/5e/ce442bab963e3388658da8bde6ddbd0a15beda230afafaa25e3c487dc391/coverage-7.11.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "bde6488c1ad509f4fb1a4f9960fd003d5a94adef61e226246f9699befbab3276"}}, + {name = "coverage-7.11.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/05/98/d484cb659ec33958ca96b6f03438f56edc23b239d1ad0417b7a97fc1848a/coverage-7.11.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "28ad84c694fa86084cfd3c1eab4149844b8cb95bd8e5cbfc4a647f3ee2cce2b3"}}, + {name = "coverage-7.11.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/f5/b2c7c494046c9c783d3cac4c812fc24d6104dd36a7a598e7dd6fea3e7927/coverage-7.11.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "211f7996265daab60a8249af4ca6641b3080769cbedcffc42cc4841118f3a305"}}, + {name = "coverage-7.11.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/49/eccfe039663e29a50a54b0c2c8d076acd174d7ac50d018ef8a5b1c37c8dc/coverage-7.11.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "6c354b111be9b2234d9573d75dd30ca4e414b7659c730e477e89be4f620b3fb5"}}, + {name = "coverage-7.11.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/e3/e1/c4b42f02fbb6ce08e05d7a2b26bcf5df11d3e67a3806e40415f7ab9511e7/coverage-7.11.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "b59736704df8b1f8b1dafb36b16f2ef8a952e4410465634442459426bd2319ae"}}, + {name = "coverage-7.11.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/05/2887d76a5e160eb1b62dc99b1f177052799c37134d38e8b208e01bd4d712/coverage-7.11.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "e17d99e4a9989ccc52d672543ed9d8741d90730ba331d452793be5733b4fee58"}}, + {name = "coverage-7.11.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/38/3f/5678792f90d4c8467531a4db9b66a8929cee0c9f28a8f5fed0e94d7e1d3e/coverage-7.11.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "086764f9fa6f4fa57035ed1c2387501c57092f2159bf1be0f090f85f9042ccf2"}}, + {name = "coverage-7.11.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/76/32/bd9f48c28e23b2f08946f8e83983617b00619f5538dbd7e1045fa7e88c00/coverage-7.11.1-py3-none-any.whl",hashes = {sha256 = "0fa848acb5f1da24765cee840e1afe9232ac98a8f9431c6112c15b34e880b9e8"}}, ] marker = "\"dev\" in extras" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "exceptiongroup" +version = "1.3.0" +requires-python = ">=3.7" +sdist = {name = "exceptiongroup-1.3.0.tar.gz", url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hashes = {sha256 = "b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}} +wheels = [ + {name = "exceptiongroup-1.3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl",hashes = {sha256 = "4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}}, +] +marker = "\"default\" in dependency_groups and python_version < \"3.11\" or \"all\" in extras and python_version < \"3.11\" or \"audio\" in extras and python_version < \"3.11\" or \"dev\" in extras and python_version < \"3.11\" or \"vision\" in extras and python_version < \"3.11\"" + +[packages.tool.pdm] +dependencies = [ + "typing-extensions>=4.6.0; python_version < \"3.13\"", +] + [[packages]] name = "h11" version = "0.16.0" @@ -3066,41 +1754,11 @@ version = "0.7.1" requires-python = ">=3.9" sdist = {name = "httptools-0.7.1.tar.gz", url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hashes = {sha256 = "abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9"}} wheels = [ - {name = "httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270"}}, - {name = "httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3"}}, {name = "httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1"}}, - {name = "httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b"}}, - {name = "httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60"}}, - {name = "httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca"}}, - {name = "httptools-0.7.1-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl",hashes = {sha256 = "cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96"}}, - {name = "httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3"}}, - {name = "httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca"}}, {name = "httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c"}}, - {name = "httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66"}}, - {name = "httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346"}}, - {name = "httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650"}}, - {name = "httptools-0.7.1-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl",hashes = {sha256 = "322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6"}}, - {name = "httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5"}}, - {name = "httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5"}}, {name = "httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03"}}, - {name = "httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2"}}, - {name = "httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362"}}, - {name = "httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c"}}, - {name = "httptools-0.7.1-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl",hashes = {sha256 = "3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321"}}, - {name = "httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657"}}, - {name = "httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70"}}, {name = "httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df"}}, - {name = "httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e"}}, - {name = "httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274"}}, - {name = "httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec"}}, - {name = "httptools-0.7.1-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl",hashes = {sha256 = "135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb"}}, - {name = "httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78"}}, - {name = "httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4"}}, {name = "httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05"}}, - {name = "httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed"}}, - {name = "httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a"}}, - {name = "httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b"}}, - {name = "httptools-0.7.1-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl",hashes = {sha256 = "cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568"}}, ] marker = "\"default\" in dependency_groups" @@ -3133,6 +1791,22 @@ marker = "\"dev\" in extras" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "importlib-metadata" +version = "8.7.0" +requires-python = ">=3.9" +sdist = {name = "importlib_metadata-8.7.0.tar.gz", url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hashes = {sha256 = "d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}} +wheels = [ + {name = "importlib_metadata-8.7.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl",hashes = {sha256 = "e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}}, +] +marker = "python_full_version < \"3.10.2\" and \"dev\" in extras" + +[packages.tool.pdm] +dependencies = [ + "zipp>=3.20", + "typing-extensions>=3.6.4; python_version < \"3.8\"", +] + [[packages]] name = "jinja2" version = "3.1.6" @@ -3154,122 +1828,22 @@ version = "6.0.2" requires-python = ">=3.8" sdist = {name = "lxml-6.0.2.tar.gz", url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hashes = {sha256 = "cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"}} wheels = [ - {name = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"}}, - {name = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"}}, {name = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl",hashes = {sha256 = "dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"}}, - {name = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"}}, - {name = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"}}, - {name = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl",hashes = {sha256 = "65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"}}, - {name = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"}}, - {name = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl",hashes = {sha256 = "d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"}}, - {name = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"}}, - {name = "lxml-6.0.2-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl",hashes = {sha256 = "be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"}}, - {name = "lxml-6.0.2-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl",hashes = {sha256 = "fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"}}, - {name = "lxml-6.0.2-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl",hashes = {sha256 = "063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"}}, - {name = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"}}, - {name = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"}}, {name = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl",hashes = {sha256 = "957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"}}, - {name = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"}}, - {name = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"}}, - {name = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl",hashes = {sha256 = "1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"}}, - {name = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"}}, - {name = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"}}, - {name = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"}}, - {name = "lxml-6.0.2-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl",hashes = {sha256 = "da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"}}, - {name = "lxml-6.0.2-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"}}, - {name = "lxml-6.0.2-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"}}, - {name = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"}}, - {name = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"}}, + {name = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"}}, + {name = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"}}, {name = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl",hashes = {sha256 = "4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"}}, - {name = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"}}, - {name = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"}}, - {name = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl",hashes = {sha256 = "2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"}}, - {name = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"}}, - {name = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl",hashes = {sha256 = "fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"}}, - {name = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"}}, - {name = "lxml-6.0.2-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl",hashes = {sha256 = "e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"}}, - {name = "lxml-6.0.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"}}, - {name = "lxml-6.0.2-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl",hashes = {sha256 = "13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"}}, - {name = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"}}, - {name = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"}}, + {name = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"}}, {name = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl",hashes = {sha256 = "064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"}}, - {name = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"}}, - {name = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"}}, - {name = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl",hashes = {sha256 = "6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"}}, - {name = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"}}, - {name = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl",hashes = {sha256 = "2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"}}, - {name = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"}}, - {name = "lxml-6.0.2-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl",hashes = {sha256 = "3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"}}, - {name = "lxml-6.0.2-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl",hashes = {sha256 = "72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"}}, - {name = "lxml-6.0.2-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl",hashes = {sha256 = "61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"}}, - {name = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"}}, - {name = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"}}, - {name = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"}}, - {name = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"}}, - {name = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"}}, - {name = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"}}, + {name = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"}}, {name = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"}}, - {name = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl",hashes = {sha256 = "b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"}}, - {name = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"}}, - {name = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"}}, - {name = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl",hashes = {sha256 = "3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"}}, - {name = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl",hashes = {sha256 = "2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"}}, - {name = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"}}, - {name = "lxml-6.0.2-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl",hashes = {sha256 = "6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"}}, - {name = "lxml-6.0.2-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl",hashes = {sha256 = "e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"}}, - {name = "lxml-6.0.2-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl",hashes = {sha256 = "4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"}}, - {name = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"}}, - {name = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"}}, - {name = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"}}, - {name = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"}}, {name = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"}}, - {name = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl",hashes = {sha256 = "27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"}}, - {name = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/db/8a/f8192a08237ef2fb1b19733f709db88a4c43bc8ab8357f01cb41a27e7f6a/lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"}}, - {name = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/12/64/27bcd07ae17ff5e5536e8d88f4c7d581b48963817a13de11f3ac3329bfa2/lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"}}, - {name = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/02/5a/a7d53b3291c324e0b6e48f3c797be63836cc52156ddf8f33cd72aac78866/lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"}}, - {name = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/55/d465e9b89df1761674d8672bb3e4ae2c47033b01ec243964b6e334c6743f/lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"}}, - {name = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/62/38/3073cd7e3e8dfc3ba3c3a139e33bee3a82de2bfb0925714351ad3d255c13/lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"}}, - {name = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/4a/d3/1e001588c5e2205637b08985597827d3827dbaaece16348c8822bfe61c29/lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"}}, + {name = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"}}, + {name = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"}}, {name = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/20/cf/cab09478699b003857ed6ebfe95e9fb9fa3d3c25f1353b905c9b73cfb624/lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"}}, - {name = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl",url = "https://files.pythonhosted.org/packages/a3/84/02a2d0c38ac9a8b9f9e5e1bbd3f24b3f426044ad618b552e9549ee91bd63/lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl",hashes = {sha256 = "f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"}}, - {name = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/56/87/e1ceadcc031ec4aa605fe95476892d0b0ba3b7f8c7dcdf88fdeff59a9c86/lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"}}, - {name = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/fe/13/5bb6cf42bb228353fd4ac5f162c6a84fd68a4d6f67c1031c8cf97e131fc6/lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"}}, - {name = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/e4/e2/ea0498552102e59834e297c5c6dff8d8ded3db72ed5e8aad77871476f073/lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl",hashes = {sha256 = "3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"}}, - {name = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/6a/9e/8de42b52a73abb8af86c66c969b3b4c2a96567b6ac74637c037d2e3baa60/lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl",hashes = {sha256 = "59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"}}, - {name = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/28/a2/de776a573dfb15114509a37351937c367530865edb10a90189d0b4b9b70a/lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"}}, - {name = "lxml-6.0.2-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/50/a0/3ae1b1f8964c271b5eec91db2043cf8c6c0bce101ebb2a633b51b044db6c/lxml-6.0.2-cp310-cp310-win32.whl",hashes = {sha256 = "1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"}}, - {name = "lxml-6.0.2-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d1/70/bd42491f0634aad41bdfc1e46f5cff98825fb6185688dc82baa35d509f1a/lxml-6.0.2-cp310-cp310-win_amd64.whl",hashes = {sha256 = "dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"}}, - {name = "lxml-6.0.2-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/d2/d0/05c6a72299f54c2c561a6c6cbb2f512e047fca20ea97a05e57931f194ac4/lxml-6.0.2-cp310-cp310-win_arm64.whl",hashes = {sha256 = "45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"}}, - {name = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/e7/9c/780c9a8fce3f04690b374f72f41306866b0400b9d0fdf3e17aaa37887eed/lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"}}, - {name = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",url = "https://files.pythonhosted.org/packages/f5/5a/1ab260c00adf645d8bf7dec7f920f744b032f69130c681302821d5debea6/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl",hashes = {sha256 = "4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"}}, - {name = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/f2/37/565f3b3d7ffede22874b6d86be1a1763d00f4ea9fc5b9b6ccb11e4ec8612/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"}}, - {name = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/22/ec/f3a1b169b2fb9d03467e2e3c0c752ea30e993be440a068b125fc7dd248b0/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"}}, {name = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/77/a2/585a28fe3e67daa1cf2f06f34490d556d121c25d500b10082a7db96e3bcd/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"}}, - {name = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7b/d9/a57dd8bcebd7c69386c20263830d4fa72d27e6b72a229ef7a48e88952d9a/lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl",hashes = {sha256 = "24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"}}, + {name = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/55/d465e9b89df1761674d8672bb3e4ae2c47033b01ec243964b6e334c6743f/lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"}}, + {name = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",url = "https://files.pythonhosted.org/packages/f2/37/565f3b3d7ffede22874b6d86be1a1763d00f4ea9fc5b9b6ccb11e4ec8612/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",hashes = {sha256 = "cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"}}, ] marker = "\"all\" in extras or \"dev\" in extras or \"openai\" in extras or \"recommended\" in extras" @@ -3282,83 +1856,13 @@ version = "3.0.3" requires-python = ">=3.9" sdist = {name = "markupsafe-3.0.3.tar.gz", url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hashes = {sha256 = "722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}} wheels = [ - {name = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}}, - {name = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}}, - {name = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}}, {name = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}}, - {name = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}}, - {name = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}}, - {name = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl",hashes = {sha256 = "0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}}, - {name = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}}, - {name = "markupsafe-3.0.3-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl",hashes = {sha256 = "729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}}, - {name = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl",hashes = {sha256 = "bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}}, - {name = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl",hashes = {sha256 = "5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}}, {name = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl",hashes = {sha256 = "915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}}, - {name = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}}, - {name = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}}, - {name = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}}, - {name = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}}, {name = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}}, - {name = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}}, - {name = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}}, - {name = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl",hashes = {sha256 = "795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}}, - {name = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}}, - {name = "markupsafe-3.0.3-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl",hashes = {sha256 = "bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}}, - {name = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl",hashes = {sha256 = "9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}}, - {name = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl",hashes = {sha256 = "7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}}, {name = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl",hashes = {sha256 = "69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}}, - {name = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}}, - {name = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}}, - {name = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}}, - {name = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}}, {name = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}}, - {name = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}}, - {name = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}}, - {name = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl",hashes = {sha256 = "83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}}, - {name = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}}, - {name = "markupsafe-3.0.3-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl",hashes = {sha256 = "d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}}, - {name = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl",hashes = {sha256 = "26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}}, - {name = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl",hashes = {sha256 = "35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}}, - {name = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}}, - {name = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}}, - {name = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}}, {name = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}}, - {name = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}}, - {name = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}}, - {name = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl",hashes = {sha256 = "7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}}, - {name = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}}, - {name = "markupsafe-3.0.3-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl",hashes = {sha256 = "0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}}, - {name = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl",hashes = {sha256 = "de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}}, - {name = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl",hashes = {sha256 = "3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}}, - {name = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}}, - {name = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}}, - {name = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}}, {name = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}}, - {name = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}}, - {name = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}}, - {name = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl",hashes = {sha256 = "d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}}, - {name = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}}, - {name = "markupsafe-3.0.3-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl",hashes = {sha256 = "2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}}, - {name = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl",hashes = {sha256 = "c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}}, - {name = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl",hashes = {sha256 = "e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras" @@ -3396,26 +1900,20 @@ dependencies = [] [[packages]] name = "multiprocess" -version = "0.70.18" +version = "0.70.16" requires-python = ">=3.8" -sdist = {name = "multiprocess-0.70.18.tar.gz", url = "https://files.pythonhosted.org/packages/72/fd/2ae3826f5be24c6ed87266bc4e59c46ea5b059a103f3d7e7eb76a52aeecb/multiprocess-0.70.18.tar.gz", hashes = {sha256 = "f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d"}} -wheels = [ - {name = "multiprocess-0.70.18-py313-none-any.whl",url = "https://files.pythonhosted.org/packages/ee/25/7d7e78e750bc1aecfaf0efbf826c69a791d2eeaf29cf20cba93ff4cced78/multiprocess-0.70.18-py313-none-any.whl",hashes = {sha256 = "871743755f43ef57d7910a38433cfe41319e72be1bbd90b79c7a5ac523eb9334"}}, - {name = "multiprocess-0.70.18-py312-none-any.whl",url = "https://files.pythonhosted.org/packages/bf/b6/5f922792be93b82ec6b5f270bbb1ef031fd0622847070bbcf9da816502cc/multiprocess-0.70.18-py312-none-any.whl",hashes = {sha256 = "9b78f8e5024b573730bfb654783a13800c2c0f2dfc0c25e70b40d184d64adaa2"}}, - {name = "multiprocess-0.70.18-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/55/4d/9af0d1279c84618bcd35bf5fd7e371657358c7b0a523e54a9cffb87461f8/multiprocess-0.70.18-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "8b8940ae30139e04b076da6c5b83e9398585ebdf0f2ad3250673fef5b2ff06d6"}}, - {name = "multiprocess-0.70.18-pp311-pypy311_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/17/bf/87323e79dd0562474fad3373c21c66bc6c3c9963b68eb2a209deb4c8575e/multiprocess-0.70.18-pp311-pypy311_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "0929ba95831adb938edbd5fb801ac45e705ecad9d100b3e653946b7716cb6bd3"}}, - {name = "multiprocess-0.70.18-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/dd/74/cb8c831e58dc6d5cf450b17c7db87f14294a1df52eb391da948b5e0a0b94/multiprocess-0.70.18-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl",hashes = {sha256 = "4d77f8e4bfe6c6e2e661925bbf9aed4d5ade9a1c6502d5dfc10129b9d1141797"}}, - {name = "multiprocess-0.70.18-py311-none-any.whl",url = "https://files.pythonhosted.org/packages/4b/88/9039f2fed1012ef584751d4ceff9ab4a51e5ae264898f0b7cbf44340a859/multiprocess-0.70.18-py311-none-any.whl",hashes = {sha256 = "5aa6eef98e691281b3ad923be2832bf1c55dd2c859acd73e5ec53a66aae06a1d"}}, - {name = "multiprocess-0.70.18-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/f8/7f9a8f08bf98cea1dfaa181e05cc8bbcb59cecf044b5a9ac3cce39f9c449/multiprocess-0.70.18-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "25d4012dcaaf66b9e8e955f58482b42910c2ee526d532844d8bcf661bbc604df"}}, - {name = "multiprocess-0.70.18-pp310-pypy310_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e5/03/b7b10dbfc17b2b3ce07d4d30b3ba8367d0ed32d6d46cd166e298f161dd46/multiprocess-0.70.18-pp310-pypy310_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "06b19433de0d02afe5869aec8931dd5c01d99074664f806c73896b0d9e527213"}}, - {name = "multiprocess-0.70.18-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/c1/a3/5f8d3b9690ea5580bee5868ab7d7e2cfca74b7e826b28192b40aa3881cdc/multiprocess-0.70.18-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl",hashes = {sha256 = "6fa1366f994373aaf2d4738b0f56e707caeaa05486e97a7f71ee0853823180c2"}}, - {name = "multiprocess-0.70.18-py310-none-any.whl",url = "https://files.pythonhosted.org/packages/ba/d8/0cba6cf51a1a31f20471fbc823a716170c73012ddc4fb85d706630ed6e8f/multiprocess-0.70.18-py310-none-any.whl",hashes = {sha256 = "60c194974c31784019c1f459d984e8f33ee48f10fcf42c309ba97b30d9bd53ea"}}, +sdist = {name = "multiprocess-0.70.16.tar.gz", url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hashes = {sha256 = "161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1"}} +wheels = [ + {name = "multiprocess-0.70.16-py312-none-any.whl",url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl",hashes = {sha256 = "fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e"}}, + {name = "multiprocess-0.70.16-py311-none-any.whl",url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl",hashes = {sha256 = "af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a"}}, + {name = "multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl",hashes = {sha256 = "d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec"}}, + {name = "multiprocess-0.70.16-py310-none-any.whl",url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl",hashes = {sha256 = "c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" [packages.tool.pdm] dependencies = [ - "dill>=0.4.0", + "dill>=0.3.8", ] [[packages]] @@ -3433,13 +1931,13 @@ dependencies = [] [[packages]] name = "networkx" -version = "3.5" -requires-python = ">=3.11" -sdist = {name = "networkx-3.5.tar.gz", url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hashes = {sha256 = "d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}} +version = "3.4.2" +requires-python = ">=3.10" +sdist = {name = "networkx-3.4.2.tar.gz", url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hashes = {sha256 = "307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}} wheels = [ - {name = "networkx-3.5-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl",hashes = {sha256 = "0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}}, + {name = "networkx-3.4.2-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl",hashes = {sha256 = "df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}}, ] -marker = "python_version ~= \"3.12\"" +marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras" [packages.tool.pdm] dependencies = [] @@ -3459,59 +1957,15 @@ dependencies = [] [[packages]] name = "pyarrow" -version = "22.0.0" -requires-python = ">=3.10" -sdist = {name = "pyarrow-22.0.0.tar.gz", url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hashes = {sha256 = "3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9"}} -wheels = [ - {name = "pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bd/b0/0fa4d28a8edb42b0a7144edd20befd04173ac79819547216f8a9f36f9e50/pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl",hashes = {sha256 = "9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d"}}, - {name = "pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/0f/a8/7a719076b3c1be0acef56a07220c586f25cd24de0e3f3102b438d18ae5df/pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl",hashes = {sha256 = "e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9"}}, - {name = "pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/89/3c/359ed54c93b47fb6fe30ed16cdf50e3f0e8b9ccfb11b86218c3619ae50a8/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl",hashes = {sha256 = "92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7"}}, - {name = "pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/55/fc/4945896cc8638536ee787a3bd6ce7cec8ec9acf452d78ec39ab328efa0a1/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl",hashes = {sha256 = "6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde"}}, - {name = "pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/cd/5e/7cb7edeb2abfaa1f79b5d5eb89432356155c8426f75d3753cbcb9592c0fd/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc"}}, - {name = "pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/88/c6/546baa7c48185f5e9d6e59277c4b19f30f48c94d9dd938c2a80d4d6b067c/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0"}}, - {name = "pyarrow-22.0.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/3c/79/755ff2d145aafec8d347bf18f95e4e81c00127f06d080135dfc86aea417c/pyarrow-22.0.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/0e/d2/237d75ac28ced3147912954e3c1a174df43a95f4f88e467809118a8165e0/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl",hashes = {sha256 = "7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/1e/2c/733dfffe6d3069740f98e57ff81007809067d68626c5faef293434d11bd6/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl",hashes = {sha256 = "c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/7c/2b/29d6e3782dc1f299727462c1543af357a0f2c1d3c160ce199950d9ca51eb/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/8d/42/aa9355ecc05997915af1b7b947a7f66c02dcaa927f3203b87871c114ba10/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ee/62/45abedde480168e83a1de005b7b7043fd553321c1e8c5a9a114425f64842/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/84/e9/7878940a5b072e4f3bf998770acafeae13b267f9893af5f6d4ab3904b67e/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80"}}, - {name = "pyarrow-22.0.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7b/03/f335d6c52b4a4761bcc83499789a1e2e16d9d201a58c327a9b5cc9a41bd9/pyarrow-22.0.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae"}}, - {name = "pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a"}}, - {name = "pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901"}}, - {name = "pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ff/c0/782344c2ce58afbea010150df07e3a2f5fdad299cd631697ae7bd3bac6e3/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl",hashes = {sha256 = "ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691"}}, - {name = "pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl",hashes = {sha256 = "e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a"}}, - {name = "pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/69/4d/76e567a4fc2e190ee6072967cb4672b7d9249ac59ae65af2d7e3047afa3b/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6"}}, - {name = "pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/01/5e/5653f0535d2a1aef8223cee9d92944cb6bccfee5cf1cd3f462d7cb022790/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941"}}, - {name = "pyarrow-22.0.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/90/81/db56870c997805bf2b0f6eeeb2d68458bf4654652dccdcf1bf7a42d80903/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl",hashes = {sha256 = "f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/1c/98/0727947f199aba8a120f47dfc229eeb05df15bcd7a6f1b669e9f882afc58/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl",hashes = {sha256 = "ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/96/b4/9babdef9c01720a0785945c7cf550e4acd0ebcd7bdd2e6f0aa7981fa85e2/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl",hashes = {sha256 = "c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f8/ca/2f8804edd6279f78a37062d813de3f16f29183874447ef6d1aadbb4efa0f/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95"}}, - {name = "pyarrow-22.0.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc"}}, - {name = "pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl",hashes = {sha256 = "bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d"}}, - {name = "pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl",hashes = {sha256 = "12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8"}}, - {name = "pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl",hashes = {sha256 = "334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5"}}, - {name = "pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl",hashes = {sha256 = "c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe"}}, - {name = "pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e"}}, - {name = "pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9"}}, - {name = "pyarrow-22.0.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d"}}, - {name = "pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/2e/b7/18f611a8cdc43417f9394a3ccd3eace2f32183c08b9eddc3d17681819f37/pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl",hashes = {sha256 = "3e294c5eadfb93d78b0763e859a0c16d4051fc1c5231ae8956d61cb0b5666f5a"}}, - {name = "pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/26/5c/f259e2526c67eb4b9e511741b19870a02363a47a35edbebc55c3178db22d/pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl",hashes = {sha256 = "69763ab2445f632d90b504a815a2a033f74332997052b721002298ed6de40f2e"}}, - {name = "pyarrow-22.0.0-cp311-cp311-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/50/8d/281f0f9b9376d4b7f146913b26fac0aa2829cd1ee7e997f53a27411bbb92/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_aarch64.whl",hashes = {sha256 = "b41f37cabfe2463232684de44bad753d6be08a7a072f6a83447eeaf0e4d2a215"}}, - {name = "pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/f5/e5/53c0a1c428f0976bf22f513d79c73000926cb00b9c138d8e02daf2102e18/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl",hashes = {sha256 = "35ad0f0378c9359b3f297299c3309778bb03b8612f987399a0333a560b43862d"}}, - {name = "pyarrow-22.0.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/95/e1/9dbe4c465c3365959d183e6345d0a8d1dc5b02ca3f8db4760b3bc834cf25/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "8382ad21458075c2e66a82a29d650f963ce51c7708c7c0ff313a8c206c4fd5e8"}}, - {name = "pyarrow-22.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/c5/b4/7caf5d21930061444c3cf4fa7535c82faf5263e22ce43af7c2759ceb5b8b/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1a812a5b727bc09c3d7ea072c4eebf657c2f7066155506ba31ebf4792f88f016"}}, - {name = "pyarrow-22.0.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ae/f3/cec89bd99fa3abf826f14d4e53d3d11340ce6f6af4d14bdcd54cd83b6576/pyarrow-22.0.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "ec5d40dd494882704fb876c16fa7261a69791e784ae34e6b5992e977bd2e238c"}}, - {name = "pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/d9/9b/cb3f7e0a345353def531ca879053e9ef6b9f38ed91aebcf68b09ba54dec0/pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl",hashes = {sha256 = "77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88"}}, - {name = "pyarrow-22.0.0-cp310-cp310-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/6c/41/3184b8192a120306270c5307f105b70320fdaa592c99843c5ef78aaefdcf/pyarrow-22.0.0-cp310-cp310-macosx_12_0_x86_64.whl",hashes = {sha256 = "44d2d26cda26d18f7af7db71453b7b783788322d756e81730acb98f24eb90ace"}}, - {name = "pyarrow-22.0.0-cp310-cp310-manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d9/3d/a1eab2f6f08001f9fb714b8ed5cfb045e2fe3e3e3c0c221f2c9ed1e6d67d/pyarrow-22.0.0-cp310-cp310-manylinux_2_28_aarch64.whl",hashes = {sha256 = "b9d71701ce97c95480fecb0039ec5bb889e75f110da72005743451339262f4ce"}}, - {name = "pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/46/46/a1d9c24baf21cfd9ce994ac820a24608decf2710521b29223d4334985127/pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl",hashes = {sha256 = "710624ab925dc2b05a6229d47f6f0dac1c1155e6ed559be7109f684eba048a48"}}, - {name = "pyarrow-22.0.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/3a/4c/f711acb13075c1391fd54bc17e078587672c575f8de2a6e62509af026dcf/pyarrow-22.0.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f963ba8c3b0199f9d6b794c90ec77545e05eadc83973897a4523c9e8d84e9340"}}, - {name = "pyarrow-22.0.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/4e/70/1f3180dd7c2eab35c2aca2b29ace6c519f827dcd4cfeb8e0dca41612cf7a/pyarrow-22.0.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "bd0d42297ace400d8febe55f13fdf46e86754842b860c978dfec16f081e5c653"}}, - {name = "pyarrow-22.0.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/80/07/fea6578112c8c60ffde55883a571e4c4c6bc7049f119d6b09333b5cc6f73/pyarrow-22.0.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84"}}, +version = "21.0.0" +requires-python = ">=3.9" +sdist = {name = "pyarrow-21.0.0.tar.gz", url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hashes = {sha256 = "5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc"}} +wheels = [ + {name = "pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl",hashes = {sha256 = "69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61"}}, + {name = "pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl",hashes = {sha256 = "3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8"}}, + {name = "pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl",hashes = {sha256 = "b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e"}}, + {name = "pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl",hashes = {sha256 = "40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569"}}, + {name = "pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/44/28/b6672962639e85dc0ac36f71ab3a8f5f38e01b51343d7aa372a6b56fa3f3/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl",hashes = {sha256 = "26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -3524,33 +1978,9 @@ version = "3.23.0" requires-python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" sdist = {name = "pycryptodomex-3.23.0.tar.gz", url = "https://files.pythonhosted.org/packages/c9/85/e24bf90972a30b0fcd16c73009add1d7d7cd9140c2498a68252028899e41/pycryptodomex-3.23.0.tar.gz", hashes = {sha256 = "71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"}} wheels = [ - {name = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/2e/00/10edb04777069a42490a38c137099d4b17ba6e36a4e6e28bdc7470e9e853/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/6b/3f/2872a9c2d3a27eac094f9ceaa5a8a483b774ae69018040ea3240d5b11154/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/70/af/774c2e2b4f6570fbf6a4972161adbb183aeeaa1863bde31e8706f123bf92/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"}}, {name = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/de/a3/71065b24cb889d537954cedc3ae5466af00a2cabcff8e29b73be047e9a19/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/c9/0b/ff6f43b7fbef4d302c8b981fe58467b8871902cdc3eb28896b52421422cc/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/02/de/9d4772c0506ab6da10b41159493657105d3f8bb5c53615d19452afc6b315/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/28/ad/8b30efcd6341707a234e5eba5493700a17852ca1ac7a75daa7945fcf6427/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl",hashes = {sha256 = "da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/0f/02/16868e9f655b7670dbb0ac4f2844145cbc42251f916fc35c414ad2359849/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/ca/18/4ca89ac737230b52ac8ffaca42f9c6f1fd07c81a6cd821e91af79db60632/pycryptodomex-3.23.0-cp313-cp313t-win32.whl",hashes = {sha256 = "a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/73/34/13e01c322db027682e00986873eca803f11c56ade9ba5bbf3225841ea2d4/pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"}}, - {name = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/54/68/9504c8796b1805d58f4425002bcca20f12880e6fa4dc2fc9a668705c7a08/pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/dd/9c/1a8f35daa39784ed8adf93a694e7e5dc15c23c741bbda06e1d45f8979e9e/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl",hashes = {sha256 = "06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/7a/62/f5221a191a97157d240cf6643747558759126c76ee92f29a3f4aee3197a5/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl",hashes = {sha256 = "b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/8c/fd/5a054543c8988d4ed7b612721d7e78a4b9bf36bc3c5ad45ef45c22d0060e/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/46/9f/bda9c49a7c1842820de674ab36c79f4fbeeee03f8ff0e4f3546c3889076b/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/03/cc/870b9bf8ca92866ca0186534801cf8d20554ad2a76ca959538041b7a7cf4/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl",hashes = {sha256 = "6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/96/e3/ce9348236d8e669fea5dd82a90e86be48b9c341210f44e25443162aba187/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl",hashes = {sha256 = "8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/a5/e9/e869bcee87beb89040263c416a8a50204f7f7a83ac11897646c9e71e0daf/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl",hashes = {sha256 = "55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-win32.whl",url = "https://files.pythonhosted.org/packages/8d/67/09ee8500dd22614af5fbaa51a4aee6e342b5fa8aecf0a6cb9cbf52fa6d45/pycryptodomex-3.23.0-cp37-abi3-win32.whl",hashes = {sha256 = "189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl",url = "https://files.pythonhosted.org/packages/69/96/11f36f71a865dd6df03716d33bd07a67e9d20f6b8d39820470b766af323c/pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl",hashes = {sha256 = "52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"}}, - {name = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f9/93/45c1cdcbeb182ccd2e144c693eaa097763b08b38cded279f0053ed53c553/pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl",hashes = {sha256 = "02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"}}, - {name = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/f3/b8/3e76d948c3c4ac71335bbe75dac53e154b40b0f8f1f022dfa295257a0c96/pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"}}, - {name = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/6a/cf/80f4297a4820dfdfd1c88cf6c4666a200f204b3488103d027b5edd9176ec/pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"}}, {name = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d1/42/1e969ee0ad19fe3134b0e1b856c39bd0b70d47a4d0e81c2a8b05727394c9/pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"}}, - {name = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/6e/c3/1de4f7631fea8a992a44ba632aa40e0008764c0fb9bf2854b0acf78c2cf2/pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"}}, - {name = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f2/5f/af7da8e6f1e42b52f44a24d08b8e4c726207434e2593732d39e7af5e7256/pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl",hashes = {sha256 = "14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"}}, + {name = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"}}, ] marker = "\"all\" in extras or \"dev\" in extras or \"openai\" in extras or \"recommended\" in extras" @@ -3575,11 +2005,11 @@ dependencies = [ [[packages]] name = "python-dotenv" -version = "1.2.1" +version = "1.1.1" requires-python = ">=3.9" -sdist = {name = "python_dotenv-1.2.1.tar.gz", url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hashes = {sha256 = "42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}} +sdist = {name = "python_dotenv-1.1.1.tar.gz", url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hashes = {sha256 = "a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}} wheels = [ - {name = "python_dotenv-1.2.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl",hashes = {sha256 = "b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}}, + {name = "python_dotenv-1.1.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl",hashes = {sha256 = "31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}}, ] marker = "\"default\" in dependency_groups" @@ -3592,20 +2022,7 @@ version = "0.6.2" requires-python = ">=3.9" sdist = {name = "safetensors-0.6.2.tar.gz", url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hashes = {sha256 = "43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9"}} wheels = [ - {name = "safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl",url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl",hashes = {sha256 = "9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba"}}, - {name = "safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl",hashes = {sha256 = "d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b"}}, - {name = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd"}}, - {name = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a"}}, - {name = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1"}}, - {name = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda"}}, {name = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f"}}, - {name = "safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl",url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl",hashes = {sha256 = "81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19"}}, - {name = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce"}}, - {name = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl",url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl",hashes = {sha256 = "fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7"}}, - {name = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl",hashes = {sha256 = "d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5"}}, - {name = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl",hashes = {sha256 = "d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac"}}, - {name = "safetensors-0.6.2-cp38-abi3-win32.whl",url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl",hashes = {sha256 = "cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1"}}, - {name = "safetensors-0.6.2-cp38-abi3-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl",hashes = {sha256 = "c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c"}}, ] marker = "\"default\" in dependency_groups" @@ -3624,19 +2041,6 @@ marker = "\"default\" in dependency_groups" [packages.tool.pdm] dependencies = [] -[[packages]] -name = "sniffio" -version = "1.3.1" -requires-python = ">=3.7" -sdist = {name = "sniffio-1.3.1.tar.gz", url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hashes = {sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}} -wheels = [ - {name = "sniffio-1.3.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl",hashes = {sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}}, -] -marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" - -[packages.tool.pdm] -dependencies = [] - [[packages]] name = "snowballstemmer" version = "3.0.1" @@ -3717,84 +2121,32 @@ dependencies = [ "html5tagger>=1.2.1", ] +[[packages]] +name = "types-pytz" +version = "2025.2.0.20250809" +requires-python = ">=3.9" +sdist = {name = "types_pytz-2025.2.0.20250809.tar.gz", url = "https://files.pythonhosted.org/packages/07/e2/c774f754de26848f53f05defff5bb21dd9375a059d1ba5b5ea943cf8206e/types_pytz-2025.2.0.20250809.tar.gz", hashes = {sha256 = "222e32e6a29bb28871f8834e8785e3801f2dc4441c715cd2082b271eecbe21e5"}} +wheels = [ + {name = "types_pytz-2025.2.0.20250809-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/db/d0/91c24fe54e565f2344d7a6821e6c6bb099841ef09007ea6321a0bac0f808/types_pytz-2025.2.0.20250809-py3-none-any.whl",hashes = {sha256 = "4f55ed1b43e925cf851a756fe1707e0f5deeb1976e15bf844bcaa025e8fbd0db"}}, +] +marker = "\"dev\" in extras" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "ujson" version = "5.11.0" requires-python = ">=3.9" sdist = {name = "ujson-5.11.0.tar.gz", url = "https://files.pythonhosted.org/packages/43/d9/3f17e3c5773fb4941c68d9a37a47b1a79c9649d6c56aefbed87cc409d18a/ujson-5.11.0.tar.gz", hashes = {sha256 = "e204ae6f909f099ba6b6b942131cee359ddda2b6e4ea39c12eb8b991fe2010e0"}} wheels = [ - {name = "ujson-5.11.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/28/08/4518146f4984d112764b1dfa6fb7bad691c44a401adadaa5e23ccd930053/ujson-5.11.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "65724738c73645db88f70ba1f2e6fb678f913281804d5da2fd02c8c5839af302"}}, - {name = "ujson-5.11.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/29/37/2107b9a62168867a692654d8766b81bd2fd1e1ba13e2ec90555861e02b0c/ujson-5.11.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "29113c003ca33ab71b1b480bde952fbab2a0b6b03a4ee4c3d71687cdcbd1a29d"}}, - {name = "ujson-5.11.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/9b/f8/25583c70f83788edbe3ca62ce6c1b79eff465d78dec5eb2b2b56b3e98b33/ujson-5.11.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "c44c703842024d796b4c78542a6fcd5c3cb948b9fc2a73ee65b9c86a22ee3638"}}, - {name = "ujson-5.11.0-cp314-cp314-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/ed/ca/19b3a632933a09d696f10dc1b0dfa1d692e65ad507d12340116ce4f67967/ujson-5.11.0-cp314-cp314-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "e750c436fb90edf85585f5c62a35b35082502383840962c6983403d1bd96a02c"}}, {name = "ujson-5.11.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/55/7a/4572af5324ad4b2bfdd2321e898a527050290147b4ea337a79a0e4e87ec7/ujson-5.11.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "f278b31a7c52eb0947b2db55a5133fbc46b6f0ef49972cd1a80843b72e135aba"}}, - {name = "ujson-5.11.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/7b/71/a2b8c19cf4e1efe53cf439cdf7198ac60ae15471d2f1040b490c1f0f831f/ujson-5.11.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ab2cb8351d976e788669c8281465d44d4e94413718af497b4e7342d7b2f78018"}}, - {name = "ujson-5.11.0-cp314-cp314-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/7a/3e/7b98668cba3bb3735929c31b999b374ebc02c19dfa98dfebaeeb5c8597ca/ujson-5.11.0-cp314-cp314-musllinux_1_2_i686.whl",hashes = {sha256 = "090b4d11b380ae25453100b722d0609d5051ffe98f80ec52853ccf8249dfd840"}}, - {name = "ujson-5.11.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/a1/ea/8870f208c20b43571a5c409ebb2fe9b9dba5f494e9e60f9314ac01ea8f78/ujson-5.11.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "80017e870d882d5517d28995b62e4e518a894f932f1e242cbc802a2fd64d365c"}}, - {name = "ujson-5.11.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/63/b6/c0e6607e37fa47929920a685a968c6b990a802dec65e9c5181e97845985d/ujson-5.11.0-cp314-cp314-win32.whl",hashes = {sha256 = "1d663b96eb34c93392e9caae19c099ec4133ba21654b081956613327f0e973ac"}}, - {name = "ujson-5.11.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/4e/56/f4fe86b4c9000affd63e9219e59b222dc48b01c534533093e798bf617a7e/ujson-5.11.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "849e65b696f0d242833f1df4182096cedc50d414215d1371fca85c541fbff629"}}, - {name = "ujson-5.11.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/0a/f3/669437f0280308db4783b12a6d88c00730b394327d8334cc7a32ef218e64/ujson-5.11.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "e73df8648c9470af2b6a6bf5250d4744ad2cf3d774dcf8c6e31f018bdd04d764"}}, - {name = "ujson-5.11.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/6e/cd/e9809b064a89fe5c4184649adeb13c1b98652db3f8518980b04227358574/ujson-5.11.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "de6e88f62796372fba1de973c11138f197d3e0e1d80bcb2b8aae1e826096d433"}}, - {name = "ujson-5.11.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/1b/be/ae26a6321179ebbb3a2e2685b9007c71bcda41ad7a77bbbe164005e956fc/ujson-5.11.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "49e56ef8066f11b80d620985ae36869a3ff7e4b74c3b6129182ec5d1df0255f3"}}, - {name = "ujson-5.11.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ae/e9/fb4a220ee6939db099f4cfeeae796ecb91e7584ad4d445d4ca7f994a9135/ujson-5.11.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "1a325fd2c3a056cf6c8e023f74a0c478dd282a93141356ae7f16d5309f5ff823"}}, - {name = "ujson-5.11.0-cp314-cp314t-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/bd/f8/fc4b952b8f5fea09ea3397a0bd0ad019e474b204cabcb947cead5d4d1ffc/ujson-5.11.0-cp314-cp314t-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "a0af6574fc1d9d53f4ff371f58c96673e6d988ed2b5bf666a6143c782fa007e9"}}, {name = "ujson-5.11.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/2e/e5/af5491dfda4f8b77e24cf3da68ee0d1552f99a13e5c622f4cef1380925c3/ujson-5.11.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "10f29e71ecf4ecd93a6610bd8efa8e7b6467454a363c3d6416db65de883eb076"}}, - {name = "ujson-5.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c4/09/0945349dd41f25cc8c38d78ace49f14c5052c5bbb7257d2f466fa7bdb533/ujson-5.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1a0a9b76a89827a592656fe12e000cf4f12da9692f51a841a4a07aa4c7ecc41c"}}, - {name = "ujson-5.11.0-cp314-cp314t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/49/44/8e04496acb3d5a1cbee3a54828d9652f67a37523efa3d3b18a347339680a/ujson-5.11.0-cp314-cp314t-musllinux_1_2_i686.whl",hashes = {sha256 = "b16930f6a0753cdc7d637b33b4e8f10d5e351e1fb83872ba6375f1e87be39746"}}, - {name = "ujson-5.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/64/ae/4bc825860d679a0f208a19af2f39206dfd804ace2403330fdc3170334a2f/ujson-5.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "04c41afc195fd477a59db3a84d5b83a871bd648ef371cf8c6f43072d89144eef"}}, - {name = "ujson-5.11.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/30/ed/5a057199fb0a5deabe0957073a1c1c1c02a3e99476cd03daee98ea21fa57/ujson-5.11.0-cp314-cp314t-win32.whl",hashes = {sha256 = "aa6d7a5e09217ff93234e050e3e380da62b084e26b9f2e277d2606406a2fc2e5"}}, - {name = "ujson-5.11.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/aa/03/b19c6176bdf1dc13ed84b886e99677a52764861b6cc023d5e7b6ebda249d/ujson-5.11.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "48055e1061c1bb1f79e75b4ac39e821f3f35a9b82de17fce92c3140149009bec"}}, - {name = "ujson-5.11.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/5d/ca/a0413a3874b2dc1708b8796ca895bf363292f9c70b2e8ca482b7dbc0259d/ujson-5.11.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "1194b943e951092db611011cb8dbdb6cf94a3b816ed07906e14d3bc6ce0e90ab"}}, - {name = "ujson-5.11.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/1c/ec/2de9dd371d52c377abc05d2b725645326c4562fc87296a8907c7bcdf2db7/ujson-5.11.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "109f59885041b14ee9569bf0bb3f98579c3fa0652317b355669939e5fc5ede53"}}, - {name = "ujson-5.11.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/5b/a4/f611f816eac3a581d8a4372f6967c3ed41eddbae4008d1d77f223f1a4e0a/ujson-5.11.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "a31c6b8004438e8c20fc55ac1c0e07dad42941db24176fe9acf2815971f8e752"}}, - {name = "ujson-5.11.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e9/c5/c161940967184de96f5cbbbcce45b562a4bf851d60f4c677704b1770136d/ujson-5.11.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "78c684fb21255b9b90320ba7e199780f653e03f6c2528663768965f4126a5b50"}}, - {name = "ujson-5.11.0-cp313-cp313-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/2b/d6/c7b2444238f5b2e2d0e3dab300b9ddc3606e4b1f0e4bed5a48157cebc792/ujson-5.11.0-cp313-cp313-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "4c9f5d6a27d035dd90a146f7761c2272cf7103de5127c9ab9c4cd39ea61e878a"}}, {name = "ujson-5.11.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/fe/a3/292551f936d3d02d9af148f53e1bc04306b00a7cf1fcbb86fa0d1c887242/ujson-5.11.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "837da4d27fed5fdc1b630bd18f519744b23a0b5ada1bbde1a36ba463f2900c03"}}, - {name = "ujson-5.11.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/90/a6/82cfa70448831b1a9e73f882225980b5c689bf539ec6400b31656a60ea46/ujson-5.11.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "787aff4a84da301b7f3bac09bc696e2e5670df829c6f8ecf39916b4e7e24e701"}}, - {name = "ujson-5.11.0-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/84/5c/96e2266be50f21e9b27acaee8ca8f23ea0b85cb998c33d4f53147687839b/ujson-5.11.0-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "6dd703c3e86dc6f7044c5ac0b3ae079ed96bf297974598116aa5fb7f655c3a60"}}, - {name = "ujson-5.11.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/8d/20/78abe3d808cf3bb3e76f71fca46cd208317bf461c905d79f0d26b9df20f1/ujson-5.11.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3772e4fe6b0c1e025ba3c50841a0ca4786825a4894c8411bf8d3afe3a8061328"}}, - {name = "ujson-5.11.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/d8/50/8856e24bec5e2fc7f775d867aeb7a3f137359356200ac44658f1f2c834b2/ujson-5.11.0-cp313-cp313-win32.whl",hashes = {sha256 = "8fa2af7c1459204b7a42e98263b069bd535ea0cd978b4d6982f35af5a04a4241"}}, - {name = "ujson-5.11.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/5b/d8/1baee0f4179a4d0f5ce086832147b6cc9b7731c24ca08e14a3fdb8d39c32/ujson-5.11.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "34032aeca4510a7c7102bd5933f59a37f63891f30a0706fb46487ab6f0edf8f0"}}, - {name = "ujson-5.11.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/a9/8c/6d85ef5be82c6d66adced3ec5ef23353ed710a11f70b0b6a836878396334/ujson-5.11.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "ce076f2df2e1aa62b685086fbad67f2b1d3048369664b4cdccc50707325401f9"}}, - {name = "ujson-5.11.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/b9/ef/a9cb1fce38f699123ff012161599fb9f2ff3f8d482b4b18c43a2dc35073f/ujson-5.11.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "7895f0d2d53bd6aea11743bd56e3cb82d729980636cd0ed9b89418bf66591702"}}, - {name = "ujson-5.11.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b1/05/dba51a00eb30bd947791b173766cbed3492269c150a7771d2750000c965f/ujson-5.11.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "12b5e7e22a1fe01058000d1b317d3b65cc3daf61bd2ea7a2b76721fe160fa74d"}}, - {name = "ujson-5.11.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/03/3c/fd11a224f73fbffa299fb9644e425f38b38b30231f7923a088dd513aabb4/ujson-5.11.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "0180a480a7d099082501cad1fe85252e4d4bf926b40960fb3d9e87a3a6fbbc80"}}, - {name = "ujson-5.11.0-cp312-cp312-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/55/b9/405103cae24899df688a3431c776e00528bd4799e7d68820e7ebcf824f92/ujson-5.11.0-cp312-cp312-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "fa79fdb47701942c2132a9dd2297a1a85941d966d8c87bfd9e29b0cf423f26cc"}}, {name = "ujson-5.11.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/17/7b/2dcbc2bbfdbf68f2368fb21ab0f6735e872290bb604c75f6e06b81edcb3f/ujson-5.11.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "8254e858437c00f17cb72e7a644fc42dad0ebb21ea981b71df6e84b1072aaa7c"}}, - {name = "ujson-5.11.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d1/71/fea2ca18986a366c750767b694430d5ded6b20b6985fddca72f74af38a4c/ujson-5.11.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1aa8a2ab482f09f6c10fba37112af5f957689a79ea598399c85009f2f29898b5"}}, - {name = "ujson-5.11.0-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/a3/bb/d4220bd7532eac6288d8115db51710fa2d7d271250797b0bfba9f1e755af/ujson-5.11.0-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "a638425d3c6eed0318df663df44480f4a40dc87cc7c6da44d221418312f6413b"}}, - {name = "ujson-5.11.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/80/47/226e540aa38878ce1194454385701d82df538ccb5ff8db2cf1641dde849a/ujson-5.11.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "7e3cff632c1d78023b15f7e3a81c3745cd3f94c044d1e8fa8efbd6b161997bbc"}}, - {name = "ujson-5.11.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/7e/81/546042f0b23c9040d61d46ea5ca76f0cc5e0d399180ddfb2ae976ebff5b5/ujson-5.11.0-cp312-cp312-win32.whl",hashes = {sha256 = "be6b0eaf92cae8cdee4d4c9e074bde43ef1c590ed5ba037ea26c9632fb479c88"}}, - {name = "ujson-5.11.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/44/1b/27c05dc8c9728f44875d74b5bfa948ce91f6c33349232619279f35c6e817/ujson-5.11.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "b7b136cc6abc7619124fd897ef75f8e63105298b5ca9bdf43ebd0e1fa0ee105f"}}, - {name = "ujson-5.11.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/22/2d/37b6557c97c3409c202c838aa9c960ca3896843b4295c4b7bb2bbd260664/ujson-5.11.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "6cd2df62f24c506a0ba322d5e4fe4466d47a9467b57e881ee15a31f7ecf68ff6"}}, - {name = "ujson-5.11.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/ea/80346b826349d60ca4d612a47cdf3533694e49b45e9d1c07071bb867a184/ujson-5.11.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "d7c46cb0fe5e7056b9acb748a4c35aa1b428025853032540bb7e41f46767321f"}}, - {name = "ujson-5.11.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/57/df/b53e747562c89515e18156513cc7c8ced2e5e3fd6c654acaa8752ffd7cd9/ujson-5.11.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "d8951bb7a505ab2a700e26f691bdfacf395bc7e3111e3416d325b513eea03a58"}}, - {name = "ujson-5.11.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/41/b8/ab67ec8c01b8a3721fd13e5cb9d85ab2a6066a3a5e9148d661a6870d6293/ujson-5.11.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "952c0be400229940248c0f5356514123d428cba1946af6fa2bbd7503395fef26"}}, - {name = "ujson-5.11.0-cp311-cp311-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/7b/c7/fb84f27cd80a2c7e2d3c6012367aecade0da936790429801803fa8d4bffc/ujson-5.11.0-cp311-cp311-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "94fcae844f1e302f6f8095c5d1c45a2f0bfb928cccf9f1b99e3ace634b980a2a"}}, {name = "ujson-5.11.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/7c/48706f7c1e917ecb97ddcfb7b1d756040b86ed38290e28579d63bd3fcc48/ujson-5.11.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "7e0ec1646db172beb8d3df4c32a9d78015e671d2000af548252769e33079d9a6"}}, - {name = "ujson-5.11.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ec/ce/48877c6eb4afddfd6bd1db6be34456538c07ca2d6ed233d3f6c6efc2efe8/ujson-5.11.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "da473b23e3a54448b008d33f742bcd6d5fb2a897e42d1fc6e7bf306ea5d18b1b"}}, - {name = "ujson-5.11.0-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/8b/7a/2c20dc97ad70cd7c31ad0596ba8e2cf8794d77191ba4d1e0bded69865477/ujson-5.11.0-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "aa6b3d4f1c0d3f82930f4cbd7fe46d905a4a9205a7c13279789c1263faf06dba"}}, - {name = "ujson-5.11.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/f5/ca454f2f6a2c840394b6f162fff2801450803f4ff56c7af8ce37640b8a2a/ujson-5.11.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "4843f3ab4fe1cc596bb7e02228ef4c25d35b4bb0809d6a260852a4bfcab37ba3"}}, - {name = "ujson-5.11.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/fe/d3/9ba310e07969bc9906eb7548731e33a0f448b122ad9705fed699c9b29345/ujson-5.11.0-cp311-cp311-win32.whl",hashes = {sha256 = "e979fbc469a7f77f04ec2f4e853ba00c441bf2b06720aa259f0f720561335e34"}}, - {name = "ujson-5.11.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/57/f7/da05b4a8819f1360be9e71fb20182f0bb3ec611a36c3f213f4d20709e099/ujson-5.11.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "683f57f0dd3acdd7d9aff1de0528d603aafcb0e6d126e3dc7ce8b020a28f5d01"}}, - {name = "ujson-5.11.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/9a/cc/f3f9ac0f24f00a623a48d97dc3814df5c2dc368cfb00031aa4141527a24b/ujson-5.11.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "7855ccea3f8dad5e66d8445d754fc1cf80265a4272b5f8059ebc7ec29b8d0835"}}, - {name = "ujson-5.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/50/17/30275aa2933430d8c0c4ead951cc4fdb922f575a349aa0b48a6f35449e97/ujson-5.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "abae0fb58cc820092a0e9e8ba0051ac4583958495bfa5262a12f628249e3b362"}}, - {name = "ujson-5.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/c3/15/42b3924258eac2551f8f33fa4e35da20a06a53857ccf3d4deb5e5d7c0b6c/ujson-5.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "fac6c0649d6b7c3682a0a6e18d3de6857977378dce8d419f57a0b20e3d775b39"}}, - {name = "ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/94/7e/0519ff7955aba581d1fe1fb1ca0e452471250455d182f686db5ac9e46119/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4b42c115c7c6012506e8168315150d1e3f76e7ba0f4f95616f4ee599a1372bbc"}}, - {name = "ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/74/cf/209d90506b7d6c5873f82c5a226d7aad1a1da153364e9ebf61eff0740c33/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "86baf341d90b566d61a394869ce77188cc8668f76d7bb2c311d77a00f4bdf844"}}, {name = "ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/e9/97/bd939bb76943cb0e1d2b692d7e68629f51c711ef60425fa5bb6968037ecd/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4598bf3965fc1a936bd84034312bcbe00ba87880ef1ee33e33c1e88f2c398b49"}}, - {name = "ujson-5.11.0-pp311-pypy311_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/52/5b/8c5e33228f7f83f05719964db59f3f9f276d272dc43752fa3bbf0df53e7b/ujson-5.11.0-pp311-pypy311_pp73-win_amd64.whl",hashes = {sha256 = "416389ec19ef5f2013592f791486bef712ebce0cd59299bf9df1ba40bb2f6e04"}}, - {name = "ujson-5.11.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/86/0c/8bf7a4fabfd01c7eed92d9b290930ce6d14910dec708e73538baa38885d1/ujson-5.11.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "446e8c11c06048611c9d29ef1237065de0af07cabdd97e6b5b527b957692ec25"}}, - {name = "ujson-5.11.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/7b/2e/eeab0b8b641817031ede4f790db4c4942df44a12f44d72b3954f39c6a115/ujson-5.11.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "16ccb973b7ada0455201808ff11d48fe9c3f034a6ab5bd93b944443c88299f89"}}, - {name = "ujson-5.11.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/21/1b/a4e7a41870797633423ea79618526747353fd7be9191f3acfbdee0bf264b/ujson-5.11.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "3134b783ab314d2298d58cda7e47e7a0f7f71fc6ade6ac86d5dbeaf4b9770fa6"}}, - {name = "ujson-5.11.0-cp310-cp310-manylinux_2_24_i686.manylinux_2_28_i686.whl",url = "https://files.pythonhosted.org/packages/94/ae/4e0d91b8f6db7c9b76423b3649612189506d5a06ddd3b6334b6d37f77a01/ujson-5.11.0-cp310-cp310-manylinux_2_24_i686.manylinux_2_28_i686.whl",hashes = {sha256 = "185f93ebccffebc8baf8302c869fac70dd5dd78694f3b875d03a31b03b062cdb"}}, {name = "ujson-5.11.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/cc/46b124c2697ca2da7c65c4931ed3cb670646978157aa57a7a60f741c530f/ujson-5.11.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "d06e87eded62ff0e5f5178c916337d2262fdbc03b31688142a3433eabb6511db"}}, - {name = "ujson-5.11.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/39/eb/20dd1282bc85dede2f1c62c45b4040bc4c389c80a05983515ab99771bca7/ujson-5.11.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "181fb5b15703a8b9370b25345d2a1fd1359f0f18776b3643d24e13ed9c036d4c"}}, - {name = "ujson-5.11.0-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/64/a2/80072439065d493e3a4b1fbeec991724419a1b4c232e2d1147d257cac193/ujson-5.11.0-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "a4df61a6df0a4a8eb5b9b1ffd673429811f50b235539dac586bb7e9e91994138"}}, - {name = "ujson-5.11.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/7e/d77f9e9c039d58299c350c978e086a804d1fceae4fd4a1cc6e8d0133f838/ujson-5.11.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "6eff24e1abd79e0ec6d7eae651dd675ddbc41f9e43e29ef81e16b421da896915"}}, - {name = "ujson-5.11.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/ab/f1/697559d45acc849cada6b3571d53522951b1a64027400507aabc6a710178/ujson-5.11.0-cp310-cp310-win32.whl",hashes = {sha256 = "30f607c70091483550fbd669a0b37471e5165b317d6c16e75dba2aa967608723"}}, - {name = "ujson-5.11.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/86/a2/70b73a0f55abe0e6b8046d365d74230c20c5691373e6902a599b2dc79ba1/ujson-5.11.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "3d2720e9785f84312b8e2cb0c2b87f1a0b1c53aaab3b2af3ab817d54409012e0"}}, - {name = "ujson-5.11.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/1c/5f/b19104afa455630b43efcad3a24495b9c635d92aa8f2da4f30e375deb1a2/ujson-5.11.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "85e6796631165f719084a9af00c79195d3ebf108151452fefdcb1c8bb50f0105"}}, ] marker = "sys_platform != \"win32\" and implementation_name == \"cpython\" and \"default\" in dependency_groups" @@ -3820,57 +2172,12 @@ version = "15.0.1" requires-python = ">=3.9" sdist = {name = "websockets-15.0.1.tar.gz", url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hashes = {sha256 = "82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}} wheels = [ - {name = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}}, - {name = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}}, - {name = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}}, - {name = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}}, - {name = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}}, {name = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}}, - {name = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}}, - {name = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}}, - {name = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}}, - {name = "websockets-15.0.1-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl",hashes = {sha256 = "ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}}, - {name = "websockets-15.0.1-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl",hashes = {sha256 = "e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}}, - {name = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}}, - {name = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}}, - {name = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}}, - {name = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}}, - {name = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}}, {name = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}}, - {name = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}}, - {name = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}}, - {name = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}}, - {name = "websockets-15.0.1-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl",hashes = {sha256 = "c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}}, - {name = "websockets-15.0.1-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl",hashes = {sha256 = "fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}}, - {name = "websockets-15.0.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl",hashes = {sha256 = "f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}}, - {name = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}}, - {name = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}}, - {name = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}}, - {name = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}}, - {name = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}}, {name = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}}, - {name = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}}, - {name = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}}, - {name = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}}, - {name = "websockets-15.0.1-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl",hashes = {sha256 = "16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}}, - {name = "websockets-15.0.1-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl",hashes = {sha256 = "27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}}, - {name = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}}, - {name = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}}, - {name = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}}, - {name = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}}, - {name = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}}, {name = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}}, - {name = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}}, - {name = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}}, - {name = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}}, - {name = "websockets-15.0.1-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl",hashes = {sha256 = "1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}}, - {name = "websockets-15.0.1-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl",hashes = {sha256 = "39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}}, - {name = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}}, - {name = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl",hashes = {sha256 = "1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}}, - {name = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}}, - {name = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}}, {name = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}}, - {name = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl",hashes = {sha256 = "cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}}, + {name = "websockets-15.0.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl",hashes = {sha256 = "f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}}, ] marker = "\"default\" in dependency_groups" @@ -3878,111 +2185,33 @@ marker = "\"default\" in dependency_groups" dependencies = [] [[packages]] -name = "win32-setctime" -version = "1.2.0" -requires-python = ">=3.5" -sdist = {name = "win32_setctime-1.2.0.tar.gz", url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hashes = {sha256 = "ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}} +name = "wrapt" +version = "1.17.3" +requires-python = ">=3.8" +sdist = {name = "wrapt-1.17.3.tar.gz", url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hashes = {sha256 = "f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}} wheels = [ - {name = "win32_setctime-1.2.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl",hashes = {sha256 = "95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}}, + {name = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}}, + {name = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}}, + {name = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}}, + {name = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}}, + {name = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}}, + {name = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}}, + {name = "wrapt-1.17.3-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl",hashes = {sha256 = "7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}}, ] -marker = "sys_platform == \"win32\" and \"default\" in dependency_groups" +marker = "\"default\" in dependency_groups" [packages.tool.pdm] dependencies = [] [[packages]] -name = "wrapt" -version = "2.0.0" -requires-python = ">=3.8" -sdist = {name = "wrapt-2.0.0.tar.gz", url = "https://files.pythonhosted.org/packages/49/19/5e5bcd855d808892fe02d49219f97a50f64cd6d8313d75df3494ee97b1a3/wrapt-2.0.0.tar.gz", hashes = {sha256 = "35a542cc7a962331d0279735c30995b024e852cf40481e384fd63caaa391cbb9"}} -wheels = [ - {name = "wrapt-2.0.0-cp314-cp314-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/f8/38/0dd39f83163fd28326afba84e3e416656938df07e60a924ac4d992b30220/wrapt-2.0.0-cp314-cp314-macosx_10_13_universal2.whl",hashes = {sha256 = "79a53d86c2aff7b32cc77267e3a308365d1fcb881e74bc9cbe26f63ee90e37f0"}}, - {name = "wrapt-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/08/ef/fa7a5c1d73f8690c712f9d2e4615700c6809942536dd3f441b9ba650a310/wrapt-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "d731a4f22ed6ffa4cb551b4d2b0c24ff940c27a88edaf8e3490a5ee3a05aef71"}}, - {name = "wrapt-2.0.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/23/d9/67cb93da492eb0a1cb17b7ed18220d059e58f00467ce6728b674d3441b3d/wrapt-2.0.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "3e02ab8c0ac766a5a6e81cd3b6cc39200c69051826243182175555872522bd5a"}}, - {name = "wrapt-2.0.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/e5/be/912bbd70cc614f491b526a1d7fe85695b283deed19287b9f32460178c54d/wrapt-2.0.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "895870602d65d7338edb3b6a717d856632ad9f14f7ff566214e4fb11f0816649"}}, - {name = "wrapt-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/b2/e1/10df8937e7da2aa9bc3662a4b623e51a323c68f42cad7b13f0e61a700ce2/wrapt-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "0b9ad4fab76a0086dc364c4f17f39ad289600e73ef5c6e9ab529aff22cac1ac3"}}, - {name = "wrapt-2.0.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/f3/60/576751b1919adab9f63168e3b5fd46c0d1565871b1cc4c2569503ccf4be6/wrapt-2.0.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "e7ca0562606d7bad2736b2c18f61295d61f50cd3f4bfc51753df13614dbcce1b"}}, - {name = "wrapt-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ec/55/243411f360cc27bae5f8e21c16f1a8d87674c5534f4558e8a97c1e0d1c6f/wrapt-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "fe089d9f5a4a3dea0108a8ae34bced114d0c4cca417bada1c5e8f42d98af9050"}}, - {name = "wrapt-2.0.0-cp314-cp314-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/d6/23/2f21f692c3b3f0857cb82708ce0c341fbac55a489d4025ae4e3fd5d5de8c/wrapt-2.0.0-cp314-cp314-musllinux_1_2_riscv64.whl",hashes = {sha256 = "e761f2d2f8dbc80384af3d547b522a80e67db3e319c7b02e7fd97aded0a8a678"}}, - {name = "wrapt-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/bd/ed/678957fad212cfb1b65b2359d62f5619f5087d1d1cf296c6a996be45171c/wrapt-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "17ba1bdc52d0c783481850996aa26cea5237720769197335abea2ae6b4c23bc0"}}, - {name = "wrapt-2.0.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/dc/e3/aeb4c3b052d3eed95e61babc20dcb1a512651e098cca4b84a6896585c06a/wrapt-2.0.0-cp314-cp314-win32.whl",hashes = {sha256 = "f73318741b141223a4674ba96992aa2291b1b3f7a5e85cb3c2c964f86171eb45"}}, - {name = "wrapt-2.0.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/aa/2a/a71c51cb211798405b59172c7df5789a5b934b18317223cf22e0c6f852de/wrapt-2.0.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "8e08d4edb13cafe7b3260f31d4de033f73d3205774540cf583bffaa4bec97db9"}}, - {name = "wrapt-2.0.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/f8/a5/acc5628035d06f69e9144cca543ca54c33b42a5a23b6f1e8fa131026db89/wrapt-2.0.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "af01695c2b7bbd8d67b869d8e3de2b123a7bfbee0185bdd138c2775f75373b83"}}, - {name = "wrapt-2.0.0-cp314-cp314t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/a7/e6/1318ca07d7fcee57e4592a78dacd9d5493b8ddd971c553a62904fb2c0cf2/wrapt-2.0.0-cp314-cp314t-macosx_10_13_universal2.whl",hashes = {sha256 = "057f02c13cce7b26c79624c06a3e1c2353e6dc9708525232232f6768118042ca"}}, - {name = "wrapt-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/e7/bf/ffac358ddf61c3923d94a8b0e7620f2af1cd1b637a0fe4963a3919aa62b7/wrapt-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "79bdd84570267f3f43d609c892ae2d30b91ee4b8614c2cbfd311a2965f1c9bdb"}}, - {name = "wrapt-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b5/af/387c51f9e7b544fe95d852fc94f9f3866e3f7d7d39c2ee65041752f90bc2/wrapt-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "93c8b4f4d54fd401a817abbfc9bf482aa72fd447f8adf19ce81d035b3f5c762c"}}, - {name = "wrapt-2.0.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/7c/99/d38d8c80b9cc352531d4d539a17e3674169a5cc25a7e6e5e3c27bc29893e/wrapt-2.0.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "5e09ffd31001dce71c2c2a4fc201bdba9a2f9f62b23700cf24af42266e784741"}}, - {name = "wrapt-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/5a/2a/e154432f274e22ecf2465583386c5ceffa5e0bab3947c1c5b26cc8e7b275/wrapt-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "d87c285ff04e26083c4b03546e7b74df7ba4f1f32f1dcb92e9ac13c2dbb4c379"}}, - {name = "wrapt-2.0.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/c5/7a/3a40c453300e2898e99c27495b8109ff7cd526997d12cfb8ebd1843199a4/wrapt-2.0.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "e52e50ea0a72ea48d1291cf8b8aaedcc99072d9dc5baba6b820486dcf4c67da8"}}, - {name = "wrapt-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/9e/e2/3116a9eade8bea2bf5eedba3fa420e3c7d193d4b047440330d8eaf1098de/wrapt-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1fd4c95536975895f32571073446e614d5e2810b666b64955586dcddfd438fd3"}}, - {name = "wrapt-2.0.0-cp314-cp314t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/43/1c/277d3fbe9d177830ab9e54fe9253f38455b75a22d639a4bd9fa092d55ae5/wrapt-2.0.0-cp314-cp314t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "d6ebfe9283209220ed9de80a3e9442aab8fc2be5a9bbf8491b99e02ca9349a89"}}, - {name = "wrapt-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d8/37/ab6ddaf182248aac5ed925725ef4c69a510594764665ecbd95bdd4481f16/wrapt-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5d3ebd784804f146b7ea55359beb138e23cc18e5a5cc2cf26ad438723c00ce3a"}}, - {name = "wrapt-2.0.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/f6/d7/df9e2d8040a3af618ff9496261cf90ca4f886fd226af0f4a69ac0c020c3b/wrapt-2.0.0-cp314-cp314t-win32.whl",hashes = {sha256 = "9b15940ae9debc8b40b15dc57e1ce4433f7fb9d3f8761c7fab1ddd94cb999d99"}}, - {name = "wrapt-2.0.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/b4/c2/502bd4557a3a9199ea73cc5932cf83354bd362682162f0b14164d2e90216/wrapt-2.0.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "7a0efbbc06d3e2077476a04f55859819d23206600b4c33f791359a8e6fa3c362"}}, - {name = "wrapt-2.0.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/1f/f2/632b13942f45db7af709f346ff38b8992c8c21b004e61ab320b0dec525fe/wrapt-2.0.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "7fec8a9455c029c8cf4ff143a53b6e7c463268d42be6c17efa847ebd2f809965"}}, - {name = "wrapt-2.0.0-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/18/0a/dd88abfe756b1aa79f0777e5ee4ce9e4b5dc4999bd805e9b04b52efc7b18/wrapt-2.0.0-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "e2ea096db28d5eb64d381af0e93464621ace38a7003a364b6b5ffb7dd713aabe"}}, - {name = "wrapt-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/7f/b9/8afebc1655a863bb2178b23c2d699b8743f3a7dab466904adc6155f3c858/wrapt-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "c92b5a82d28491e3f14f037e1aae99a27a5e6e0bb161e65f52c0445a3fa7c940"}}, - {name = "wrapt-2.0.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bb/8b/f710a6528ccc52e21943f42c8cf64814cde90f9adbd3bcd58c7c274b4f75/wrapt-2.0.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "81d234718aabe632d179fac52c7f69f0f99fbaac4d4bcd670e62462bbcbfcad7"}}, - {name = "wrapt-2.0.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/e4/5f/e4eabd0cc6684c5b208c2abc5c3459449c4d15be1694a9bbcf51e0e135fd/wrapt-2.0.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "db2eea83c43f84e4e41dbbb4c1de371a53166e55f900a6b130c3ef51c6345c1a"}}, - {name = "wrapt-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/6f/c4/ec31ee17cc7866960d323609ba7402be786d211a6d713a59f776c4270bb3/wrapt-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "65f50e356c425c061e1e17fe687ff30e294fed9bf3441dc1f13ef73859c2a817"}}, - {name = "wrapt-2.0.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/b0/2b/a4b10c3c0022e40aeae9bec009bafb049f440493f0575ebb27ecf61c32f8/wrapt-2.0.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "887f2a667e3cbfb19e204032d42ad7dedaa43972e4861dc7a3d51ae951d9b578"}}, - {name = "wrapt-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/2a/4a/ade23a76967e1f148e461076a4d0e24a7950a5f18b394c9107fe60224ae2/wrapt-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "9054829da4be461e3ad3192e4b6bbf1fc18af64c9975ce613aec191924e004dc"}}, - {name = "wrapt-2.0.0-cp313-cp313-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/cb/ba/33b5f3e2edede4e1cfd259f0d9c203cf370f259bb9b215dd58fc6cbb94e9/wrapt-2.0.0-cp313-cp313-musllinux_1_2_riscv64.whl",hashes = {sha256 = "b952ffd77133a5a2798ee3feb18e51b0a299d2f440961e5bb7737dbb02e57289"}}, - {name = "wrapt-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/eb/bf/b7f95bb4529a35ca11eb95d48f9d1a563b495471f7cf404c644566fb4293/wrapt-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e25fde03c480061b8234d8ee4863eb5f40a9be4fb258ce105b364de38fc6bcf9"}}, - {name = "wrapt-2.0.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/f8/71/984849df6f052592474a44aafd6b847e1cffad39b0debc5390a04aa46331/wrapt-2.0.0-cp313-cp313-win32.whl",hashes = {sha256 = "49e982b7860d325094978292a49e0418833fc7fc42c0dc7cd0b7524d7d06ee74"}}, - {name = "wrapt-2.0.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f9/3b/4e1fc0f2e1355fbc55ab248311bf4c958dbbd96bd9183b9e96882cc16213/wrapt-2.0.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "6e5c86389d9964050ce50babe247d172a5e3911d59a64023b90db2b4fa00ae7c"}}, - {name = "wrapt-2.0.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/20/0a/9384e0551f56fe361f41bb8f209a13bb9ef689c3a18264225b249849b12c/wrapt-2.0.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "b96fdaa4611e05c7231937930567d3c16782be9dbcf03eb9f60d83e57dd2f129"}}, - {name = "wrapt-2.0.0-cp313-cp313t-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/68/70/37b90d3ee5bf0d0dc4859306383da08b685c9a51abff6fd6b0a7c052e117/wrapt-2.0.0-cp313-cp313t-macosx_10_13_universal2.whl",hashes = {sha256 = "f2c7b7fead096dbf1dcc455b7f59facb05de3f5bfb04f60a69f98cdfe6049e5f"}}, - {name = "wrapt-2.0.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/95/23/0ce69cc90806b90b3ee4cfd9ad8d2ee9becc3a1aab7df3c3bfc7d0904cb6/wrapt-2.0.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "04c7c8393f25b11c0faa5d907dd9eb462e87e4e7ba55e308a046d7ed37f4bbe2"}}, - {name = "wrapt-2.0.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/54/76/03ec08170c02f38f3be3646977920976b968e0b704a0693a98f95d02f4d2/wrapt-2.0.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "a93e0f8b376c0735b2f4daf58018b4823614d2b896cb72b6641c4d3dbdca1d75"}}, - {name = "wrapt-2.0.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/75/c1/04ce0511e504cdcd84cdb6980bc7d4efa38ac358e8103d6dd0cd278bfc6d/wrapt-2.0.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "b42d13603da4416c43c430dbc6313c8d7ff745c40942f146ed4f6dd02c7d2547"}}, - {name = "wrapt-2.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/17/06/cd2e32b5f744701189c954f9ab5eee449c86695b13f414bb8ea7a83f6d48/wrapt-2.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "c8bbd2472abf8c33480ad2314b1f8fac45d592aba6cc093e8839a7b2045660e6"}}, - {name = "wrapt-2.0.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/7d/a2/a6d920695cca62563c1b969064e5cd2051344a6e330c184b6f80383d87e4/wrapt-2.0.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "e64a3a1fd9a308ab9b815a2ad7a65b679730629dbf85f8fc3f7f970d634ee5df"}}, - {name = "wrapt-2.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c6/90/7fd2abe4ec646bc43cb6b0d05086be6fcf15e64f06f51fc4198804396d68/wrapt-2.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "d61214525eaf88e0d0edf3d1ad5b5889863c6f88e588c6cdc6aa4ee5d1f10a4a"}}, - {name = "wrapt-2.0.0-cp313-cp313t-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/5f/8d/6cce7f8c41633e677ac8aa34e84b53a22a645ec2a680deb991785ca2798d/wrapt-2.0.0-cp313-cp313t-musllinux_1_2_riscv64.whl",hashes = {sha256 = "04f7a5f92c5f7324a1735043cc467b1295a1c5b4e0c1395472b7c44706e3dc61"}}, - {name = "wrapt-2.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/42/9570349e03afa9d83daf7f33ffb17e8cdc62d7e84c0d09005d0f51912efa/wrapt-2.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "2356f76cb99b3de5b4e5b8210367fbbb81c7309fe39b622f5d199dd88eb7f765"}}, - {name = "wrapt-2.0.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/f2/d8/448728e6fe030e5c4f1022c82cd3af1de1c672fa53d2d5b36b32a55ce7bf/wrapt-2.0.0-cp313-cp313t-win32.whl",hashes = {sha256 = "0a921b657a224e40e4bc161b5d33934583b34f0c9c5bdda4e6ac66f9d2fcb849"}}, - {name = "wrapt-2.0.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8f/b1/ad812b1fe1cd85f6498dc3a3c9809a1e880d6108283b1735119bec217041/wrapt-2.0.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "c16f6d4eea98080f6659a8a7fc559d4a0a337ee66960659265cad2c8a40f7c0f"}}, - {name = "wrapt-2.0.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/7f/29/c105b1e76650c82823c491952a7a8eafe09b78944f7a43f22d37ed860229/wrapt-2.0.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "52878edc13dc151c58a9966621d67163a80654bc6cff4b2e1c79fa62d0352b26"}}, - {name = "wrapt-2.0.0-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/3c/28/7f266b5bf50c3ad0c99c524d99faa0f7d6eecb045d950e7d2c9e1f0e1338/wrapt-2.0.0-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "73c6f734aecb1a030d9a265c13a425897e1ea821b73249bb14471445467ca71c"}}, - {name = "wrapt-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/06/0c/bbdcad7eb535fae9d6b0fcfa3995c364797cd8e2b423bba5559ab2d88dcf/wrapt-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "b4a7f8023b8ce8a36370154733c747f8d65c8697cb977d8b6efeb89291fff23e"}}, - {name = "wrapt-2.0.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/d3/8a/bba3e7a4ebf4d1624103ee59d97b78a1fbb08fb5753ff5d1b69f5ef5e863/wrapt-2.0.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "a1cb62f686c50e9dab5983c68f6c8e9cbf14a6007935e683662898a7d892fa69"}}, - {name = "wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/ff/0c/0f565294897a72493dbafe7b46229b5f09f3776795a894d6b737e98387de/wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "43dc0550ae15e33e6bb45a82a5e1b5495be2587fbaa996244b509921810ee49f"}}, - {name = "wrapt-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/da/80/7f03501a8a078ad79b19b1a888f9192a9494e62ddf8985267902766a4f30/wrapt-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "39c5b45b056d630545e40674d1f5e1b51864b3546f25ab6a4a331943de96262e"}}, - {name = "wrapt-2.0.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/37/6b/ad0e1ff98359f13b4b0c2c52848e792841146fe79ac5f56899b9a028fc0d/wrapt-2.0.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "804e88f824b76240a1b670330637ccfd2d18b9efa3bb4f02eb20b2f64880b324"}}, - {name = "wrapt-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/ac/6c/a90437bba8cb1ce2ed639af979515e09784678c2a7f4ffc79f2cf7de809e/wrapt-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c2c476aa3fc2b9899c3f7b20963fac4f952e7edb74a31fc92f7745389a2e3618"}}, - {name = "wrapt-2.0.0-cp312-cp312-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/2c/a9/b3982f9bd15bd45857a23c48b7c36e47d05db4a4dcc5061c31f169238845/wrapt-2.0.0-cp312-cp312-musllinux_1_2_riscv64.whl",hashes = {sha256 = "8d851e526891216f89fcb7a1820dad9bd503ba3468fb9635ee28e93c781aa98e"}}, - {name = "wrapt-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/73/e2/b7a8b1afac9f791d8f5eac0d9726559f1d7ec4a2b5a6b4e67ac145b007a5/wrapt-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "b95733c2360c4a8656ee93c7af78e84c0bd617da04a236d7a456c8faa34e7a2d"}}, - {name = "wrapt-2.0.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/a2/0f/37920eeea96094f450ae35505d39f1135df951a2cdee0d4e01d4f843396a/wrapt-2.0.0-cp312-cp312-win32.whl",hashes = {sha256 = "ea56817176834edf143df1109ae8fdaa087be82fdad3492648de0baa8ae82bf2"}}, - {name = "wrapt-2.0.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/f0/db/b395f3b0c7f2c60d9219afacc54ceb699801ccf2d3d969ba556dc6d3af20/wrapt-2.0.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "3c7d3bee7be7a2665286103f4d1f15405c8074e6e1f89dac5774f9357c9a3809"}}, - {name = "wrapt-2.0.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/86/22/33d660214548af47fc59d9eec8c0e0693bcedc5b3a0b52e8cbdd61f3b646/wrapt-2.0.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "680f707e1d26acbc60926659799b15659f077df5897a6791c7c598a5d4a211c4"}}, - {name = "wrapt-2.0.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/00/5c/c34575f96a0a038579683c7f10fca943c15c7946037d1d254ab9db1536ec/wrapt-2.0.0-py3-none-any.whl",hashes = {sha256 = "02482fb0df89857e35427dfb844319417e14fae05878f295ee43fa3bf3b15502"}}, - {name = "wrapt-2.0.0-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/12/8f/8e4c8b6da60b4205191d588cbac448fb9ff4f5ed89f4e555dc4813ab30cf/wrapt-2.0.0-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "b7e221abb6c5387819db9323dac3c875b459695057449634f1111955d753c621"}}, - {name = "wrapt-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/22/9a/01a29ccb029aa8e78241f8b53cb89ae8826c240129abbbb6ebba3416eff9/wrapt-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "1147a84c8fc852426580af8b6e33138461ddbc65aa459a25ea539374d32069fa"}}, - {name = "wrapt-2.0.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/3d/ec/e058997971428b7665b5c3665a55b18bb251ea7e08d002925e3ca017c020/wrapt-2.0.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "5d6691d4a711504a0bc10de789842ad6ac627bed22937b10f37a1211a8ab7bb3"}}, - {name = "wrapt-2.0.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/70/c3/c82263503f554715aa1847e85dc75a69631a54e9d7ab0f1a55e34a22d44a/wrapt-2.0.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "f460e1eb8e75a17c3918c8e35ba57625721eef2439ef0bcf05304ac278a65e1d"}}, - {name = "wrapt-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/dc/97/d95e88a3a1bc2890a1aa47880c2762cf0eb6d231b5a64048e351cec6f071/wrapt-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "12c37784b77bf043bf65cc96c7195a5db474b8e54173208af076bdbb61df7b3e"}}, - {name = "wrapt-2.0.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/dc/36/cba0bf954f2303897b80fa5342499b43f8c5201110dddf0d578d6841b149/wrapt-2.0.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "75e5c049eb583835f7a0e0e311d9dde9bfbaac723a6dd89d052540f9b2809977"}}, - {name = "wrapt-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d7/2b/8cb88e63bec989f641d208acb3fd198bfdbbb4ef7dfb71f0cac3c90b07a9/wrapt-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "e50bcbd5b65dac21b82319fcf18486e6ac439947e9305034b00704eb7405f553"}}, - {name = "wrapt-2.0.0-cp311-cp311-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/bb/60/a6d5fb94648cd430648705bef9f4241bd22ead123ead552b6d2873ad5240/wrapt-2.0.0-cp311-cp311-musllinux_1_2_riscv64.whl",hashes = {sha256 = "06b78cb6b9320f57737a52fede882640d93cface98332d1a3df0c5696ec9ae9f"}}, - {name = "wrapt-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d0/44/1963854edf0592ae806307899dc7bf891e76cec19e598f55845c94603a65/wrapt-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "8c8349ebfc3cd98bc9105e0112dd8c8ac1f3c7cb5601f9d02248cae83a63f748"}}, - {name = "wrapt-2.0.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/62/ec/4b1d76cb6d96ac511aaaa92efc57f528e57f06082a595b8b2663fcdb0f20/wrapt-2.0.0-cp311-cp311-win32.whl",hashes = {sha256 = "028f19ec29e204fe725139d4a8b09f77ecfb64f8f02b7ab5ee822c85e330b68b"}}, - {name = "wrapt-2.0.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d4/cf/df8ff9bd64d4a75f9a9f6c1c93480a51904d0c9bd71c11994301c47d8a33/wrapt-2.0.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "c6961f05e58d919153ba311b397b7b904b907132b7b8344dde47865d4bb5ec89"}}, - {name = "wrapt-2.0.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/69/d8/61e245fe387d58d84b3f913d5da9d909c4f239b887db692a05105aaf2a1b/wrapt-2.0.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "be7e316c2accd5a31dbcc230de19e2a846a325f8967fdea72704d00e38e6af06"}}, - {name = "wrapt-2.0.0-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/ee/db/ac9546e89b645e525686727f8749847485e3b45ffc4507b61c4669358638/wrapt-2.0.0-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "a7cebcee61f21b1e46aa32db8d9d93826d0fbf1ad85defc2ccfb93b4adef1435"}}, - {name = "wrapt-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/74/bc/3b57c8012bbd0d02eec5ae838681c1a819df6c5e765ebc897f52623b5eb1/wrapt-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "827e6e3a3a560f6ec1f5ee92d4319c21a0549384f896ec692f3201eda31ebd11"}}, - {name = "wrapt-2.0.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b8/6e/b5e7d47713e3d46c30ec6ae83fafd369bc34de8148668c6e3168d9301863/wrapt-2.0.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "1a91075a5383a7cbfe46aed1845ef7c3f027e8e20e7d9a8a75e36ebc9b0dd15e"}}, - {name = "wrapt-2.0.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",url = "https://files.pythonhosted.org/packages/28/8d/d5df2af58ae479785473607a3b25726c295640cdcaee830847cee339eff9/wrapt-2.0.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",hashes = {sha256 = "b6a18c813196e18146b8d041e20875bdb0cb09b94ac1d1e1146e0fa87b2deb0d"}}, - {name = "wrapt-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/f9/b7/9501c45ab93b4d6ba396ef02fcfb55867866bc8579fff045bb54cae58423/wrapt-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ec5028d26011a53c76bd91bb6198b30b438c6e0f7adb45f2ad84fe2655b6a104"}}, - {name = "wrapt-2.0.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",url = "https://files.pythonhosted.org/packages/5e/3a/bfebe2ba51cf98ae80c5dbb6fa5892ae75d1acf1a4c404eda88e28f5ab06/wrapt-2.0.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl",hashes = {sha256 = "bed9b04900204721a24bcefc652ca267b01c1e8ad8bc8c0cff81558a45a3aadc"}}, - {name = "wrapt-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/00/e7/cd50a32bed022d98f61a90e57faf782aa063f7930f57eb67eb105d3189be/wrapt-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "03442f2b45fa3f2b98a94a1917f52fb34670de8f96c0a009c02dbd512d855a3d"}}, - {name = "wrapt-2.0.0-cp310-cp310-musllinux_1_2_riscv64.whl",url = "https://files.pythonhosted.org/packages/9d/2c/c709578271df0c70a27ab8f797c44c258650f24a32b452f03d7afedc070d/wrapt-2.0.0-cp310-cp310-musllinux_1_2_riscv64.whl",hashes = {sha256 = "17d0b5c42495ba142a1cee52b76414f9210591c84aae94dffda70240753bfb3c"}}, - {name = "wrapt-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/60/ef/cb58f6eea41f129600bda68d1ae4c80b14d4e0663eec1d5220cbffe50be5/wrapt-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "ee44215e7d13e112a8fc74e12ed1a1f41cab2bc07b11cc703f2398cd114b261c"}}, - {name = "wrapt-2.0.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/59/55/97e6c4e1c175fb27f8dec717a3e36493ff0c4e50173a95f439496556910f/wrapt-2.0.0-cp310-cp310-win32.whl",hashes = {sha256 = "fe6eafac3bc3c957ab6597a0c0654a0a308868458d00d218743e5b5fae51951c"}}, - {name = "wrapt-2.0.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/3b/0a/898b1d81ae1f3dd9a79fd2e0330a7c8dd793982f815a318548777cb21ee5/wrapt-2.0.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "9e070c3491397fba0445b8977900271eca9656570cca7c900d9b9352186703a0"}}, - {name = "wrapt-2.0.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/44/f1/e7e92f9535f5624ee22879f09456df9d1f1ae9bb338eef711077b48e456a/wrapt-2.0.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "806e2e73186eb5e3546f39fb5d0405040e0088db0fc8b2f667fd1863de2b3c99"}}, +name = "zipp" +version = "3.23.0" +requires-python = ">=3.9" +sdist = {name = "zipp-3.23.0.tar.gz", url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hashes = {sha256 = "a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}} +wheels = [ + {name = "zipp-3.23.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl",hashes = {sha256 = "071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}}, ] -marker = "\"default\" in dependency_groups" +marker = "python_full_version < \"3.10.2\" and \"dev\" in extras" [packages.tool.pdm] dependencies = [] @@ -4005,13 +2234,26 @@ dependencies = [ "typing-extensions>=4.5; python_version < \"3.13\"", ] +[[packages]] +name = "sniffio" +version = "1.3.1" +requires-python = ">=3.7" +sdist = {name = "sniffio-1.3.1.tar.gz", url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hashes = {sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}} +wheels = [ + {name = "sniffio-1.3.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl",hashes = {sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}}, +] +marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "iniconfig" -version = "2.3.0" -requires-python = ">=3.10" -sdist = {name = "iniconfig-2.3.0.tar.gz", url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hashes = {sha256 = "c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}} +version = "2.1.0" +requires-python = ">=3.8" +sdist = {name = "iniconfig-2.1.0.tar.gz", url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hashes = {sha256 = "3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}} wheels = [ - {name = "iniconfig-2.3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl",hashes = {sha256 = "f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}}, + {name = "iniconfig-2.1.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl",hashes = {sha256 = "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}}, ] marker = "\"dev\" in extras" @@ -4039,53 +2281,13 @@ version = "2.3.3" requires-python = ">=3.9" sdist = {name = "pandas-2.3.3.tar.gz", url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hashes = {sha256 = "e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}} wheels = [ - {name = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}}, - {name = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}}, - {name = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}}, {name = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}}, - {name = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}}, - {name = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}}, - {name = "pandas-2.3.3-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl",hashes = {sha256 = "1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}}, - {name = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}}, - {name = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}}, - {name = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}}, {name = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}}, - {name = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}}, - {name = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}}, - {name = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}}, - {name = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}}, - {name = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}}, {name = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}}, - {name = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}}, - {name = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}}, - {name = "pandas-2.3.3-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl",hashes = {sha256 = "f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}}, - {name = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}}, - {name = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}}, - {name = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}}, {name = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}}, - {name = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}}, - {name = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}}, - {name = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}}, - {name = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}}, - {name = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}}, {name = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}}, - {name = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}}, - {name = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}}, - {name = "pandas-2.3.3-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl",hashes = {sha256 = "a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}}, - {name = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}}, - {name = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}}, - {name = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}}, {name = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}}, - {name = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}}, - {name = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}}, - {name = "pandas-2.3.3-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl",hashes = {sha256 = "f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}}, - {name = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}}, - {name = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}}, - {name = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}}, {name = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}}, - {name = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}}, - {name = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}}, - {name = "pandas-2.3.3-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl",hashes = {sha256 = "503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" @@ -4167,11 +2369,11 @@ dependencies = [] [[packages]] name = "ruamel-yaml" -version = "0.18.16" +version = "0.18.15" requires-python = ">=3.8" -sdist = {name = "ruamel.yaml-0.18.16.tar.gz", url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hashes = {sha256 = "a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a"}} +sdist = {name = "ruamel.yaml-0.18.15.tar.gz", url = "https://files.pythonhosted.org/packages/3e/db/f3950f5e5031b618aae9f423a39bf81a55c148aecd15a34527898e752cf4/ruamel.yaml-0.18.15.tar.gz", hashes = {sha256 = "dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700"}} wheels = [ - {name = "ruamel.yaml-0.18.16-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/0f/73/bb1bc2529f852e7bf64a2dec885e89ff9f5cc7bbf6c9340eed30ff2c69c5/ruamel.yaml-0.18.16-py3-none-any.whl",hashes = {sha256 = "048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba"}}, + {name = "ruamel.yaml-0.18.15-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/d1/e5/f2a0621f1781b76a38194acae72f01e37b1941470407345b6e8653ad7640/ruamel.yaml-0.18.15-py3-none-any.whl",hashes = {sha256 = "148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701"}}, ] marker = "\"dev\" in extras" @@ -4186,52 +2388,12 @@ version = "0.2.14" requires-python = ">=3.9" sdist = {name = "ruamel.yaml.clib-0.2.14.tar.gz", url = "https://files.pythonhosted.org/packages/d8/e9/39ec4d4b3f91188fad1842748f67d4e749c77c37e353c4e545052ee8e893/ruamel.yaml.clib-0.2.14.tar.gz", hashes = {sha256 = "803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e"}} wheels = [ - {name = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/21/e2/a59ff65c26aaf21a24eb38df777cb9af5d87ba8fc8107c163c2da9d1e85e/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_10_15_universal2.whl",hashes = {sha256 = "7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f"}}, - {name = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6b/fa/3234f913fe9a6525a7b97c6dad1f51e72b917e6872e051a5e2ffd8b16fbb/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl",hashes = {sha256 = "70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83"}}, - {name = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/ef/ec/4edbf17ac2c87fa0845dd366ef8d5852b96eb58fcd65fc1ecf5fe27b4641/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl",hashes = {sha256 = "a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27"}}, - {name = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/15/18/b0e1fafe59051de9e79cdd431863b03593ecfa8341c110affad7c8121efc/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/d7/ae/e3811f05415594025e96000349d3400978adaed88d8f98d494352d9761ee/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl",hashes = {sha256 = "7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl",url = "https://files.pythonhosted.org/packages/72/06/7d51f4688d6d72bb72fa74254e1593c4f5ebd0036be5b41fe39315b275e9/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl",hashes = {sha256 = "dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/5a/08/b4499234a420ef42960eeb05585df5cc7eb25ccb8c980490b079e6367050/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl",hashes = {sha256 = "1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e"}}, {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b6/ba/1975a27dedf1c4c33306ee67c948121be8710b19387aada29e2f139c43ee/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/20/15/8a19a13d27f3bd09fa18813add8380a29115a47b553845f08802959acbce/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/19/ee/8d6146a079ad21e534b5083c9ee4a4c8bec42f79cf87594b60978286b39a/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/a9/f5/426b714abdc222392e68f3b8ad323930d05a214a27c7e7a0f06c69126401/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/3d/ac/3c5c2b27a183f4fda8a57c82211721c016bcb689a4a175865f7646db9f94/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/92/2e/06f56a71fd55021c993ed6e848c9b2e5e9cfce180a42179f0ddd28253f7c/ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl",hashes = {sha256 = "f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2"}}, - {name = "ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/51/79/76aba16a1689b50528224b182f71097ece338e7a4ab55e84c2e73443b78a/ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl",hashes = {sha256 = "090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl",url = "https://files.pythonhosted.org/packages/b4/42/ccfb34a25289afbbc42017e4d3d4288e61d35b2e00cfc6b92974a6a1f94b/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl",hashes = {sha256 = "6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/82/73/e628a92e80197ff6a79ab81ec3fa00d4cc082d58ab78d3337b7ba7043301/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl",hashes = {sha256 = "5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/2b/c5/346c7094344a60419764b4b1334d9e0285031c961176ff88ffb652405b0c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl",hashes = {sha256 = "a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a"}}, {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/df/99/65080c863eb06d4498de3d6c86f3e90595e02e159fd8529f1565f56cfe2c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/3d/e3/0de85f3e3333f8e29e4b10244374a202a87665d1131798946ee22cf05c7c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/d9/25/0d2f09d8833c7fd77ab8efeff213093c16856479a9d293180a0d89f6bed9/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/d3/8c/959f10c2e2153cbdab834c46e6954b6dd9e3b109c8f8c0a3cf1618310985/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ed/6b/e580a7c18b485e1a5f30a32cda96b20364b0ba649d9d2baaf72f8bd21f83/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/ef/44/3455eebc761dc8e8fdced90f2b0a3fa61e32ba38b50de4130e2d57db0f21/ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl",hashes = {sha256 = "b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54"}}, - {name = "ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/76/ab/5121f7f3b651db93de546f8c982c241397aad0a4765d793aca1dac5eadee/ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl",hashes = {sha256 = "a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/b3/9f/3c51e9578b8c36fcc4bdd271a1a5bb65963a74a4b6ad1a989768a22f6c2a/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4a/16/cb02815bc2ae9c66760c0c061d23c7358f9ba51dae95ac85247662b7fbe2/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl",hashes = {sha256 = "0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/31/c6/fc687cd1b93bff8e40861eea46d6dc1a6a778d9a085684e4045ff26a8e40/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl",hashes = {sha256 = "10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9"}}, {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/45/5d/65a2bc08b709b08576b3f307bf63951ee68a8e047cbbda6f1c9864ecf9a7/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/fb/d0/a70a03614d9a6788a3661ab1538879ed2aae4e84d861f101243116308a37/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/77/30/c93fa457611f79946d5cb6cc97493ca5425f3f21891d7b1f9b44eaa1b38e/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/40/85/e2c54ad637117cd13244a4649946eaa00f32edcb882d1f92df90e079ab00/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/81/50/f899072c38877d8ef5382e0b3d47f8c4346226c1f52d6945d6f64fec6a2f/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/99/7c/96d4b5075e30c65ea2064e40c2d657c7c235d7b6ef18751cf89a935b9041/ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl",hashes = {sha256 = "915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a"}}, - {name = "ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7d/8c/73ee2babd04e8bfcf1fd5c20aa553d18bf0ebc24b592b4f831d12ae46cc0/ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl",hashes = {sha256 = "4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/b4/56/35a0a752415ae01992c68f5a6513bdef0e1b6fbdb60d7619342ce12346a0/ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_13_0_arm64.whl",url = "https://files.pythonhosted.org/packages/98/6a/9a68184ab93619f4607ff1675e4ef01e8accfcbff0d482f4ca44c10d8eab/ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_13_0_arm64.whl",hashes = {sha256 = "aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/2b/3f/cfed5f088628128a9ec66f46794fd4d165642155c7b78c26d83b16c6bf7b/ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux2014_aarch64.whl",hashes = {sha256 = "a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543"}}, {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/3a/d5/5ce2cc156c1da48160171968d91f066d305840fbf930ee955a509d025a44/ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/2b/71/d0b56bc902b38ebe4be8e270f730f929eec4edaf8a0fa7028f4ef64fa950/ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/4b/db/1f37449dd89c540218598316ccafc1a0aed60215e72efa315c5367cfd015/ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/5d/53/c498b30f35efcd9f47cb084d7ad9374f2b907470f73913dec6396b81397d/ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/34/79/492cfad9baed68914840c39e5f3c1cc251f51a897ddb3f532601215cbb12/ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/ca/f5/479ebfd5ba396e209ade90f7282d84b90c57b3e07be8dc6fcd02a6df7ffc/ruamel.yaml.clib-0.2.14-cp310-cp310-win32.whl",hashes = {sha256 = "27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182"}}, - {name = "ruamel.yaml.clib-0.2.14-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/57/31/a044520fdb3bd409889f67f1efebda0658033c7ab3f390cee37531cc9a9e/ruamel.yaml.clib-0.2.14-cp310-cp310-win_amd64.whl",hashes = {sha256 = "4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8"}}, ] -marker = "platform_python_implementation == \"CPython\" and python_version < \"3.14\" and python_full_version >= \"3.10.0\" and \"dev\" in extras" +marker = "platform_python_implementation == \"CPython\" and python_version < \"3.14\" and \"dev\" in extras" [packages.tool.pdm] dependencies = [] @@ -4307,300 +2469,24 @@ version = "3.6.0" requires-python = ">=3.7" sdist = {name = "xxhash-3.6.0.tar.gz", url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hashes = {sha256 = "f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6"}} wheels = [ - {name = "xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl",hashes = {sha256 = "a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e"}}, - {name = "xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl",hashes = {sha256 = "a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405"}}, - {name = "xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3"}}, - {name = "xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6"}}, - {name = "xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063"}}, - {name = "xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7"}}, {name = "xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b"}}, - {name = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl",hashes = {sha256 = "c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd"}}, - {name = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl",hashes = {sha256 = "9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0"}}, - {name = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152"}}, - {name = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl",hashes = {sha256 = "8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11"}}, - {name = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl",hashes = {sha256 = "653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5"}}, - {name = "xxhash-3.6.0-cp314-cp314-win32.whl",url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl",hashes = {sha256 = "a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f"}}, - {name = "xxhash-3.6.0-cp314-cp314-win_amd64.whl",url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl",hashes = {sha256 = "39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad"}}, - {name = "xxhash-3.6.0-cp314-cp314-win_arm64.whl",url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl",hashes = {sha256 = "25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679"}}, - {name = "xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl",hashes = {sha256 = "c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4"}}, - {name = "xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl",hashes = {sha256 = "1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67"}}, - {name = "xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad"}}, - {name = "xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b"}}, - {name = "xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b"}}, - {name = "xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca"}}, {name = "xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a"}}, - {name = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99"}}, - {name = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl",hashes = {sha256 = "a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3"}}, - {name = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6"}}, - {name = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl",hashes = {sha256 = "c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93"}}, - {name = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518"}}, - {name = "xxhash-3.6.0-cp314-cp314t-win32.whl",url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl",hashes = {sha256 = "5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119"}}, - {name = "xxhash-3.6.0-cp314-cp314t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl",hashes = {sha256 = "0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f"}}, - {name = "xxhash-3.6.0-cp314-cp314t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl",hashes = {sha256 = "bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95"}}, - {name = "xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl",hashes = {sha256 = "599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec"}}, - {name = "xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl",hashes = {sha256 = "7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1"}}, - {name = "xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6"}}, - {name = "xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263"}}, - {name = "xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546"}}, - {name = "xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89"}}, {name = "xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d"}}, - {name = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7"}}, - {name = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db"}}, - {name = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42"}}, - {name = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl",hashes = {sha256 = "40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11"}}, - {name = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd"}}, - {name = "xxhash-3.6.0-cp313-cp313-win32.whl",url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl",hashes = {sha256 = "2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799"}}, - {name = "xxhash-3.6.0-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl",hashes = {sha256 = "757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392"}}, - {name = "xxhash-3.6.0-cp313-cp313-win_arm64.whl",url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl",hashes = {sha256 = "457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6"}}, - {name = "xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl",hashes = {sha256 = "a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702"}}, - {name = "xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl",hashes = {sha256 = "568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db"}}, - {name = "xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54"}}, - {name = "xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f"}}, - {name = "xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5"}}, - {name = "xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1"}}, {name = "xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee"}}, - {name = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl",hashes = {sha256 = "794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd"}}, - {name = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl",hashes = {sha256 = "6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729"}}, - {name = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292"}}, - {name = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl",hashes = {sha256 = "d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf"}}, - {name = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033"}}, - {name = "xxhash-3.6.0-cp313-cp313t-win32.whl",url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl",hashes = {sha256 = "1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec"}}, - {name = "xxhash-3.6.0-cp313-cp313t-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl",hashes = {sha256 = "b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8"}}, - {name = "xxhash-3.6.0-cp313-cp313t-win_arm64.whl",url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl",hashes = {sha256 = "ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746"}}, - {name = "xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl",hashes = {sha256 = "01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c"}}, - {name = "xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl",hashes = {sha256 = "b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204"}}, - {name = "xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490"}}, - {name = "xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2"}}, - {name = "xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa"}}, - {name = "xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0"}}, {name = "xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2"}}, - {name = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9"}}, - {name = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e"}}, - {name = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374"}}, - {name = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl",hashes = {sha256 = "7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d"}}, - {name = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae"}}, - {name = "xxhash-3.6.0-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl",hashes = {sha256 = "50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb"}}, - {name = "xxhash-3.6.0-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl",hashes = {sha256 = "c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c"}}, - {name = "xxhash-3.6.0-cp312-cp312-win_arm64.whl",url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl",hashes = {sha256 = "eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829"}}, - {name = "xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a"}}, - {name = "xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa"}}, - {name = "xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248"}}, - {name = "xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62"}}, - {name = "xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f"}}, - {name = "xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e"}}, {name = "xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8"}}, - {name = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0"}}, - {name = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77"}}, - {name = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c"}}, - {name = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl",hashes = {sha256 = "929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b"}}, - {name = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3"}}, - {name = "xxhash-3.6.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl",hashes = {sha256 = "d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd"}}, - {name = "xxhash-3.6.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef"}}, - {name = "xxhash-3.6.0-cp311-cp311-win_arm64.whl",url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl",hashes = {sha256 = "d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7"}}, - {name = "xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0"}}, - {name = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296"}}, - {name = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13"}}, {name = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd"}}, - {name = "xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl",hashes = {sha256 = "15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d"}}, - {name = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}}, - {name = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}}, - {name = "xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl",hashes = {sha256 = "89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8"}}, - {name = "xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058"}}, - {name = "xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl",hashes = {sha256 = "b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2"}}, - {name = "xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl",hashes = {sha256 = "a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc"}}, {name = "xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc"}}, - {name = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07"}}, - {name = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4"}}, - {name = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06"}}, - {name = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl",url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl",hashes = {sha256 = "5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4"}}, - {name = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b"}}, - {name = "xxhash-3.6.0-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl",hashes = {sha256 = "aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b"}}, - {name = "xxhash-3.6.0-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl",hashes = {sha256 = "e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb"}}, - {name = "xxhash-3.6.0-cp310-cp310-win_arm64.whl",url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl",hashes = {sha256 = "4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d"}}, ] marker = "\"default\" in dependency_groups or \"all\" in extras or \"audio\" in extras or \"dev\" in extras or \"vision\" in extras" [packages.tool.pdm] dependencies = [] -[[packages]] -name = "numpy" -version = "2.2.6" -requires-python = ">=3.10" -sdist = {name = "numpy-2.2.6.tar.gz", url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hashes = {sha256 = "e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}} -wheels = [ - {name = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}}, - {name = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}}, - {name = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl",hashes = {sha256 = "3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}}, - {name = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl",hashes = {sha256 = "481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}}, - {name = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}}, - {name = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}}, - {name = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}}, - {name = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}}, - {name = "numpy-2.2.6-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl",hashes = {sha256 = "0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}}, - {name = "numpy-2.2.6-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl",hashes = {sha256 = "e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}}, - {name = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}}, - {name = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}}, - {name = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl",hashes = {sha256 = "37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}}, - {name = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl",hashes = {sha256 = "5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}}, - {name = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}}, - {name = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}}, - {name = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}}, - {name = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}}, - {name = "numpy-2.2.6-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl",hashes = {sha256 = "b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}}, - {name = "numpy-2.2.6-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl",hashes = {sha256 = "f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}}, - {name = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl",hashes = {sha256 = "0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}}, - {name = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl",hashes = {sha256 = "7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}}, - {name = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}}, - {name = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl",url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl",hashes = {sha256 = "d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}}, -] -marker = "python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"default\" in dependency_groups or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"all\" in extras or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"audio\" in extras or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"dev\" in extras or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"vision\" in extras" - -[packages.tool.pdm] -dependencies = [] - -[[packages]] -name = "scipy" -version = "1.15.3" -requires-python = ">=3.10" -sdist = {name = "scipy-1.15.3.tar.gz", url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hashes = {sha256 = "eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf"}} -wheels = [ - {name = "scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl",hashes = {sha256 = "993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b"}}, - {name = "scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl",hashes = {sha256 = "34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba"}}, - {name = "scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl",hashes = {sha256 = "3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65"}}, - {name = "scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl",hashes = {sha256 = "6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1"}}, - {name = "scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889"}}, - {name = "scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982"}}, - {name = "scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9"}}, - {name = "scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594"}}, - {name = "scipy-1.15.3-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl",hashes = {sha256 = "ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb"}}, - {name = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl",url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl",hashes = {sha256 = "a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}}, - {name = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl",hashes = {sha256 = "ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}}, - {name = "scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl",hashes = {sha256 = "aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f"}}, - {name = "scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl",hashes = {sha256 = "1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92"}}, - {name = "scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82"}}, - {name = "scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40"}}, - {name = "scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e"}}, - {name = "scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c"}}, - {name = "scipy-1.15.3-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl",hashes = {sha256 = "9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13"}}, -] -marker = "python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"dev\" in extras" - -[packages.tool.pdm] -dependencies = [ - "numpy<2.5,>=1.23.5", -] - -[[packages]] -name = "tomli" -version = "2.3.0" -requires-python = ">=3.8" -sdist = {name = "tomli-2.3.0.tar.gz", url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hashes = {sha256 = "64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}} -wheels = [ - {name = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}}, - {name = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}}, - {name = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}}, - {name = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}}, - {name = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}}, - {name = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}}, - {name = "tomli-2.3.0-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl",hashes = {sha256 = "00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}}, - {name = "tomli-2.3.0-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl",hashes = {sha256 = "4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}}, - {name = "tomli-2.3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl",hashes = {sha256 = "e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}}, -] -marker = "python_full_version ~= \"3.10.0\" and \"dev\" in extras" - -[packages.tool.pdm] -dependencies = [] - -[[packages]] -name = "backports-asyncio-runner" -version = "1.2.0" -requires-python = "<3.11,>=3.8" -sdist = {name = "backports_asyncio_runner-1.2.0.tar.gz", url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hashes = {sha256 = "a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}} -wheels = [ - {name = "backports_asyncio_runner-1.2.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl",hashes = {sha256 = "0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}}, -] -marker = "python_full_version ~= \"3.10.0\" and \"dev\" in extras" - -[packages.tool.pdm] -dependencies = [] - -[[packages]] -name = "async-timeout" -version = "5.0.1" -requires-python = ">=3.8" -sdist = {name = "async_timeout-5.0.1.tar.gz", url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hashes = {sha256 = "d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}} -wheels = [ - {name = "async_timeout-5.0.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl",hashes = {sha256 = "39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}}, -] -marker = "\"default\" in dependency_groups and python_full_version ~= \"3.10.0\" or \"all\" in extras and python_full_version ~= \"3.10.0\" or \"audio\" in extras and python_full_version ~= \"3.10.0\" or \"dev\" in extras and python_full_version ~= \"3.10.0\" or \"vision\" in extras and python_full_version ~= \"3.10.0\"" - -[packages.tool.pdm] -dependencies = [] - -[[packages]] -name = "exceptiongroup" -version = "1.3.0" -requires-python = ">=3.7" -sdist = {name = "exceptiongroup-1.3.0.tar.gz", url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hashes = {sha256 = "b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}} -wheels = [ - {name = "exceptiongroup-1.3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl",hashes = {sha256 = "4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}}, -] -marker = "\"default\" in dependency_groups and python_full_version ~= \"3.10.0\" or \"all\" in extras and python_full_version ~= \"3.10.0\" or \"audio\" in extras and python_full_version ~= \"3.10.0\" or \"dev\" in extras and python_full_version ~= \"3.10.0\" or \"vision\" in extras and python_full_version ~= \"3.10.0\"" - -[packages.tool.pdm] -dependencies = [ - "typing-extensions>=4.6.0; python_version < \"3.13\"", -] - -[[packages]] -name = "importlib-metadata" -version = "8.7.0" -requires-python = ">=3.9" -sdist = {name = "importlib_metadata-8.7.0.tar.gz", url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hashes = {sha256 = "d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}} -wheels = [ - {name = "importlib_metadata-8.7.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl",hashes = {sha256 = "e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}}, -] -marker = "python_full_version >= \"3.10.0\" and python_full_version < \"3.10.2\" and \"dev\" in extras" - -[packages.tool.pdm] -dependencies = [ - "zipp>=3.20", - "typing-extensions>=3.6.4; python_version < \"3.8\"", -] - -[[packages]] -name = "networkx" -version = "3.4.2" -requires-python = ">=3.10" -sdist = {name = "networkx-3.4.2.tar.gz", url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hashes = {sha256 = "307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}} -wheels = [ - {name = "networkx-3.4.2-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl",hashes = {sha256 = "df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}}, -] -marker = "python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"default\" in dependency_groups or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"all\" in extras or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"audio\" in extras or python_full_version >= \"3.10.0\" and python_version < \"3.12\" and \"dev\" in extras" - -[packages.tool.pdm] -dependencies = [] - -[[packages]] -name = "zipp" -version = "3.23.0" -requires-python = ">=3.9" -sdist = {name = "zipp-3.23.0.tar.gz", url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hashes = {sha256 = "a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}} -wheels = [ - {name = "zipp-3.23.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl",hashes = {sha256 = "071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}}, -] -marker = "python_full_version >= \"3.10.0\" and python_full_version < \"3.10.2\" and \"dev\" in extras" - -[packages.tool.pdm] -dependencies = [] - [tool.pdm] -hashes = {sha256 = "2d67e2fe1846153c1fd3621ad191958a54326b5ba14cd11b68fb7577391502e7"} +hashes = {sha256 = "efdb83fe02571cdba60ede688cdc3074f3a4bd9a22e74b623e5f477af65e96a6"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] -requires_python = "~=3.12" - -[[tool.pdm.targets]] -requires_python = ">=3.10.0,<3.12" +requires_python = ">=3.10.0,<4.0" +platform = "manylinux_2_40_x86_64" diff --git a/pyproject.toml b/pyproject.toml index 8ac5cd1e..4a96e6d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,7 @@ dependencies = [ "pyyaml>=6.0.0", "rich", "sanic", + "tabulate", "transformers", "uvloop>=0.18", "torch", @@ -129,6 +130,7 @@ dev = [ "mdformat-gfm~=0.3.6", # type-checking + "pandas-stubs", "types-PyYAML~=6.0.1", "types-requests~=2.32.0", "types-toml", diff --git a/src/guidellm/__main__.py b/src/guidellm/__main__.py index 1faaaafa..87230ff6 100644 --- a/src/guidellm/__main__.py +++ b/src/guidellm/__main__.py @@ -45,26 +45,12 @@ reimport_benchmarks_report, ) from guidellm.mock_server import MockServer, MockServerConfig -from guidellm.preprocess.dataset import ShortPromptStrategy, process_dataset from guidellm.scheduler import StrategyType from guidellm.schemas import GenerativeRequestType from guidellm.settings import print_config from guidellm.utils import Console, DefaultGroupHandler, get_literal_vals from guidellm.utils import cli as cli_tools -__all__ = [ - "STRATEGY_PROFILE_CHOICES", - "benchmark", - "cli", - "config", - "dataset", - "decode_escaped_str", - "from_file", - "mock_server", - "preprocess", - "run", -] - STRATEGY_PROFILE_CHOICES: list[str] = list(get_literal_vals(ProfileType | StrategyType)) """Available strategy and profile type choices for benchmark execution.""" @@ -256,7 +242,7 @@ def benchmark(): help="Number of worker processes for data loading.", ) @click.option( - "--dataloader_kwargs", + "--dataloader-kwargs", default=BenchmarkGenerativeTextArgs.get_default("dataloader_kwargs"), callback=cli_tools.parse_json, help="JSON string of arguments to pass to the dataloader constructor.", @@ -305,22 +291,45 @@ def benchmark(): "--warmup", "--warmup-percent", # legacy alias "warmup", - type=float, default=BenchmarkGenerativeTextArgs.get_default("warmup"), + callback=cli_tools.parse_json, help=( - "Warmup specification: if in (0,1) = percent, if >=1 = number of " - "requests/seconds (depends on active constraint)." + "Warmup specification: int, float, or dict as string " + "(json or key=value). " + "Controls time or requests before measurement starts. " + "Numeric in (0, 1): percent of duration or request count. " + "Numeric >=1: duration in seconds or request count. " + "Advanced config: see TransientPhaseConfig schema." ), ) @click.option( "--cooldown", "--cooldown-percent", # legacy alias "cooldown", - type=float, default=BenchmarkGenerativeTextArgs.get_default("cooldown"), + callback=cli_tools.parse_json, + help=( + "Cooldown specification: int, float, or dict as string " + "(json or key=value). " + "Controls time or requests after measurement ends. " + "Numeric in (0, 1): percent of duration or request count. " + "Numeric >=1: duration in seconds or request count. " + "Advanced config: see TransientPhaseConfig schema." + ), +) +@click.option( + "--rampup", + default=BenchmarkGenerativeTextArgs.get_default("rampup"), + callback=cli_tools.parse_json, help=( - "Cooldown specification: if in (0,1) = percent, if >=1 = number of " - "requests/seconds (depends on active constraint)." + "Rampup specification: int, float, or dict as string " + "(json or key=value). " + "Controls time to linearly ramp up requests. " + "Only for Throughput/Concurrent strategies, " + "not Synchronous/Rate-based. " + "Numeric in (0, 1): percent of duration. " + "Numeric >=1: duration in seconds. " + "Advanced config: see TransientPhaseConfig schema." ), ) @click.option( @@ -469,128 +478,6 @@ def preprocess(): """Dataset preprocessing utilities.""" -@preprocess.command( - "dataset", - help=( - "Process a dataset to have specific prompt and output token sizes. " - "Supports multiple strategies for handling prompts and optional " - "Hugging Face Hub upload.\n\n" - "DATA: Path to the input dataset or dataset ID.\n\n" - "OUTPUT_PATH: Path to save the processed dataset, including file suffix." - ), - context_settings={"auto_envvar_prefix": "GUIDELLM"}, -) -@click.argument( - "data", - type=str, - required=True, -) -@click.argument( - "output_path", - type=click.Path(file_okay=True, dir_okay=False, writable=True, resolve_path=True), - required=True, -) -@click.option( - "--processor", - type=str, - required=True, - help="Processor or tokenizer name for calculating token counts.", -) -@click.option( - "--processor-args", - default=None, - callback=cli_tools.parse_json, - help="JSON string of arguments to pass to the processor constructor.", -) -@click.option( - "--data-args", - callback=cli_tools.parse_json, - help="JSON string of arguments to pass to dataset creation.", -) -@click.option( - "--short-prompt-strategy", - type=click.Choice([s.value for s in ShortPromptStrategy]), - default=ShortPromptStrategy.IGNORE.value, - show_default=True, - help="Strategy for handling prompts shorter than target length.", -) -@click.option( - "--pad-char", - type=str, - default="", - callback=decode_escaped_str, - help="Character to pad short prompts with when using 'pad' strategy.", -) -@click.option( - "--concat-delimiter", - type=str, - default="", - help=( - "Delimiter for concatenating short prompts (used with 'concatenate' strategy)." - ), -) -@click.option( - "--prompt-tokens", - type=str, - default=None, - help="Prompt tokens configuration (JSON, YAML file, or key=value string).", -) -@click.option( - "--output-tokens", - type=str, - default=None, - help="Output tokens configuration (JSON, YAML file, or key=value string).", -) -@click.option( - "--push-to-hub", - is_flag=True, - help="Push the processed dataset to Hugging Face Hub.", -) -@click.option( - "--hub-dataset-id", - type=str, - default=None, - help=("Hugging Face Hub dataset ID for upload (required if --push-to-hub is set)."), -) -@click.option( - "--random-seed", - type=int, - default=42, - show_default=True, - help="Random seed for reproducible token sampling.", -) -def dataset( - data, - output_path, - processor, - processor_args, - data_args, - short_prompt_strategy, - pad_char, - concat_delimiter, - prompt_tokens, - output_tokens, - push_to_hub, - hub_dataset_id, - random_seed, -): - process_dataset( - data=data, - output_path=output_path, - processor=processor, - prompt_tokens=prompt_tokens, - output_tokens=output_tokens, - processor_args=processor_args, - data_args=data_args, - short_prompt_strategy=short_prompt_strategy, - pad_char=pad_char, - concat_delimiter=concat_delimiter, - push_to_hub=push_to_hub, - hub_dataset_id=hub_dataset_id, - random_seed=random_seed, - ) - - @cli.command( "mock-server", help=( diff --git a/src/guidellm/backends/backend.py b/src/guidellm/backends/backend.py index 89169a48..bc3fe37a 100644 --- a/src/guidellm/backends/backend.py +++ b/src/guidellm/backends/backend.py @@ -102,9 +102,8 @@ def requests_limit(self) -> int | None: return None @abstractmethod - async def default_model(self) -> str | None: + async def default_model(self) -> str: """ :return: The default model name or identifier for generation requests, - None if no default model is available """ ... diff --git a/src/guidellm/backends/openai.py b/src/guidellm/backends/openai.py index 1e74fc6e..57e2d95a 100644 --- a/src/guidellm/backends/openai.py +++ b/src/guidellm/backends/openai.py @@ -18,10 +18,7 @@ import httpx from guidellm.backends.backend import Backend -from guidellm.backends.response_handlers import ( - GenerationResponseHandler, - GenerationResponseHandlerFactory, -) +from guidellm.backends.response_handlers import GenerationResponseHandlerFactory from guidellm.schemas import GenerationRequest, GenerationResponse, RequestInfo __all__ = ["OpenAIHTTPBackend"] @@ -54,7 +51,7 @@ class OpenAIHTTPBackend(Backend): def __init__( self, target: str, - model: str | None = None, + model: str = "", api_routes: dict[str, str] | None = None, response_handlers: dict[str, Any] | None = None, timeout: float = 60.0, @@ -192,7 +189,7 @@ async def available_models(self) -> list[str]: return [item["id"] for item in response.json()["data"]] - async def default_model(self) -> str | None: + async def default_model(self) -> str: """ Get the default model for this backend. @@ -202,9 +199,9 @@ async def default_model(self) -> str | None: return self.model models = await self.available_models() - return models[0] if models else None + return models[0] if models else "" - async def resolve( + async def resolve( # type: ignore[override] self, request: GenerationRequest, request_info: RequestInfo, @@ -230,11 +227,9 @@ async def resolve( if history is not None: raise NotImplementedError("Multi-turn requests not yet supported") - response_handler = self._resolve_response_handler( - request_type=request.request_type - ) if (request_path := self.api_routes.get(request.request_type)) is None: raise ValueError(f"Unsupported request type '{request.request_type}'") + request_url = f"{self.target}/{request_path}" request_files = ( { @@ -246,6 +241,9 @@ async def resolve( ) request_json = request.arguments.body if not request_files else None request_data = request.arguments.body if request_files else None + response_handler = GenerationResponseHandlerFactory.create( + request.request_type, handler_overrides=self.response_handlers + ) if not request.arguments.stream: request_info.timings.request_start = time.time() @@ -282,24 +280,22 @@ async def resolve( async for chunk in stream.aiter_lines(): iter_time = time.time() - if ( - (iterations := response_handler.add_streaming_line(chunk)) - is None - or iterations < 0 - or end_reached - ): + if request_info.timings.first_request_iteration is None: + request_info.timings.first_request_iteration = iter_time + request_info.timings.last_request_iteration = iter_time + request_info.timings.request_iterations += 1 + + iterations = response_handler.add_streaming_line(chunk) + if iterations is None or iterations <= 0 or end_reached: end_reached = end_reached or iterations is None continue - if ( - request_info.timings.first_iteration is None - or request_info.timings.iterations is None - ): - request_info.timings.first_iteration = iter_time - request_info.timings.iterations = 0 + if request_info.timings.first_token_iteration is None: + request_info.timings.first_token_iteration = iter_time + request_info.timings.token_iterations = 0 - request_info.timings.last_iteration = iter_time - request_info.timings.iterations += iterations + request_info.timings.last_token_iteration = iter_time + request_info.timings.token_iterations += iterations request_info.timings.request_end = time.time() yield response_handler.compile_streaming(request), request_info @@ -336,20 +332,3 @@ def _resolve_validate_kwargs( validate_kwargs["method"] = "GET" return validate_kwargs - - def _resolve_response_handler(self, request_type: str) -> GenerationResponseHandler: - if ( - self.response_handlers is not None - and (handler := self.response_handlers.get(request_type)) is not None - ): - return handler - - handler_class = GenerationResponseHandlerFactory.get_registered_object( - request_type - ) - if handler_class is None: - raise ValueError( - f"No response handler registered for request type '{request_type}'" - ) - - return handler_class() diff --git a/src/guidellm/backends/response_handlers.py b/src/guidellm/backends/response_handlers.py index b7bd06ad..f1e83bd8 100644 --- a/src/guidellm/backends/response_handlers.py +++ b/src/guidellm/backends/response_handlers.py @@ -9,7 +9,7 @@ from __future__ import annotations -from typing import Any, Protocol +from typing import Any, Protocol, cast from guidellm.schemas import GenerationRequest, GenerationResponse, UsageMetrics from guidellm.utils import RegistryMixin, json @@ -72,6 +72,33 @@ class GenerationResponseHandlerFactory(RegistryMixin[type[GenerationResponseHand responses from different generation services. """ + @classmethod + def create( + cls, + request_type: str, + handler_overrides: dict[str, type[GenerationResponseHandler]] | None = None, + ) -> GenerationResponseHandler: + """ + Create a response handler class for the given request type. + + :param request_type: The type of generation request (e.g., "text_completions") + :param handler_overrides: Optional mapping of request types to handler classes + to override the default registry by checking first and then falling back + to the registered handlers. + :return: The corresponding instantiated GenerationResponseHandler + :raises ValueError: When no handler is registered for the request type + """ + if handler_overrides and request_type in handler_overrides: + return handler_overrides[request_type]() + + handler_cls = cls.get_registered_object(request_type) + if not handler_cls: + raise ValueError( + f"No response handler registered for type '{request_type}'." + ) + + return handler_cls() + @GenerationResponseHandlerFactory.register("text_completions") class TextCompletionsResponseHandler(GenerationResponseHandler): @@ -109,14 +136,16 @@ def compile_non_streaming( :return: Standardized GenerationResponse with extracted text and metrics """ choices, usage = self.extract_choices_and_usage(response) - input_metrics, output_metrics = self.extract_metrics(usage) + choice = choices[0] if choices else {} + text = choice.get("text", "") + input_metrics, output_metrics = self.extract_metrics(usage, text) return GenerationResponse( request_id=request.request_id, request_args=str( request.arguments.model_dump() if request.arguments else None ), - text=choices[0].get("text", "") if choices else "", + text=text, input_metrics=input_metrics, output_metrics=output_metrics, ) @@ -136,8 +165,9 @@ def add_streaming_line(self, line: str) -> int | None: updated = False choices, usage = self.extract_choices_and_usage(data) + choice = choices[0] if choices else {} - if text := choices[0].get("text"): + if choices and (text := choice.get("text")): self.streaming_texts.append(text) updated = True @@ -153,14 +183,15 @@ def compile_streaming(self, request: GenerationRequest) -> GenerationResponse: :param request: Original generation request :return: Standardized GenerationResponse with concatenated text and metrics """ - input_metrics, output_metrics = self.extract_metrics(self.streaming_usage) + text = "".join(self.streaming_texts) + input_metrics, output_metrics = self.extract_metrics(self.streaming_usage, text) return GenerationResponse( request_id=request.request_id, request_args=str( request.arguments.model_dump() if request.arguments else None ), - text="".join(self.streaming_texts), + text=text, input_metrics=input_metrics, output_metrics=output_metrics, ) @@ -194,25 +225,34 @@ def extract_choices_and_usage( return response.get("choices", []), response.get("usage", {}) def extract_metrics( - self, usage: dict[str, int | dict[str, int]] | None + self, usage: dict[str, int | dict[str, int]] | None, text: str ) -> tuple[UsageMetrics, UsageMetrics]: """ Extract input and output usage metrics from API response usage data. :param usage: Usage data dictionary from API response + :param text: Generated text for calculating word and character counts :return: Tuple of input_metrics and output_metrics as UsageMetrics objects """ if not usage: - return UsageMetrics(), UsageMetrics() + return UsageMetrics(), UsageMetrics( + text_words=len(text.split()) if text else 0, + text_characters=len(text) if text else 0, + ) - input_details: dict[str, int] = usage.get("prompt_tokens_details", {}) or {} - output_details: dict[str, int] = ( - usage.get("completion_tokens_details", {}) or {} + input_details: dict[str, int] = cast( + "dict[str, int]", usage.get("prompt_tokens_details", {}) or {} + ) + output_details: dict[str, int] = cast( + "dict[str, int]", usage.get("completion_tokens_details", {}) or {} ) + usage_metrics: dict[str, int] = cast("dict[str, int]", usage) return UsageMetrics( text_tokens=( - input_details.get("prompt_tokens") or usage.get("prompt_tokens") + input_details.get("prompt_tokens") + or usage_metrics.get("prompt_tokens") + or 0 ), image_tokens=input_details.get("image_tokens"), video_tokens=input_details.get("video_tokens"), @@ -221,8 +261,11 @@ def extract_metrics( ), UsageMetrics( text_tokens=( output_details.get("completion_tokens") - or usage.get("completion_tokens") + or usage_metrics.get("completion_tokens") + or 0 ), + text_words=len(text.split()) if text else 0, + text_characters=len(text) if text else 0, image_tokens=output_details.get("image_tokens"), video_tokens=output_details.get("video_tokens"), audio_tokens=output_details.get("audio_tokens"), @@ -254,14 +297,16 @@ def compile_non_streaming( :return: Standardized GenerationResponse with extracted content and metrics """ choices, usage = self.extract_choices_and_usage(response) - input_metrics, output_metrics = self.extract_metrics(usage) + choice: dict[str, dict] = choices[0] if choices else {} + text = choice.get("message", {}).get("content", "") + input_metrics, output_metrics = self.extract_metrics(usage, text) return GenerationResponse( request_id=request.request_id, request_args=str( request.arguments.model_dump() if request.arguments else None ), - text=(choices[0].get("message", {}).get("content", "") if choices else ""), + text=text, input_metrics=input_metrics, output_metrics=output_metrics, ) @@ -281,8 +326,9 @@ def add_streaming_line(self, line: str) -> int | None: updated = False choices, usage = self.extract_choices_and_usage(data) + choice: dict[str, dict] = choices[0] if choices else {} - if choices and (content := choices[0].get("delta", {}).get("content")): + if choices and (content := choice.get("delta", {}).get("content")): self.streaming_texts.append(content) updated = True @@ -298,14 +344,15 @@ def compile_streaming(self, request: GenerationRequest) -> GenerationResponse: :param request: Original generation request :return: Standardized GenerationResponse with concatenated content and metrics """ - input_metrics, output_metrics = self.extract_metrics(self.streaming_usage) + text = "".join(self.streaming_texts) + input_metrics, output_metrics = self.extract_metrics(self.streaming_usage, text) return GenerationResponse( request_id=request.request_id, request_args=str( request.arguments.model_dump() if request.arguments else None ), - text="".join(self.streaming_texts), + text=text, input_metrics=input_metrics, output_metrics=output_metrics, ) @@ -352,10 +399,9 @@ def compile_non_streaming( :param response: Complete API response containing text and usage data :return: Standardized GenerationResponse with extracted text and metrics """ - usage: dict[str, int | dict[str, int]] = response.get("usage", {}) - input_details: dict[str, int] = usage.get("input_token_details", {}) or {} - output_details: dict[str, int] = usage.get("output_token_details", {}) or {} text: str = response.get("text", "") + usage: dict[str, int | dict[str, int]] = response.get("usage", {}) + input_metrics, output_metrics = self.extract_metrics(usage, text) return GenerationResponse( request_id=request.request_id, @@ -363,18 +409,8 @@ def compile_non_streaming( request.arguments.model_dump() if request.arguments else None ), text=text, - input_metrics=UsageMetrics( - text_tokens=input_details.get("text_tokens", usage.get("input_tokens")), - audio_tokens=input_details.get( - "audio_tokens", usage.get("input_tokens") - ), - audio_seconds=input_details.get("seconds", usage.get("seconds")), - ), - output_metrics=UsageMetrics( - text_tokens=output_details.get( - "text_tokens", usage.get("output_tokens") - ), - ), + input_metrics=input_metrics, + output_metrics=output_metrics, ) def add_streaming_line(self, line: str) -> int | None: @@ -394,8 +430,6 @@ def add_streaming_line(self, line: str) -> int | None: return 0 data: dict[str, Any] = json.loads(line) - text: str - usage: dict[str, int | dict[str, int]] updated = False if text := data.get("text"): @@ -414,20 +448,21 @@ def compile_streaming(self, request: GenerationRequest) -> GenerationResponse: :param request: Original generation request :return: Standardized GenerationResponse with concatenated text and metrics """ - input_metrics, output_metrics = self.extract_metrics(self.streaming_usage) + text = "".join(self.streaming_texts) + input_metrics, output_metrics = self.extract_metrics(self.streaming_usage, text) return GenerationResponse( request_id=request.request_id, request_args=str( request.arguments.model_dump() if request.arguments else None ), - text="".join(self.streaming_texts), + text=text, input_metrics=input_metrics, output_metrics=output_metrics, ) def extract_metrics( - self, usage: dict[str, int | dict[str, int]] | None + self, usage: dict[str, int | dict[str, int]] | None, text: str ) -> tuple[UsageMetrics, UsageMetrics]: """ Extract input and output usage metrics from audio API response usage data. @@ -436,20 +471,40 @@ def extract_metrics( in addition to standard text token counts. :param usage: Usage data dictionary from audio API response + :param text: Generated text for calculating word and character counts :return: Tuple of input_metrics and output_metrics as UsageMetrics objects """ if not usage: - return UsageMetrics(), UsageMetrics() + return UsageMetrics(), UsageMetrics( + text_words=len(text.split()) if text else 0, + text_characters=len(text) if text else 0, + ) - input_details: dict[str, int] = usage.get("input_token_details", {}) or {} - output_details: dict[str, int] = usage.get("output_token_details", {}) or {} + input_details: dict[str, int] = cast( + "dict[str, int]", usage.get("input_token_details", {}) or {} + ) + output_details: dict[str, int] = cast( + "dict[str, int]", usage.get("output_token_details", {}) or {} + ) + usage_metrics: dict[str, int] = cast("dict[str, int]", usage) return UsageMetrics( - text_tokens=(input_details.get("text_tokens") or usage.get("input_tokens")), + text_tokens=input_details.get("text_tokens") or 0, audio_tokens=( - input_details.get("audio_tokens") or usage.get("audio_tokens") + input_details.get("audio_tokens") + or usage_metrics.get("audio_tokens") + or usage_metrics.get("input_tokens") + or 0 + ), + audio_seconds=( + input_details.get("seconds") or usage_metrics.get("seconds") or 0 ), - audio_seconds=(input_details.get("seconds") or usage.get("seconds")), ), UsageMetrics( - text_tokens=output_details.get("text_tokens") or usage.get("output_tokens"), + text_tokens=( + output_details.get("text_tokens") + or usage_metrics.get("output_tokens") + or 0 + ), + text_words=len(text.split()) if text else 0, + text_characters=len(text) if text else 0, ) diff --git a/src/guidellm/benchmark/__init__.py b/src/guidellm/benchmark/__init__.py index ef7b2900..6cbf80c9 100644 --- a/src/guidellm/benchmark/__init__.py +++ b/src/guidellm/benchmark/__init__.py @@ -12,13 +12,13 @@ from .benchmarker import Benchmarker from .entrypoints import benchmark_generative_text, reimport_benchmarks_report -from .output import ( +from .outputs import ( GenerativeBenchmarkerConsole, GenerativeBenchmarkerCSV, GenerativeBenchmarkerHTML, GenerativeBenchmarkerOutput, ) -from .profile import ( +from .profiles import ( AsyncProfile, ConcurrentProfile, Profile, @@ -31,34 +31,43 @@ from .scenarios import get_builtin_scenarios from .schemas import ( Benchmark, - BenchmarkerArgs, - BenchmarkerDict, + BenchmarkAccumulator, + BenchmarkAccumulatorT, + BenchmarkConfig, BenchmarkGenerativeTextArgs, - BenchmarkSchedulerStats, - EstimatedBenchmarkState, + BenchmarkT, GenerativeAudioMetricsSummary, GenerativeBenchmark, + GenerativeBenchmarkAccumulator, GenerativeBenchmarksReport, + GenerativeBenchmarkTimings, GenerativeImageMetricsSummary, GenerativeMetrics, + GenerativeMetricsAccumulator, GenerativeMetricsSummary, + GenerativeRequestsAccumulator, + GenerativeTextMetricsSummary, GenerativeVideoMetricsSummary, - SchedulerDict, + RunningMetricStats, + SchedulerMetrics, + SchedulerMetricsAccumulator, ) __all__ = [ "AsyncProfile", "Benchmark", + "BenchmarkAccumulator", + "BenchmarkAccumulatorT", + "BenchmarkConfig", "BenchmarkGenerativeTextArgs", - "BenchmarkSchedulerStats", + "BenchmarkT", "Benchmarker", - "BenchmarkerArgs", - "BenchmarkerDict", "BenchmarkerProgress", "ConcurrentProfile", - "EstimatedBenchmarkState", "GenerativeAudioMetricsSummary", "GenerativeBenchmark", + "GenerativeBenchmarkAccumulator", + "GenerativeBenchmarkTimings", "GenerativeBenchmarkerCSV", "GenerativeBenchmarkerConsole", "GenerativeBenchmarkerHTML", @@ -67,11 +76,16 @@ "GenerativeConsoleBenchmarkerProgress", "GenerativeImageMetricsSummary", "GenerativeMetrics", + "GenerativeMetricsAccumulator", "GenerativeMetricsSummary", + "GenerativeRequestsAccumulator", + "GenerativeTextMetricsSummary", "GenerativeVideoMetricsSummary", "Profile", "ProfileType", - "SchedulerDict", + "RunningMetricStats", + "SchedulerMetrics", + "SchedulerMetricsAccumulator", "SweepProfile", "SynchronousProfile", "ThroughputProfile", diff --git a/src/guidellm/benchmark/benchmarker.py b/src/guidellm/benchmark/benchmarker.py index 8a46d44e..56cdb9a7 100644 --- a/src/guidellm/benchmark/benchmarker.py +++ b/src/guidellm/benchmark/benchmarker.py @@ -2,10 +2,10 @@ Benchmark execution orchestration and lifecycle management. Provides the core benchmarking engine that coordinates request scheduling, -data aggregation, and result compilation across different execution strategies -and environments. The Benchmarker acts as the primary workflow coordinator, -managing the complete benchmark lifecycle from request submission through -result compilation while supporting thread-safe singleton operations. +data aggregation, and result compilation across execution strategies and +environments. The Benchmarker manages the complete benchmark lifecycle from +request submission through result compilation while implementing thread-safe +singleton operations for consistent state management across concurrent workflows. """ from __future__ import annotations @@ -13,24 +13,29 @@ import uuid from abc import ABC from collections.abc import AsyncIterator, Iterable -from typing import Any, Generic +from typing import Generic -from guidellm.benchmark.profile import Profile +from guidellm.benchmark.profiles import Profile from guidellm.benchmark.progress import BenchmarkerProgress from guidellm.benchmark.schemas import ( - BenchmarkerArgs, + BenchmarkAccumulatorT, + BenchmarkConfig, BenchmarkT, - EstimatedBenchmarkState, ) +from guidellm.benchmark.schemas.base import TransientPhaseConfig from guidellm.logger import logger from guidellm.scheduler import ( BackendInterface, + Constraint, Environment, + MultiTurnRequestT, RequestT, ResponseT, Scheduler, + SchedulingStrategy, ) from guidellm.utils import ThreadSafeSingletonMixin +from guidellm.utils.mixins import InfoMixin __all__ = ["Benchmarker"] @@ -41,48 +46,47 @@ class Benchmarker( ThreadSafeSingletonMixin, ): """ - Abstract benchmark orchestrator for request processing workflows. + Orchestrates benchmark execution across scheduling strategies. - Coordinates execution of benchmarking runs across different scheduling - strategies, aggregating metrics and compiling results. Manages the complete - benchmark lifecycle from request submission through result compilation while - implementing thread-safe singleton pattern to ensure consistent state across - concurrent operations. + Coordinates benchmarking runs by managing request scheduling, metric aggregation, + and result compilation. Implements a thread-safe singleton pattern to ensure + consistent state management across concurrent operations while supporting multiple + scheduling strategies and execution environments. """ async def run( self, + accumulator_class: type[BenchmarkAccumulatorT], benchmark_class: type[BenchmarkT], - requests: Iterable[RequestT | Iterable[RequestT | tuple[RequestT, float]]], + requests: Iterable[RequestT | MultiTurnRequestT[RequestT]], backend: BackendInterface[RequestT, ResponseT], profile: Profile, environment: Environment, - data: list[Any], - progress: BenchmarkerProgress[BenchmarkT] | None = None, + warmup: TransientPhaseConfig, + cooldown: TransientPhaseConfig, sample_requests: int | None = 20, - warmup: float | None = None, - cooldown: float | None = None, prefer_response_metrics: bool = True, + progress: ( + BenchmarkerProgress[BenchmarkAccumulatorT, BenchmarkT] | None + ) = None, ) -> AsyncIterator[BenchmarkT]: """ - Execute benchmark runs across multiple scheduling strategies. - - Orchestrates the complete benchmark workflow by iterating through scheduling - strategies from the profile, executing requests through the scheduler, - aggregating metrics, and compiling final benchmark results. - - :param benchmark_class: Class for constructing final benchmark objects - :param requests: Request datasets for processing across strategies - :param backend: Backend interface for request processing - :param profile: Benchmark profile defining strategies and constraints - :param environment: Execution environment for coordination - :param progress: Optional progress tracker for benchmark lifecycle events - :param sample_requests: Number of sample requests to use for estimation - :param warmup: Optional warmup duration in seconds before benchmarking - :param cooldown: Optional cooldown duration in seconds after benchmarking - :param prefer_response_metrics: Whether to prefer response-based metrics over - request-based metrics - :yield: Compiled benchmark results for each strategy execution + Execute benchmark runs across scheduling strategies in the profile. + + :param accumulator_class: Class for accumulating metrics during execution + :param benchmark_class: Class for constructing final benchmark results + :param requests: Request datasets to process across strategies + :param backend: Backend interface for executing requests + :param profile: Profile defining scheduling strategies and constraints + :param environment: Environment for execution coordination + :param warmup: Warmup phase configuration before benchmarking + :param cooldown: Cooldown phase configuration after benchmarking + :param sample_requests: Number of requests to sample for estimation, + defaults to 20 + :param prefer_response_metrics: Whether to prefer response metrics over + request metrics, defaults to True + :param progress: Optional tracker for benchmark lifecycle events + :yield: Compiled benchmark result for each strategy execution :raises Exception: If benchmark execution or compilation fails """ with self.thread_lock: @@ -91,21 +95,38 @@ async def run( run_id = str(uuid.uuid4()) strategies_generator = profile.strategies_generator() + strategy: SchedulingStrategy | None + constraints: dict[str, Constraint] | None strategy, constraints = next(strategies_generator) while strategy is not None: if progress: await progress.on_benchmark_start(strategy) - args = BenchmarkerArgs( + config = BenchmarkConfig( run_id=run_id, run_index=len(profile.completed_strategies), + strategy=strategy, + constraints=( + { + key: InfoMixin.extract_from_obj(val) + for key, val in constraints.items() + } + if isinstance(constraints, dict) + else {"constraint": InfoMixin.extract_from_obj(constraints)} + if constraints + else {} + ), sample_requests=sample_requests, warmup=warmup, cooldown=cooldown, prefer_response_metrics=prefer_response_metrics, + profile=profile, + requests=InfoMixin.extract_from_obj(requests), + backend=InfoMixin.extract_from_obj(backend), + environment=InfoMixin.extract_from_obj(environment), ) - estimated_state = EstimatedBenchmarkState() + accumulator = accumulator_class(config=config) scheduler_state = None scheduler: Scheduler[RequestT, ResponseT] = Scheduler() @@ -118,14 +139,11 @@ async def run( requests=requests, backend=backend, strategy=strategy, - startup_duration=warmup if warmup and warmup >= 1 else 0.0, env=environment, **constraints or {}, ): try: - benchmark_class.update_estimate( - args, - estimated_state, + accumulator.update_estimate( response, request, request_info, @@ -133,7 +151,7 @@ async def run( ) if progress: await progress.on_benchmark_update( - estimated_state, scheduler_state + accumulator, scheduler_state ) except Exception as err: # noqa: BLE001 logger.error( @@ -141,17 +159,10 @@ async def run( ) benchmark = benchmark_class.compile( - args=args, - estimated_state=estimated_state, + accumulator=accumulator, scheduler_state=scheduler_state, - profile=profile, - requests=requests, - backend=backend, - environment=environment, - strategy=strategy, - constraints=constraints, - data=data, ) + if progress: await progress.on_benchmark_complete(benchmark) diff --git a/src/guidellm/benchmark/entrypoints.py b/src/guidellm/benchmark/entrypoints.py index e095ed12..febafce3 100644 --- a/src/guidellm/benchmark/entrypoints.py +++ b/src/guidellm/benchmark/entrypoints.py @@ -1,18 +1,17 @@ """ -High-level entry points for executing generative text benchmarks. - -This module provides the primary interface for running generative text benchmarks -through the `benchmark_generative_text` function and re-importing existing benchmark -reports via `reimport_benchmarks_report`. It orchestrates the initialization and -coordination of backends, data loaders, profiles, and output formats to execute -comprehensive benchmarking workflows. The module handles all resolution logic for -converting user-provided arguments into fully configured components ready for -benchmarking execution. +Primary interface for executing and re-importing generative text benchmarks. + +This module orchestrates comprehensive benchmarking workflows by coordinating backend +initialization, data loading, profile configuration, and output generation. It provides +two main entry points: `benchmark_generative_text` for executing new benchmarks and +`reimport_benchmarks_report` for re-exporting existing results. The resolution functions +convert user-provided arguments into fully configured components, handling backend +validation, data preprocessing, profile constraints, and output format specifications. """ from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Mapping, MutableMapping from pathlib import Path from typing import Any, Literal @@ -22,20 +21,23 @@ from guidellm.backends import Backend, BackendType from guidellm.benchmark.benchmarker import Benchmarker -from guidellm.benchmark.output import GenerativeBenchmarkerOutput -from guidellm.benchmark.profile import Profile, ProfileType +from guidellm.benchmark.outputs import GenerativeBenchmarkerOutput +from guidellm.benchmark.profiles import Profile, ProfileType from guidellm.benchmark.progress import GenerativeConsoleBenchmarkerProgress from guidellm.benchmark.schemas import ( BenchmarkGenerativeTextArgs, GenerativeBenchmark, + GenerativeBenchmarkAccumulator, GenerativeBenchmarksReport, ) +from guidellm.benchmark.schemas.base import TransientPhaseConfig from guidellm.data import ( DataLoader, DatasetPreprocessor, GenerativeRequestCollator, PreprocessorRegistry, ProcessorFactory, + RequestFormatter, ) from guidellm.data.preprocessors import GenerativeColumnMapper from guidellm.scheduler import ( @@ -44,6 +46,7 @@ StrategyType, ) from guidellm.schemas import GenerationRequest, GenerationResponse +from guidellm.settings import settings from guidellm.utils import Console, InfoMixin __all__ = [ @@ -52,17 +55,22 @@ ] -# Helper Functions +# Type Aliases OutputFormatT = TypeAliasType( "OutputFormatT", tuple[str, ...] | list[str] - | dict[str, str | dict[str, Any] | GenerativeBenchmarkerOutput] + | Mapping[str, str | dict[str, Any] | GenerativeBenchmarkerOutput] | None, ) +"""Output format specification as strings, mappings, or configured output instances""" ProcessorInputT = TypeAliasType("ProcessorInputT", str | Path | PreTrainedTokenizerBase) +"""Processor input as model identifier, path to tokenizer, or tokenizer instance""" + + +# Helper Functions async def resolve_backend( @@ -71,9 +79,14 @@ async def resolve_backend( model: str | None, console: Console | None = None, **backend_kwargs: dict[str, Any], -) -> tuple[Backend, str | None]: +) -> tuple[Backend, str]: """ - Initialize and validate a backend instance for benchmarking. + Initialize and validate a backend instance for benchmarking execution. + + Handles backend creation from type identifiers or pre-configured instances, + performs startup validation, and resolves the default model if not specified. + The backend is shut down after validation to ensure clean state for subsequent + benchmark execution. :param backend: Backend type identifier or pre-configured Backend instance :param target: Target endpoint URL or connection string for the backend @@ -87,17 +100,19 @@ async def resolve_backend( if console else None ) - backend = ( + backend_instance = ( Backend.create(backend, target=target, model=model, **(backend_kwargs or {})) if not isinstance(backend, Backend) else backend ) if console_step: - console_step.update(f"{backend.__class__.__name__} backend initialized") + console_step.update( + f"{backend_instance.__class__.__name__} backend initialized" + ) - await backend.process_startup() - await backend.validate() + await backend_instance.process_startup() + await backend_instance.validate() if model is None: if console_step: @@ -105,20 +120,21 @@ async def resolve_backend( title="Resolving default model from backend.default_model", status_level="info", ) - model = await backend.default_model() + model = await backend_instance.default_model() - await backend.process_shutdown() + await backend_instance.process_shutdown() if console_step: console_step.finish( title=( - f"{backend.__class__.__name__} backend validated with model {model}" + f"{backend_instance.__class__.__name__} backend validated " + f"with model {model}" ), - details=backend.info, + details=backend_instance.info, status_level="success", ) - return backend, model + return backend_instance, model async def resolve_processor( @@ -127,7 +143,7 @@ async def resolve_processor( console: Console | None = None, ) -> ProcessorInputT | None: """ - Resolve the processor for tokenization, defaulting to model if not provided. + Resolve the tokenization processor, defaulting to model if not provided. :param processor: Processor identifier, path, tokenizer instance, or None :param model: Model identifier to use as fallback processor @@ -161,15 +177,17 @@ async def resolve_processor( async def resolve_request_loader( data: list[Any], - model: str | None, + model: str, data_args: list[dict[str, Any]] | None, data_samples: int, processor: ProcessorInputT | None, processor_args: dict[str, Any] | None, data_column_mapper: ( - DatasetPreprocessor | dict[str, str] | Literal["generative_column_mapper"] + DatasetPreprocessor + | dict[str, str | list[str]] + | Literal["generative_column_mapper"] ), - data_request_formatter: (DatasetPreprocessor | dict[str, str] | str), + data_request_formatter: (RequestFormatter | dict[str, str] | str), data_collator: Callable | Literal["generative"] | None, data_sampler: Sampler[int] | Literal["shuffle"] | None, data_num_workers: int | None, @@ -180,6 +198,11 @@ async def resolve_request_loader( """ Construct a DataLoader for GenerationRequest objects from raw data inputs. + Initializes and configures the data pipeline including column mapping, request + formatting, collation, and sampling. Resolves string-based preprocessor identifiers + from the PreprocessorRegistry and creates appropriate instances with provided + configurations. + :param data: List of data sources to load requests from :param model: Model identifier for request formatting :param data_args: Arguments for each data source in the data list @@ -195,6 +218,10 @@ async def resolve_request_loader( :param console: Console instance for progress reporting, or None :param dataloader_kwargs: Additional arguments passed to DataLoader initialization :return: Configured DataLoader instance for GenerationRequest objects + :raises ValueError: If request formatter type is not registered in + PreprocessorRegistry + :raises TypeError: If registered request formatter is not a RequestFormatter + subclass """ console_step = ( console.print_update_step(title=f"Initializing request loader from {data}") @@ -202,38 +229,63 @@ async def resolve_request_loader( else None ) - if not isinstance(data_column_mapper, DatasetPreprocessor): + data_column_mapper_instance: DatasetPreprocessor + if isinstance(data_column_mapper, DatasetPreprocessor): + data_column_mapper_instance = data_column_mapper + else: column_mappings = ( data_column_mapper if isinstance(data_column_mapper, dict) else None ) - data_column_mapper = GenerativeColumnMapper( - column_mappings=column_mappings, - ) - if not isinstance(data_request_formatter, DatasetPreprocessor): - request_type = ( - data_request_formatter - if isinstance(data_request_formatter, str) - else data_request_formatter.pop("request_type", "chat_completions") + data_column_mapper_instance = GenerativeColumnMapper( + column_mappings=column_mappings # type: ignore[arg-type] ) - data_request_formatter = PreprocessorRegistry.get_registered_object( - request_type - )( + + data_request_formatter_instance: RequestFormatter + if isinstance(data_request_formatter, RequestFormatter): + data_request_formatter_instance = data_request_formatter + else: + if isinstance(data_request_formatter, str): + request_type = data_request_formatter + formatter_kwargs: dict[str, Any] = {} + else: + # Extract request_type from formatter dictionary + formatter_dict = dict(data_request_formatter) + request_type = formatter_dict.pop("request_type", settings.preferred_route) + formatter_kwargs = formatter_dict + + if ( + formatter_class := PreprocessorRegistry.get_registered_object(request_type) + ) is None: + raise ValueError( + f"Request formatter '{request_type}' is not registered in the " + f"PreprocessorRegistry." + ) + if not issubclass(formatter_class, RequestFormatter): + raise TypeError( + f"Request formatter '{request_type}' is not a subclass of " + f"RequestFormatter." + ) + + data_request_formatter_instance = formatter_class( model=model, - **( - data_request_formatter - if isinstance(data_request_formatter, dict) - else {} - ), + **formatter_kwargs, ) - request_loader = DataLoader( + # Cast to proper types for the DataLoader preprocessors list + preprocessors_list: list[DatasetPreprocessor] = [ + data_column_mapper_instance, + data_request_formatter_instance, + ] + + request_loader: DataLoader[GenerationRequest] = DataLoader( data=data, data_args=data_args, data_samples=data_samples, processor_factory=ProcessorFactory( - processor=processor, processor_args=processor_args + processor=processor if processor is not None else model, + processor_args=processor_args, ), - preprocessors=[data_column_mapper, data_request_formatter], + preprocessors=preprocessors_list, collator=( data_collator if callable(data_collator) else GenerativeRequestCollator() ), @@ -259,9 +311,10 @@ async def resolve_request_loader( async def resolve_profile( profile: StrategyType | ProfileType | Profile, - rate: float | list[float] | None, + rate: list[float] | None, random_seed: int, - constraints: dict[str, ConstraintInitializer | Any], + rampup: TransientPhaseConfig, + constraints: MutableMapping[str, ConstraintInitializer | Any], max_seconds: int | float | None, max_requests: int | None, max_errors: int | None, @@ -272,9 +325,14 @@ async def resolve_profile( """ Resolve and configure a benchmark profile with rate and constraint settings. + Constructs a Profile instance from type identifiers or validates pre-configured + profiles. Constraint parameters are merged into the constraints dictionary before + profile creation. + :param profile: Profile type identifier or pre-configured Profile instance :param rate: Request rate(s) for the benchmark execution :param random_seed: Seed for reproducible random operations + :param rampup: Ramp-up phase configuration for the benchmark execution :param constraints: Dictionary of constraint initializers for benchmark limits :param max_seconds: Maximum duration in seconds for the benchmark :param max_requests: Maximum number of requests to process @@ -300,11 +358,16 @@ async def resolve_profile( }.items(): if val is not None: constraints[key] = val + rampup_duration, _ = rampup.compute_limits( + max_requests=max_requests, max_seconds=max_seconds + ) + if not isinstance(profile, Profile): profile = Profile.create( rate_type=profile, rate=rate, random_seed=random_seed, + rampup_duration=rampup_duration or 0.0, constraints={**constraints}, ) elif constraints: @@ -312,6 +375,11 @@ async def resolve_profile( "Constraints must be empty when providing a Profile instance. " f"Provided constraints: {constraints} ; provided profile: {profile}" ) + elif rampup_duration is not None: + raise ValueError( + "Ramp-up duration must not be set when providing a Profile instance. " + f"Provided rampup: {rampup} ; provided profile: {profile}" + ) if console_step: console_step.finish( @@ -361,20 +429,22 @@ async def benchmark_generative_text( args: BenchmarkGenerativeTextArgs, progress: GenerativeConsoleBenchmarkerProgress | None = None, console: Console | None = None, - **constraints: dict[str, ConstraintInitializer | Any], + **constraints: str | ConstraintInitializer | Any, ) -> tuple[GenerativeBenchmarksReport, dict[str, Any]]: """ Execute a comprehensive generative text benchmarking workflow. - Orchestrates the full benchmarking pipeline by resolving all components (backend, - data loader, profile, outputs) from provided arguments, executing the benchmark - runs, and finalizing results in the specified output formats. + Orchestrates the full benchmarking pipeline by resolving all components from + provided arguments, executing benchmark runs across configured profiles, and + finalizing results in specified output formats. Components include backend + initialization, data loading, profile configuration, and output generation. :param args: Configuration arguments for the benchmark execution :param progress: Progress tracker for benchmark execution, or None for no tracking :param console: Console instance for status reporting, or None for silent operation :param constraints: Additional constraint initializers for benchmark limits - :return: Tuple of GenerativeBenchmarksReport and dictionary of output format results + :return: Tuple of GenerativeBenchmarksReport and dictionary of output format + results """ backend, model = await resolve_backend( backend=args.backend, @@ -402,10 +472,29 @@ async def benchmark_generative_text( console=console, **(args.dataloader_kwargs or {}), ) + + rampup = TransientPhaseConfig.create_from_value(args.rampup) + rampup.mode = "duration" + warmup = TransientPhaseConfig.create_from_value(args.warmup) + cooldown = TransientPhaseConfig.create_from_value(args.cooldown) + if console: + console.print_update( + title="Resolved transient phase configurations", + details="\n".join( + [ + f"Rampup: {rampup}", + f"Warmup: {warmup}", + f"Cooldown: {cooldown}", + ] + ), + status="success", + ) + profile = await resolve_profile( profile=args.profile, rate=args.rate, random_seed=args.random_seed, + rampup=rampup, constraints=constraints, max_seconds=args.max_seconds, max_requests=args.max_requests, @@ -431,16 +520,16 @@ async def benchmark_generative_text( GenerativeBenchmark, GenerationRequest, GenerationResponse ] = Benchmarker() async for benchmark in benchmarker.run( - benchmark_class=args.benchmark_cls, + accumulator_class=GenerativeBenchmarkAccumulator, + benchmark_class=GenerativeBenchmark, requests=request_loader, backend=backend, profile=profile, environment=NonDistributedEnvironment(), - data=args.data, progress=progress, sample_requests=args.sample_requests, - warmup=args.warmup, - cooldown=args.cooldown, + warmup=warmup, + cooldown=cooldown, prefer_response_metrics=args.prefer_response_metrics, ): if benchmark: @@ -472,12 +561,13 @@ async def reimport_benchmarks_report( output_formats: OutputFormatT = ("console", "json", "html", "csv"), ) -> tuple[GenerativeBenchmarksReport, dict[str, Any]]: """ - Load and re-export an existing benchmarks report in specified formats. + Load and re-export an existing benchmarks report in specified output formats. :param file: Path to the existing benchmark report file to load :param output_path: Base path for output file generation, or None for default :param output_formats: Specification of desired output formats for the report - :return: Tuple of loaded GenerativeBenchmarksReport and dictionary of output results + :return: Tuple of loaded GenerativeBenchmarksReport and dictionary of output + results """ console = Console() @@ -490,11 +580,11 @@ async def reimport_benchmarks_report( f" loaded {len(report.benchmarks)} benchmark(s)" ) - output_formats = await resolve_output_formats( + resolved_output_formats = await resolve_output_formats( output_formats, output_path, console=console ) output_format_results = {} - for key, output in output_formats.items(): + for key, output in resolved_output_formats.items(): output_result = await output.finalize(report) output_format_results[key] = output_result diff --git a/src/guidellm/benchmark/output.py b/src/guidellm/benchmark/output.py deleted file mode 100644 index 6e17de5b..00000000 --- a/src/guidellm/benchmark/output.py +++ /dev/null @@ -1,745 +0,0 @@ -from __future__ import annotations - -import csv -import json -import math -from abc import ABC, abstractmethod -from collections import OrderedDict -from copy import deepcopy -from datetime import datetime -from pathlib import Path -from typing import Any, ClassVar - -from pydantic import BaseModel, ConfigDict, Field -from rich.console import Console -from rich.padding import Padding -from rich.text import Text - -from guidellm.benchmark.profile import ( - AsyncProfile, - ConcurrentProfile, - SweepProfile, - ThroughputProfile, -) -from guidellm.benchmark.schemas import ( - GenerativeBenchmark, - GenerativeBenchmarksReport, - GenerativeMetrics, -) -from guidellm.presentation import UIDataBuilder -from guidellm.presentation.injector import create_report -from guidellm.settings import settings -from guidellm.utils import ( - Colors, - DistributionSummary, - RegistryMixin, - StatusDistributionSummary, - camelize_str, - recursive_key_update, - safe_format_timestamp, - split_text_list_by_length, -) - -__all__ = [ - "GenerativeBenchmarkerCSV", - "GenerativeBenchmarkerConsole", - "GenerativeBenchmarkerHTML", - "GenerativeBenchmarkerOutput", -] - - -class GenerativeBenchmarkerOutput( - BaseModel, RegistryMixin[type["GenerativeBenchmarkerOutput"]], ABC -): - model_config = ConfigDict( - extra="ignore", - arbitrary_types_allowed=True, - validate_assignment=True, - from_attributes=True, - use_enum_values=True, - ) - - @classmethod - @abstractmethod - def validated_kwargs(cls, *args, **kwargs) -> dict[str, Any]: - """ - Validate and process arguments for constraint creation. - - Must be implemented by subclasses to handle their specific parameter patterns. - - :param args: Positional arguments passed to the constraint - :param kwargs: Keyword arguments passed to the constraint - :return: Validated dictionary of parameters for constraint creation - :raises NotImplementedError: Must be implemented by subclasses - """ - ... - - @classmethod - def resolve( - cls, - output_formats: ( - tuple[str, ...] - | list[str] - | dict[ - str, - Any | dict[str, Any] | GenerativeBenchmarkerOutput, - ] - | None - ), - output_path: str | Path | None, - ) -> dict[str, GenerativeBenchmarkerOutput]: - if not output_formats: - return {} - - if isinstance(output_formats, list | tuple): - # support list of output keys: ["csv", "json"] - # support list of files: ["path/to/file.json", "path/to/file.csv"] - formats_list = output_formats - output_formats = {} - for output_format in formats_list: - if not isinstance(output_format, str): - raise TypeError( - f"Expected string format, got {type(output_format)} for " - f"{output_format} in {formats_list}" - ) - try: - if cls.is_registered(output_format): - output_formats[output_format] = {} - else: - # treat it as a file save location - path = Path(output_format) - format_type = path.suffix[1:].lower() - output_formats[format_type] = {"output_path": path} - - except Exception as err: - raise ValueError( - f"Failed to resolve output format '{output_format}': {err}" - ) from err - - resolved = {} - - for key, val in output_formats.items(): - if isinstance(val, GenerativeBenchmarkerOutput): - resolved[key] = val - else: - output_class = cls.get_registered_object(key) - kwargs = {"output_path": output_path} - - if isinstance(val, dict): - kwargs.update(val) - kwargs = output_class.validated_kwargs(**kwargs) - else: - kwargs = output_class.validated_kwargs(val, **kwargs) - - resolved[key] = output_class(**kwargs) - - return resolved - - @abstractmethod - async def finalize(self, report: GenerativeBenchmarksReport) -> Any: ... - - -@GenerativeBenchmarkerOutput.register(["json", "yaml"]) -class GenerativeBenchmarkerSerialized(GenerativeBenchmarkerOutput): - @classmethod - def validated_kwargs( - cls, output_path: str | Path | None, **_kwargs - ) -> dict[str, Any]: - new_kwargs = {} - if output_path is not None: - new_kwargs["output_path"] = ( - Path(output_path) if not isinstance(output_path, Path) else output_path - ) - return new_kwargs - - output_path: Path = Field(default_factory=lambda: Path.cwd()) - - async def finalize(self, report: GenerativeBenchmarksReport) -> Path: - return report.save_file(self.output_path) - - -@GenerativeBenchmarkerOutput.register("console") -class GenerativeBenchmarkerConsole(GenerativeBenchmarkerOutput): - """Console output formatter for benchmark results with rich formatting.""" - - @classmethod - def validated_kwargs(cls, *_args, **_kwargs) -> dict[str, Any]: - return {} - - console: Console = Field(default_factory=Console) - - async def finalize(self, report: GenerativeBenchmarksReport) -> str: - """ - Print the complete benchmark report to the console. - - :param report: The completed benchmark report. - :return: - """ - self._print_benchmarks_metadata(report.benchmarks) - self._print_benchmarks_info(report.benchmarks) - self._print_benchmarks_stats(report.benchmarks) - - return "printed to console" - - def _print_benchmarks_metadata(self, benchmarks: list[GenerativeBenchmark]): - start_time = benchmarks[0].run_stats.start_time - end_time = benchmarks[-1].run_stats.end_time - duration = end_time - start_time - - self._print_section_header("Benchmarks Metadata") - self._print_labeled_line("Run id", str(benchmarks[0].run_id)) - self._print_labeled_line("Duration", f"{duration:.1f} seconds") - self._print_labeled_line("Profile", self._get_profile_str(benchmarks[0])) - - def _print_benchmarks_info(self, benchmarks: list[GenerativeBenchmark]): - sections = { - "Metadata": (0, 3), - "Requests Made": (4, 6), - "Prompt Tok/Req": (7, 9), - "Output Tok/Req": (10, 12), - "Prompt Tok Total": (13, 15), - "Output Tok Total": (16, 18), - } - headers = [ - "Benchmark", - "Start Time", - "End Time", - "Duration (s)", - "Comp", - "Inc", - "Err", - "Comp", - "Inc", - "Err", - "Comp", - "Inc", - "Err", - "Comp", - "Inc", - "Err", - "Comp", - "Inc", - "Err", - ] - - rows = [] - for benchmark in benchmarks: - rows.append( - [ - str(benchmark.scheduler.strategy), - safe_format_timestamp(benchmark.start_time), - safe_format_timestamp(benchmark.end_time), - f"{(benchmark.end_time - benchmark.start_time):.1f}", - f"{benchmark.request_totals.successful:.0f}", - f"{benchmark.request_totals.incomplete:.0f}", - f"{benchmark.request_totals.errored:.0f}", - f"{benchmark.metrics.prompt_token_count.successful.mean:.1f}", - f"{benchmark.metrics.prompt_token_count.incomplete.mean:.1f}", - f"{benchmark.metrics.prompt_token_count.errored.mean:.1f}", - f"{benchmark.metrics.output_token_count.successful.mean:.1f}", - f"{benchmark.metrics.output_token_count.incomplete.mean:.1f}", - f"{benchmark.metrics.output_token_count.errored.mean:.1f}", - f"{benchmark.metrics.prompt_token_count.successful.total_sum:.0f}", - f"{benchmark.metrics.prompt_token_count.incomplete.total_sum:.0f}", - f"{benchmark.metrics.prompt_token_count.errored.total_sum:.0f}", - f"{benchmark.metrics.output_token_count.successful.total_sum:.0f}", - f"{benchmark.metrics.output_token_count.incomplete.total_sum:.0f}", - f"{benchmark.metrics.output_token_count.errored.total_sum:.0f}", - ] - ) - - self._print_table(headers, rows, "Benchmarks Info", sections) - - def _print_benchmarks_stats(self, benchmarks: list[GenerativeBenchmark]): - sections = { - "Metadata": (0, 0), - "Request Stats": (1, 2), - "Out Tok/sec": (3, 3), - "Tot Tok/sec": (4, 4), - "Req Latency (sec)": (5, 7), - "TTFT (ms)": (8, 10), - "ITL (ms)": (11, 13), - "TPOT (ms)": (14, 16), - } - headers = [ - "Benchmark", - "Per Second", - "Concurrency", - "mean", - "mean", - "mean", - "median", - "p99", - "mean", - "median", - "p99", - "mean", - "median", - "p99", - "mean", - "median", - "p99", - ] - - rows = [] - for benchmark in benchmarks: - rows.append( - [ - str(benchmark.scheduler.strategy), - f"{benchmark.metrics.requests_per_second.successful.mean:.2f}", - f"{benchmark.metrics.request_concurrency.successful.mean:.2f}", - f"{benchmark.metrics.output_tokens_per_second.successful.mean:.1f}", - f"{benchmark.metrics.tokens_per_second.successful.mean:.1f}", - f"{benchmark.metrics.request_latency.successful.mean:.2f}", - f"{benchmark.metrics.request_latency.successful.median:.2f}", - f"{benchmark.metrics.request_latency.successful.percentiles.p99:.2f}", - f"{benchmark.metrics.time_to_first_token_ms.successful.mean:.1f}", - f"{benchmark.metrics.time_to_first_token_ms.successful.median:.1f}", - f"{benchmark.metrics.time_to_first_token_ms.successful.percentiles.p99:.1f}", - f"{benchmark.metrics.inter_token_latency_ms.successful.mean:.1f}", - f"{benchmark.metrics.inter_token_latency_ms.successful.median:.1f}", - f"{benchmark.metrics.inter_token_latency_ms.successful.percentiles.p99:.1f}", - f"{benchmark.metrics.time_per_output_token_ms.successful.mean:.1f}", - f"{benchmark.metrics.time_per_output_token_ms.successful.median:.1f}", - f"{benchmark.metrics.time_per_output_token_ms.successful.percentiles.p99:.1f}", - ] - ) - - self._print_table(headers, rows, "Benchmarks Stats", sections) - - def _get_profile_str(self, benchmark: GenerativeBenchmark) -> str: - profile = benchmark.benchmarker.profile - if profile is None: - return "None" - - profile_args = OrderedDict( - { - "type": profile.type_, - "strategies": getattr(profile, "strategy_types", []), - } - ) - - if isinstance(profile, ConcurrentProfile): - profile_args["streams"] = str(profile.streams) - elif isinstance(profile, ThroughputProfile): - profile_args["max_concurrency"] = str(profile.max_concurrency) - elif isinstance(profile, AsyncProfile): - profile_args["max_concurrency"] = str(profile.max_concurrency) - profile_args["rate"] = str(profile.rate) - elif isinstance(profile, SweepProfile): - profile_args["sweep_size"] = str(profile.sweep_size) - - return ", ".join(f"{key}={value}" for key, value in profile_args.items()) - - def _print_section_header(self, title: str, indent: int = 0, new_lines: int = 2): - self._print_line( - f"{title}:", - f"bold underline {Colors.info}", - indent=indent, - new_lines=new_lines, - ) - - def _print_labeled_line( - self, label: str, value: str, indent: int = 4, new_lines: int = 0 - ): - self._print_line( - [label + ":", value], - ["bold " + Colors.info, "italic"], - new_lines=new_lines, - indent=indent, - ) - - def _print_line( - self, - value: str | list[str], - style: str | list[str] = "", - indent: int = 0, - new_lines: int = 0, - ): - text = Text() - for _ in range(new_lines): - text.append("\n") - - if not isinstance(value, list): - value = [value] - if not isinstance(style, list): - style = [style for _ in range(len(value))] - - if len(value) != len(style): - raise ValueError( - f"Value and style length mismatch: {len(value)} vs {len(style)}" - ) - - for val, sty in zip(value, style, strict=False): - text.append(val, style=sty) - - self.console.print(Padding.indent(text, indent)) - - def _print_table( - self, - headers: list[str], - rows: list[list[Any]], - title: str, - sections: dict[str, tuple[int, int]] | None = None, - max_char_per_col: int = 1024, - indent: int = 0, - new_lines: int = 2, - ): - if rows and any(len(row) != len(headers) for row in rows): - raise ValueError( - "Headers and rows length mismatch: " - f"{len(headers)} vs {len(rows[0]) if rows else 'N/A'}" - ) - - max_chars_per_column = self._calculate_max_chars_per_column( - headers, rows, sections, max_char_per_col - ) - - self._print_section_header(title, indent=indent, new_lines=new_lines) - self._print_table_divider(max_chars_per_column, False, indent) - if sections: - self._print_table_sections(sections, max_chars_per_column, indent) - self._print_table_row( - split_text_list_by_length(headers, max_chars_per_column), - f"bold {Colors.info}", - indent, - ) - self._print_table_divider(max_chars_per_column, True, indent) - for row in rows: - self._print_table_row( - split_text_list_by_length(row, max_chars_per_column), - "italic", - indent, - ) - self._print_table_divider(max_chars_per_column, False, indent) - - def _calculate_max_chars_per_column( - self, - headers: list[str], - rows: list[list[Any]], - sections: dict[str, tuple[int, int]] | None, - max_char_per_col: int, - ) -> list[int]: - """Calculate maximum characters per column for table formatting.""" - max_chars_per_column = [] - for ind in range(len(headers)): - max_chars_per_column.append(min(len(headers[ind]), max_char_per_col)) - for row in rows: - max_chars_per_column[ind] = max( - max_chars_per_column[ind], len(str(row[ind])) - ) - - if not sections: - return max_chars_per_column - - for section, (start_col, end_col) in sections.items(): - min_section_len = len(section) + (end_col - start_col) - chars_in_columns = sum( - max_chars_per_column[start_col : end_col + 1] - ) + 2 * (end_col - start_col) - if min_section_len > chars_in_columns: - add_chars_per_col = math.ceil( - (min_section_len - chars_in_columns) / (end_col - start_col + 1) - ) - for col in range(start_col, end_col + 1): - max_chars_per_column[col] += add_chars_per_col - - return max_chars_per_column - - def _print_table_divider( - self, max_chars_per_column: list[int], include_separators: bool, indent: int = 0 - ): - """Print table divider line.""" - if include_separators: - columns = [ - settings.table_headers_border_char * max_chars - + settings.table_column_separator_char - + settings.table_headers_border_char - for max_chars in max_chars_per_column - ] - else: - columns = [ - settings.table_border_char * (max_chars + 2) - for max_chars in max_chars_per_column - ] - columns[-1] = columns[-1][:-2] - self._print_line(columns, Colors.info, indent) - - def _print_table_sections( - self, - sections: dict[str, tuple[int, int]], - max_chars_per_column: list[int], - indent: int = 0, - ): - section_tuples = [(start, end, name) for name, (start, end) in sections.items()] - section_tuples.sort(key=lambda x: x[0]) - - if any(start > end for start, end, _ in section_tuples): - raise ValueError(f"Invalid section ranges: {section_tuples}") - - if ( - any( - section_tuples[ind][1] + 1 != section_tuples[ind + 1][0] - for ind in range(len(section_tuples) - 1) - ) - or section_tuples[0][0] != 0 - or section_tuples[-1][1] != len(max_chars_per_column) - 1 - ): - raise ValueError(f"Invalid section ranges: {section_tuples}") - - line_values = [] - line_styles = [] - for section, (start_col, end_col) in sections.items(): - section_length = sum(max_chars_per_column[start_col : end_col + 1]) + 2 * ( - end_col - start_col + 1 - ) - num_separators = end_col - start_col - line_values.extend( - [ - section, - " " * (section_length - len(section) - num_separators - 2), - settings.table_column_separator_char * num_separators, - settings.table_column_separator_char + " ", - ] - ) - line_styles.extend(["bold " + Colors.info, "", "", Colors.info]) - - line_values = line_values[:-1] - line_styles = line_styles[:-1] - self._print_line(line_values, line_styles, indent) - - def _print_table_row( - self, column_lines: list[list[str]], style: str, indent: int = 0 - ): - for row in range(len(column_lines[0])): - print_line = [] - print_styles = [] - for column in range(len(column_lines)): - print_line.extend( - [ - column_lines[column][row], - settings.table_column_separator_char, - " ", - ] - ) - print_styles.extend([style, Colors.info, ""]) - print_line = print_line[:-2] - print_styles = print_styles[:-2] - self._print_line(print_line, print_styles, indent) - - -@GenerativeBenchmarkerOutput.register("csv") -class GenerativeBenchmarkerCSV(GenerativeBenchmarkerOutput): - """CSV output formatter for benchmark results.""" - - DEFAULT_FILE: ClassVar[str] = "benchmarks.csv" - - @classmethod - def validated_kwargs( - cls, output_path: str | Path | None, **_kwargs - ) -> dict[str, Any]: - new_kwargs = {} - if output_path is not None: - new_kwargs["output_path"] = ( - Path(output_path) if not isinstance(output_path, Path) else output_path - ) - return new_kwargs - - output_path: Path = Field(default_factory=lambda: Path.cwd()) - - async def finalize(self, report: GenerativeBenchmarksReport) -> Path: - """ - Save the benchmark report as a CSV file. - - :param report: The completed benchmark report. - :return: Path to the saved CSV file. - """ - output_path = self.output_path - if output_path.is_dir(): - output_path = output_path / GenerativeBenchmarkerCSV.DEFAULT_FILE - output_path.parent.mkdir(parents=True, exist_ok=True) - - with output_path.open("w", newline="") as file: - writer = csv.writer(file) - headers: list[str] = [] - rows: list[list[str | float | list[float]]] = [] - - for benchmark in report.benchmarks: - benchmark_headers: list[str] = [] - benchmark_values: list[str | float | list[float]] = [] - - # Add basic run description info - desc_headers, desc_values = self._get_benchmark_desc_headers_and_values( - benchmark - ) - benchmark_headers.extend(desc_headers) - benchmark_values.extend(desc_values) - - # Add status-based metrics - for status in StatusDistributionSummary.model_fields: - status_headers, status_values = ( - self._get_benchmark_status_headers_and_values(benchmark, status) - ) - benchmark_headers.extend(status_headers) - benchmark_values.extend(status_values) - - # Add extra fields - extras_headers, extras_values = ( - self._get_benchmark_extras_headers_and_values(benchmark) - ) - benchmark_headers.extend(extras_headers) - benchmark_values.extend(extras_values) - - if not headers: - headers = benchmark_headers - rows.append(benchmark_values) - - writer.writerow(headers) - for row in rows: - writer.writerow(row) - - return output_path - - def _get_benchmark_desc_headers_and_values( - self, benchmark: GenerativeBenchmark - ) -> tuple[list[str], list[str | float]]: - """Get description headers and values for a benchmark.""" - headers = [ - "Type", - "Run Id", - "Id", - "Name", - "Start Time", - "End Time", - "Duration", - ] - values: list[str | float] = [ - benchmark.type_, - benchmark.run_id, - benchmark.id_, - str(benchmark.scheduler.strategy), - datetime.fromtimestamp(benchmark.start_time).strftime("%Y-%m-%d %H:%M:%S"), - datetime.fromtimestamp(benchmark.end_time).strftime("%Y-%m-%d %H:%M:%S"), - benchmark.duration, - ] - return headers, values - - def _get_benchmark_status_headers_and_values( - self, benchmark: GenerativeBenchmark, status: str - ) -> tuple[list[str], list[float | list[float]]]: - """Get status-based metrics headers and values for a benchmark.""" - headers = [f"{status.capitalize()} Requests"] - values = [getattr(benchmark.request_totals, status)] - - for metric in GenerativeMetrics.model_fields: - metric_headers, metric_values = self._get_benchmark_status_metrics_stats( - benchmark, status, metric - ) - headers.extend(metric_headers) - values.extend(metric_values) - - return headers, values - - def _get_benchmark_status_metrics_stats( - self, benchmark: GenerativeBenchmark, status: str, metric: str - ) -> tuple[list[str], list[float | list[float]]]: - """Get statistical metrics for a specific status and metric.""" - status_display = status.capitalize() - metric_display = metric.replace("_", " ").capitalize() - status_dist_summary: StatusDistributionSummary = getattr( - benchmark.metrics, metric - ) - if not hasattr(status_dist_summary, status): - return [], [] - dist_summary: DistributionSummary = getattr(status_dist_summary, status) - - headers = [ - f"{status_display} {metric_display} mean", - f"{status_display} {metric_display} median", - f"{status_display} {metric_display} std dev", - ( - f"{status_display} {metric_display} " - "[min, 0.1, 1, 5, 10, 25, 75, 90, 95, 99, max]" - ), - ] - values: list[float | list[float]] = [ - dist_summary.mean, - dist_summary.median, - dist_summary.std_dev, - [ - dist_summary.min, - dist_summary.percentiles.p001, - dist_summary.percentiles.p01, - dist_summary.percentiles.p05, - dist_summary.percentiles.p10, - dist_summary.percentiles.p25, - dist_summary.percentiles.p75, - dist_summary.percentiles.p90, - dist_summary.percentiles.p95, - dist_summary.percentiles.p99, - dist_summary.max, - ], - ] - return headers, values - - def _get_benchmark_extras_headers_and_values( - self, - benchmark: GenerativeBenchmark, - ) -> tuple[list[str], list[str]]: - headers = ["Profile", "Backend", "Generator Data"] - values: list[str] = [ - benchmark.benchmarker.profile.model_dump_json(), - json.dumps(benchmark.benchmarker.backend), - json.dumps(benchmark.benchmarker.requests["data"]), - ] - - if len(headers) != len(values): - raise ValueError("Headers and values length mismatch.") - - return headers, values - - -@GenerativeBenchmarkerOutput.register("html") -class GenerativeBenchmarkerHTML(GenerativeBenchmarkerOutput): - """HTML output formatter for benchmark results.""" - - DEFAULT_FILE: ClassVar[str] = "benchmarks.html" - - @classmethod - def validated_kwargs( - cls, output_path: str | Path | None, **_kwargs - ) -> dict[str, Any]: - new_kwargs = {} - if output_path is not None: - new_kwargs["output_path"] = ( - Path(output_path) if not isinstance(output_path, Path) else output_path - ) - return new_kwargs - - output_path: Path = Field(default_factory=lambda: Path.cwd()) - - async def finalize(self, report: GenerativeBenchmarksReport) -> Path: - """ - Save the benchmark report as an HTML file. - - :param report: The completed benchmark report. - :return: Path to the saved HTML file. - """ - output_path = self.output_path - if output_path.is_dir(): - output_path = output_path / GenerativeBenchmarkerHTML.DEFAULT_FILE - output_path.parent.mkdir(parents=True, exist_ok=True) - - data_builder = UIDataBuilder(report.benchmarks) - data = data_builder.to_dict() - camel_data = recursive_key_update(deepcopy(data), camelize_str) - - ui_api_data = {} - for k, v in camel_data.items(): - placeholder_key = f"window.{k} = {{}};" - replacement_value = f"window.{k} = {json.dumps(v, indent=2)};\n" - ui_api_data[placeholder_key] = replacement_value - - create_report(ui_api_data, output_path) - - return output_path diff --git a/src/guidellm/benchmark/outputs/__init__.py b/src/guidellm/benchmark/outputs/__init__.py new file mode 100644 index 00000000..2e321605 --- /dev/null +++ b/src/guidellm/benchmark/outputs/__init__.py @@ -0,0 +1,24 @@ +""" +Output formatters for benchmark results. + +Provides output formatter implementations that transform benchmark reports into +various file formats including JSON, CSV, HTML, and console display. All formatters +extend the base GenerativeBenchmarkerOutput interface, enabling dynamic resolution +and flexible output configuration for benchmark result persistence and analysis. +""" + +from __future__ import annotations + +from .console import GenerativeBenchmarkerConsole +from .csv import GenerativeBenchmarkerCSV +from .html import GenerativeBenchmarkerHTML +from .output import GenerativeBenchmarkerOutput +from .serialized import GenerativeBenchmarkerSerialized + +__all__ = [ + "GenerativeBenchmarkerCSV", + "GenerativeBenchmarkerConsole", + "GenerativeBenchmarkerHTML", + "GenerativeBenchmarkerOutput", + "GenerativeBenchmarkerSerialized", +] diff --git a/src/guidellm/benchmark/outputs/console.py b/src/guidellm/benchmark/outputs/console.py new file mode 100644 index 00000000..3c900d69 --- /dev/null +++ b/src/guidellm/benchmark/outputs/console.py @@ -0,0 +1,633 @@ +""" +Console output formatter for generative benchmarker results. + +This module provides console-based output formatting for benchmark reports, organizing +metrics into structured tables that display request statistics, latency measurements, +throughput data, and modality-specific metrics (text, image, video, audio). It uses +the Console utility to render multi-column tables with proper alignment and formatting +for terminal display. +""" + +from __future__ import annotations + +from collections import defaultdict +from collections.abc import Sequence +from dataclasses import dataclass, field +from typing import Any, Literal, cast + +from pydantic import Field + +from guidellm.benchmark.outputs.output import GenerativeBenchmarkerOutput +from guidellm.benchmark.schemas import GenerativeBenchmarksReport +from guidellm.schemas import DistributionSummary, StatusDistributionSummary +from guidellm.utils import Console, safe_format_number, safe_format_timestamp + +__all__ = ["GenerativeBenchmarkerConsole"] + + +StatTypesAlias = Literal["mean", "median", "p95"] + + +@dataclass +class ConsoleTableColumn: + """ + Data structure for a single console table column. + + Stores column metadata (group, name, units, type) and accumulated values for + rendering formatted table output with proper type-specific formatting and precision. + + :cvar group: Optional group header for related columns + :cvar name: Column name displayed in header + :cvar units: Optional unit label for numeric values + :cvar type_: Data type determining formatting (number, text, timestamp) + :cvar precision: Decimal precision for numeric formatting + :cvar values: Accumulated values for this column across rows + """ + + group: str | None = None + name: str | None = None + units: str | None = None + type_: Literal["number", "text", "timestamp"] = "number" + precision: int = 1 + values: list[str | float | int | None] = field(default_factory=list) + + +class ConsoleTableColumnsCollection(dict[str, ConsoleTableColumn]): + """ + Collection manager for console table columns. + + Extends dict to provide specialized methods for adding values and statistics to + columns, automatically creating columns as needed and organizing them by composite + keys for consistent table rendering. + """ + + def add_value( + self, + value: str | float | int | None, + group: str | None = None, + name: str | None = None, + units: str | None = None, + type_: Literal["number", "text", "timestamp"] = "number", + precision: int = 1, + ): + """ + Add a value to a column, creating the column if it doesn't exist. + + :param value: The value to add to the column + :param group: Optional group header for the column + :param name: Column name for display + :param units: Optional unit label + :param type_: Data type for formatting + :param precision: Decimal precision for numbers + """ + key = f"{group}_{name}_{units}" + + if key not in self: + self[key] = ConsoleTableColumn( + group=group, name=name, units=units, type_=type_, precision=precision + ) + + self[key].values.append(value) + + def add_stats( + self, + stats: StatusDistributionSummary | None, + status: Literal["successful", "incomplete", "errored", "total"] = "successful", + group: str | None = None, + name: str | None = None, + precision: int = 1, + types: Sequence[StatTypesAlias] = ("median", "p95"), + ): + """ + Add statistical summary columns (mean and p95) for a metric. + + Creates paired mean/p95 columns automatically and appends values from the + specified status category of the distribution summary. + + :param stats: Distribution summary containing status-specific statistics + :param status: Status category to extract statistics from + :param group: Optional group header for the columns + :param name: Column name for display + :param precision: Decimal precision for numbers + """ + key = f"{group}_{name}" + status_stats: DistributionSummary | None = ( + getattr(stats, status) if stats else None + ) + + for stat_type in types: + col_key = f"{key}_{stat_type}" + col_name, col_value = self._get_stat_type_name_val(stat_type, status_stats) + if col_key not in self: + self[col_key] = ConsoleTableColumn( + group=group, + name=name, + units=col_name, + precision=precision, + ) + self[col_key].values.append(col_value) + + def get_table_data(self) -> tuple[list[list[str]], list[list[str]]]: + """ + Convert column collection to formatted table data. + + Transforms stored columns and values into header and value lists suitable for + console table rendering, applying type-specific formatting. + + :return: Tuple of (headers, values) where each is a list of column string lists + """ + headers: list[list[str]] = [] + values: list[list[str]] = [] + + for column in self.values(): + headers.append([column.group or "", column.name or "", column.units or ""]) + formatted_values: list[str] = [] + for value in column.values: + if column.type_ == "text": + formatted_values.append(str(value)) + continue + + if not isinstance(value, float | int) and value is not None: + raise ValueError( + f"Expected numeric value for column '{column.name}', " + f"got: {value}" + ) + + if column.type_ == "timestamp": + formatted_values.append( + safe_format_timestamp(cast("float | None", value)) + ) + elif column.type_ == "number": + formatted_values.append( + safe_format_number( + value, + precision=column.precision, + ) + ) + else: + raise ValueError(f"Unsupported column type: {column.type_}") + values.append(formatted_values) + + return headers, values + + @classmethod + def _get_stat_type_name_val( + cls, stat_type: StatTypesAlias, stats: DistributionSummary | None + ) -> tuple[str, float | None]: + if stat_type == "mean": + return "Mean", stats.mean if stats else None + elif stat_type == "median": + return "Mdn", stats.median if stats else None + elif stat_type == "p95": + return "p95", stats.percentiles.p95 if stats else None + else: + raise ValueError(f"Unsupported stat type: {stat_type}") + + +@GenerativeBenchmarkerOutput.register("console") +class GenerativeBenchmarkerConsole(GenerativeBenchmarkerOutput): + """ + Console output formatter for benchmark reports. + + Renders benchmark results as formatted tables in the terminal, organizing metrics + by category (run summary, request counts, latency, throughput, modality-specific) + with proper alignment and type-specific formatting for readability. + """ + + @classmethod + def validated_kwargs(cls, *_args, **_kwargs) -> dict[str, Any]: + """ + Validate and return keyword arguments for initialization. + + :return: Empty dict as no additional kwargs are required + """ + return {} + + console: Console = Field( + default_factory=Console, + description="Console utility for rendering formatted tables", + ) + + async def finalize(self, report: GenerativeBenchmarksReport) -> str: + """ + Print the complete benchmark report to the console. + + Renders all metric tables including run summary, request counts, latency, + throughput, and modality-specific statistics to the console. + + :param report: The completed benchmark report + :return: Status message indicating output location + """ + self.print_run_summary_table(report) + self.print_text_table(report) + self.print_image_table(report) + self.print_video_table(report) + self.print_audio_table(report) + self.print_request_counts_table(report) + self.print_request_latency_table(report) + self.print_server_throughput_table(report) + + return "printed to console" + + def print_run_summary_table(self, report: GenerativeBenchmarksReport): + """ + Print the run summary table with timing and token information. + + :param report: The benchmark report containing run metadata + """ + columns = ConsoleTableColumnsCollection() + + for benchmark in report.benchmarks: + columns.add_value( + benchmark.config.strategy.type_, + group="Benchmark", + name="Strategy", + type_="text", + ) + columns.add_value( + benchmark.start_time, group="Timings", name="Start", type_="timestamp" + ) + columns.add_value( + benchmark.end_time, group="Timings", name="End", type_="timestamp" + ) + columns.add_value( + benchmark.duration, group="Timings", name="Dur", units="Sec" + ) + columns.add_value( + benchmark.warmup_duration, group="Timings", name="Warm", units="Sec" + ) + columns.add_value( + benchmark.cooldown_duration, group="Timings", name="Cool", units="Sec" + ) + + for token_metrics, group in [ + (benchmark.metrics.prompt_token_count, "Input Tokens"), + (benchmark.metrics.output_token_count, "Output Tokens"), + ]: + columns.add_value( + token_metrics.successful.total_sum, + group=group, + name="Comp", + units="Tot", + ) + columns.add_value( + token_metrics.incomplete.total_sum, + group=group, + name="Inc", + units="Tot", + ) + columns.add_value( + token_metrics.errored.total_sum, + group=group, + name="Err", + units="Tot", + ) + + headers, values = columns.get_table_data() + self.console.print("\n") + self.console.print_table(headers, values, title="Run Summary Info") + + def print_text_table(self, report: GenerativeBenchmarksReport): + """ + Print text-specific metrics table if any text data exists. + + :param report: The benchmark report containing text metrics + """ + self._print_modality_table( + report=report, + modality="text", + title="Text Metrics Statistics (Completed Requests)", + metric_groups=[ + ("tokens", "Tokens"), + ("words", "Words"), + ("characters", "Characters"), + ], + ) + + def print_image_table(self, report: GenerativeBenchmarksReport): + """ + Print image-specific metrics table if any image data exists. + + :param report: The benchmark report containing image metrics + """ + self._print_modality_table( + report=report, + modality="image", + title="Image Metrics Statistics (Completed Requests)", + metric_groups=[ + ("tokens", "Tokens"), + ("images", "Images"), + ("pixels", "Pixels"), + ("bytes", "Bytes"), + ], + ) + + def print_video_table(self, report: GenerativeBenchmarksReport): + """ + Print video-specific metrics table if any video data exists. + + :param report: The benchmark report containing video metrics + """ + self._print_modality_table( + report=report, + modality="video", + title="Video Metrics Statistics (Completed Requests)", + metric_groups=[ + ("tokens", "Tokens"), + ("frames", "Frames"), + ("seconds", "Seconds"), + ("bytes", "Bytes"), + ], + ) + + def print_audio_table(self, report: GenerativeBenchmarksReport): + """ + Print audio-specific metrics table if any audio data exists. + + :param report: The benchmark report containing audio metrics + """ + self._print_modality_table( + report=report, + modality="audio", + title="Audio Metrics Statistics (Completed Requests)", + metric_groups=[ + ("tokens", "Tokens"), + ("samples", "Samples"), + ("seconds", "Seconds"), + ("bytes", "Bytes"), + ], + ) + + def print_request_counts_table(self, report: GenerativeBenchmarksReport): + """ + Print request token count statistics table. + + :param report: The benchmark report containing request count metrics + """ + columns = ConsoleTableColumnsCollection() + + for benchmark in report.benchmarks: + columns.add_value( + benchmark.config.strategy.type_, + group="Benchmark", + name="Strategy", + type_="text", + ) + columns.add_stats( + benchmark.metrics.prompt_token_count, + group="Input Tok", + name="Per Req", + ) + columns.add_stats( + benchmark.metrics.output_token_count, + group="Output Tok", + name="Per Req", + ) + columns.add_stats( + benchmark.metrics.total_token_count, + group="Total Tok", + name="Per Req", + ) + columns.add_stats( + benchmark.metrics.request_streaming_iterations_count, + group="Stream Iter", + name="Per Req", + ) + columns.add_stats( + benchmark.metrics.output_tokens_per_iteration, + group="Output Tok", + name="Per Stream Iter", + ) + + headers, values = columns.get_table_data() + self.console.print("\n") + self.console.print_table( + headers, + values, + title="Request Token Statistics (Completed Requests)", + ) + + def print_request_latency_table(self, report: GenerativeBenchmarksReport): + """ + Print request latency metrics table. + + :param report: The benchmark report containing latency metrics + """ + columns = ConsoleTableColumnsCollection() + + for benchmark in report.benchmarks: + columns.add_value( + benchmark.config.strategy.type_, + group="Benchmark", + name="Strategy", + type_="text", + ) + columns.add_stats( + benchmark.metrics.request_latency, + group="Request Latency", + name="Sec", + ) + columns.add_stats( + benchmark.metrics.time_to_first_token_ms, + group="TTFT", + name="ms", + ) + columns.add_stats( + benchmark.metrics.inter_token_latency_ms, + group="ITL", + name="ms", + ) + columns.add_stats( + benchmark.metrics.time_per_output_token_ms, + group="TPOT", + name="ms", + ) + + headers, values = columns.get_table_data() + self.console.print("\n") + self.console.print_table( + headers, + values, + title="Request Latency Statistics (Completed Requests)", + ) + + def print_server_throughput_table(self, report: GenerativeBenchmarksReport): + """ + Print server throughput metrics table. + + :param report: The benchmark report containing throughput metrics + """ + columns = ConsoleTableColumnsCollection() + + for benchmark in report.benchmarks: + columns.add_value( + benchmark.config.strategy.type_, + group="Benchmark", + name="Strategy", + type_="text", + ) + columns.add_stats( + benchmark.metrics.requests_per_second, + group="Requests", + name="Per Sec", + types=("median", "mean"), + ) + columns.add_stats( + benchmark.metrics.request_concurrency, + group="Requests", + name="Concurrency", + types=("median", "mean"), + ) + columns.add_stats( + benchmark.metrics.prompt_tokens_per_second, + group="Input Tokens", + name="Per Sec", + types=("median", "mean"), + ) + columns.add_stats( + benchmark.metrics.output_tokens_per_second, + group="Output Tokens", + name="Per Sec", + types=("median", "mean"), + ) + columns.add_stats( + benchmark.metrics.tokens_per_second, + group="Total Tokens", + name="Per Sec", + types=("median", "mean"), + ) + + headers, values = columns.get_table_data() + self.console.print("\n") + self.console.print_table(headers, values, title="Server Throughput Statistics") + + def _print_modality_table( + self, + report: GenerativeBenchmarksReport, + modality: Literal["text", "image", "video", "audio"], + title: str, + metric_groups: list[tuple[str, str]], + ): + columns: dict[str, ConsoleTableColumnsCollection] = defaultdict( + ConsoleTableColumnsCollection + ) + + for benchmark in report.benchmarks: + columns["labels"].add_value( + benchmark.config.strategy.type_, + group="Benchmark", + name="Strategy", + type_="text", + ) + + modality_metrics = getattr(benchmark.metrics, modality) + + for metric_attr, display_name in metric_groups: + metric_obj = getattr(modality_metrics, metric_attr, None) + input_stats: StatusDistributionSummary | None = ( + getattr(metric_obj, "input", None) if metric_obj else None + ) + columns[f"{metric_attr}.input"].add_stats( + input_stats, + group=f"Input {display_name}", + name="Per Request", + ) + input_per_second_stats: StatusDistributionSummary | None = ( + getattr(metric_obj, "input_per_second", None) + if metric_obj + else None + ) + columns[f"{metric_attr}.input"].add_stats( + input_per_second_stats, + group=f"Input {display_name}", + name="Per Second", + types=("median", "mean"), + ) + output_stats: StatusDistributionSummary | None = ( + getattr(metric_obj, "output", None) if metric_obj else None + ) + columns[f"{metric_attr}.output"].add_stats( + output_stats, + group=f"Output {display_name}", + name="Per Request", + ) + output_per_second_stats: StatusDistributionSummary | None = ( + getattr(metric_obj, "output_per_second", None) + if metric_obj + else None + ) + columns[f"{metric_attr}.output"].add_stats( + output_per_second_stats, + group=f"Output {display_name}", + name="Per Second", + types=("median", "mean"), + ) + + self._print_inp_out_tables( + title=title, + labels=columns["labels"], + groups=[ + (columns[f"{metric_attr}.input"], columns[f"{metric_attr}.output"]) + for metric_attr, _ in metric_groups + ], + ) + + def _print_inp_out_tables( + self, + title: str, + labels: ConsoleTableColumnsCollection, + groups: list[ + tuple[ConsoleTableColumnsCollection, ConsoleTableColumnsCollection] + ], + ): + input_headers, input_values = [], [] + output_headers, output_values = [], [] + input_has_data = False + output_has_data = False + + for input_columns, output_columns in groups: + # Check if columns have any non-None values + type_input_has_data = any( + any(value is not None for value in column.values) + for column in input_columns.values() + ) + type_output_has_data = any( + any(value is not None for value in column.values) + for column in output_columns.values() + ) + + if not (type_input_has_data or type_output_has_data): + continue + + input_has_data = input_has_data or type_input_has_data + output_has_data = output_has_data or type_output_has_data + + input_type_headers, input_type_columns = input_columns.get_table_data() + output_type_headers, output_type_columns = output_columns.get_table_data() + + input_headers.extend(input_type_headers) + input_values.extend(input_type_columns) + output_headers.extend(output_type_headers) + output_values.extend(output_type_columns) + + if not (input_has_data or output_has_data): + return + + labels_headers, labels_values = labels.get_table_data() + header_cols_groups = [] + value_cols_groups = [] + + if input_has_data: + header_cols_groups.append(labels_headers + input_headers) + value_cols_groups.append(labels_values + input_values) + if output_has_data: + header_cols_groups.append(labels_headers + output_headers) + value_cols_groups.append(labels_values + output_values) + + if header_cols_groups and value_cols_groups: + self.console.print("\n") + self.console.print_tables( + header_cols_groups=header_cols_groups, + value_cols_groups=value_cols_groups, + title=title, + ) diff --git a/src/guidellm/benchmark/outputs/csv.py b/src/guidellm/benchmark/outputs/csv.py new file mode 100644 index 00000000..3cc658d8 --- /dev/null +++ b/src/guidellm/benchmark/outputs/csv.py @@ -0,0 +1,692 @@ +""" +CSV output formatter for benchmark results. + +This module provides the GenerativeBenchmarkerCSV class which exports benchmark +reports to CSV format with comprehensive metrics including timing, throughput, +latency, modality data, and scheduler information. The CSV output uses multi-row +headers to organize metrics hierarchically and includes both summary statistics +and distribution percentiles. +""" + +from __future__ import annotations + +import csv +import json +from pathlib import Path +from typing import Annotated, Any, ClassVar, Literal + +from pydantic import Field + +from guidellm.benchmark.outputs.output import GenerativeBenchmarkerOutput +from guidellm.benchmark.schemas import GenerativeBenchmark, GenerativeBenchmarksReport +from guidellm.schemas import DistributionSummary, StatusDistributionSummary +from guidellm.utils import safe_format_timestamp + +__all__ = ["GenerativeBenchmarkerCSV"] + +TIMESTAMP_FORMAT: Annotated[str, "Format string for timestamp output in CSV files"] = ( + "%Y-%m-%d %H:%M:%S" +) +MODALITY_METRICS: Annotated[ + dict[str, list[tuple[str, str]]], + "Mapping of modality types to their metric names and display labels", +] = { + "text": [ + ("tokens", "Tokens"), + ("words", "Words"), + ("characters", "Characters"), + ], + "image": [ + ("tokens", "Tokens"), + ("images", "Images"), + ("pixels", "Pixels"), + ("bytes", "Bytes"), + ], + "video": [ + ("tokens", "Tokens"), + ("frames", "Frames"), + ("seconds", "Seconds"), + ("bytes", "Bytes"), + ], + "audio": [ + ("tokens", "Tokens"), + ("samples", "Samples"), + ("seconds", "Seconds"), + ("bytes", "Bytes"), + ], +} + + +@GenerativeBenchmarkerOutput.register("csv") +class GenerativeBenchmarkerCSV(GenerativeBenchmarkerOutput): + """ + CSV output formatter for benchmark results. + + Exports comprehensive benchmark data to CSV format with multi-row headers + organizing metrics into categories including run information, timing, request + counts, latency, throughput, modality-specific data, and scheduler state. Each + benchmark run becomes a row with statistical distributions represented as + mean, median, standard deviation, and percentiles. + + :cvar DEFAULT_FILE: Default filename for CSV output + """ + + DEFAULT_FILE: ClassVar[str] = "benchmarks.csv" + + @classmethod + def validated_kwargs( + cls, output_path: str | Path | None, **_kwargs + ) -> dict[str, Any]: + """ + Validate and normalize constructor keyword arguments. + + :param output_path: Path for CSV output file or directory + :param _kwargs: Additional keyword arguments (ignored) + :return: Normalized keyword arguments dictionary + """ + new_kwargs = {} + if output_path is not None: + new_kwargs["output_path"] = ( + Path(output_path) if not isinstance(output_path, Path) else output_path + ) + return new_kwargs + + output_path: Path = Field( + default_factory=lambda: Path.cwd(), + description=( + "Path where the CSV file will be saved, defaults to current directory" + ), + ) + + async def finalize(self, report: GenerativeBenchmarksReport) -> Path: + """ + Save the benchmark report as a CSV file. + + :param report: The completed benchmark report + :return: Path to the saved CSV file + """ + output_path = self.output_path + if output_path.is_dir(): + output_path = output_path / GenerativeBenchmarkerCSV.DEFAULT_FILE + output_path.parent.mkdir(parents=True, exist_ok=True) + + with output_path.open("w", newline="") as file: + writer = csv.writer(file) + headers: list[list[str]] = [] + rows: list[list[str | int | float]] = [] + + for benchmark in report.benchmarks: + benchmark_headers: list[list[str]] = [] + benchmark_values: list[str | int | float] = [] + + self._add_run_info(benchmark, benchmark_headers, benchmark_values) + self._add_benchmark_info(benchmark, benchmark_headers, benchmark_values) + self._add_timing_info(benchmark, benchmark_headers, benchmark_values) + self._add_request_counts(benchmark, benchmark_headers, benchmark_values) + self._add_request_latency_metrics( + benchmark, benchmark_headers, benchmark_values + ) + self._add_server_throughput_metrics( + benchmark, benchmark_headers, benchmark_values + ) + for modality_name in ["text", "image", "video", "audio"]: + self._add_modality_metrics( + benchmark, + modality_name, # type: ignore[arg-type] + benchmark_headers, + benchmark_values, + ) + self._add_scheduler_info(benchmark, benchmark_headers, benchmark_values) + + if not headers: + headers = benchmark_headers + rows.append(benchmark_values) + + self._write_multirow_header(writer, headers) + for row in rows: + writer.writerow(row) + + return output_path + + def _write_multirow_header(self, writer: Any, headers: list[list[str]]) -> None: + """ + Write multi-row header to CSV for hierarchical metric organization. + + :param writer: CSV writer instance + :param headers: List of column header hierarchies as string lists + """ + max_rows = max((len(col) for col in headers), default=0) + for row_idx in range(max_rows): + row = [col[row_idx] if row_idx < len(col) else "" for col in headers] + writer.writerow(row) + + def _add_field( + self, + headers: list[list[str]], + values: list[str | int | float], + group: str, + field_name: str, + value: Any, + units: str = "", + ) -> None: + """ + Add a single field to headers and values lists. + + :param headers: List of header hierarchies to append to + :param values: List of values to append to + :param group: Top-level category for the field + :param field_name: Name of the field + :param value: Value for the field + :param units: Optional units for the field + """ + headers.append([group, field_name, units]) + values.append(value) + + def _add_run_info( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add overall run identification and configuration information. + + :param benchmark: Benchmark data to extract run info from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + self._add_field(headers, values, "Run Info", "Run ID", benchmark.config.run_id) + self._add_field( + headers, values, "Run Info", "Run Index", benchmark.config.run_index + ) + self._add_field( + headers, + values, + "Run Info", + "Profile", + benchmark.config.profile.model_dump_json(), + ) + self._add_field( + headers, + values, + "Run Info", + "Requests", + json.dumps(benchmark.config.requests), + ) + self._add_field( + headers, values, "Run Info", "Backend", json.dumps(benchmark.config.backend) + ) + self._add_field( + headers, + values, + "Run Info", + "Environment", + json.dumps(benchmark.config.environment), + ) + + def _add_benchmark_info( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add individual benchmark configuration details. + + :param benchmark: Benchmark data to extract configuration from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + self._add_field(headers, values, "Benchmark", "Type", benchmark.type_) + self._add_field(headers, values, "Benchmark", "ID", benchmark.config.id_) + self._add_field( + headers, values, "Benchmark", "Strategy", benchmark.config.strategy.type_ + ) + self._add_field( + headers, + values, + "Benchmark", + "Constraints", + json.dumps(benchmark.config.constraints), + ) + + def _add_timing_info( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add timing information including start, end, duration, warmup, and cooldown. + + :param benchmark: Benchmark data to extract timing from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + timing_fields: list[tuple[str, Any]] = [ + ("Start Time", benchmark.scheduler_metrics.start_time), + ("Request Start Time", benchmark.scheduler_metrics.request_start_time), + ("Measure Start Time", benchmark.scheduler_metrics.measure_start_time), + ("Measure End Time", benchmark.scheduler_metrics.measure_end_time), + ("Request End Time", benchmark.scheduler_metrics.request_end_time), + ("End Time", benchmark.scheduler_metrics.end_time), + ] + for field_name, timestamp in timing_fields: + self._add_field( + headers, + values, + "Timings", + field_name, + safe_format_timestamp(timestamp, TIMESTAMP_FORMAT), + ) + + duration_fields: list[tuple[str, float | str]] = [ + ("Duration", benchmark.duration), + ("Warmup", benchmark.warmup_duration), + ("Cooldown", benchmark.cooldown_duration), + ] + for field_name, duration_value in duration_fields: + self._add_field( + headers, values, "Timings", field_name, duration_value, "Sec" + ) + + def _add_request_counts( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add request count totals by status. + + :param benchmark: Benchmark data to extract request counts from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + for status in ["successful", "incomplete", "errored", "total"]: + self._add_field( + headers, + values, + "Request Counts", + status.capitalize(), + getattr(benchmark.metrics.request_totals, status), + ) + + def _add_request_latency_metrics( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add request latency and streaming metrics. + + :param benchmark: Benchmark data to extract latency metrics from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + self._add_stats_for_metric( + headers, values, benchmark.metrics.request_latency, "Request Latency", "Sec" + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.request_streaming_iterations_count, + "Streaming Iterations", + "Count", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.time_to_first_token_ms, + "Time to First Token", + "ms", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.time_per_output_token_ms, + "Time per Output Token", + "ms", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.inter_token_latency_ms, + "Inter Token Latency", + "ms", + ) + + def _add_server_throughput_metrics( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add server throughput metrics including requests, tokens, and concurrency. + + :param benchmark: Benchmark data to extract throughput metrics from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.requests_per_second, + "Server Throughput", + "Requests/Sec", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.request_concurrency, + "Server Throughput", + "Concurrency", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.prompt_token_count, + "Token Metrics", + "Input Tokens", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.output_token_count, + "Token Metrics", + "Output Tokens", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.total_token_count, + "Token Metrics", + "Total Tokens", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.prompt_tokens_per_second, + "Token Throughput", + "Input Tokens/Sec", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.output_tokens_per_second, + "Token Throughput", + "Output Tokens/Sec", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.tokens_per_second, + "Token Throughput", + "Total Tokens/Sec", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.output_tokens_per_iteration, + "Token Streaming", + "Output Tokens/Iter", + ) + self._add_stats_for_metric( + headers, + values, + benchmark.metrics.iter_tokens_per_iteration, + "Token Streaming", + "Iter Tokens/Iter", + ) + + def _add_modality_metrics( + self, + benchmark: GenerativeBenchmark, + modality: Literal["text", "image", "video", "audio"], + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add modality-specific metrics for text, image, video, or audio data. + + :param benchmark: Benchmark data to extract modality metrics from + :param modality: Type of modality to extract metrics for + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + modality_summary = getattr(benchmark.metrics, modality) + metric_definitions = MODALITY_METRICS[modality] + + for metric_name, display_name in metric_definitions: + metric_obj = getattr(modality_summary, metric_name, None) + if metric_obj is None: + continue + + for io_type in ["input", "output", "total"]: + dist_summary = getattr(metric_obj, io_type, None) + if dist_summary is None: + continue + + if not self._has_distribution_data(dist_summary): + continue + + self._add_stats_for_metric( + headers, + values, + dist_summary, + f"{modality.capitalize()} {display_name}", + io_type.capitalize(), + ) + + def _has_distribution_data(self, dist_summary: StatusDistributionSummary) -> bool: + """ + Check if distribution summary contains any data. + + :param dist_summary: Distribution summary to check + :return: True if summary contains data, False otherwise + """ + return any( + getattr(dist_summary, status, None) is not None + and getattr(dist_summary, status).total_sum > 0.0 + for status in ["successful", "incomplete", "errored"] + ) + + def _add_scheduler_info( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add scheduler state and performance information. + + :param benchmark: Benchmark data to extract scheduler info from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + self._add_scheduler_state(benchmark, headers, values) + self._add_scheduler_metrics(benchmark, headers, values) + + def _add_scheduler_state( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add scheduler state information including request counts and timing. + + :param benchmark: Benchmark data to extract scheduler state from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + state = benchmark.scheduler_state + + state_fields: list[tuple[str, Any]] = [ + ("Node ID", state.node_id), + ("Num Processes", state.num_processes), + ("Created Requests", state.created_requests), + ("Processed Requests", state.processed_requests), + ("Successful Requests", state.successful_requests), + ("Errored Requests", state.errored_requests), + ("Cancelled Requests", state.cancelled_requests), + ] + + for field_name, value in state_fields: + self._add_field(headers, values, "Scheduler State", field_name, value) + + if state.end_queuing_time: + self._add_field( + headers, + values, + "Scheduler State", + "End Queuing Time", + safe_format_timestamp(state.end_queuing_time, TIMESTAMP_FORMAT), + ) + end_queuing_constraints_dict = { + key: constraint.model_dump() + for key, constraint in state.end_queuing_constraints.items() + } + self._add_field( + headers, + values, + "Scheduler State", + "End Queuing Constraints", + json.dumps(end_queuing_constraints_dict), + ) + + if state.end_processing_time: + self._add_field( + headers, + values, + "Scheduler State", + "End Processing Time", + safe_format_timestamp(state.end_processing_time, TIMESTAMP_FORMAT), + ) + end_processing_constraints_dict = { + key: constraint.model_dump() + for key, constraint in state.end_processing_constraints.items() + } + self._add_field( + headers, + values, + "Scheduler State", + "End Processing Constraints", + json.dumps(end_processing_constraints_dict), + ) + + def _add_scheduler_metrics( + self, + benchmark: GenerativeBenchmark, + headers: list[list[str]], + values: list[str | int | float], + ) -> None: + """ + Add scheduler performance metrics including delays and processing times. + + :param benchmark: Benchmark data to extract scheduler metrics from + :param headers: List of header hierarchies to append to + :param values: List of values to append to + """ + metrics = benchmark.scheduler_metrics + + requests_made_fields: list[tuple[str, int]] = [ + ("Requests Made Successful", metrics.requests_made.successful), + ("Requests Made Incomplete", metrics.requests_made.incomplete), + ("Requests Made Errored", metrics.requests_made.errored), + ("Requests Made Total", metrics.requests_made.total), + ] + for field_name, value in requests_made_fields: + self._add_field(headers, values, "Scheduler Metrics", field_name, value) + + timing_metrics: list[tuple[str, float]] = [ + ("Queued Time Avg", metrics.queued_time_avg), + ("Resolve Start Delay Avg", metrics.resolve_start_delay_avg), + ( + "Resolve Targeted Start Delay Avg", + metrics.resolve_targeted_start_delay_avg, + ), + ("Request Start Delay Avg", metrics.request_start_delay_avg), + ( + "Request Targeted Start Delay Avg", + metrics.request_targeted_start_delay_avg, + ), + ("Request Time Avg", metrics.request_time_avg), + ("Resolve End Delay Avg", metrics.resolve_end_delay_avg), + ("Resolve Time Avg", metrics.resolve_time_avg), + ("Finalized Delay Avg", metrics.finalized_delay_avg), + ("Processed Delay Avg", metrics.processed_delay_avg), + ] + for field_name, timing in timing_metrics: + self._add_field( + headers, values, "Scheduler Metrics", field_name, timing, "Sec" + ) + + def _add_stats_for_metric( + self, + headers: list[list[str]], + values: list[str | int | float], + metric: StatusDistributionSummary | DistributionSummary, + group: str, + units: str, + ) -> None: + """ + Add statistical summaries for a metric across all statuses. + + :param headers: List of header hierarchies to append to + :param values: List of values to append to + :param metric: Distribution summary to extract statistics from + :param group: Top-level category for the metric + :param units: Units for the metric values + """ + if isinstance(metric, StatusDistributionSummary): + for status in ["successful", "incomplete", "errored"]: + dist = getattr(metric, status, None) + if dist is None or dist.total_sum == 0.0: + continue + self._add_distribution_stats( + headers, values, dist, group, units, status + ) + else: + self._add_distribution_stats(headers, values, metric, group, units, None) + + def _add_distribution_stats( + self, + headers: list[list[str]], + values: list[str | int | float], + dist: DistributionSummary, + group: str, + units: str, + status: str | None, + ) -> None: + """ + Add distribution statistics including mean, median, and percentiles. + + :param headers: List of header hierarchies to append to + :param values: List of values to append to + :param dist: Distribution summary with statistical data + :param group: Top-level category for the metric + :param units: Units for the metric values + :param status: Request status (successful, incomplete, errored) or None + """ + status_prefix = f"{status.capitalize()} " if status else "" + + headers.append([group, f"{status_prefix}{units}", "Mean"]) + values.append(dist.mean) + + headers.append([group, f"{status_prefix}{units}", "Median"]) + values.append(dist.median) + + headers.append([group, f"{status_prefix}{units}", "Std Dev"]) + values.append(dist.std_dev) + + headers.append([group, f"{status_prefix}{units}", "Percentiles"]) + percentiles_str = ( + f"[{dist.min}, {dist.percentiles.p001}, {dist.percentiles.p01}, " + f"{dist.percentiles.p05}, {dist.percentiles.p10}, {dist.percentiles.p25}, " + f"{dist.percentiles.p75}, {dist.percentiles.p90}, {dist.percentiles.p95}, " + f"{dist.percentiles.p99}, {dist.max}]" + ) + values.append(percentiles_str) diff --git a/src/guidellm/benchmark/outputs/html.py b/src/guidellm/benchmark/outputs/html.py new file mode 100644 index 00000000..34cf7107 --- /dev/null +++ b/src/guidellm/benchmark/outputs/html.py @@ -0,0 +1,422 @@ +""" +HTML output formatter for benchmark results. + +Transforms benchmark data into interactive web-based reports by building UI data +structures, converting keys to camelCase for JavaScript compatibility, and injecting +formatted data into HTML templates. The formatter processes GenerativeBenchmark +instances and their associated metrics, creating histogram buckets for distributions, +formatting percentile statistics for tabular display, and embedding all data as +JavaScript objects within an HTML template for client-side rendering and visualization. +""" + +from __future__ import annotations + +import json +import random +import re +from collections import defaultdict +from copy import deepcopy +from math import ceil +from pathlib import Path +from typing import Any, ClassVar + +from loguru import logger +from pydantic import BaseModel, Field, computed_field + +from guidellm.benchmark.outputs.output import GenerativeBenchmarkerOutput +from guidellm.benchmark.schemas import ( + BenchmarkGenerativeTextArgs, + GenerativeBenchmark, + GenerativeBenchmarksReport, +) +from guidellm.schemas import DistributionSummary +from guidellm.settings import settings +from guidellm.utils import camelize_str, recursive_key_update +from guidellm.utils.text import load_text + +__all__ = ["GenerativeBenchmarkerHTML"] + + +@GenerativeBenchmarkerOutput.register("html") +class GenerativeBenchmarkerHTML(GenerativeBenchmarkerOutput): + """ + HTML output formatter for benchmark results. + + Generates interactive HTML reports from benchmark data by transforming results + into camelCase JSON structures and injecting them into HTML templates. The + formatter processes benchmark metrics, creates histogram distributions, and + embeds all data into a pre-built HTML template for browser-based visualization. + Reports are saved to the specified output path or current working directory. + + :cvar DEFAULT_FILE: Default filename for HTML output when a directory is provided + """ + + DEFAULT_FILE: ClassVar[str] = "benchmarks.html" + + output_path: Path = Field( + default_factory=lambda: Path.cwd(), + description=( + "Directory or file path for saving the HTML report, " + "defaults to current working directory" + ), + ) + + @classmethod + def validated_kwargs( + cls, output_path: str | Path | None, **_kwargs + ) -> dict[str, Any]: + """ + Validate and normalize output path argument. + + :param output_path: Output file or directory path for the HTML report + :return: Dictionary containing validated output_path if provided + """ + validated: dict[str, Any] = {} + if output_path is not None: + validated["output_path"] = ( + Path(output_path) if not isinstance(output_path, Path) else output_path + ) + return validated + + async def finalize(self, report: GenerativeBenchmarksReport) -> Path: + """ + Generate and save the HTML benchmark report. + + Transforms benchmark data into camelCase JSON format, injects it into the + HTML template, and writes the resulting report to the output path. Creates + parent directories if they don't exist. + + :param report: Completed benchmark report containing all results + :return: Path to the saved HTML report file + """ + output_path = self.output_path + if output_path.is_dir(): + output_path = output_path / self.DEFAULT_FILE + output_path.parent.mkdir(parents=True, exist_ok=True) + + data = _build_ui_data(report.benchmarks, report.args) + camel_data = recursive_key_update(deepcopy(data), camelize_str) + + ui_api_data = { + f"window.{key} = {{}};": f"window.{key} = {json.dumps(value, indent=2)};\n" + for key, value in camel_data.items() + } + + _create_html_report(ui_api_data, output_path) + + return output_path + + +class _Bucket(BaseModel): + """ + Histogram bucket for data distribution visualization. + + Represents a single bucket in a histogram with its starting value and count + of data points falling within the bucket range. Used to create distribution + histograms for metrics like token counts and request timings. + """ + + value: float | int = Field(description="Starting value of the bucket range") + count: int = Field(description="Number of data points falling within this bucket") + + @staticmethod + def from_data( + data: list[float] | list[int], + bucket_width: float | None = None, + n_buckets: int | None = None, + ) -> tuple[list[_Bucket], float]: + """ + Create histogram buckets from numeric data values. + + Divides the data range into equal-width buckets and counts values within + each bucket. Either bucket_width or n_buckets can be specified; if neither + is provided, defaults to 10 buckets. + + :param data: Numeric values to bucket + :param bucket_width: Width of each bucket, computed if None + :param n_buckets: Number of buckets, defaults to 10 if width not specified + :return: Tuple of bucket list and computed bucket width + """ + if not data: + return [], 1.0 + + min_v = min(data) + max_v = max(data) + range_v = (1 + max_v) - min_v + + if bucket_width is None: + if n_buckets is None: + n_buckets = 10 + bucket_width = range_v / n_buckets + else: + n_buckets = ceil(range_v / bucket_width) + + bucket_counts: defaultdict[float | int, int] = defaultdict(int) + for val in data: + idx = int((val - min_v) // bucket_width) + if idx >= n_buckets: + idx = n_buckets - 1 + bucket_start = min_v + idx * bucket_width + bucket_counts[bucket_start] += 1 + + buckets = [ + _Bucket(value=start, count=count) + for start, count in sorted(bucket_counts.items()) + ] + return buckets, bucket_width + + +class _TabularDistributionSummary(DistributionSummary): + """ + Distribution summary with tabular percentile representation. + + Extends DistributionSummary to provide percentile data formatted for table + display in the HTML report. Filters to show only key percentiles (p50, p90, + p95, p99) for concise presentation. + """ + + @computed_field + def percentile_rows(self) -> list[dict[str, str | float]]: + """ + Format percentiles as table rows for UI display. + + :return: List of dictionaries with percentile names and values + """ + rows = [ + {"percentile": name, "value": value} + for name, value in self.percentiles.model_dump().items() + ] + return list( + filter(lambda row: row["percentile"] in ["p50", "p90", "p95", "p99"], rows) + ) + + @classmethod + def from_distribution_summary( + cls, distribution: DistributionSummary + ) -> _TabularDistributionSummary: + """ + Convert standard DistributionSummary to tabular format. + + :param distribution: Source distribution summary to convert + :return: Tabular distribution summary with formatted percentile rows + """ + return cls(**distribution.model_dump()) + + +def _create_html_report(js_data: dict[str, str], output_path: Path) -> Path: + """ + Create HTML report by injecting JavaScript data into template. + + Loads the HTML template, injects JavaScript data into the head section, and + writes the final report to the specified output path. + + :param js_data: Dictionary mapping placeholder strings to JavaScript code + :param output_path: Path where HTML report will be saved + :return: Path to the saved report file + """ + html_content = load_text(settings.report_generation.source) + report_content = _inject_data(js_data, html_content) + + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(report_content) + return output_path + + +def _inject_data(js_data: dict[str, str], html: str) -> str: + """ + Inject JavaScript data into HTML head section. + + Replaces placeholder strings in the HTML head section with actual JavaScript + code containing benchmark data. Returns original HTML if no head section found. + + :param js_data: Dictionary mapping placeholder strings to JavaScript code + :param html: HTML template content + :return: HTML with injected JavaScript data + """ + head_match = re.search(r"
]*>(.*?)", html, re.DOTALL | re.IGNORECASE) + if not head_match: + logger.warning(" section missing, returning original HTML.") + return html + + head_content = head_match.group(1) + + for placeholder, script in js_data.items(): + head_content = head_content.replace(placeholder, script) + + new_head = f"{head_content}" + return html[: head_match.start()] + new_head + html[head_match.end() :] + + +def _build_ui_data( + benchmarks: list[GenerativeBenchmark], args: BenchmarkGenerativeTextArgs +) -> dict[str, Any]: + """ + Build complete UI data structure from benchmarks. + + Aggregates benchmark results into a structured format for the HTML UI, + including run metadata, workload details, and per-benchmark metrics. + + :param benchmarks: List of completed benchmark results + :param args: Benchmark configuration arguments + :return: Dictionary with run_info, workload_details, and benchmarks sections + """ + return { + "run_info": _build_run_info(benchmarks, args), + "workload_details": _build_workload_details(benchmarks, args), + "benchmarks": _build_benchmarks(benchmarks), + } + + +def _build_run_info( + benchmarks: list[GenerativeBenchmark], args: BenchmarkGenerativeTextArgs +) -> dict[str, Any]: + """ + Build run metadata from benchmarks. + + Extracts model name, timestamp, and dataset information from the benchmark + configuration and results. + + :param benchmarks: List of completed benchmark results + :param args: Benchmark configuration arguments + :return: Dictionary with model, task, timestamp, and dataset information + """ + model = args.model or "N/A" + timestamp = max(bm.start_time for bm in benchmarks if bm.start_time is not None) + return { + "model": {"name": model, "size": 0}, + "task": "N/A", + "timestamp": timestamp, + "dataset": {"name": "N/A"}, + } + + +def _build_workload_details( + benchmarks: list[GenerativeBenchmark], args: BenchmarkGenerativeTextArgs +) -> dict[str, Any]: + """ + Build workload details from benchmarks. + + Aggregates prompt and generation samples, token distribution statistics, + request timing histograms, and server configuration. Samples up to 5 random + prompts and outputs for display. + + :param benchmarks: List of completed benchmark results + :param args: Benchmark configuration arguments + :return: Dictionary with prompts, generations, request timing, and server info + """ + target = args.target + rate_type = benchmarks[0].config.strategy.type_ + successful_requests = [req for bm in benchmarks for req in bm.requests.successful] + + sample_indices = random.sample( + range(len(successful_requests)), min(5, len(successful_requests)) + ) + sample_prompts = [ + req.request_args.replace("\n", " ").replace('"', "'") + if (req := successful_requests[i]).request_args + else "" + for i in sample_indices + ] + sample_outputs = [ + req.output.replace("\n", " ").replace('"', "'") + if (req := successful_requests[i]).output + else "" + for i in sample_indices + ] + + prompt_tokens = [ + float(req.prompt_tokens) if req.prompt_tokens is not None else -1 + for bm in benchmarks + for req in bm.requests.successful + ] + output_tokens = [ + float(req.output_tokens) if req.output_tokens is not None else -1 + for bm in benchmarks + for req in bm.requests.successful + ] + + prompt_token_buckets, _prompt_bucket_width = _Bucket.from_data(prompt_tokens, 1) + output_token_buckets, _output_bucket_width = _Bucket.from_data(output_tokens, 1) + + prompt_token_stats = DistributionSummary.from_values(prompt_tokens) + output_token_stats = DistributionSummary.from_values(output_tokens) + + min_start_time = benchmarks[0].start_time + all_req_times = [ + req.info.timings.request_start - min_start_time + for bm in benchmarks + for req in bm.requests.successful + if req.info.timings.request_start is not None + ] + + number_of_buckets = len(benchmarks) + request_buckets, bucket_width = _Bucket.from_data( + all_req_times, None, number_of_buckets + ) + + return { + "prompts": { + "samples": sample_prompts, + "token_distributions": { + "statistics": prompt_token_stats.model_dump() + if prompt_token_stats + else None, + "buckets": [b.model_dump() for b in prompt_token_buckets], + "bucket_width": 1, + }, + }, + "generations": { + "samples": sample_outputs, + "token_distributions": { + "statistics": output_token_stats.model_dump() + if output_token_stats + else None, + "buckets": [b.model_dump() for b in output_token_buckets], + "bucket_width": 1, + }, + }, + "requests_over_time": { + "requests_over_time": { + "buckets": [b.model_dump() for b in request_buckets], + "bucket_width": bucket_width, + }, + "num_benchmarks": number_of_buckets, + }, + "rate_type": rate_type, + "server": {"target": target}, + } + + +def _build_benchmarks(benchmarks: list[GenerativeBenchmark]) -> list[dict[str, Any]]: + """ + Build benchmark metrics data for UI display. + + Extracts key performance metrics from each benchmark including requests per + second, inter-token latency, time to first token, throughput, and request + latency. Formats distribution summaries for tabular display. + + :param benchmarks: List of completed benchmark results + :return: List of dictionaries with formatted benchmark metrics + """ + result = [] + for bm in benchmarks: + result.append( + { + "requests_per_second": bm.metrics.requests_per_second.successful.mean, + "itl": _TabularDistributionSummary.from_distribution_summary( + bm.metrics.inter_token_latency_ms.successful + ).model_dump(), + "ttft": _TabularDistributionSummary.from_distribution_summary( + bm.metrics.time_to_first_token_ms.successful + ).model_dump(), + "throughput": _TabularDistributionSummary.from_distribution_summary( + bm.metrics.output_tokens_per_second.successful + ).model_dump(), + "time_per_request": ( + _TabularDistributionSummary.from_distribution_summary( + bm.metrics.request_latency.successful + ).model_dump() + ), + } + ) + return result diff --git a/src/guidellm/benchmark/outputs/output.py b/src/guidellm/benchmark/outputs/output.py new file mode 100644 index 00000000..8eb021b0 --- /dev/null +++ b/src/guidellm/benchmark/outputs/output.py @@ -0,0 +1,158 @@ +""" +Base output interface for generative benchmarking results. + +This module defines the abstract base class for all benchmark output formatters in +the guidellm system. Output formatters transform benchmark reports into various file +formats (JSON, CSV, HTML, etc.) enabling flexible result persistence and analysis. +The module leverages a registry pattern for dynamic format resolution and supports +both direct instantiation and configuration-based initialization. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import Any + +from pydantic import BaseModel, ConfigDict + +from guidellm.benchmark.schemas import GenerativeBenchmarksReport +from guidellm.utils import RegistryMixin + +__all__ = ["GenerativeBenchmarkerOutput"] + + +class GenerativeBenchmarkerOutput( + BaseModel, RegistryMixin[type["GenerativeBenchmarkerOutput"]], ABC +): + """ + Abstract base for benchmark output formatters with registry support. + + Defines the interface for transforming benchmark reports into various output + formats. Subclasses implement specific formatters (JSON, CSV, HTML) that can be + registered and resolved dynamically. Supports flexible initialization from string + identifiers, file paths, or configuration dictionaries enabling declarative + output configuration in benchmark runs. + + Example: + :: + # Register and resolve output formats + outputs = GenerativeBenchmarkerOutput.resolve( + output_formats=["json", "csv"], + output_path="./results" + ) + + # Finalize outputs with benchmark report + for output in outputs.values(): + await output.finalize(report) + """ + + model_config = ConfigDict( + extra="ignore", + arbitrary_types_allowed=True, + validate_assignment=True, + from_attributes=True, + use_enum_values=True, + ) + + @classmethod + @abstractmethod + def validated_kwargs(cls, *args, **kwargs) -> dict[str, Any]: + """ + Validate and normalize initialization arguments for output formatter. + + Processes positional and keyword arguments into a validated parameter + dictionary suitable for formatter instantiation. Subclasses implement + format-specific validation logic handling their unique parameter patterns. + + :param args: Positional arguments for formatter configuration + :param kwargs: Keyword arguments for formatter configuration + :return: Validated dictionary of parameters for formatter creation + :raises NotImplementedError: Must be implemented by subclasses + """ + ... + + @classmethod + def resolve( + cls, + output_formats: ( + Sequence[str] + | Mapping[str, Any | dict[str, Any] | GenerativeBenchmarkerOutput] + | None + ), + output_path: str | Path | None, + ) -> dict[str, GenerativeBenchmarkerOutput]: + """ + Resolve output format specifications into formatter instances. + + Supports multiple input patterns: format identifiers (["json", "csv"]), + file paths (["results.json"]), format configurations ({"json": {"indent": 2}}), + or pre-instantiated formatters. Registered format types are resolved from the + registry and instantiated with validated parameters. + + :param output_formats: Format specifications as sequence of identifiers/paths, + mapping of format configurations, or None for no outputs + :param output_path: Default output directory path for all formatters + :return: Dictionary mapping format keys to instantiated formatter instances + :raises TypeError: If format specification type is invalid + :raises ValueError: If format resolution or validation fails + """ + resolved: dict[str, GenerativeBenchmarkerOutput] = {} + + if not output_formats: + return resolved + + if isinstance(output_formats, list | tuple): + # convert to dict for uniform processing + formats_list = output_formats + output_formats = {} + for output_format in formats_list: + # Check for registered type, if not, then assume it's a file path + if cls.is_registered(output_format): + output_formats[output_format] = {} + else: + path = Path(output_format) + format_type = path.suffix[1:].lower() + output_formats[format_type] = {"output_path": path} + + for key, val in output_formats.items(): # type: ignore[union-attr] + if isinstance(val, GenerativeBenchmarkerOutput): + resolved[key] = val + else: + output_class = cls.get_registered_object(key) + if output_class is None: + available_formats = ( + list(cls.registry.keys()) if cls.registry else [] + ) + raise ValueError( + f"Output format '{key}' is not registered. " + f"Available formats: {available_formats}" + ) + + kwargs: dict[str, Any] = {"output_path": output_path} + + if isinstance(val, dict): + kwargs.update(val) + kwargs = output_class.validated_kwargs(**kwargs) + else: + kwargs = output_class.validated_kwargs(val, **kwargs) + + resolved[key] = output_class(**kwargs) + + return resolved + + @abstractmethod + async def finalize(self, report: GenerativeBenchmarksReport) -> Any: + """ + Process and persist benchmark report in the formatter's output format. + + Transforms the provided benchmark report into the target format and writes + results to the configured output destination. Implementation details vary by + formatter type (file writing, API calls, etc.). + + :param report: Benchmark report containing results to format and output + :return: Format-specific output result (file path, response object, etc.) + :raises NotImplementedError: Must be implemented by subclasses + """ + ... diff --git a/src/guidellm/benchmark/outputs/serialized.py b/src/guidellm/benchmark/outputs/serialized.py new file mode 100644 index 00000000..52dc632a --- /dev/null +++ b/src/guidellm/benchmark/outputs/serialized.py @@ -0,0 +1,69 @@ +""" +Serialized output handler for generative benchmark reports. + +This module provides a serialized output implementation that saves benchmark reports +to JSON or YAML file formats. It extends the base GenerativeBenchmarkerOutput to +handle file-based persistence of benchmark results, supporting both directory and +explicit file path specifications for report serialization. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from pydantic import Field + +from guidellm.benchmark.outputs.output import GenerativeBenchmarkerOutput +from guidellm.benchmark.schemas import GenerativeBenchmarksReport + +__all__ = ["GenerativeBenchmarkerSerialized"] + + +@GenerativeBenchmarkerOutput.register(["json", "yaml"]) +class GenerativeBenchmarkerSerialized(GenerativeBenchmarkerOutput): + """ + Serialized output handler for benchmark reports in JSON or YAML formats. + + This output handler persists generative benchmark reports to the file system in + either JSON or YAML format. It supports flexible path specification, allowing + users to provide either a directory (where a default filename will be generated) + or an explicit file path for the serialized report output. + + Example: + :: + output = GenerativeBenchmarkerSerialized(output_path="/path/to/output.json") + result_path = await output.finalize(report) + """ + + output_path: Path = Field( + default_factory=lambda: Path.cwd(), + description="Directory or file path for saving the serialized report", + ) + + @classmethod + def validated_kwargs( + cls, output_path: str | Path | None, **_kwargs + ) -> dict[str, Any]: + """ + Validate and normalize output path keyword arguments. + + :param output_path: Directory or file path for serialization output + :param _kwargs: Additional keyword arguments (ignored) + :return: Dictionary of validated keyword arguments for class initialization + """ + validated: dict[str, Any] = {} + if output_path is not None: + validated["output_path"] = ( + Path(output_path) if not isinstance(output_path, Path) else output_path + ) + return validated + + async def finalize(self, report: GenerativeBenchmarksReport) -> Path: + """ + Serialize and save the benchmark report to the configured output path. + + :param report: The generative benchmarks report to serialize + :return: Path to the saved report file + """ + return report.save_file(self.output_path) diff --git a/src/guidellm/benchmark/profile.py b/src/guidellm/benchmark/profiles.py similarity index 80% rename from src/guidellm/benchmark/profile.py rename to src/guidellm/benchmark/profiles.py index 4b3f36fd..1cd01b97 100644 --- a/src/guidellm/benchmark/profile.py +++ b/src/guidellm/benchmark/profiles.py @@ -1,17 +1,19 @@ """ -Profile configurations for orchestrating multi-strategy benchmark execution. - -Provides configurable abstractions for coordinating sequential execution of -scheduling strategies during benchmarking workflows. Profiles automatically -generate strategies based on configuration parameters, manage runtime -constraints, and track completion state across the execution sequence. +Orchestrate multi-strategy benchmark execution through configurable profiles. + +Provides abstractions for coordinating sequential execution of scheduling strategies +during benchmarking workflows. Profiles automatically generate strategies based on +configuration parameters, manage runtime constraints, and track completion state +across execution sequences. Each profile type implements a specific execution pattern +(synchronous, concurrent, throughput-focused, rate-based async, or adaptive sweep) +that determines how benchmark requests are scheduled and executed. """ from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import Generator -from typing import TYPE_CHECKING, Any, ClassVar, Literal +from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal import numpy as np from pydantic import ( @@ -33,11 +35,10 @@ ConstraintInitializer, ConstraintsInitializerFactory, SchedulingStrategy, - StrategyType, SynchronousStrategy, ThroughputStrategy, ) -from guidellm.utils import PydanticClassRegistryMixin +from guidellm.schemas import PydanticClassRegistryMixin if TYPE_CHECKING: from guidellm.benchmark.schemas import Benchmark @@ -52,28 +53,36 @@ "ThroughputProfile", ] -ProfileType = Literal["synchronous", "concurrent", "throughput", "async", "sweep"] +ProfileType = Annotated[ + Literal["synchronous", "concurrent", "throughput", "async", "sweep"], + "Profile type identifiers for polymorphic deserialization", +] class Profile( - PydanticClassRegistryMixin["type[Profile]"], + PydanticClassRegistryMixin["Profile"], ABC, ): """ - Abstract base for coordinating multi-strategy benchmark execution. + Coordinate multi-strategy benchmark execution with automatic strategy generation. Manages sequential execution of scheduling strategies with automatic strategy generation, constraint management, and completion tracking. Subclasses define specific execution patterns like synchronous, concurrent, throughput-focused, rate-based async, or adaptive sweep profiles. - :cvar schema_discriminator: Field name used for polymorphic deserialization + :cvar schema_discriminator: Field name for polymorphic deserialization """ schema_discriminator: ClassVar[str] = "type_" @classmethod def __pydantic_schema_base_type__(cls) -> type[Profile]: + """ + Return base type for polymorphic validation hierarchy. + + :return: Base Profile class for schema validation + """ if cls.__name__ == "Profile": return cls @@ -88,7 +97,7 @@ def create( **kwargs: Any, ) -> Profile: """ - Factory method to create a profile instance based on type. + Create profile instances based on type identifier. :param rate_type: Profile type identifier to instantiate :param rate: Rate configuration for the profile strategy @@ -97,7 +106,10 @@ def create( :return: Configured profile instance for the specified type :raises ValueError: If rate_type is not registered """ - profile_class: type[Profile] = cls.get_registered_object(rate_type) + profile_class = cls.get_registered_object(rate_type) + if profile_class is None: + raise ValueError(f"Profile type '{rate_type}' is not registered") + resolved_kwargs = profile_class.resolve_args( rate_type=rate_type, rate=rate, random_seed=random_seed, **kwargs ) @@ -129,28 +141,31 @@ def resolve_args( ) completed_strategies: list[SchedulingStrategy] = Field( default_factory=list, - description="Strategies that have completed execution in this profile", + description="Strategies that completed execution in this profile", ) constraints: dict[str, Any | dict[str, Any] | ConstraintInitializer] | None = Field( default=None, description="Runtime constraints applied to strategy execution", ) + rampup_duration: NonNegativeFloat = Field( + default=0.0, + description=( + "Duration in seconds to ramp up the targeted scheduling rate, if applicable" + ), + ) @computed_field # type: ignore[misc] @property - def strategy_types(self) -> list[StrategyType]: + def strategy_types(self) -> list[str]: """ - :return: Strategy types executed or expected to execute in this profile + :return: Strategy types executed or to be executed in this profile """ return [strat.type_ for strat in self.completed_strategies] def strategies_generator( self, ) -> Generator[ - tuple[ - SchedulingStrategy | None, - dict[str, Any | dict[str, Any] | Constraint] | None, - ], + tuple[SchedulingStrategy, dict[str, Constraint] | None], Benchmark | None, None, ]: @@ -183,7 +198,7 @@ def next_strategy( prev_benchmark: Benchmark | None, ) -> SchedulingStrategy | None: """ - Generate the next strategy in the profile execution sequence. + Generate next strategy in the profile execution sequence. :param prev_strategy: Previously completed strategy instance :param prev_benchmark: Benchmark results from previous strategy execution @@ -196,9 +211,9 @@ def next_strategy_constraints( next_strategy: SchedulingStrategy | None, prev_strategy: SchedulingStrategy | None, prev_benchmark: Benchmark | None, - ) -> dict[str, Any | dict[str, Any] | Constraint] | None: + ) -> dict[str, Constraint] | None: """ - Generate constraints for the next strategy execution. + Generate constraints for next strategy execution. :param next_strategy: Strategy to be executed next :param prev_strategy: Previously completed strategy instance @@ -225,14 +240,16 @@ def _constraints_validator( return { key: ( - val - if not isinstance(val, ConstraintInitializer) - else ConstraintsInitializerFactory.deserialize(initializer_dict=val) + ConstraintsInitializerFactory.deserialize(initializer_dict=val) + if isinstance(val, dict) + and "type_" in val + and not isinstance(val, ConstraintInitializer) + else val ) for key, val in value.items() } - @field_serializer + @field_serializer("constraints") def _constraints_serializer( self, constraints: dict[str, Any | dict[str, Any] | ConstraintInitializer] | None, @@ -252,7 +269,12 @@ def _constraints_serializer( @Profile.register("synchronous") class SynchronousProfile(Profile): - """Single synchronous strategy execution profile.""" + """ + Execute single synchronous strategy for baseline performance metrics. + + Executes requests sequentially with one request at a time, establishing + baseline performance metrics without concurrent execution overhead. + """ type_: Literal["synchronous"] = "synchronous" # type: ignore[assignment] @@ -281,7 +303,7 @@ def resolve_args( return kwargs @property - def strategy_types(self) -> list[StrategyType]: + def strategy_types(self) -> list[str]: """ :return: Single synchronous strategy type """ @@ -293,7 +315,7 @@ def next_strategy( prev_benchmark: Benchmark | None, ) -> SynchronousStrategy | None: """ - Generate synchronous strategy or None if already completed. + Generate synchronous strategy for first execution only. :param prev_strategy: Previously completed strategy (unused) :param prev_benchmark: Benchmark results from previous execution (unused) @@ -308,19 +330,17 @@ def next_strategy( @Profile.register("concurrent") class ConcurrentProfile(Profile): - """Fixed-concurrency strategy execution profile with configurable stream counts.""" + """ + Execute strategies with fixed concurrency levels for performance testing. + + Executes requests with a fixed number of concurrent streams, useful for + testing system performance under specific concurrency levels. + """ type_: Literal["concurrent"] = "concurrent" # type: ignore[assignment] streams: list[PositiveInt] = Field( description="Concurrent stream counts for request scheduling", ) - startup_duration: NonNegativeFloat = Field( - default=0.0, - description=( - "Duration in seconds for distributing startup requests " - "before completion-based timing" - ), - ) @classmethod def resolve_args( @@ -346,7 +366,7 @@ def resolve_args( return kwargs @property - def strategy_types(self) -> list[StrategyType]: + def strategy_types(self) -> list[str]: """ :return: Concurrent strategy types for each configured stream count """ @@ -358,7 +378,7 @@ def next_strategy( prev_benchmark: Benchmark | None, ) -> ConcurrentStrategy | None: """ - Generate concurrent strategy for the next stream count. + Generate concurrent strategy for next stream count. :param prev_strategy: Previously completed strategy (unused) :param prev_benchmark: Benchmark results from previous execution (unused) @@ -371,14 +391,17 @@ def next_strategy( return ConcurrentStrategy( streams=self.streams[len(self.completed_strategies)], - startup_duration=self.startup_duration, + rampup_duration=self.rampup_duration, ) @Profile.register("throughput") class ThroughputProfile(Profile): """ - Maximum throughput strategy execution profile with optional concurrency limits. + Maximize system throughput with optional concurrency constraints. + + Maximizes system throughput by maintaining maximum concurrent requests, + optionally constrained by a concurrency limit. """ type_: Literal["throughput"] = "throughput" # type: ignore[assignment] @@ -386,13 +409,6 @@ class ThroughputProfile(Profile): default=None, description="Maximum concurrent requests to schedule", ) - startup_duration: NonNegativeFloat = Field( - default=0.0, - description=( - "Duration in seconds for distributing startup requests " - "before full throughput scheduling" - ), - ) @classmethod def resolve_args( @@ -419,7 +435,7 @@ def resolve_args( return kwargs @property - def strategy_types(self) -> list[StrategyType]: + def strategy_types(self) -> list[str]: """ :return: Single throughput strategy type """ @@ -431,7 +447,7 @@ def next_strategy( prev_benchmark: Benchmark | None, ) -> ThroughputStrategy | None: """ - Generate throughput strategy or None if already completed. + Generate throughput strategy for first execution only. :param prev_strategy: Previously completed strategy (unused) :param prev_benchmark: Benchmark results from previous execution (unused) @@ -442,28 +458,25 @@ def next_strategy( return None return ThroughputStrategy( - max_concurrency=self.max_concurrency, - startup_duration=self.startup_duration, + max_concurrency=self.max_concurrency, rampup_duration=self.rampup_duration ) @Profile.register(["async", "constant", "poisson"]) class AsyncProfile(Profile): - """Rate-based asynchronous strategy execution profile with configurable patterns.""" + """ + Schedule requests at specified rates using constant or Poisson patterns. + + Schedules requests at specified rates using either constant interval or + Poisson distribution patterns for realistic load simulation. + """ type_: Literal["async", "constant", "poisson"] = "async" # type: ignore[assignment] strategy_type: Literal["constant", "poisson"] = Field( - description="Asynchronous strategy pattern type to use", + description="Asynchronous strategy pattern type", ) rate: list[PositiveFloat] = Field( - description="Request scheduling rate in requests per second", - ) - startup_duration: NonNegativeFloat = Field( - default=0.0, - description=( - "Duration in seconds for distributing startup requests " - "to converge quickly to desired rate" - ), + description="Request scheduling rates in requests per second", ) max_concurrency: PositiveInt | None = Field( default=None, @@ -510,7 +523,7 @@ def resolve_args( return kwargs @property - def strategy_types(self) -> list[StrategyType]: + def strategy_types(self) -> list[str]: """ :return: Async strategy types for each configured rate """ @@ -523,7 +536,7 @@ def next_strategy( prev_benchmark: Benchmark | None, ) -> AsyncConstantStrategy | AsyncPoissonStrategy | None: """ - Generate async strategy for the next configured rate. + Generate async strategy for next configured rate. :param prev_strategy: Previously completed strategy (unused) :param prev_benchmark: Benchmark results from previous execution (unused) @@ -540,14 +553,11 @@ def next_strategy( if self.strategy_type == "constant": return AsyncConstantStrategy( - rate=current_rate, - startup_duration=self.startup_duration, - max_concurrency=self.max_concurrency, + rate=current_rate, max_concurrency=self.max_concurrency ) elif self.strategy_type == "poisson": return AsyncPoissonStrategy( rate=current_rate, - startup_duration=self.startup_duration, max_concurrency=self.max_concurrency, random_seed=self.random_seed, ) @@ -557,7 +567,13 @@ def next_strategy( @Profile.register("sweep") class SweepProfile(Profile): - """Adaptive multi-strategy sweep execution profile with rate discovery.""" + """ + Discover optimal rate range through adaptive multi-strategy execution. + + Automatically discovers optimal rate range by executing synchronous and + throughput strategies first, then interpolating rates for async strategies + to comprehensively sweep the performance space. + """ type_: Literal["sweep"] = "sweep" # type: ignore[assignment] sweep_size: int = Field( @@ -565,13 +581,6 @@ class SweepProfile(Profile): ge=2, ) strategy_type: Literal["constant", "poisson"] = "constant" - startup_duration: NonNegativeFloat = Field( - default=0.0, - description=( - "Duration in seconds for distributing startup requests " - "to converge quickly to desired rate" - ), - ) max_concurrency: PositiveInt | None = Field( default=None, description="Maximum concurrent requests to schedule", @@ -622,7 +631,7 @@ def resolve_args( return kwargs @property - def strategy_types(self) -> list[StrategyType]: + def strategy_types(self) -> list[str]: """ :return: Strategy types for the complete sweep sequence """ @@ -637,12 +646,12 @@ def next_strategy( ) -> ( AsyncConstantStrategy | AsyncPoissonStrategy - | SynchronousProfile - | ThroughputProfile + | SynchronousStrategy + | ThroughputStrategy | None ): """ - Generate the next strategy in the adaptive sweep sequence. + Generate next strategy in adaptive sweep sequence. Executes synchronous and throughput strategies first to measure baseline rates, then generates interpolated rates for async strategies. @@ -656,19 +665,15 @@ def next_strategy( return SynchronousStrategy() if prev_strategy.type_ == "synchronous": - self.synchronous_rate = prev_benchmark.get_request_metrics_sample()[ - "request_throughput" - ] + self.synchronous_rate = prev_benchmark.request_throughput.successful.mean return ThroughputStrategy( max_concurrency=self.max_concurrency, - startup_duration=self.startup_duration, + rampup_duration=self.rampup_duration, ) if prev_strategy.type_ == "throughput": - self.throughput_rate = prev_benchmark.get_request_metrics_sample()[ - "request_throughput" - ] + self.throughput_rate = prev_benchmark.request_throughput.successful.mean if self.synchronous_rate <= 0 and self.throughput_rate <= 0: raise RuntimeError( "Invalid rates in sweep; aborting. " @@ -695,13 +700,11 @@ def next_strategy( if self.strategy_type == "constant": return AsyncConstantStrategy( rate=self.measured_rates[next_rate_index], - startup_duration=self.startup_duration, max_concurrency=self.max_concurrency, ) elif self.strategy_type == "poisson": return AsyncPoissonStrategy( rate=self.measured_rates[next_rate_index], - startup_duration=self.startup_duration, max_concurrency=self.max_concurrency, random_seed=self.random_seed, ) diff --git a/src/guidellm/benchmark/progress.py b/src/guidellm/benchmark/progress.py index 558def67..3713779f 100644 --- a/src/guidellm/benchmark/progress.py +++ b/src/guidellm/benchmark/progress.py @@ -1,17 +1,11 @@ """ -Benchmark progress tracking and console display abstractions. +Progress tracking and console display for benchmark execution monitoring. -Provides progress tracking interfaces and implementations for monitoring benchmark -execution, displaying real-time statistics, and managing UI updates during -generative benchmarking operations. - -Classes: - BenchmarkerProgress: Abstract base for benchmark progress tracking. - BenchmarkerProgressGroup: Composite progress handler for multiple instances. - GenerativeConsoleBenchmarkerProgress: Console-based progress display. - -Type Variables: - BenchmarkT: Generic benchmark object type. +Provides abstract interfaces and concrete implementations for tracking benchmark +progress during execution. The module enables real-time display of benchmark +statistics, metrics, and execution state through console-based UI components. +Primary use cases include monitoring generative benchmark runs with detailed +request/token statistics and scheduler state updates. """ from __future__ import annotations @@ -35,95 +29,94 @@ TimeRemainingColumn, ) -from guidellm.benchmark.profile import Profile +from guidellm.benchmark.profiles import Profile from guidellm.benchmark.schemas import ( + BenchmarkAccumulatorT, BenchmarkT, - EstimatedBenchmarkState, GenerativeBenchmark, + GenerativeBenchmarkAccumulator, ) -from guidellm.scheduler import SchedulerState, SchedulingStrategy, StrategyType +from guidellm.scheduler import SchedulerState, SchedulingStrategy from guidellm.utils import Colors, format_value_display __all__ = ["BenchmarkerProgress", "GenerativeConsoleBenchmarkerProgress"] -class BenchmarkerProgress(Generic[BenchmarkT], ABC): +class BenchmarkerProgress(Generic[BenchmarkAccumulatorT, BenchmarkT], ABC): """ - Abstract base class for tracking and displaying benchmark progress. + Abstract interface for tracking and displaying benchmark execution progress. - Provides lifecycle hooks for monitoring benchmark execution stages including - initialization, start, updates, completion, and finalization. Supports - enable/disable functionality for conditional progress tracking. + Provides lifecycle hooks for monitoring benchmark stages including initialization, + execution start, progress updates, completion, and finalization. Implementations + handle display updates, progress tracking, and resource management for benchmark + monitoring. """ def __init__(self): - """ - Initialize progress tracker. - - :param enabled: Whether to enable progress tracking and display. - """ - self.profile: Profile = None - self.current_strategy: SchedulingStrategy = None + """Initialize progress tracker with default state.""" + self.profile: Profile | None = None + self.current_strategy: SchedulingStrategy | None = None @abstractmethod async def on_initialize(self, profile: Profile): """ - Initialize progress tracking for benchmark profile. + Initialize progress tracking for the given benchmark profile. - :param profile: Benchmark profile configuration. + :param profile: Benchmark profile configuration defining execution parameters """ @abstractmethod async def on_benchmark_start(self, strategy: SchedulingStrategy): """ - Handle start of new benchmark strategy execution. + Handle benchmark strategy execution start event. - :param strategy: Scheduling strategy being executed. + :param strategy: Scheduling strategy configuration being executed """ @abstractmethod async def on_benchmark_update( - self, estimated_state: EstimatedBenchmarkState, scheduler_state: SchedulerState + self, accumulator: BenchmarkAccumulatorT, scheduler_state: SchedulerState ): """ - Handle benchmark execution progress update. + Handle benchmark execution progress update with current metrics. - :param estimated_state: Current benchmark metrics and statistics. - :param scheduler_state: Current scheduler execution state. + :param accumulator: Current accumulated benchmark metrics and statistics + :param scheduler_state: Current scheduler execution state and counters """ @abstractmethod async def on_benchmark_complete(self, benchmark: BenchmarkT): """ - Handle completion of benchmark strategy execution. + Handle benchmark strategy execution completion event. - :param benchmark: Completed benchmark results. + :param benchmark: Completed benchmark results with final metrics """ @abstractmethod async def on_finalize(self): - """Finalize progress tracking and cleanup resources.""" + """Finalize progress tracking and release associated resources.""" class GenerativeConsoleBenchmarkerProgress( - BenchmarkerProgress[GenerativeBenchmark], Live + BenchmarkerProgress[GenerativeBenchmarkAccumulator, GenerativeBenchmark], Live ): """ - Console-based progress display for generative benchmarks. + Console-based real-time progress display for generative benchmarks. - Provides real-time visual progress tracking using Rich library components, - displaying benchmark execution statistics, timing information, and progress - bars in a structured console interface. + Renders live benchmark execution statistics using Rich library components with + structured progress bars, timing information, request/token metrics, and optional + scheduler statistics. Updates refresh automatically during benchmark execution. + + :cvar display_scheduler_stats: Whether to include scheduler statistics in display """ def __init__(self, display_scheduler_stats: bool = False): """ - Initialize console progress display. + Initialize console progress display with rendering configuration. - :param enabled: Whether to enable progress tracking and display. - :param display_scheduler_stats: Whether to display scheduler statistics. + :param display_scheduler_stats: Whether to display scheduler timing statistics """ - BenchmarkerProgress.__init__(self) + super().__init__() Live.__init__( self, refresh_per_second=4, @@ -132,15 +125,15 @@ def __init__(self, display_scheduler_stats: bool = False): redirect_stderr=True, ) self.display_scheduler_stats: bool = display_scheduler_stats - self.run_progress: Progress = None - self.run_progress_task: TaskID = None - self.tasks_progress: _GenerativeProgressTasks = None + self.run_progress: Progress | None = None + self.run_progress_task: TaskID | None = None + self.tasks_progress: _GenerativeProgressTasks | None = None async def on_initialize(self, profile: Profile): """ - Initialize console display components and start rendering. + Initialize console display components and begin live rendering. - :param profile: Benchmark profile configuration. + :param profile: Benchmark profile configuration defining execution parameters """ self.tasks_progress = _GenerativeProgressTasks( profile=profile, display_scheduler_stats=self.display_scheduler_stats @@ -179,41 +172,46 @@ async def on_initialize(self, profile: Profile): async def on_benchmark_start(self, strategy: SchedulingStrategy): """ - Update display for new benchmark strategy start. + Update display for benchmark strategy execution start. - :param strategy: Scheduling strategy being executed. + :param strategy: Scheduling strategy configuration being executed """ - self.tasks_progress.start_benchmark(strategy) - self._sync_run_progress() + if self.tasks_progress is not None: + self.tasks_progress.start_benchmark(strategy) + self._sync_run_progress() async def on_benchmark_update( self, - aggregator_update: EstimatedBenchmarkState | None, + accumulator: GenerativeBenchmarkAccumulator, scheduler_state: SchedulerState, ): """ - Update display with current benchmark progress. + Update display with current benchmark progress and metrics. - :param aggregator_update: Current benchmark metrics and statistics. - :param scheduler_state: Current scheduler execution state. + :param accumulator: Current accumulated benchmark metrics and statistics + :param scheduler_state: Current scheduler execution state and counters """ - self.tasks_progress.update_benchmark(aggregator_update, scheduler_state) - self._sync_run_progress() + if self.tasks_progress is not None: + self.tasks_progress.update_benchmark(accumulator, scheduler_state) + self._sync_run_progress() async def on_benchmark_complete(self, benchmark: GenerativeBenchmark): """ - Update display for completed benchmark. + Update display for completed benchmark strategy. - :param benchmark: Completed benchmark results. + :param benchmark: Completed benchmark results with final metrics """ - self.tasks_progress.complete_benchmark(benchmark) - self._sync_run_progress() + if self.tasks_progress is not None: + self.tasks_progress.complete_benchmark(benchmark) + self._sync_run_progress() async def on_finalize(self): - """Stop display rendering and cleanup resources.""" - self.tasks_progress.finalize() - self._sync_run_progress() - self.run_progress.stop_task(self.run_progress_task) + """Stop display rendering and release resources.""" + if self.tasks_progress is not None: + self.tasks_progress.finalize() + self._sync_run_progress() + if self.run_progress is not None and self.run_progress_task is not None: + self.run_progress.stop_task(self.run_progress_task) self.stop() self.run_progress = None self.run_progress_task = None @@ -221,13 +219,18 @@ async def on_finalize(self): def _sync_run_progress(self): """Synchronize overall progress display with task progress.""" - self.run_progress.update( - self.run_progress_task, - total=self.tasks_progress.steps_total, - completed=self.tasks_progress.steps_progress, - completed_benchmarks=self.tasks_progress.tasks_progress, - total_benchmarks=self.tasks_progress.tasks_total, - ) + if ( + self.run_progress is not None + and self.run_progress_task is not None + and self.tasks_progress is not None + ): + self.run_progress.update( + self.run_progress_task, + total=self.tasks_progress.steps_total, + completed=self.tasks_progress.steps_progress, + completed_benchmarks=self.tasks_progress.tasks_progress, + total_benchmarks=self.tasks_progress.tasks_total, + ) # Scaling factor for progress calculations to provide granular progress updates @@ -283,7 +286,7 @@ def steps_progress(self) -> int: ) progress_total = self.current_index + (progress_current_task or 0) - return progress_total * _PROGRESS_SCALE + return int(progress_total * _PROGRESS_SCALE) def start_benchmark(self, strategy: SchedulingStrategy): self.current_index += 1 @@ -294,32 +297,36 @@ def start_benchmark(self, strategy: SchedulingStrategy): task_state.task_id = task_id self.benchmark_task_states.append(task_state) - self.benchmark_task_states[self.current_index].start(strategy) - self.update( - self.benchmark_task_states[self.current_index].task_id, - start=True, - **self.benchmark_task_states[self.current_index].current, - ) + current_state = self.benchmark_task_states[self.current_index] + current_state.start(strategy) + if current_state.task_id is not None: + self.update( + current_state.task_id, + start=True, + **current_state.current, + ) def update_benchmark( self, - aggregator_update: EstimatedBenchmarkState, + accumulator: GenerativeBenchmarkAccumulator, scheduler_state: SchedulerState, ): - self.benchmark_task_states[self.current_index].update( - aggregator_update, scheduler_state - ) - self.update( - self.benchmark_task_states[self.current_index].task_id, - **self.benchmark_task_states[self.current_index].current, - ) + current_state = self.benchmark_task_states[self.current_index] + current_state.update(accumulator, scheduler_state) + if current_state.task_id is not None: + self.update( + current_state.task_id, + **current_state.current, + ) def complete_benchmark(self, benchmark: GenerativeBenchmark): - self.benchmark_task_states[self.current_index].complete(benchmark) - self.update( - self.benchmark_task_states[self.current_index].task_id, - **self.benchmark_task_states[self.current_index].current, - ) + current_state = self.benchmark_task_states[self.current_index] + current_state.complete(benchmark) + if current_state.task_id is not None: + self.update( + current_state.task_id, + **current_state.current, + ) def finalize(self): self.stop() @@ -327,29 +334,29 @@ def finalize(self): @dataclass class _GenerativeProgressTaskState: - strategy_type: StrategyType - task_id: TaskID = None + strategy_type: str + task_id: TaskID | None = None strategy: SchedulingStrategy | None = None benchmark_status: Literal[ - "pending", "in_warmup", "in_progress", "in_cooldown", "completed" + "pending", "warmup", "active", "cooldown", "completed" ] = "pending" progress: float | None = None start_time: float = -1.0 successful_requests: int = 0 cancelled_requests: int = 0 errored_requests: int = 0 - request_concurrency: int = 0 - requests_per_second: float = 0 - request_latency: float = 0 - output_tokens: int = 0 - output_tokens_rate: float = 0 - prompt_tokens: int = 0 - total_tokens_rate: float = 0 - time_to_first_token: float = 0 - inter_token_latency: float = 0 - queued_time: float = 0 - request_targeted_start_delay: float = 0 - scheduler_overheads_time: float = 0 + request_concurrency: float = 0.0 + requests_per_second: float = 0.0 + request_latency: float = 0.0 + output_tokens: float = 0 + output_tokens_rate: float = 0.0 + prompt_tokens: float = 0 + total_tokens_rate: float = 0.0 + time_to_first_token: float = 0.0 + inter_token_latency: float = 0.0 + queued_time: float = 0.0 + request_targeted_start_delay: float = 0.0 + scheduler_overheads_time: float = 0.0 @property def current(self) -> dict[str, Any]: @@ -367,12 +374,12 @@ def current(self) -> dict[str, Any]: @property def completed(self) -> float: if self.benchmark_status == "pending": - return 0 + return 0.0 if self.benchmark_status == "completed": - return _PROGRESS_SCALE + return float(_PROGRESS_SCALE) - return self.progress * _PROGRESS_SCALE if self.progress is not None else None + return self.progress * _PROGRESS_SCALE if self.progress is not None else 0.0 @property def total(self) -> float: @@ -387,13 +394,13 @@ def formatted_start_time(self) -> str: @property def formatted_progress_status(self) -> str: - if self.benchmark_status == "in_warmup": + if self.benchmark_status == "warmup": status = "warmup" color = Colors.progress - elif self.benchmark_status == "in_progress": + elif self.benchmark_status == "active": status = "running" color = Colors.progress - elif self.benchmark_status == "in_cooldown": + elif self.benchmark_status == "cooldown": status = "cooldown" color = Colors.progress elif self.benchmark_status == "completed": @@ -560,7 +567,7 @@ def start(self, strategy: SchedulingStrategy): def update( self, - estimated_state: EstimatedBenchmarkState, + accumulator: GenerativeBenchmarkAccumulator, scheduler_state: SchedulerState, ): self.progress = ( @@ -569,76 +576,40 @@ def update( else 0.0 ) self._update_processing_states( - benchmark_status=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_state_group, - key="status", - default=None, - ), - start_time=scheduler_state.start_time, + benchmark_status=self._map_status(accumulator.timings.status), + start_time=accumulator.timings.measure_start, successful_requests=scheduler_state.successful_requests, cancelled_requests=scheduler_state.cancelled_requests, errored_requests=scheduler_state.errored_requests, ) self._update_request_stats( - request_concurrency=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="concurrency_requests", - ), - requests_per_second=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_requests_per_second", - ), - request_latency=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_request_latency", - ), + request_concurrency=accumulator.concurrency_metric.time_weighted_mean, + requests_per_second=accumulator.completed_metrics.requests.rate_per_second, + request_latency=accumulator.completed_metrics.request_latency.mean, ) self._update_token_stats( - output_tokens=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_output_tokens_total", - ), - output_tokens_rate=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_output_tokens", - ), - prompt_tokens=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_input_tokens_total", - ), - total_tokens_rate=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_total_tokens", - ), - time_to_first_token=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_time_to_first_token", - ), - inter_token_latency=estimated_state.get_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="completed_inter_token_latency", - ), + output_tokens=accumulator.completed_metrics.total_tokens.mean, + output_tokens_rate=accumulator.completed_metrics.output_tokens.rate_per_second, + prompt_tokens=accumulator.completed_metrics.input_tokens.mean, + total_tokens_rate=accumulator.completed_metrics.total_tokens.rate_per_second, + time_to_first_token=accumulator.completed_metrics.time_to_first_token_ms.mean, + inter_token_latency=accumulator.completed_metrics.inter_token_latency_ms.mean, + converted=True, + ) + self._update_system_stats( + request_targeted_start_delay=accumulator.scheduler_metrics.request_targeted_start_delay.mean, + queued_time=accumulator.scheduler_metrics.queued_time.mean, + scheduler_overheads_time=accumulator.scheduler_metrics.resolve_end_delay.mean, + converted=False, ) - if estimated_state.get("updated_scheduler_stats"): - self._update_system_stats( - request_targeted_start_delay=estimated_state.get_metric( - group=EstimatedBenchmarkState.scheduler_state_group, - key="request_targeted_start_delay", - ), - queued_time=estimated_state.get_metric( - group=EstimatedBenchmarkState.scheduler_state_group, - key="queued_time", - ), - scheduler_overheads_time=0.0, # Need to add up metrics here - ) def complete(self, benchmark: GenerativeBenchmark): self._update_processing_states( benchmark_status="completed", start_time=benchmark.start_time, - successful_requests=benchmark.request_totals.successful, - cancelled_requests=benchmark.request_totals.incomplete, - errored_requests=benchmark.request_totals.errored, + successful_requests=benchmark.metrics.request_totals.successful, + cancelled_requests=benchmark.metrics.request_totals.incomplete, + errored_requests=benchmark.metrics.request_totals.errored, ) self._update_request_stats( request_concurrency=benchmark.metrics.request_concurrency.successful.mean, @@ -659,11 +630,19 @@ def complete(self, benchmark: GenerativeBenchmark): converted=True, ) + @staticmethod + def _map_status( + status: Literal["pending", "warmup", "active", "cooldown", "completed"], + ) -> Literal["pending", "warmup", "active", "cooldown", "completed"]: + """Map accumulator status to internal progress status representation.""" + return status + def _update_processing_states( self, benchmark_status: Literal[ - "pending", "in_warmup", "in_progress", "in_cooldown", "completed" - ], + "pending", "warmup", "active", "cooldown", "completed" + ] + | None = None, start_time: float | None = None, successful_requests: int | None = None, cancelled_requests: int | None = None, @@ -682,7 +661,7 @@ def _update_processing_states( def _update_request_stats( self, - request_concurrency: int | None = None, + request_concurrency: float | None = None, requests_per_second: float | None = None, request_latency: float | None = None, ): @@ -695,9 +674,9 @@ def _update_request_stats( def _update_token_stats( self, - output_tokens: int | None = None, + output_tokens: float | None = None, output_tokens_rate: float | None = None, - prompt_tokens: int | None = None, + prompt_tokens: float | None = None, total_tokens_rate: float | None = None, time_to_first_token: float | None = None, inter_token_latency: float | None = None, diff --git a/src/guidellm/benchmark/scenarios/chat.json b/src/guidellm/benchmark/scenarios/chat.json index 58fd18e2..a4137147 100644 --- a/src/guidellm/benchmark/scenarios/chat.json +++ b/src/guidellm/benchmark/scenarios/chat.json @@ -3,4 +3,4 @@ "data": [ "prompt_tokens=512,prompt_tokens_stdev=128,prompt_tokens_min=1,prompt_tokens_max=1024,output_tokens=256,output_tokens_stdev=64,output_tokens_min=1,output_tokens_max=1024" ] -} \ No newline at end of file +} diff --git a/src/guidellm/benchmark/scenarios/rag.json b/src/guidellm/benchmark/scenarios/rag.json index ea38d76e..0a82e9e9 100644 --- a/src/guidellm/benchmark/scenarios/rag.json +++ b/src/guidellm/benchmark/scenarios/rag.json @@ -3,4 +3,4 @@ "data": [ "prompt_tokens=4096,prompt_tokens_stdev=512,prompt_tokens_min=2048,prompt_tokens_max=6144,output_tokens=512,output_tokens_stdev=128,output_tokens_min=1,output_tokens_max=1024" ] -} \ No newline at end of file +} diff --git a/src/guidellm/benchmark/schemas.py b/src/guidellm/benchmark/schemas.py deleted file mode 100644 index b2fd15f5..00000000 --- a/src/guidellm/benchmark/schemas.py +++ /dev/null @@ -1,2128 +0,0 @@ -""" -Benchmark data models and metrics for generative AI performance measurement. - -Provides comprehensive data structures for capturing, storing, and analyzing -benchmark results from scheduler-driven generative AI workload executions. -Core abstractions include base benchmark interfaces, generative-specific -metrics with token/latency distributions, request-level statistics tracking, -and multi-benchmark reporting capabilities. These models enable detailed -performance analysis including throughput, latency, concurrency patterns, and -domain-specific metrics for text, image, video, and audio generation tasks. -""" - -from __future__ import annotations - -import inspect -import json -import random -import time -import uuid -from abc import ABC, abstractmethod -from collections.abc import Callable, Iterable -from pathlib import Path -from typing import Any, ClassVar, Literal, TypeVar, cast - -import yaml -from pydantic import ( - AliasChoices, - AliasGenerator, - ConfigDict, - Field, - ValidationError, - ValidatorFunctionWrapHandler, - computed_field, - field_validator, - model_serializer, -) -from torch.utils.data import Sampler -from transformers import PreTrainedTokenizerBase - -from guidellm.backends import Backend, BackendType -from guidellm.benchmark.profile import Profile, ProfileType -from guidellm.benchmark.scenarios import get_builtin_scenarios -from guidellm.data import DatasetPreprocessor -from guidellm.scheduler import ( - BackendInterface, - Environment, - SchedulerState, - SchedulingStrategy, - StrategyType, -) -from guidellm.schemas import ( - GenerationRequest, - GenerationResponse, - GenerativeRequestStats, - RequestInfo, - UsageMetrics, -) -from guidellm.utils import ( - InfoMixin, - StandardBaseDict, - StandardBaseModel, - StatusBreakdown, - StatusDistributionSummary, -) - -__all__ = [ - "Benchmark", - "BenchmarkGenerativeTextArgs", - "BenchmarkSchedulerStats", - "BenchmarkT", - "BenchmarkerArgs", - "BenchmarkerDict", - "EstimatedBenchmarkState", - "GenerativeAudioMetricsSummary", - "GenerativeBenchmark", - "GenerativeBenchmarksReport", - "GenerativeImageMetricsSummary", - "GenerativeMetrics", - "GenerativeMetricsSummary", - "GenerativeTextMetricsSummary", - "GenerativeVideoMetricsSummary", - "SchedulerDict", -] - - -class EstimatedBenchmarkState(dict[str, Any]): - """ - Accumulator for real-time benchmark metrics during scheduler execution. - - Tracks incremental metrics, running averages, and time-based statistics as - requests are processed. Maintains grouped metrics for benchmark state, - benchmark-level metrics, and scheduler-level metrics with support for - average, rate, and time-averaged metric calculations. - - :cvar benchmark_state_group: Metric group key for benchmark state tracking - :cvar benchmark_metrics_group: Metric group key for benchmark-level metrics - :cvar scheduler_state_group: Metric group key for scheduler-level metrics - """ - - benchmark_state_group: ClassVar[Literal["benchmark_state"]] = "benchmark_state" - benchmark_metrics_group: ClassVar[Literal["benchmark_metrics"]] = ( - "benchmark_metrics" - ) - scheduler_state_group: ClassVar[Literal["scheduler_state"]] = "scheduler_state" - - def get_metric( - self, - group: str, - key: str, - default: int | float | None = None, - ) -> int | float | None: - """ - Retrieve a grouped metric value by group and key. - - :param group: Metric group identifier - :param key: Metric key within the group - :param default: Value returned if metric doesn't exist - :return: The metric value or default if not found - """ - return self.get(f"{group}_{key}", default) - - def set_metric( - self, - group: str, - key: str, - value: bool | int | float | None, - start_val: bool | int | float | None = None, - ) -> bool | int | float | None: - """ - Set a grouped metric value, optionally adjusting by a starting value. - - :param group: Metric group identifier - :param key: Metric key within the group - :param value: Metric value to set - :param start_val: Optional starting value to subtract from the metric value - :return: The adjusted metric value or None if value is None - """ - if value is None: - return None - - if start_val is not None: - value -= start_val - self[f"{group}_{key}"] = value - - return value - - def add_avg_metric( - self, - group: str, - key: str, - value: bool | int | float | None, - start_val: bool | int | float | None = 0.0, - count: int | None = 1, - ): - """ - Add a value to a running average metric calculation. - - :param group: Metric group identifier - :param key: Metric key within the group - :param value: Value to add to the average - :param start_val: Optional starting value to subtract before adding - :param count: Number of observations this value represents - """ - if value is None or count is None: - return - - if start_val is not None: - value -= start_val - - total_key = f"{group}_{key}_total" - count_key = f"{group}_{key}_count" - self[total_key] = self.get(total_key, 0) + value - self[count_key] = self.get(count_key, 0) + count - - average = self[total_key] / self[count_key] if self[count_key] > 0 else 0.0 - self.set_metric( - group=group, - key=key, - value=average, - ) - - def add_avg_rate_metric( - self, - group: str, - key: str, - value: bool | int | float | None, - start_val: bool | int | float | None = 0.0, - start_time: float | None = None, - end_time: float | None = None, - numerator_type: Literal["avg", "total", "count"] = "total", - ): - """ - Add a value to a rate-based average metric calculation. - - :param group: Metric group identifier - :param key: Metric key within the group - :param value: Value to add to the average - :param start_val: Optional starting value to subtract before adding - :param start_time: Start time for rate calculation, defaults to current time - :param end_time: End time for rate calculation, defaults to current time - :param numerator_type: Type of numerator for rate calculation - """ - if value is None: - return - - self.add_avg_metric( - group=group, - key=key, - value=value, - start_val=start_val, - ) - start_time_key = f"{group}_{key}_start_time" - if self.get(start_time_key) is None: - if start_time is None: - start_time = time.time() - self[start_time_key] = start_time - else: - self[start_time_key] = start_time or self[start_time_key] - - end_time = end_time or time.time() - elapsed_time = end_time - self[start_time_key] - - if elapsed_time > 0: - numerator_key = ( - f"{group}_{key}_{numerator_type}" - if numerator_type != "avg" - else f"{group}_{key}" - ) - rate = self[numerator_key] / elapsed_time - self.set_metric( - group=group, - key=f"{key}_per_second", - value=rate, - ) - - def add_time_averaged_metric( - self, - group: str, - key: str, - value: bool | int | float | None, - recorded_time: float | None = None, - ): - """ - Add a value to a time-weighted average metric calculation. - - :param group: Metric group identifier - :param key: Metric key within the group - :param value: Value to add to the time-weighted average - :param recorded_time: Time of the observation, defaults to current time - """ - if value is None: - return - - if recorded_time is None: - recorded_time = time.time() - - time_avg_numerator_key = f"{group}_{key}_time_avg_numerator" - time_avg_denominator_key = f"{group}_{key}_time_avg_denominator" - last_recorded_time_key = f"{group}_{key}_last_recorded_time" - last_recorded_value_key = f"{group}_{key}_last_recorded_value" - - if last_recorded_time_key not in self: - self[last_recorded_time_key] = recorded_time - self[last_recorded_value_key] = value - self[time_avg_numerator_key] = value - self[time_avg_denominator_key] = 0.0 - else: - time_delta = recorded_time - self[last_recorded_time_key] - self[time_avg_numerator_key] += self[last_recorded_value_key] * time_delta - self[time_avg_denominator_key] += time_delta - self[last_recorded_time_key] = recorded_time - self[last_recorded_value_key] = value - - if self[time_avg_denominator_key] > 0: - average = self[time_avg_numerator_key] / self[time_avg_denominator_key] - else: - average = value - - self.set_metric( - group=group, - key=key, - value=average, - ) - - -class BenchmarkerArgs(StandardBaseDict): - """ - Configuration parameters for benchmark execution and request sampling. - - Defines run identification, request sampling strategy, warmup/cooldown phases, - and metric preferences for benchmark executions. Provides methods to determine - whether a request falls within warmup or cooldown periods based on time, - request count, or percentage-based thresholds. - """ - - run_id: str = Field( - default_factory=lambda: str(uuid.uuid4()), - description="Unique identifier for the benchmark run", - ) - run_index: int = Field(default=0, description="Index of the benchmark run") - sample_requests: int | None = Field( - default=20, - description=( - "Number of requests to sample and keep in the final benchmark for metrics" - ), - ) - warmup: int | float | None = Field( - default=None, description="Warmup time before benchmarking starts" - ) - cooldown: int | float | None = Field( - default=None, description="Cooldown time after benchmarking ends" - ) - prefer_response_metrics: bool = Field( - default=True, - description="Whether to prefer response metrics over request metrics", - ) - - def is_in_warmup( - self, request_info: RequestInfo, scheduler_state: SchedulerState - ) -> bool: - """ - Check if a request is in the warmup phase. - - :param request_info: Information about the current request - :param scheduler_state: Current state of the scheduler - :return: True if the request is in warmup phase, False otherwise - """ - if self.warmup is not None and 0 < self.warmup < 1: - # Percentage-based warmup - return ( - scheduler_state.remaining_fraction is not None - and scheduler_state.remaining_fraction > (1 - self.warmup) - ) - - if self.warmup is not None and self.warmup > 1: - # Count/time-based warmup - if scheduler_state.processed_requests < self.warmup: - return True - - current_time = request_info.timings.targeted_start - return ( - current_time is not None - and (current_time - scheduler_state.start_time) < self.warmup - ) - - return False - - def is_in_cooldown( - self, request_info: RequestInfo, scheduler_state: SchedulerState - ) -> bool: - """ - Check if a request is in the cooldown phase. - - :param request_info: Information about the current request - :param scheduler_state: Current state of the scheduler - :return: True if the request is in cooldown phase, False otherwise - """ - if self.cooldown is not None and 0 < self.cooldown < 1: - # Percentage-based cooldown - return ( - scheduler_state.remaining_fraction is not None - and scheduler_state.remaining_fraction < self.cooldown - ) - - if self.cooldown is not None and self.cooldown > 1: - # Count/time-based cooldown - if ( - scheduler_state.remaining_requests is not None - and scheduler_state.remaining_requests <= self.cooldown - ): - return True - - current_time = ( - request_info.timings.resolve_end or request_info.timings.targeted_start - ) - return ( - current_time is not None - and scheduler_state.remaining_duration is not None - and scheduler_state.remaining_duration < self.cooldown - ) - - return False - - -class Benchmark(ABC): - """ - Abstract base interface for benchmark result implementations. - - Defines the contract for benchmark classes to provide run metrics sampling, - request metrics sampling, real-time estimate updates, and final compilation - of benchmark results from scheduler execution data. - """ - - @abstractmethod - def get_run_metrics_sample( - self, - ) -> dict[Literal["start_time", "end_time", "duration"], float]: - """ - Get a sample of run-level timing metrics. - - :return: Dictionary containing start_time, end_time, and duration metrics - """ - ... - - @abstractmethod - def get_request_metrics_sample( - self, - ) -> dict[ - Literal[ - "request_count", - "request_latency", - "request_throughput", - "request_concurrency", - ], - float, - ]: - """ - Get a sample of request-level performance metrics. - - :return: Dictionary containing request count, latency, throughput, and - concurrency metrics - """ - ... - - @classmethod - @abstractmethod - def update_estimate( - cls, - args: BenchmarkerArgs, - state: EstimatedBenchmarkState, - response: Any, - request: Any, - request_info: RequestInfo, - scheduler_state: SchedulerState, - ): - """ - Update real-time benchmark estimates with new request data. - - :param args: Benchmark configuration arguments - :param state: Current estimated benchmark state to update - :param response: Response received from the backend - :param request: Original request sent to the backend - :param request_info: Metadata about the request execution - :param scheduler_state: Current state of the scheduler - """ - ... - - @classmethod - @abstractmethod - def compile( - cls, - args: BenchmarkerArgs, - estimated_state: EstimatedBenchmarkState, - scheduler_state: SchedulerState, - profile: Profile, - requests: Iterable, - backend: BackendInterface, - environment: Environment, - strategy: SchedulingStrategy, - constraints: dict[str, dict[str, Any]], - ) -> Any: - """ - Compile final benchmark results from accumulated state. - - :param args: Benchmark configuration arguments - :param estimated_state: Accumulated benchmark state from execution - :param scheduler_state: Final state of the scheduler - :param profile: Benchmark profile configuration - :param requests: Collection of requests executed - :param backend: Backend interface used for execution - :param environment: Execution environment configuration - :param strategy: Scheduling strategy used - :param constraints: Execution constraints applied - :return: Compiled benchmark results instance - """ - ... - - -BenchmarkT = TypeVar("BenchmarkT", bound=Benchmark) - - -class BenchmarkSchedulerStats(StandardBaseDict): - """Scheduler timing and performance statistics.""" - - group_name: ClassVar[Literal["scheduler_stats"]] = "scheduler_stats" - - start_time: float = Field( - description="Unix timestamp when the benchmark run started" - ) - end_time: float = Field(description="Unix timestamp when the benchmark run ended") - requests_made: StatusBreakdown[int, int, int, int] = Field( - description="Request counts by status: successful, incomplete, errored, total" - ) - queued_time_avg: float = Field( - description="Avg time requests spent in the queue (seconds)" - ) - worker_resolve_start_delay_avg: float = Field( - description="Avg delay before worker begins resolving req after dequeue (sec)" - ) - worker_resolve_time_avg: float = Field( - description="Avg time for worker to resolve requests (seconds)" - ) - worker_resolve_end_delay_avg: float = Field( - description="Avg delay after request end till worker resolves (seconds)" - ) - finalized_delay_avg: float = Field( - description="Avg delay after resolve til finalized with in scheduler (sec)" - ) - worker_targeted_start_delay_avg: float = Field( - description="Avg delay from targeted start to actual worker start (seconds)" - ) - request_start_delay_avg: float = Field( - description="Avg delay after resolve til request start (seconds)" - ) - request_time_avg: float = Field(description="Avg request processing time (seconds)") - request_targeted_start_delay_avg: float = Field( - description="Avg delay from targeted start to actual request start" - ) - - @classmethod - def update_estimate(cls, state: EstimatedBenchmarkState, request_info: RequestInfo): - """ - Update estimated scheduler statistics with request timing information. - - :param state: Current estimated benchmark state to update - :param request_info: Metadata about the request execution with timing data - """ - state.set_metric(group=cls.group_name, key="updated", value=True) - state.add_avg_metric( - group=cls.group_name, - key="queued_time", - value=request_info.timings.dequeued, - start_val=request_info.timings.queued, - ) - state.add_avg_metric( - group=cls.group_name, - key="worker_resolve_start_delay", - value=request_info.timings.resolve_start, - start_val=request_info.timings.scheduled_at, - ) - state.add_avg_metric( - group=cls.group_name, - key="worker_resolve_time", - value=request_info.timings.resolve_end, - start_val=request_info.timings.resolve_start, - ) - state.add_avg_metric( - group=cls.group_name, - key="worker_resolve_end_delay", - value=request_info.timings.request_end, - start_val=request_info.timings.resolve_end, - ) - state.add_avg_metric( - group=cls.group_name, - key="finalized_delay", - value=request_info.timings.finalized, - start_val=request_info.timings.resolve_end, - ) - state.add_avg_metric( - group=cls.group_name, - key="worker_targeted_start_delay", - value=request_info.timings.resolve_start, - start_val=request_info.timings.targeted_start, - ) - state.add_avg_metric( - group=cls.group_name, - key="request_start_delay", - value=request_info.timings.request_start, - start_val=request_info.timings.resolve_start, - ) - state.add_avg_metric( - group=cls.group_name, - key="request_time", - value=request_info.timings.request_end, - start_val=request_info.timings.request_start, - ) - state.add_avg_metric( - group=cls.group_name, - key="request_targeted_start_delay", - value=request_info.timings.request_start, - start_val=request_info.timings.targeted_start, - ) - - @classmethod - def compile( - cls, estimated_state: EstimatedBenchmarkState, scheduler_state: SchedulerState - ) -> BenchmarkSchedulerStats: - """ - Compile final scheduler statistics from accumulated state. - - :param estimated_state: Accumulated benchmark state with scheduler metrics - :param scheduler_state: Final state of the scheduler - :return: Compiled scheduler statistics instance - """ - return BenchmarkSchedulerStats( - start_time=scheduler_state.start_time, - end_time=scheduler_state.end_time or scheduler_state.start_time, - requests_made=StatusBreakdown[int, int, int, int]( - successful=scheduler_state.successful_requests, - incomplete=scheduler_state.cancelled_requests, - errored=scheduler_state.errored_requests, - total=( - scheduler_state.successful_requests - + scheduler_state.cancelled_requests - + scheduler_state.errored_requests - ), - ), - queued_time_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="queued_time", default=-1.0 - ), - ), - worker_resolve_start_delay_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="worker_resolve_start_delay", default=-1.0 - ), - ), - worker_resolve_time_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="worker_resolve_time", default=-1.0 - ), - ), - worker_resolve_end_delay_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="worker_resolve_end_delay", default=-1.0 - ), - ), - finalized_delay_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="finalized_delay", default=-1.0 - ), - ), - worker_targeted_start_delay_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, - key="worker_targeted_start_delay", - default=-1.0, - ), - ), - request_start_delay_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="request_start_delay", default=-1.0 - ), - ), - request_time_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, key="request_time", default=-1.0 - ), - ), - request_targeted_start_delay_avg=cast( - "float", - estimated_state.get_metric( - group=cls.group_name, - key="request_targeted_start_delay", - default=-1.0, - ), - ), - ) - - -class GenerativeMetricsSummary(StandardBaseDict): - """ - Statistical summaries for input, output, and total metrics. - - Provides distribution summaries across successful, incomplete, and errored - requests for absolute values, per-second rates, and concurrency levels. - """ - - input: StatusDistributionSummary = Field( - description="Distribution of input metric values" - ) - input_per_second: StatusDistributionSummary = Field( - description="Distribution of input metric rates per second" - ) - input_concurrency: StatusDistributionSummary = Field( - description="Distribution of concurrent input metric values" - ) - - output: StatusDistributionSummary = Field( - description="Distribution of output metric values" - ) - output_per_second: StatusDistributionSummary = Field( - description="Distribution of output metric rates per second" - ) - output_concurrency: StatusDistributionSummary = Field( - description="Distribution of concurrent output metric values" - ) - - total: StatusDistributionSummary = Field( - description="Distribution of total metric values (input + output)" - ) - total_per_second: StatusDistributionSummary = Field( - description="Distribution of total metric rates per second" - ) - total_concurrency: StatusDistributionSummary = Field( - description="Distribution of concurrent total metric values" - ) - - @classmethod - def compile( - cls, - request_types: list[Literal["successful", "incomplete", "error"]], - request_times: list[tuple[float, float]], - input_values: list[int | float], - output_values: list[int | float], - ) -> GenerativeMetricsSummary: - """ - Compile generative metrics summary from request data. - - :param request_types: Status types for each request - :param request_times: Start and end times for each request - :param input_values: Input metric values for each request - :param output_values: Output metric values for each request - :return: Compiled generative metrics summary - """ - total_values = [ - input_val + output_val - for input_val, output_val in zip(input_values, output_values, strict=False) - ] - - return GenerativeMetricsSummary( - input=StatusDistributionSummary.from_values( - value_types=request_types, - values=input_values, - ), - input_per_second=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="rate", - weights=input_values, - ), - input_concurrency=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="concurrency", - weights=input_values, - ), - output=StatusDistributionSummary.from_values( - value_types=request_types, - values=output_values, - ), - output_per_second=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="rate", - weights=output_values, - ), - output_concurrency=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="concurrency", - weights=output_values, - ), - total=StatusDistributionSummary.from_values( - value_types=request_types, - values=total_values, - ), - total_per_second=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="rate", - weights=total_values, - ), - total_concurrency=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="concurrency", - weights=total_values, - ), - ) - - -class GenerativeTextMetricsSummary(StandardBaseDict): - """ - Text-specific metric summaries for generative benchmarks. - - Tracks token, word, and character-level metrics across input, output, and - total usage for text generation workloads. - """ - - tokens: GenerativeMetricsSummary = Field( - description="Token count metrics and distributions" - ) - words: GenerativeMetricsSummary = Field( - description="Word count metrics and distributions" - ) - characters: GenerativeMetricsSummary = Field( - description="Character count metrics and distributions" - ) - - @classmethod - def compile( - cls, - request_types: list[Literal["successful", "incomplete", "error"]], - request_times: list[tuple[float, float]], - input_metrics: list[UsageMetrics], - output_metrics: list[UsageMetrics], - ) -> GenerativeTextMetricsSummary: - """ - Compile text metrics summary from request usage data. - - :param request_types: Status types for each request - :param request_times: Start and end times for each request - :param input_metrics: Input usage metrics for each request - :param output_metrics: Output usage metrics for each request - :return: Compiled text metrics summary - """ - return GenerativeTextMetricsSummary( - tokens=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.text_tokens or 0 for metrics in input_metrics], - output_values=[metrics.text_tokens or 0 for metrics in output_metrics], - ), - words=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.text_words or 0 for metrics in input_metrics], - output_values=[metrics.text_words or 0 for metrics in output_metrics], - ), - characters=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[ - metrics.text_characters or 0 for metrics in input_metrics - ], - output_values=[ - metrics.text_characters or 0 for metrics in output_metrics - ], - ), - ) - - -class GenerativeImageMetricsSummary(StandardBaseDict): - """ - Image-specific metric summaries for generative benchmarks. - - Tracks token, image count, pixel, and byte-level metrics across input, output, - and total usage for image generation workloads. - """ - - tokens: GenerativeMetricsSummary = Field( - description="Image token count metrics and distributions" - ) - images: GenerativeMetricsSummary = Field( - description="Image count metrics and distributions" - ) - pixels: GenerativeMetricsSummary = Field( - description="Pixel count metrics and distributions" - ) - bytes: GenerativeMetricsSummary = Field( - description="Byte size metrics and distributions" - ) - - @classmethod - def compile( - cls, - request_types: list[Literal["successful", "incomplete", "error"]], - request_times: list[tuple[float, float]], - input_metrics: list[UsageMetrics], - output_metrics: list[UsageMetrics], - ) -> GenerativeImageMetricsSummary: - """ - Compile image metrics summary from request usage data. - - :param request_types: Status types for each request - :param request_times: Start and end times for each request - :param input_metrics: Input usage metrics for each request - :param output_metrics: Output usage metrics for each request - :return: Compiled image metrics summary - """ - return GenerativeImageMetricsSummary( - tokens=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.image_tokens or 0 for metrics in input_metrics], - output_values=[metrics.image_tokens or 0 for metrics in output_metrics], - ), - images=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.image_count or 0 for metrics in input_metrics], - output_values=[metrics.image_count or 0 for metrics in output_metrics], - ), - pixels=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.image_pixels or 0 for metrics in input_metrics], - output_values=[metrics.image_pixels or 0 for metrics in output_metrics], - ), - bytes=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.image_bytes or 0 for metrics in input_metrics], - output_values=[metrics.image_bytes or 0 for metrics in output_metrics], - ), - ) - - -class GenerativeVideoMetricsSummary(StandardBaseDict): - """ - Video-specific metric summaries for generative benchmarks. - - Tracks token, frame count, duration, and byte-level metrics across input, - output, and total usage for video generation workloads. - """ - - tokens: GenerativeMetricsSummary = Field( - description="Video token count metrics and distributions" - ) - frames: GenerativeMetricsSummary = Field( - description="Frame count metrics and distributions" - ) - seconds: GenerativeMetricsSummary = Field( - description="Duration metrics in seconds and distributions" - ) - bytes: GenerativeMetricsSummary = Field( - description="Byte size metrics and distributions" - ) - - @classmethod - def compile( - cls, - request_types: list[Literal["successful", "incomplete", "error"]], - request_times: list[tuple[float, float]], - input_metrics: list[UsageMetrics], - output_metrics: list[UsageMetrics], - ) -> GenerativeVideoMetricsSummary: - """ - Compile video metrics summary from request usage data. - - :param request_types: Status types for each request - :param request_times: Start and end times for each request - :param input_metrics: Input usage metrics for each request - :param output_metrics: Output usage metrics for each request - :return: Compiled video metrics summary - """ - return GenerativeVideoMetricsSummary( - tokens=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.video_tokens or 0 for metrics in input_metrics], - output_values=[metrics.video_tokens or 0 for metrics in output_metrics], - ), - frames=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.video_frames or 0 for metrics in input_metrics], - output_values=[metrics.video_frames or 0 for metrics in output_metrics], - ), - seconds=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.video_seconds or 0 for metrics in input_metrics], - output_values=[ - metrics.video_seconds or 0 for metrics in output_metrics - ], - ), - bytes=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.video_bytes or 0 for metrics in input_metrics], - output_values=[metrics.video_bytes or 0 for metrics in output_metrics], - ), - ) - - -class GenerativeAudioMetricsSummary(StandardBaseDict): - """ - Audio-specific metric summaries for generative benchmarks. - - Tracks token, sample count, duration, and byte-level metrics across input, - output, and total usage for audio generation workloads. - """ - - tokens: GenerativeMetricsSummary = Field( - description="Audio token count metrics and distributions" - ) - samples: GenerativeMetricsSummary = Field( - description="Sample count metrics and distributions" - ) - seconds: GenerativeMetricsSummary = Field( - description="Duration metrics in seconds and distributions" - ) - bytes: GenerativeMetricsSummary = Field( - description="Byte size metrics and distributions" - ) - - @classmethod - def compile( - cls, - request_types: list[Literal["successful", "incomplete", "error"]], - request_times: list[tuple[float, float]], - input_metrics: list[UsageMetrics], - output_metrics: list[UsageMetrics], - ) -> GenerativeAudioMetricsSummary: - """ - Compile audio metrics summary from request usage data. - - :param request_types: Status types for each request - :param request_times: Start and end times for each request - :param input_metrics: Input usage metrics for each request - :param output_metrics: Output usage metrics for each request - :return: Compiled audio metrics summary - """ - return GenerativeAudioMetricsSummary( - tokens=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.audio_tokens or 0 for metrics in input_metrics], - output_values=[metrics.audio_tokens or 0 for metrics in output_metrics], - ), - samples=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.audio_samples or 0 for metrics in input_metrics], - output_values=[ - metrics.audio_samples or 0 for metrics in output_metrics - ], - ), - seconds=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.audio_seconds or 0 for metrics in input_metrics], - output_values=[ - metrics.audio_seconds or 0 for metrics in output_metrics - ], - ), - bytes=GenerativeMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_values=[metrics.audio_bytes or 0 for metrics in input_metrics], - output_values=[metrics.audio_bytes or 0 for metrics in output_metrics], - ), - ) - - -class GenerativeMetrics(StandardBaseDict): - """Comprehensive metrics for generative AI benchmarks.""" - - # Request stats - requests_per_second: StatusDistributionSummary = Field( - description="Distribution of requests per second across benchmark execution" - ) - request_concurrency: StatusDistributionSummary = Field( - description="Distribution of concurrent request counts during execution" - ) - request_latency: StatusDistributionSummary = Field( - description="Distribution of request latencies for completed requests" - ) - request_streaming_iterations_count: StatusDistributionSummary = Field( - description="Distribution of stream iterations for completed requests" - ) - - # General token stats - prompt_token_count: StatusDistributionSummary = Field( - description="Distribution of prompt token counts by request status" - ) - output_token_count: StatusDistributionSummary = Field( - description="Distribution of output token counts by request status" - ) - total_token_count: StatusDistributionSummary = Field( - description="Distribution of total token counts by request status" - ) - time_to_first_token_ms: StatusDistributionSummary = Field( - description="Distribution of first token latencies in milliseconds" - ) - time_per_output_token_ms: StatusDistributionSummary = Field( - description="Distribution of average time per output token in milliseconds" - ) - inter_token_latency_ms: StatusDistributionSummary = Field( - description="Distribution of inter-token latencies in milliseconds" - ) - output_tokens_wo_first_per_iteration: StatusDistributionSummary = Field( - description=( - "Distribution of output tokens (without first) generated per " - "streaming iteration" - ) - ) - output_tokens_per_second: StatusDistributionSummary = Field( - description="Distribution of output token generation rates" - ) - output_tokens_per_iteration: StatusDistributionSummary = Field( - description="Distribution of output tokens generated per streaming iteration" - ) - tokens_per_second: StatusDistributionSummary = Field( - description="Distribution of total token throughput including prompt and output" - ) - - # Domain specific stats - text: GenerativeTextMetricsSummary = Field( - description="Text-specific metrics for tokens, words, and characters" - ) - image: GenerativeImageMetricsSummary = Field( - description="Image-specific metrics for tokens, images, pixels, and bytes" - ) - video: GenerativeVideoMetricsSummary = Field( - description="Video-specific metrics for tokens, frames, duration, and bytes" - ) - audio: GenerativeAudioMetricsSummary = Field( - description="Audio-specific metrics for tokens, samples, duration, and bytes" - ) - - @classmethod - def update_estimate( - cls, - state: EstimatedBenchmarkState, - response: GenerationResponse | None, - request: GenerationRequest, - request_info: RequestInfo, - scheduler_state: SchedulerState, - ): - """ - Update real-time generative metrics estimates with new request data. - - :param state: Current estimated benchmark state to update - :param response: Response received from the backend - :param request: Original request sent to the backend - :param request_info: Metadata about the request execution - :param scheduler_state: Current state of the scheduler - """ - benchmark_start_time = scheduler_state.start_time - request_start_time = ( - request_info.timings.request_start or request_info.timings.resolve_start - ) - request_end_time = ( - request_info.timings.request_end or request_info.timings.resolve_end - ) - event_occurence_time = ( - request_info.timings.queued - if request_info.status == "queued" - else ( - request_info.timings.dequeued - if request_info.status == "pending" - else request_start_time - if request_info.status == "in_progress" - else request_end_time - ) - ) - benchmark_duration = ( - event_occurence_time - benchmark_start_time - if event_occurence_time - else None - ) - request_duration = ( - (request_end_time - request_start_time) - if request_end_time and request_start_time - else None - ) - - # Always track concurrency - if event_occurence_time is not None: - state.add_time_averaged_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="concurrency_requests", - value=scheduler_state.processing_requests, - recorded_time=event_occurence_time, - ) - - if request_info.status not in {"completed", "errored", "cancelled"}: - return - - state.set_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="updated", - value=True, - ) - - for prefix in (request_info.status, "total"): - requests_count = ( - scheduler_state.successful_requests - if prefix == "completed" - else scheduler_state.errored_requests - if prefix == "errored" - else scheduler_state.cancelled_requests - if prefix == "cancelled" - else scheduler_state.processed_requests - ) - input_tokens = ( - (response.input_metrics.total_tokens if response else None) - or request.input_metrics.total_tokens - or 0 - ) - output_tokens = ( - (response.output_metrics.total_tokens if response else None) - or request.output_metrics.total_tokens - or 0 - ) - - # Request distribution stats - state.set_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_requests", - value=requests_count, - ) - state.set_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_requests_per_second", - value=( - requests_count / benchmark_duration if benchmark_duration else None - ), - ) - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_request_latency", - value=request_duration, - ) - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_request_streaming_iterations", - value=request_info.timings.iterations or 0, - ) - - # Token iteration stats - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="output_tokens_iterations", - value=output_tokens, - count=request_info.timings.iterations or 1, - ) - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="output_tokens_wo_first_iterations", - value=output_tokens - 1 if output_tokens > 1 else 0, - count=request_info.timings.iterations or 1, - ) - - # Token metrics stats - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_time_to_first_token", - value=request_info.timings.first_iteration, - start_val=request_start_time, - ) - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_inter_token_latency", - value=request_info.timings.last_iteration, - start_val=request_info.timings.first_iteration, - count=(output_tokens or 1) - 1, - ) - state.add_avg_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key=f"{prefix}_time_per_output_token", - value=request_duration, - count=output_tokens or 0, - ) - - # Input/output throughput stats - if event_occurence_time is not None: - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="input_tokens", - value=input_tokens, - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="output_tokens", - value=output_tokens, - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="total_tokens", - value=input_tokens + output_tokens, - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="input_text_tokens", - value=( - (response.input_metrics.text_tokens if response else None) - or request.input_metrics.text_tokens - or 0 - ), - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="input_images", - value=( - (response.input_metrics.image_count if response else None) - or request.input_metrics.image_count - or 0 - ), - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="input_video_frames", - value=( - (response.input_metrics.video_frames if response else None) - or request.input_metrics.video_frames - or 0 - ), - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - state.add_avg_rate_metric( - group=EstimatedBenchmarkState.benchmark_metrics_group, - key="input_audio_seconds", - value=request.input_metrics.audio_seconds or 0, - start_time=benchmark_start_time, - end_time=event_occurence_time, - ) - - @classmethod - def compile( - cls, - completed: list[GenerativeRequestStats], - errored: list[GenerativeRequestStats], - incomplete: list[GenerativeRequestStats], - ) -> GenerativeMetrics: - """ - Compile final generative metrics from request statistics. - - :param completed: Successfully completed request statistics - :param errored: Failed request statistics - :param incomplete: Incomplete/cancelled request statistics - :return: Compiled generative metrics with full distributions - """ - requests = completed + errored + incomplete - request_types = cast( - "list[Literal['successful', 'error', 'incomplete']]", - ["successful"] * len(completed) - + ["error"] * len(errored) - + ["incomplete"] * len(incomplete), - ) - request_times = [ - ( - req.info.timings.request_start or req.info.timings.resolve_start or 0, - req.info.timings.request_end or req.info.timings.resolve_end or 0, - ) - for req in requests - ] - input_metrics = [req.input_metrics for req in requests] - output_metrics = [req.output_metrics for req in requests] - - return GenerativeMetrics( - # Request stats - requests_per_second=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="rate", - ), - request_concurrency=StatusDistributionSummary.from_request_times( - request_types=request_types, - requests=request_times, - distribution_type="concurrency", - ), - request_latency=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.request_latency or 0.0 for req in requests], - ), - request_streaming_iterations_count=StatusDistributionSummary.from_values( - value_types=request_types, - values=[float(req.info.timings.iterations or 0) for req in requests], - ), - # General token stats - prompt_token_count=StatusDistributionSummary.from_values( - value_types=request_types, - values=[float(req.prompt_tokens or 0) for req in requests], - ), - output_token_count=StatusDistributionSummary.from_values( - value_types=request_types, - values=[float(req.output_tokens or 0) for req in requests], - ), - total_token_count=StatusDistributionSummary.from_values( - value_types=request_types, - values=[float(req.total_tokens or 0) for req in requests], - ), - time_to_first_token_ms=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.time_to_first_token_ms or 0.0 for req in requests], - ), - time_per_output_token_ms=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.time_per_output_token_ms or 0.0 for req in requests], - ), - inter_token_latency_ms=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.inter_token_latency_ms or 0.0 for req in requests], - ), - output_tokens_wo_first_per_iteration=StatusDistributionSummary.from_values( - value_types=request_types, - values=[ - max(0.0, (req.output_metrics.total_tokens or 1.0) - 1.0) - for req in requests - ], - weights=[req.info.timings.iterations or 1 for req in requests], - ), - output_tokens_per_second=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.output_tokens_per_second or 0.0 for req in requests], - ), - output_tokens_per_iteration=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.output_tokens_per_iteration or 0.0 for req in requests], - weights=[req.info.timings.iterations or 1 for req in requests], - ), - tokens_per_second=StatusDistributionSummary.from_values( - value_types=request_types, - values=[req.tokens_per_second or 0.0 for req in requests], - ), - # Domain-specific stats - text=GenerativeTextMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_metrics=input_metrics, - output_metrics=output_metrics, - ), - image=GenerativeImageMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_metrics=input_metrics, - output_metrics=output_metrics, - ), - video=GenerativeVideoMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_metrics=input_metrics, - output_metrics=output_metrics, - ), - audio=GenerativeAudioMetricsSummary.compile( - request_types=request_types, - request_times=request_times, - input_metrics=input_metrics, - output_metrics=output_metrics, - ), - ) - - -class SchedulerDict(StandardBaseDict): - """Scheduler configuration and execution state dictionary.""" - - strategy: SchedulingStrategy = Field( - description="Scheduling strategy used for request distribution" - ) - constraints: dict[str, dict[str, Any]] = Field( - description="Execution constraints applied during benchmarking" - ) - state: SchedulerState = Field( - description="Final state of the scheduler after execution" - ) - - -class BenchmarkerDict(StandardBaseDict): - """Benchmarker configuration and component settings dictionary.""" - - profile: Profile = Field(description="Benchmark profile configuration") - requests: dict[str, Any] = Field( - description="Request configuration and dataset information" - ) - backend: dict[str, Any] = Field( - description="Backend configuration and connection details" - ) - environment: dict[str, Any] = Field( - description="Execution environment configuration" - ) - - -class GenerativeBenchmark(Benchmark, StandardBaseDict): - """Complete generative AI benchmark results with specialized metrics.""" - - group_name: ClassVar[Literal["generative_benchmark"]] = "generative_benchmark" - - type_: Literal["generative_benchmark"] = "generative_benchmark" # type: ignore[assignment] - id_: str = Field( - default_factory=lambda: str(uuid.uuid4()), - description="Unique identifier for this benchmark execution", - ) - run_id: str = Field( - description="Identifier for the benchmarker run containing this benchmark" - ) - run_index: int = Field( - description="Sequential index of this benchmark within the benchmarker run" - ) - scheduler: SchedulerDict = Field( - description="Scheduler configuration and execution state" - ) - benchmarker: BenchmarkerDict = Field( - description="Benchmarker configuration and component settings" - ) - run_stats: BenchmarkSchedulerStats = Field( - description="Scheduler timing and performance statistics" - ) - start_time: float = Field( - default=-1.0, description="Unix timestamp when the first request was initiated" - ) - end_time: float = Field( - default=-1.0, description="Unix timestamp when the last request completed" - ) - - def get_run_metrics_sample( - self, - ) -> dict[Literal["start_time", "end_time", "duration"], float]: - return { - "start_time": self.start_time, - "end_time": self.end_time, - "duration": self.duration, - } - - def get_request_metrics_sample( - self, - ) -> dict[ - Literal[ - "request_count", - "request_latency", - "request_throughput", - "request_concurrency", - ], - float, - ]: - return { - "request_count": self.request_totals.successful, - "request_latency": self.metrics.request_latency.successful.mean, - "request_throughput": self.metrics.requests_per_second.successful.mean, - "request_concurrency": self.metrics.request_concurrency.successful.mean, - } - - @computed_field # type: ignore[misc] - @property - def duration(self) -> float: - """ - Benchmark execution duration in seconds. - - :return: Time elapsed from first request start to last request completion. - """ - return self.end_time - self.start_time - - metrics: GenerativeMetrics = Field( - description="Performance metrics and statistical distributions" - ) - request_totals: StatusBreakdown[int, int, int, int] = Field( - description="Request counts by status: successful, incomplete, errored, total" - ) - requests: StatusBreakdown[ - list[GenerativeRequestStats], - list[GenerativeRequestStats], - list[GenerativeRequestStats], - None, - ] = Field( - description="Request details grouped by status: successful, incomplete, errored" - ) - - @classmethod - def update_estimate( - cls, - args: BenchmarkerArgs, - state: EstimatedBenchmarkState, - response: GenerationResponse | None, - request: GenerationRequest, - request_info: RequestInfo, - scheduler_state: SchedulerState, - ): - """ - Update generative benchmark estimates with new request data. - - Handles warmup/cooldown filtering, request sampling via reservoir sampling, - and delegates metric updates to child metric classes. - - :param args: Benchmark configuration arguments - :param state: Current estimated benchmark state to update - :param response: Response received from the backend - :param request: Original request sent to the backend - :param request_info: Metadata about the request execution - :param scheduler_state: Current state of the scheduler - """ - if ( - request_info.status == "cancelled" - and request_info.timings.resolve_start is None - ): - # Cancelled requests that never started should be ignored - return - - # Update child metric groups - BenchmarkSchedulerStats.update_estimate(state, request_info) - GenerativeMetrics.update_estimate( - state, response, request, request_info, scheduler_state - ) - - # Store requests and sampling info, update counts - if "requests_completed" not in state: - state["requests_completed"] = [] - state["samples_completed"] = [] - state["requests_errored"] = [] - state["samples_errored"] = [] - state["requests_incomplete"] = [] - state["samples_incomplete"] = [] - in_warmup = state.set_metric( - group=EstimatedBenchmarkState.benchmark_state_group, - key="in_warmup", - value=args.is_in_warmup(request_info, scheduler_state), - ) - in_cooldown = state.set_metric( - group=EstimatedBenchmarkState.benchmark_state_group, - key="in_cooldown", - value=args.is_in_cooldown(request_info, scheduler_state), - ) - state[f"{EstimatedBenchmarkState.benchmark_state_group}_status"] = ( - "in_cooldown" - if in_cooldown - else "in_warmup" - if in_warmup - else "in_progress" - ) - - if ( - request_info.status not in {"completed", "errored", "cancelled"} - or in_warmup - or in_cooldown - ): - # Must be fully resolved to be added - return - - state.set_metric( - group=EstimatedBenchmarkState.benchmark_state_group, - key="updated", - value=True, - ) - - if response is None: - response = GenerationResponse( - request_id=request.request_id, request_args=str(request.arguments) - ) - - stats = response.compile_stats( - request, request_info, args.prefer_response_metrics - ) - - # Determine status and get corresponding lists - if request_info.status == "completed": - requests_list = state["requests_completed"] - samples_list = state["samples_completed"] - elif request_info.status == "errored": - requests_list = state["requests_errored"] - samples_list = state["samples_errored"] - else: # cancelled (incomplete) - requests_list = state["requests_incomplete"] - samples_list = state["samples_incomplete"] - - # Add to requests list - requests_list.append(stats) - current_index = len(requests_list) - 1 - - # Handle request sampling logic - if args.sample_requests is None: - # No sampling, add index to samples list - samples_list.append(current_index) - elif args.sample_requests > 0 and len(samples_list) < args.sample_requests: - # Space in samples list, add index - samples_list.append(current_index) - elif ( - args.sample_requests > 0 - and (replace_index := random.randrange(len(requests_list))) - < args.sample_requests - ): - # No space, adding based on reservoir sampling - samples_list[replace_index] = current_index - # Sampling set to 0, don't keep any requests - - @classmethod - def compile( - cls, - args: BenchmarkerArgs, - estimated_state: EstimatedBenchmarkState, - scheduler_state: SchedulerState, - profile: Profile, - requests: Iterable, # noqa: ARG003 - backend: BackendInterface, - environment: Environment, - strategy: SchedulingStrategy, - constraints: dict[str, dict[str, Any]], - data: list[Any], - ) -> GenerativeBenchmark: - """ - Compile final generative benchmark from accumulated state. - - :param args: Benchmark configuration arguments - :param estimated_state: Accumulated benchmark state from execution - :param scheduler_state: Final state of the scheduler - :param profile: Benchmark profile configuration - :param requests: Collection of requests executed - :param backend: Backend interface used for execution - :param environment: Execution environment configuration - :param strategy: Scheduling strategy used - :param constraints: Execution constraints applied - :return: Compiled generative benchmark instance - """ - return GenerativeBenchmark( - run_id=args.run_id, - run_index=args.run_index, - scheduler=SchedulerDict( - strategy=strategy, - constraints={ - key: InfoMixin.extract_from_obj(val) - for key, val in constraints.items() - }, - state=scheduler_state, - ), - benchmarker=BenchmarkerDict( - profile=profile, - requests={"data": data}, - backend=backend.info, - environment=environment.info, - ), - run_stats=BenchmarkSchedulerStats.compile(estimated_state, scheduler_state), - start_time=scheduler_state.start_time or -1.0, - end_time=scheduler_state.end_time or -1.0, - metrics=GenerativeMetrics.compile( - completed=estimated_state.get("requests_completed", []), - errored=estimated_state.get("requests_errored", []), - incomplete=estimated_state.get("requests_incomplete", []), - ), - request_totals=StatusBreakdown[int, int, int, int]( - successful=len(estimated_state.get("requests_completed", [])), - incomplete=len(estimated_state.get("requests_incomplete", [])), - errored=len(estimated_state.get("requests_errored", [])), - total=( - len(estimated_state.get("requests_completed", [])) - + len(estimated_state.get("requests_incomplete", [])) - + len(estimated_state.get("requests_errored", [])) - ), - ), - requests=StatusBreakdown[ - list[GenerativeRequestStats], - list[GenerativeRequestStats], - list[GenerativeRequestStats], - None, - ]( - successful=estimated_state.get("requests_completed", []), - incomplete=estimated_state.get("requests_incomplete", []), - errored=estimated_state.get("requests_errored", []), - total=None, - ), - ) - - -class BenchmarkGenerativeTextArgs(StandardBaseModel): - """ - Configuration arguments for generative text benchmark execution. - - Defines all parameters for benchmark setup including target endpoint, data - sources, backend configuration, processing pipeline, output formatting, and - execution constraints. Supports loading from scenario files and merging with - runtime overrides. - """ - - @classmethod - def create( - cls, scenario: Path | str | None, **kwargs: dict[str, Any] - ) -> BenchmarkGenerativeTextArgs: - """ - Create benchmark args from scenario file and/or keyword arguments. - - :param scenario: Path to scenario file or name of built-in scenario - :param kwargs: Additional keyword arguments to override scenario values - :return: Configured benchmark args instance - :raises ValueError: If scenario is not found or file format is unsupported - """ - constructor_kwargs = {} - - if scenario is not None: - if isinstance(scenario, str) and scenario in ( - builtin_scenarios := get_builtin_scenarios() - ): - scenario_path = builtin_scenarios[scenario] - elif Path(scenario).exists() and Path(scenario).is_file(): - scenario_path = Path(scenario) - else: - raise ValueError(f"Scenario '{scenario}' not found.") - - with scenario_path.open() as file: - if scenario_path.suffix == ".json": - scenario_data = json.load(file) - elif scenario_path.suffix in {".yaml", ".yml"}: - scenario_data = yaml.safe_load(file) - else: - raise ValueError( - f"Unsupported scenario file format: {scenario_path.suffix}" - ) - if "args" in scenario_data: - # loading from a report file - scenario_data = scenario_data["args"] - constructor_kwargs.update(scenario_data) - - # Apply overrides from kwargs - constructor_kwargs.update(kwargs) - - return cls.model_validate(constructor_kwargs) - - @classmethod - def get_default(cls: type[BenchmarkGenerativeTextArgs], field: str) -> Any: - """ - Get default value for a model field. - - :param field: Name of the field to retrieve default for - :return: Default value for the specified field - :raises ValueError: If field is not found in model - """ - if field not in BenchmarkGenerativeTextArgs.model_fields: - raise ValueError( - f"Field '{field}' not found in BenchmarkGenerativeTextArgs" - ) - - field_info = BenchmarkGenerativeTextArgs.model_fields[field] - factory = field_info.default_factory - - if factory is None: - return field_info.default - - if len(inspect.signature(factory).parameters) == 0: - return factory() # type: ignore[call-arg] # Confirmed correct at runtime by code above - else: - return factory({}) # type: ignore[call-arg] # Confirmed correct at runtime by code above - - model_config = ConfigDict( - extra="ignore", - use_enum_values=True, - from_attributes=True, - arbitrary_types_allowed=True, - validate_by_alias=True, - validate_by_name=True, - alias_generator=AliasGenerator( - # Support field names with hyphens - validation_alias=lambda field_name: AliasChoices( - field_name, field_name.replace("_", "-") - ), - ), - ) - - # Required - target: str = Field(description="Target endpoint URL for benchmark execution") - data: list[Any] = Field( - description="List of dataset sources or data files", - default_factory=list, - min_length=1, - ) - # Benchmark configuration - profile: StrategyType | ProfileType | Profile = Field( - default="sweep", description="Benchmark profile or scheduling strategy type" - ) - rate: list[float] | None = Field( - default=None, description="Request rate(s) for rate-based scheduling" - ) - # Backend configuration - backend: BackendType | Backend = Field( - default="openai_http", description="Backend type or instance for execution" - ) - backend_kwargs: dict[str, Any] | None = Field( - default=None, description="Additional backend configuration arguments" - ) - model: str | None = Field(default=None, description="Model identifier for backend") - # Data configuration - processor: str | Path | PreTrainedTokenizerBase | None = Field( - default=None, description="Tokenizer path, name, or instance for processing" - ) - processor_args: dict[str, Any] | None = Field( - default=None, description="Additional tokenizer configuration arguments" - ) - data_args: list[dict[str, Any]] | None = Field( - default_factory=list, description="Per-dataset configuration arguments" - ) - data_samples: int = Field( - default=-1, description="Number of samples to use from datasets (-1 for all)" - ) - data_column_mapper: ( - DatasetPreprocessor | dict[str, str] | Literal["generative_column_mapper"] - ) = Field( - default="generative_column_mapper", - description="Column mapping preprocessor for dataset fields", - ) - data_request_formatter: DatasetPreprocessor | dict[str, str] | str = Field( - default="chat_completions", - description="Request formatting preprocessor or template name", - validation_alias=AliasChoices( - "data_request_formatter", - "data-request-formatter", - "request_type", - "request-type", - ), - ) - data_collator: Callable | Literal["generative"] | None = Field( - default="generative", description="Data collator for batch processing" - ) - data_sampler: Sampler[int] | Literal["shuffle"] | None = Field( - default=None, description="Data sampler for request ordering" - ) - data_num_workers: int | None = Field( - default=None, description="Number of workers for data loading" - ) - dataloader_kwargs: dict[str, Any] | None = Field( - default=None, description="Additional dataloader configuration arguments" - ) - random_seed: int = Field(default=42, description="Random seed for reproducibility") - # Output configuration - output_path: str | Path | None = Field( - default_factory=Path.cwd, description="Directory path for output files" - ) - output_formats: list[str] | dict[str, str | dict[str, Any]] | None = Field( - default_factory=lambda: ["console", "json"], - description="Output format names or configuration mappings", - ) - # Benchmarker configuration - benchmark_cls: type[GenerativeBenchmark] = Field( - default=GenerativeBenchmark, - description="Benchmark class to use for result compilation", - ) - sample_requests: int | None = Field( - default=10, - description="Number of requests to sample for detailed metrics (None for all)", - ) - warmup: float | None = Field( - default=None, - description="Warmup period in seconds, requests, or fraction (0-1)", - ) - cooldown: float | None = Field( - default=None, - description="Cooldown period in seconds, requests, or fraction (0-1)", - ) - prefer_response_metrics: bool = Field( - default=True, - description="Whether to prefer backend response metrics over request metrics", - ) - # Constraints configuration - max_seconds: int | float | None = Field( - default=None, description="Maximum benchmark execution time in seconds" - ) - max_requests: int | None = Field( - default=None, description="Maximum number of requests to execute" - ) - max_errors: int | None = Field( - default=None, description="Maximum number of errors before stopping" - ) - max_error_rate: float | None = Field( - default=None, description="Maximum error rate (0-1) before stopping" - ) - max_global_error_rate: float | None = Field( - default=None, description="Maximum global error rate (0-1) before stopping" - ) - - @field_validator("data", "data_args", "rate", mode="wrap") - @classmethod - def single_to_list( - cls, value: Any, handler: ValidatorFunctionWrapHandler - ) -> list[Any]: - """ - Ensures field is always a list. - - :param value: Input value for the 'data' field - :return: List of data sources - """ - try: - return handler(value) - except ValidationError as err: - # If validation fails, try wrapping the value in a list - if err.errors()[0]["type"] == "list_type": - return handler([value]) - else: - raise - - @model_serializer - def serialize_model(self): - """ - Custom serialization logic for benchmark args. - - Converts complex types to serializable formats including Profile to type - string, Backend to type string, and Path objects to strings. - - :return: Dictionary representation suitable for JSON/YAML serialization - """ - return { - # target - serialize as is - "target": self.target, - "data": [ - item if isinstance(item, str | type(None)) else str(item) - for item in self.data - ], # data - for each item in the list, if not a str or None, save str(item) - "profile": ( - self.profile.type_ - if isinstance(self.profile, Profile) - else self.profile - ), # profile - if instance of Profile, then save as profile.type_ - "rate": self.rate, - "backend": ( - self.backend.type_ - if isinstance(self.backend, Backend) - else self.backend - ), # backend - if instance of Backend, then save as backend.type_ - "backend_kwargs": self.backend_kwargs, - "model": self.model, - "processor": ( - self.processor - if isinstance(self.processor, str) - else str(self.processor) - if self.processor is not None - else None - ), # processor - if not str, then save as str(processor) - "processor_args": self.processor_args, - "data_args": self.data_args, - "data_samples": self.data_samples, - "data_column_mapper": ( - self.data_column_mapper - if isinstance(self.data_column_mapper, dict | str) - else {} - ), # data_column_mapper - if not dict or str, then save as an empty dict - "data_request_formatter": ( - self.data_request_formatter - if isinstance(self.data_request_formatter, dict | str) - else {} - ), # data_request_formatter - if not dict or str, then save as empty dict - "data_collator": ( - self.data_collator if isinstance(self.data_collator, str) else None - ), # data_collator - if not str, then save as None - "data_sampler": ( - self.data_sampler if isinstance(self.data_sampler, str) else None - ), # data_sampler - if not str, then save as None - "data_num_workers": self.data_num_workers, - "dataloader_kwargs": self.dataloader_kwargs, - "random_seed": self.random_seed, - "output_path": ( - str(self.output_path) if self.output_path is not None else None - ), # output_path - if not None, then ensure it's a str - "output_formats": self.output_formats, - # benchmark_cls - don't save at all (excluded) - "sample_requests": self.sample_requests, - "warmup": self.warmup, - "cooldown": self.cooldown, - "prefer_response_metrics": self.prefer_response_metrics, - "max_seconds": self.max_seconds, - "max_requests": self.max_requests, - "max_errors": self.max_errors, - "max_error_rate": self.max_error_rate, - "max_global_error_rate": self.max_global_error_rate, - } - - -class GenerativeBenchmarksReport(StandardBaseModel): - """Container for multiple benchmark results with load/save functionality.""" - - DEFAULT_FILE: ClassVar[str] = "benchmarks.json" - - @staticmethod - def load_file( - path: str | Path, type_: Literal["json", "yaml"] | None = None - ) -> GenerativeBenchmarksReport: - """ - Load a report from a file. - - :param path: The path to load the report from. - :param type_: File type override, auto-detected from extension if None. - :return: The loaded report. - :raises ValueError: If file type is unsupported. - """ - path = Path(path) if not isinstance(path, Path) else path - - if path.is_dir(): - path = path / GenerativeBenchmarksReport.DEFAULT_FILE - - path.parent.mkdir(parents=True, exist_ok=True) - path_suffix = path.suffix.lower()[1:] - - with path.open("r") as file: - if (type_ or path_suffix) == "json": - model_dict = json.loads(file.read()) - elif (type_ or path_suffix) in ["yaml", "yml"]: - model_dict = yaml.safe_load(file) - else: - raise ValueError(f"Unsupported file type: {type_} for {path}.") - - return GenerativeBenchmarksReport.model_validate(model_dict) - - args: BenchmarkGenerativeTextArgs = Field( - description="The benchmark arguments used for all benchmarks in the report." - ) - benchmarks: list[GenerativeBenchmark] = Field( - description="The list of completed benchmarks contained within the report.", - default_factory=list, - ) - - def save_file( - self, path: str | Path | None, type_: Literal["json", "yaml"] | None = None - ) -> Path: - """ - Save the report to a file. - - :param path: The path to save the report to. - :param type_: File type override, auto-detected from extension if None. - :return: The path to the saved report. - :raises ValueError: If file type is unsupported. - """ - if path is None: - path = Path.cwd() - elif not isinstance(path, Path): - path = Path(path) - - if path.is_dir(): - path = path / GenerativeBenchmarksReport.DEFAULT_FILE - - path.parent.mkdir(parents=True, exist_ok=True) - path_suffix = path.suffix.lower()[1:] - model_dict = self.model_dump() - - if (type_ or path_suffix) == "json": - save_str = json.dumps(model_dict) - elif (type_ or path_suffix) in ["yaml", "yml"]: - save_str = yaml.dump(model_dict) - else: - raise ValueError(f"Unsupported file type: {type_} for {path}.") - - with path.open("w") as file: - file.write(save_str) - - return path diff --git a/src/guidellm/benchmark/schemas/__init__.py b/src/guidellm/benchmark/schemas/__init__.py new file mode 100644 index 00000000..fd0f5016 --- /dev/null +++ b/src/guidellm/benchmark/schemas/__init__.py @@ -0,0 +1,64 @@ +""" +Benchmark schemas for performance measurement and result analysis. + +This module consolidates the complete benchmark schema ecosystem, providing both +base abstractions for benchmark execution and domain-specific implementations +for generative AI tasks. It exports core configuration objects, accumulator +interfaces for real-time metric collection, benchmark result containers with +statistical summaries, and reporting utilities. The schemas support flexible +scheduling strategies, comprehensive metric tracking including latency and +throughput distributions, and multi-modal generative benchmarks for text, image, +video, and audio generation tasks. +""" + +from __future__ import annotations + +from .base import ( + Benchmark, + BenchmarkAccumulator, + BenchmarkAccumulatorT, + BenchmarkConfig, + BenchmarkT, +) +from .generative import ( + BenchmarkGenerativeTextArgs, + GenerativeAudioMetricsSummary, + GenerativeBenchmark, + GenerativeBenchmarkAccumulator, + GenerativeBenchmarksReport, + GenerativeBenchmarkTimings, + GenerativeImageMetricsSummary, + GenerativeMetrics, + GenerativeMetricsAccumulator, + GenerativeMetricsSummary, + GenerativeRequestsAccumulator, + GenerativeTextMetricsSummary, + GenerativeVideoMetricsSummary, + RunningMetricStats, + SchedulerMetrics, + SchedulerMetricsAccumulator, +) + +__all__ = [ + "Benchmark", + "BenchmarkAccumulator", + "BenchmarkAccumulatorT", + "BenchmarkConfig", + "BenchmarkGenerativeTextArgs", + "BenchmarkT", + "GenerativeAudioMetricsSummary", + "GenerativeBenchmark", + "GenerativeBenchmarkAccumulator", + "GenerativeBenchmarkTimings", + "GenerativeBenchmarksReport", + "GenerativeImageMetricsSummary", + "GenerativeMetrics", + "GenerativeMetricsAccumulator", + "GenerativeMetricsSummary", + "GenerativeRequestsAccumulator", + "GenerativeTextMetricsSummary", + "GenerativeVideoMetricsSummary", + "RunningMetricStats", + "SchedulerMetrics", + "SchedulerMetricsAccumulator", +] diff --git a/src/guidellm/benchmark/schemas/base.py b/src/guidellm/benchmark/schemas/base.py new file mode 100644 index 00000000..7976a484 --- /dev/null +++ b/src/guidellm/benchmark/schemas/base.py @@ -0,0 +1,400 @@ +""" +Base schemas for benchmark execution, metric accumulation, and result compilation. + +Defines abstract interfaces and configuration models for coordinating benchmark +execution with schedulers. The module centers around three key abstractions: +BenchmarkConfig encapsulates execution parameters and constraints; BenchmarkAccumulator +tracks incremental metrics during scheduler runs; and Benchmark compiles final results +with comprehensive latency, throughput, and concurrency distributions. Supports +configurable warmup/cooldown phases, transient period handling, and flexible metric +sampling strategies. +""" + +from __future__ import annotations + +import uuid +from abc import ABC, abstractmethod +from typing import Any, Generic, Literal, TypeVar + +from pydantic import Field, NonNegativeFloat, NonNegativeInt + +from guidellm.benchmark.profiles import Profile +from guidellm.scheduler import ( + MultiTurnRequestT, + RequestT, + ResponseT, + SchedulerState, + SchedulingStrategy, +) +from guidellm.schemas import ( + RequestInfo, + StandardBaseDict, + StandardBaseModel, + StatusDistributionSummary, +) + +__all__ = [ + "Benchmark", + "BenchmarkAccumulator", + "BenchmarkAccumulatorT", + "BenchmarkConfig", + "BenchmarkT", +] + +BenchmarkAccumulatorT = TypeVar( + "BenchmarkAccumulatorT", bound="BenchmarkAccumulator[Any, Any]" +) +"Generic type variable for benchmark accumulator implementations" + +BenchmarkT = TypeVar("BenchmarkT", bound="Benchmark") +"Generic type variable for benchmark result implementations" + + +class TransientPhaseConfig(StandardBaseModel): + """ + Configure warmup and cooldown phases for benchmark execution. + + Supports flexible phase definition through percentage or absolute value + specifications with multiple interpretation modes. Phases can be bounded + by duration, request count, or both, enabling precise control over transient + periods that should be excluded from final benchmark metrics. + """ + + @classmethod + def create_from_value( + cls, value: int | float | dict | TransientPhaseConfig | None + ) -> TransientPhaseConfig: + """ + Create configuration from flexible input formats. + + :param value: Configuration as int/float (percent if <1.0, absolute + otherwise), dict (validated to model), TransientPhaseConfig instance, + or None for defaults + :return: Configured TransientPhaseConfig instance + :raises ValueError: If value type is unsupported + """ + if value is None: + return TransientPhaseConfig() + + if isinstance(value, TransientPhaseConfig): + return value + + if isinstance(value, dict): + return TransientPhaseConfig.model_validate(value) + + if isinstance(value, int | float): + kwargs = { + "percent": value if value < 1.0 else None, + "value": value if value >= 1.0 else None, + } + return TransientPhaseConfig.model_validate(kwargs) + + raise ValueError(f"Unsupported type for TransientPhaseConfig: {type(value)}") + + percent: NonNegativeFloat | None = Field( + default=None, + description=( + "Phase size as percentage (0.0-1.0) of total duration/requests; " + "interpretation depends on mode. Takes precedence over value when target " + "mode is available, otherwise falls back to value" + ), + lt=1.0, + ) + value: NonNegativeInt | NonNegativeFloat | None = Field( + default=None, + description=( + "Phase size as absolute duration (seconds) or request count; " + "interpretation depends on mode. Used when percent is unset or " + "target mode unavailable" + ), + ) + mode: Literal[ + "duration", "requests", "prefer_duration", "prefer_requests", "both" + ] = Field( + default="prefer_duration", + description=( + "Interpretation mode: 'duration' for time-based phases, 'requests' for " + "count-based phases, 'prefer_duration'/'prefer_requests' for fallback " + "behavior, 'both' requires satisfying both conditions" + ), + ) + + def compute_limits( + self, max_requests: int | None, max_seconds: float | None + ) -> tuple[float | None, int | None]: + """ + Calculate phase boundaries from benchmark constraints. + + :param max_requests: Total request budget for benchmark execution + :param max_seconds: Total duration budget for benchmark execution + :return: Tuple of (phase duration in seconds, phase request count) + """ + duration: float | None = None + requests: int | None = None + + if self.mode in ["duration", "prefer_duration", "both"]: + if self.percent is not None and max_seconds is not None: + duration = self.percent * max_seconds + elif self.value is not None: + duration = float(self.value) + + if self.mode in ["requests", "prefer_requests", "both"]: + if self.percent is not None and max_requests is not None: + requests = int(self.percent * max_requests) + elif self.value is not None: + requests = int(self.value) + + return duration, requests + + def compute_transition_time( + self, + start_time: float, + request_start: float | None, + request_end: float | None, + current_requests: int, + current_duration: float, + remaining_requests: int | None, + remaining_duration: float | None, + period: Literal["start", "end"], + ) -> tuple[bool, float | None]: + """ + Determine transition timestamp for entering or exiting phase. + + :param start_time: Benchmark start timestamp in seconds since epoch + :param request_start: Current request start timestamp + :param request_end: Current request end timestamp + :param current_requests: Requests completed at transition point + :param current_duration: Elapsed duration at transition point + :param remaining_requests: Requests remaining in benchmark budget + :param remaining_duration: Duration remaining in benchmark budget + :param period: Phase period ('start' for warmup, 'end' for cooldown) + :return: Tuple of (phase active flag, transition timestamp if applicable) + """ + max_duration = ( + current_duration + remaining_duration + if remaining_duration is not None + else None + ) + max_requests = ( + current_requests + remaining_requests + if remaining_requests is not None + else None + ) + target_duration, target_requests = self.compute_limits( + max_requests=max_requests, max_seconds=max_duration + ) + + if target_duration is None and target_requests is None: + return False, None + + duration_transition_time: float | None = None + request_transition_time: float | None = None + phase_active: bool = False + + if ( + target_duration is not None + and max_duration is not None + and remaining_duration is not None + ): + duration_transition_time = ( + start_time + target_duration + if period == "start" + else start_time + max_duration - target_duration + ) + phase_active = True + if ( + target_requests is not None + and max_requests is not None + and remaining_requests is not None + ): + request_transition_time = ( + request_start + if period == "start" and current_requests > target_requests + else request_end + if period == "end" and remaining_requests < target_requests + 1 + else -1.0 + ) + phase_active = True + + transition_time: float | None = None + + match self.mode: + case "duration": + transition_time = duration_transition_time + case "requests": + transition_time = request_transition_time + case "prefer_duration": + transition_time = duration_transition_time or request_transition_time + case "prefer_requests": + transition_time = request_transition_time or duration_transition_time + case "both": + transition_time = ( + -1.0 + if request_transition_time == -1.0 + else request_transition_time + if duration_transition_time is None + else duration_transition_time + if request_transition_time is None + else min(duration_transition_time, request_transition_time) + if period == "start" + else max(duration_transition_time, request_transition_time) + ) + + return phase_active, transition_time if transition_time != -1.0 else None + + +class BenchmarkConfig(StandardBaseDict): + """ + Encapsulate execution parameters and constraints for benchmark runs. + + Defines comprehensive configuration including scheduler strategy, constraint + sets, transient phase handling, metric sampling preferences, and execution + metadata. Coordinates profile, request, backend, and environment configurations + to enable reproducible benchmark execution with precise control over metric + collection. + """ + + id_: str = Field( + default_factory=lambda: str(uuid.uuid4()), + description="Unique identifier for this benchmark execution", + ) + run_id: str = Field( + description="Identifier grouping related benchmark runs in a series", + ) + run_index: int = Field( + description="Zero-based index of this run within the benchmark series", + ) + strategy: SchedulingStrategy = Field( + description="Scheduler strategy controlling request execution patterns", + ) + constraints: dict[str, dict[str, Any]] = Field( + description="Constraint definitions applied to scheduler strategy execution", + ) + sample_requests: int | None = Field( + default=20, + description="Request count for statistical sampling in final metrics", + ) + warmup: TransientPhaseConfig = Field( + default_factory=TransientPhaseConfig, + description="Warmup phase configuration excluding initial transient period", + ) + cooldown: TransientPhaseConfig = Field( + default_factory=TransientPhaseConfig, + description="Cooldown phase configuration excluding final transient period", + ) + prefer_response_metrics: bool = Field( + default=True, + description="Prioritize response-based metrics over request-based metrics", + ) + profile: Profile = Field( + description="Profile instance coordinating multi-strategy execution", + ) + requests: dict[str, Any] = Field( + description="Request generation configuration and dataset metadata", + ) + backend: dict[str, Any] = Field( + description="Backend connection parameters and service configuration", + ) + environment: dict[str, Any] = Field( + description="Execution environment details and system metadata", + ) + + +class BenchmarkAccumulator(StandardBaseDict, ABC, Generic[RequestT, ResponseT]): + """ + Track and accumulate benchmark metrics during scheduler execution. + + Maintains incremental metric estimates as requests are processed, enabling + real-time progress monitoring and efficient metric compilation. Subclasses + implement specific metric calculation strategies based on request/response + characteristics and scheduler state evolution. + """ + + config: BenchmarkConfig = Field( + description="Benchmark execution configuration and constraints", + ) + + @abstractmethod + def update_estimate( + self, + response: ResponseT | None, + request: RequestT | MultiTurnRequestT[RequestT], + info: RequestInfo, + scheduler_state: SchedulerState, + ): + """ + Incrementally update metrics with completed request data. + + :param response: Backend response data if request succeeded + :param request: Request instance submitted to backend + :param info: Request timing, status, and execution metadata + :param scheduler_state: Current scheduler state with queue and concurrency info + """ + ... + + +class Benchmark(StandardBaseDict, ABC, Generic[BenchmarkAccumulatorT]): + """ + Compile and expose final benchmark execution metrics. + + Defines the interface for benchmark result implementations capturing + comprehensive performance metrics including latency distributions, throughput + measurements, and concurrency patterns. Subclasses implement compilation + logic to transform accumulated metrics and scheduler state into structured + results with statistical summaries. + """ + + @property + @abstractmethod + def start_time(self) -> float: + """ + :return: Benchmark start timestamp in seconds since epoch + """ + + @property + @abstractmethod + def end_time(self) -> float: + """ + :return: Benchmark completion timestamp in seconds since epoch + """ + + @property + @abstractmethod + def duration(self) -> float: + """ + :return: Benchmark execution duration in seconds + """ + + @property + @abstractmethod + def request_latency(self) -> StatusDistributionSummary: + """ + :return: Statistical distribution of request latencies + """ + + @property + @abstractmethod + def request_throughput(self) -> StatusDistributionSummary: + """ + :return: Statistical distribution of throughput measurements + """ + + @property + @abstractmethod + def request_concurrency(self) -> StatusDistributionSummary: + """ + :return: Statistical distribution of concurrent request counts + """ + + @classmethod + @abstractmethod + def compile( + cls, accumulator: BenchmarkAccumulatorT, scheduler_state: SchedulerState + ) -> Any: + """ + Transform accumulated metrics into final benchmark results. + + :param accumulator: Accumulator instance with collected metrics and state + :param scheduler_state: Scheduler's final state after execution completion + :return: Compiled benchmark instance with complete statistical results + """ diff --git a/src/guidellm/benchmark/schemas/generative/__init__.py b/src/guidellm/benchmark/schemas/generative/__init__.py new file mode 100644 index 00000000..ad70fde0 --- /dev/null +++ b/src/guidellm/benchmark/schemas/generative/__init__.py @@ -0,0 +1,54 @@ +""" +Generative AI benchmark schemas for performance measurement and analysis. + +This module provides the complete schema ecosystem for executing, tracking, and +analyzing generative AI benchmarks. It encompasses configuration entrypoints for +benchmark setup, real-time metric accumulators for execution monitoring, +comprehensive result containers with statistical summaries, and multi-benchmark +reporting capabilities. The schemas support domain-specific metrics for text, +image, video, and audio generation tasks, enabling detailed performance analysis +including throughput, latency distributions, concurrency patterns, and scheduler +behavior tracking across successful, incomplete, and errored requests. +""" + +from __future__ import annotations + +from .accumulator import ( + GenerativeBenchmarkAccumulator, + GenerativeBenchmarkTimings, + GenerativeMetricsAccumulator, + GenerativeRequestsAccumulator, + RunningMetricStats, + SchedulerMetricsAccumulator, +) +from .benchmark import GenerativeBenchmark +from .entrypoints import BenchmarkGenerativeTextArgs +from .metrics import ( + GenerativeAudioMetricsSummary, + GenerativeImageMetricsSummary, + GenerativeMetrics, + GenerativeMetricsSummary, + GenerativeTextMetricsSummary, + GenerativeVideoMetricsSummary, + SchedulerMetrics, +) +from .report import GenerativeBenchmarksReport + +__all__ = [ + "BenchmarkGenerativeTextArgs", + "GenerativeAudioMetricsSummary", + "GenerativeBenchmark", + "GenerativeBenchmarkAccumulator", + "GenerativeBenchmarkTimings", + "GenerativeBenchmarksReport", + "GenerativeImageMetricsSummary", + "GenerativeMetrics", + "GenerativeMetricsAccumulator", + "GenerativeMetricsSummary", + "GenerativeRequestsAccumulator", + "GenerativeTextMetricsSummary", + "GenerativeVideoMetricsSummary", + "RunningMetricStats", + "SchedulerMetrics", + "SchedulerMetricsAccumulator", +] diff --git a/src/guidellm/benchmark/schemas/generative/accumulator.py b/src/guidellm/benchmark/schemas/generative/accumulator.py new file mode 100644 index 00000000..73e6488b --- /dev/null +++ b/src/guidellm/benchmark/schemas/generative/accumulator.py @@ -0,0 +1,844 @@ +""" +Real-time metric accumulation for generative benchmark execution. + +Captures and computes performance metrics during benchmark runs, tracking timing phases, +request statistics, token throughput, and latency distributions. Components include +timing trackers for warmup/cooldown phases, running statistical accumulators for +throughput and latency metrics, and reservoir sampling for request data. Enables +comprehensive performance measurement including scheduler overhead, time-to-first-token, +inter-token latency, and token generation rates across completed, errored, and +incomplete requests. +""" + +from __future__ import annotations + +import random +import time +from typing import Literal + +from pydantic import Field + +from guidellm.benchmark.schemas.base import BenchmarkAccumulator, BenchmarkConfig +from guidellm.scheduler import MultiTurnRequestT, SchedulerState +from guidellm.schemas import ( + GenerationRequest, + GenerationResponse, + GenerativeRequestStats, + RequestInfo, + RequestTimings, + StandardBaseModel, + StatusBreakdown, +) + +__all__ = [ + "GenerativeBenchmarkAccumulator", + "GenerativeBenchmarkTimings", + "GenerativeMetricsAccumulator", + "GenerativeRequestsAccumulator", + "RunningMetricStats", + "SchedulerMetricsAccumulator", +] + + +class GenerativeBenchmarkTimings(StandardBaseModel): + """ + Tracks timing phases and transitions during benchmark execution. + + Monitors timestamps throughout benchmark execution including request submission, + measurement period boundaries (warmup/active/cooldown), and completion events. + Provides duration calculations and phase status determination based on configured + warmup and cooldown periods. + """ + + request_start: float | None = Field( + description="Timestamp when the first request was sent", default=None + ) + measure_start: float | None = Field( + description="Timestamp when measurement period started", default=None + ) + measure_end: float | None = Field( + description="Timestamp when measurement period ended", default=None + ) + request_end: float | None = Field( + description="Timestamp when the last request was completed", default=None + ) + current_update: float | None = Field( + description="Most recent timestamp observed during execution", default=None + ) + current_request: float | None = Field( + description="Most recent request completion timestamp observed", default=None + ) + last_update: float | None = Field( + description="Previous timestamp observed before the current one", default=None + ) + last_request: float | None = Field( + description="Previous request completion timestamp before the current one", + default=None, + ) + + @property + def status(self) -> Literal["pending", "warmup", "active", "cooldown", "completed"]: + """ + :return: Current execution phase based on timing thresholds + """ + if self.request_start is None or self.current_update is None: + return "pending" + + if self.measure_start is None or self.current_update <= self.measure_start: + return "warmup" + + if ( + self.measure_end is not None + and self.measure_end != -1.0 + and self.current_update >= self.measure_end + ): + return "cooldown" + + if ( + self.request_end is not None + and self.current_update > self.request_end + 1.0 + ): + return "completed" + + return "active" + + @property + def duration(self) -> float: + """ + :return: Elapsed time since measurement or request start in seconds + """ + if self.request_start is None or self.current_update is None: + return 0.0 + + return self.current_update - self.request_start + + @property + def elapsed_time_last_update(self) -> float: + """ + :return: Time elapsed between the last two update timestamps in seconds + """ + if self.current_update is None or self.last_update is None: + return 0.0 + + return self.current_update - self.last_update + + @property + def elapsed_time_last_request(self) -> float: + """ + :return: Time elapsed between the last two request completions in seconds + """ + if self.current_request is None or self.last_request is None: + return 0.0 + + return self.current_request - self.last_request + + def update_estimate( + self, + info: RequestInfo, + scheduler_state: SchedulerState, + config: BenchmarkConfig, + ): + """ + Update timing estimates based on request info and scheduler state. + + Advances timing markers through benchmark phases (warmup to active to cooldown) + based on configured thresholds. Updates current/last timestamps for updates and + request completions, determining measurement period boundaries. + + :param info: Request information containing timing data + :param scheduler_state: Current scheduler state with progress metrics + :param config: Benchmark configuration with warmup/cooldown settings + """ + request_start = info.timings.request_start or info.timings.resolve_start + request_end = info.timings.request_end or info.timings.resolve_end + current_time = info.timings.last_reported + + if self.request_start is None: + self.request_start = request_start + + current_duration = ( + current_time - self.request_start + if self.request_start is not None and current_time + else 0.0 + ) + current_requests = scheduler_state.processed_requests + + if self.measure_start is None and self.request_start is not None: + warmup_active, measure_start = config.warmup.compute_transition_time( + start_time=self.request_start, + request_start=request_start, + request_end=request_end, + current_requests=current_requests, + current_duration=current_duration, + remaining_requests=scheduler_state.remaining_requests, # type: ignore[arg-type] + remaining_duration=scheduler_state.remaining_duration, + period="start", + ) + self.measure_start = measure_start if warmup_active else self.request_start + + if self.measure_end is None and self.measure_start is not None: + cooldown_active, measure_end = config.cooldown.compute_transition_time( + start_time=self.measure_start, + request_start=request_start, + request_end=request_end, + current_requests=current_requests, + current_duration=current_duration, + remaining_requests=scheduler_state.remaining_requests, # type: ignore[arg-type] + remaining_duration=scheduler_state.remaining_duration, + period="end", + ) + self.measure_end = ( + measure_end + if cooldown_active + else -1.0 # -1 to signify no cooldown and to pull from request_end + ) + + if request_end is not None and ( + self.request_end is None or request_end > self.request_end + ): + # Always update request end to the max seen so far + self.request_end = request_end + + # Update last and current update times + self.last_update = self.current_update + if current_time is not None and ( + self.current_update is None or current_time > self.current_update + ): + self.current_update = current_time + + # Update last and current request times, if applicable + if info.status in {"completed", "errored", "cancelled"}: + self.last_request = self.current_request + if request_end is not None and ( + self.current_request is None or request_end > self.current_request + ): + self.current_request = request_end + + +class RunningMetricStats(StandardBaseModel): + """ + Maintains running statistics for a metric stream without storing all samples. + + Accumulates count, sum, time-weighted sum, and duration to compute mean, rate, + and time-weighted statistics incrementally. Efficient for real-time metric tracking + during long-running benchmarks where storing individual samples is impractical. + """ + + count: int = Field(description="Number of samples accumulated", default=0) + value_sum: float = Field(description="Total sum of accumulated values", default=0.0) + time_weighted_sum: float = Field( + description="Time-weighted sum of accumulated values", default=0.0 + ) + duration: float = Field( + description="Total duration over which values were accumulated", default=0.0 + ) + last_value: float | None = Field( + description="Most recent value added to the accumulator", default=None + ) + + @property + def mean(self) -> float | None: + """ + :return: Arithmetic mean of accumulated values, or None if no samples + """ + if self.count <= 0: + return None + + return self.value_sum / self.count + + @property + def time_weighted_mean(self) -> float | None: + """ + :return: Time-weighted mean considering duration between samples, or None + """ + if self.duration <= 0.0: + return None + + return self.time_weighted_sum / self.duration + + @property + def rate_per_item(self) -> float | None: + """ + :return: Average value per accumulated item, or None if no samples + """ + if self.count <= 0: + return None + + return self.value_sum / self.count + + @property + def rate_per_second(self) -> float | None: + """ + :return: Average value per second of duration, or None if no duration + """ + if self.duration <= 0.0: + return None + + return self.value_sum / self.duration + + def update_estimate( + self, + value: float | None, + count: int = 1, + duration: float | None = None, + elapsed: float | None = None, + ): + """ + Incorporate a new metric value into running statistics. + + Updates count, sum, and time-weighted statistics using the new value and timing + information. Time-weighted calculations use the previous value over the elapsed + interval to capture sustained metric behavior. + + :param value: New metric value to accumulate + :param count: Number of occurrences this value represents + :param duration: Total duration to set, overriding incremental elapsed updates + :param elapsed: Time elapsed since last update for time-weighted calculations + """ + self.count += count + self.value_sum += (value or 0.0) * count + + if elapsed is not None: + self.time_weighted_sum += (self.last_value or 0.0) * elapsed + + self.duration = ( + duration if duration is not None else (self.duration + (elapsed or 0.0)) + ) + self.last_value = value + + +class SchedulerMetricsAccumulator(StandardBaseModel): + """ + Tracks scheduler-level timing and overhead metrics during execution. + + Monitors request lifecycle timing from queuing through completion, capturing delays + at each stage: queue time, worker start delays, request processing time, and + finalization overhead. Provides insight into scheduler efficiency and bottleneck + identification in request orchestration. + """ + + requests_made: StatusBreakdown[int, int, int, int] = Field( + description="Request counts by status: successful, incomplete, errored, total", + default_factory=lambda: StatusBreakdown[int, int, int, int]( + successful=0, errored=0, incomplete=0, total=0 + ), + ) + # Timings flow: + # Request scheduling: queued->dequeued->scheduled_at->resolve_start-> + # Request processing: request_start->*_iteration->request_end-> + # Request finalizing: resolve_end->finalized->accumulation update processed + queued_time: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Running stats for time requests spent in the queue", + ) + resolve_start_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description=( + "Running stats for delay before worker begins resolving req after dequeue" + ), + ) + resolve_targeted_start_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description=( + "Running stats for delay from targeted start to actual worker start" + ), + ) + request_start_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Running stats for delay after resolve til request start", + ) + request_targeted_start_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description=( + "Running stats for delay from targeted start to actual request start" + ), + ) + request_time: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Running stats for request processing time", + ) + resolve_end_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Running stats for delay after request end till worker resolves", + ) + resolve_time: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Running stats for time for worker to resolve requests", + ) + finalized_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Running stats for delay after resolve til finalized in scheduler", + ) + processed_delay: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description=( + "Running stats for delay from finalized til request being " + "processed by accumulation" + ), + ) + + def update_estimate( + self, scheduler_state: SchedulerState, stats: GenerativeRequestStats + ): + """ + Update scheduler metrics with completed request timing data. + + Extracts timing information from request statistics to update running metrics + for each scheduler lifecycle stage. Validates that required timing markers are + present before processing. + + :param scheduler_state: Current scheduler state with request counts + :param stats: Completed request statistics with detailed timing information + :raises ValueError: If required timing markers are missing + """ + # Update request counts + self.requests_made.successful = scheduler_state.successful_requests + self.requests_made.errored = scheduler_state.errored_requests + self.requests_made.incomplete = scheduler_state.cancelled_requests + self.requests_made.total = ( + scheduler_state.successful_requests + + scheduler_state.errored_requests + + scheduler_state.cancelled_requests + ) + + # All requests must have queued, dequeued, resolve_end, and finalized timings + timings: RequestTimings = stats.info.timings + if any( + timing is None + for timing in [ + timings.queued, + timings.dequeued, + timings.resolve_end, + timings.finalized, + ] + ): + raise ValueError( + "Required timings 'queued', 'dequeued', 'resolve_end', and " + "'finalized' must not be None" + ) + + # Store validated non-None timings for type safety + queued: float = timings.queued # type: ignore[assignment] + dequeued: float = timings.dequeued # type: ignore[assignment] + resolve_end: float = timings.resolve_end # type: ignore[assignment] + finalized: float = timings.finalized # type: ignore[assignment] + + # Update timing metrics in occurrence order + self.queued_time.update_estimate(value=dequeued - queued) + + if timings.scheduled_at is not None and timings.resolve_start is not None: + self.resolve_start_delay.update_estimate( + value=timings.resolve_start - timings.scheduled_at + ) + + if timings.targeted_start is not None and timings.resolve_start is not None: + self.resolve_targeted_start_delay.update_estimate( + value=timings.resolve_start - timings.targeted_start + ) + + if timings.resolve_start is not None and timings.request_start is not None: + self.request_start_delay.update_estimate( + value=timings.request_start - timings.resolve_start + ) + + if timings.targeted_start is not None and timings.request_start is not None: + self.request_targeted_start_delay.update_estimate( + value=timings.request_start - timings.targeted_start + ) + + if timings.request_start is not None and timings.request_end is not None: + self.request_time.update_estimate( + value=timings.request_end - timings.request_start + ) + + if timings.request_end is not None: + self.resolve_end_delay.update_estimate( + value=resolve_end - timings.request_end + ) + + if timings.resolve_start is not None: + self.resolve_time.update_estimate(value=resolve_end - timings.resolve_start) + + self.finalized_delay.update_estimate(value=finalized - resolve_end) + self.processed_delay.update_estimate(value=time.time() - finalized) + + +class GenerativeMetricsAccumulator(StandardBaseModel): + """ + Accumulates generative model performance metrics during execution. + + Tracks token throughput, latency characteristics, and request timing for generative + workloads. Maintains running statistics for input/output tokens, + time-to-first-token, inter-token latency, and streaming patterns for comprehensive + performance analysis. + """ + + requests: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated request count statistics", + ) + request_latency: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated request latency statistics", + ) + input_tokens: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated input token count statistics", + ) + output_tokens: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated output token count statistics", + ) + total_tokens: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated total token count statistics", + ) + time_to_first_token_ms: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated time to first token statistics in milliseconds", + ) + time_per_output_token_ms: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated time per output token statistics in milliseconds", + ) + inter_token_latency_ms: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated inter-token latency statistics in milliseconds", + ) + streaming_iterations: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated streaming iteration count statistics", + ) + output_tokens_by_iteration: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated output tokens per iteration statistics", + ) + iter_tokens_by_iteration: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated iteration tokens per iteration statistics", + ) + + def update_estimate(self, stats: GenerativeRequestStats, duration: float): + """ + Update generative metrics with completed request statistics. + + Incorporates token counts, latency measurements, and streaming characteristics + from a completed request into running metric accumulators with time-weighted + calculations. + + :param stats: Request statistics containing token and latency measurements + :param duration: Current benchmark duration for time-weighted metrics + """ + self.requests.update_estimate(1.0, duration=duration) + self.input_tokens.update_estimate(stats.input_tokens, duration=duration) + self.output_tokens.update_estimate(stats.output_tokens, duration=duration) + self.total_tokens.update_estimate(stats.total_tokens, duration=duration) + self.request_latency.update_estimate(stats.request_latency, duration=duration) + self.time_to_first_token_ms.update_estimate( + stats.time_to_first_token_ms, duration=duration + ) + self.time_per_output_token_ms.update_estimate( + stats.time_per_output_token_ms, + count=int(stats.output_tokens or 0), + duration=duration, + ) + self.inter_token_latency_ms.update_estimate( + stats.inter_token_latency_ms, + count=int((stats.output_tokens or 1) - 1), + duration=duration, + ) + self.streaming_iterations.update_estimate( + stats.token_iterations, duration=duration + ) + self.output_tokens_by_iteration.update_estimate( + stats.output_tokens_per_iteration, + count=int(stats.token_iterations or 0), + duration=duration, + ) + self.iter_tokens_by_iteration.update_estimate( + stats.iter_tokens_per_iteration, + count=int((stats.token_iterations or 1) - 1), + duration=duration, + ) + + +class GenerativeRequestsAccumulator(StandardBaseModel): + """ + Manages request statistics collection with optional reservoir sampling. + + Collects detailed request statistics while optionally sampling to limit memory usage + in long-running benchmarks. Supports configurable sampling rates and selective data + retention (clearing request arguments and/or outputs for non-sampled requests). + """ + + sample_requests: int | None = Field( + default=None, + description=( + "Number of requests to sample and keep in the final benchmark for metrics" + ), + ) + requests_stats: list[GenerativeRequestStats] = Field( + description="List of generative request statistics", default_factory=list + ) + samples: list[int] | None = Field( + description="Indices of sampled generative requests", default=None + ) + clear_nonsampled_request_args: bool = Field( + default=True, + description=( + "Whether to clear request arguments and outputs for non-sampled requests" + ), + ) + clear_nonsampled_outputs: bool = Field( + default=True, + description=( + "Whether to clear outputs for non-sampled requests while keeping args" + ), + ) + + def get_sampled(self) -> list[GenerativeRequestStats]: + """ + Retrieve the list of sampled request statistics. + + :return: List of sampled generative request statistics + """ + if self.samples is None: + return self.requests_stats + + return [self.requests_stats[ind] for ind in self.samples] + + def get_within_range( + self, start_time: float, end_time: float + ) -> list[GenerativeRequestStats]: + """ + Retrieve request statistics within a specified time range. + + :param start_time: Start timestamp for filtering (requests must end after this) + :param end_time: End timestamp for filtering (requests must start before this) + :return: List of request statistics within the time range + """ + return [ + stats + for stats in self.requests_stats + if (stats.request_end_time >= start_time) + and ( + ( + stats.request_start_time is not None + and stats.request_start_time <= end_time + ) + or ( + stats.request_start_time is None + and stats.request_end_time <= end_time + ) + ) + ] + + def update_estimate( + self, + response: GenerationResponse | None, + request: GenerationRequest | MultiTurnRequestT[GenerationRequest], + info: RequestInfo, + prefer_response_metrics: bool, + ) -> GenerativeRequestStats: + """ + Record request statistics and apply reservoir sampling if configured. + + Compiles statistics from the completed request and adds to the collection. + Uses reservoir sampling algorithm to maintain uniform sample distribution when + enabled, clearing non-sampled request data to manage memory. + + :param response: Generation response containing output and metrics + :param request: Original generation request with input data + :param info: Request execution information and timing + :param prefer_response_metrics: Whether to prefer metrics from response + :return: Compiled request statistics + """ + stats = self.compile_stats(response, request, info, prefer_response_metrics) + + current_index = len(self.requests_stats) + self.requests_stats.append(stats) + + if self.sample_requests is None: + # Keeping all requests, don't need to sample + self.samples = None + elif self.sample_requests <= 0: + # Not keeping any requests, clear out unnecessary memory usage for current + self.clear_stats_data(stats) + elif self.sample_requests >= len(self.requests_stats): + # Add directly to samples, haven't filled yet + if self.samples is None: + self.samples = [] + self.samples.append(current_index) + elif self.sample_requests / len(self.requests_stats) >= random.random(): + # Sampling logic: choose to replace with decreasing probability s / n + # where s is sample size, n is current number of requests. + # If chosen, choose random existing sample to replace. + # P(new item in samples) = s / n + # P(prev item in samples) = P(item was in samples) * P(not replaced) + # P(prev item in samples) = + # P(before replacement) * P(new item selected) * P(chosen from samples) + # P(prev item in samples) = (s / (n - 1)) * (s / n) * (1 / s) = s / n + # P(prev item in samples) = P(new item in samples) + if self.samples is None: + self.samples = [] + replace_index = random.randrange(len(self.samples)) + self.clear_stats_data(self.samples[replace_index]) + self.samples[replace_index] = current_index + + return stats + + def clear_stats_data(self, stats: GenerativeRequestStats | int): + if isinstance(stats, int): + stats = self.requests_stats[stats] + + if self.clear_nonsampled_request_args: + stats.request_args = None + if self.clear_nonsampled_outputs: + stats.output = None + + @classmethod + def compile_stats( + cls, + response: GenerationResponse | None, + request: GenerationRequest | MultiTurnRequestT[GenerationRequest], + info: RequestInfo, + prefer_response_metrics: bool, + ) -> GenerativeRequestStats: + """ + Compile statistics from request, response, and execution info. + + :param response: Generation response with output and metrics, or None + :param request: Original generation request with input data + :param info: Request execution information and timing + :param prefer_response_metrics: Whether to prefer metrics from response + :return: Compiled generative request statistics + """ + # Extract the first request for arguments if multi-turn + first_request: GenerationRequest + if isinstance(request, GenerationRequest): + first_request = request + else: + # Multi-turn request: extract first item + first_item = request[0] + first_request = ( + first_item[0] if isinstance(first_item, tuple) else first_item + ) + + if response is None: + response = GenerationResponse( + request_id=info.request_id, request_args=str(first_request.arguments) + ) + + return response.compile_stats( + request=first_request, + info=info, + prefer_response=prefer_response_metrics, + ) + + +class GenerativeBenchmarkAccumulator( + BenchmarkAccumulator[GenerationRequest, GenerationResponse] +): + """ + Primary accumulator for generative benchmark execution metrics and statistics. + + Orchestrates real-time metric collection across timing, scheduler, concurrency, and + generative performance dimensions. Maintains separate accumulators for completed, + errored, and incomplete requests while tracking overall metrics. Integrates with + scheduler state to monitor warmup/cooldown phases and compute time-weighted + statistics for throughput and latency analysis. + """ + + timings: GenerativeBenchmarkTimings = Field( + default_factory=GenerativeBenchmarkTimings, + description="Timing phases and transitions during benchmark execution", + ) + completed: GenerativeRequestsAccumulator = Field( + default_factory=GenerativeRequestsAccumulator, + description="Accumulator for completed requests", + ) + errored: GenerativeRequestsAccumulator = Field( + default_factory=GenerativeRequestsAccumulator, + description="Accumulator for errored requests", + ) + incomplete: GenerativeRequestsAccumulator = Field( + default_factory=GenerativeRequestsAccumulator, + description="Accumulator for incomplete requests", + ) + scheduler_metrics: SchedulerMetricsAccumulator = Field( + default_factory=SchedulerMetricsAccumulator, + description="Running metrics for scheduler state", + ) + concurrency_metric: RunningMetricStats = Field( + default_factory=RunningMetricStats, + description="Accumulated request concurrency statistics", + ) + total_metrics: GenerativeMetricsAccumulator = Field( + default_factory=GenerativeMetricsAccumulator, + description="Running metrics for all requests", + ) + completed_metrics: GenerativeMetricsAccumulator = Field( + default_factory=GenerativeMetricsAccumulator, + description="Running metrics for completed requests", + ) + errored_metrics: GenerativeMetricsAccumulator = Field( + default_factory=GenerativeMetricsAccumulator, + description="Running metrics for errored requests", + ) + incomplete_metrics: GenerativeMetricsAccumulator = Field( + default_factory=GenerativeMetricsAccumulator, + description="Running metrics for incomplete requests", + ) + + def update_estimate( + self, + response: GenerationResponse | None, + request: GenerationRequest | MultiTurnRequestT[GenerationRequest], + info: RequestInfo, + scheduler_state: SchedulerState, + ): + """ + Update all benchmark metrics with a completed request. + + Processes request completion by updating timing phases, concurrency metrics, + scheduler statistics, and generative performance metrics. Routes request to + appropriate status-specific accumulator (completed/errored/incomplete) and + updates aggregate totals. Cancelled requests that never started are ignored. + + :param response: Generation response with output and metrics, or None + :param request: Original generation request with input data + :param info: Request execution information and timing + :param scheduler_state: Current scheduler state for phase tracking + """ + self.timings.update_estimate(info, scheduler_state, self.config) + duration = self.timings.duration + elapsed_time_last_update = self.timings.elapsed_time_last_update + self.concurrency_metric.update_estimate( + value=scheduler_state.processing_requests, + duration=duration, + elapsed=elapsed_time_last_update, + ) + + requests_accumulator: GenerativeRequestsAccumulator + metrics_accumulator: GenerativeMetricsAccumulator + + if info.status == "completed": + requests_accumulator = self.completed + metrics_accumulator = self.completed_metrics + elif info.status == "errored": + requests_accumulator = self.errored + metrics_accumulator = self.errored_metrics + elif info.status == "cancelled" and info.timings.resolve_start is not None: + requests_accumulator = self.incomplete + metrics_accumulator = self.incomplete_metrics + else: + # Not a terminal status or cancelled before starting + # Do not include in requests or metrics + return + + stats = requests_accumulator.update_estimate( + response, request, info, self.config.prefer_response_metrics + ) + metrics_accumulator.update_estimate(stats, duration) + self.total_metrics.update_estimate(stats, duration) + self.scheduler_metrics.update_estimate(scheduler_state, stats) diff --git a/src/guidellm/benchmark/schemas/generative/benchmark.py b/src/guidellm/benchmark/schemas/generative/benchmark.py new file mode 100644 index 00000000..c4d45878 --- /dev/null +++ b/src/guidellm/benchmark/schemas/generative/benchmark.py @@ -0,0 +1,163 @@ +""" +Benchmark data models and metrics for generative AI performance measurement. + +Provides comprehensive data structures for capturing, storing, and analyzing +benchmark results from scheduler-driven generative AI workload executions. +Core abstractions include base benchmark interfaces, generative-specific +metrics with token/latency distributions, request-level statistics tracking, +and multi-benchmark reporting capabilities. These models enable detailed +performance analysis including throughput, latency, concurrency patterns, and +domain-specific metrics for text, image, video, and audio generation tasks. +""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import Field, computed_field + +from guidellm.benchmark.schemas.base import Benchmark, BenchmarkConfig +from guidellm.benchmark.schemas.generative.accumulator import ( + GenerativeBenchmarkAccumulator, +) +from guidellm.benchmark.schemas.generative.metrics import ( + GenerativeMetrics, + SchedulerMetrics, +) +from guidellm.scheduler import SchedulerState +from guidellm.schemas import ( + GenerativeRequestStats, + StatusBreakdown, + StatusDistributionSummary, +) + +__all__ = ["GenerativeBenchmark"] + + +class GenerativeBenchmark(Benchmark[GenerativeBenchmarkAccumulator]): + """ + Complete generative AI benchmark results with specialized metrics. + + Encapsulates comprehensive performance data from scheduler-driven generative + workload executions including request-level statistics, token/latency distributions, + throughput analysis, and concurrency patterns. Provides computed fields for temporal + analysis and status-grouped request details for detailed post-execution reporting. + """ + + type_: Literal["generative_benchmark"] = "generative_benchmark" # type: ignore[assignment] + + config: BenchmarkConfig = Field( + description="Configuration parameters for this benchmark execution", + ) + scheduler_state: SchedulerState = Field( + description="Final state of the scheduler after benchmark completion", + ) + scheduler_metrics: SchedulerMetrics = Field( + description="Scheduler timing and performance statistics", + ) + metrics: GenerativeMetrics = Field( + description="Performance metrics and statistical distributions", + ) + requests: StatusBreakdown[ + list[GenerativeRequestStats], + list[GenerativeRequestStats], + list[GenerativeRequestStats], + None, + ] = Field( + description=( + "Request details grouped by status: successful, incomplete, errored" + ), + ) + + @computed_field # type: ignore[prop-decorator] + @property + def start_time(self) -> float: + """ + :return: Benchmark start time in seconds since epoch + """ + return self.scheduler_metrics.measure_start_time + + @computed_field # type: ignore[prop-decorator] + @property + def end_time(self) -> float: + """ + :return: Benchmark end time in seconds since epoch + """ + return self.scheduler_metrics.measure_end_time + + @computed_field # type: ignore[prop-decorator] + @property + def duration(self) -> float: + """ + :return: Total benchmark execution duration in seconds + """ + return self.end_time - self.start_time + + @computed_field # type: ignore[prop-decorator] + @property + def warmup_duration(self) -> float: + """ + :return: Warmup phase duration in seconds + """ + return ( + self.scheduler_metrics.measure_start_time + - self.scheduler_metrics.request_start_time + ) + + @computed_field # type: ignore[prop-decorator] + @property + def cooldown_duration(self) -> float: + """ + :return: Cooldown phase duration in seconds + """ + return ( + self.scheduler_metrics.request_end_time + - self.scheduler_metrics.measure_end_time + ) + + @property + def request_latency(self) -> StatusDistributionSummary: + """ + :return: Statistical distribution of request latencies across all requests + """ + return self.metrics.request_latency + + @property + def request_throughput(self) -> StatusDistributionSummary: + """ + :return: Statistical distribution of throughput measured in requests per second + """ + return self.metrics.requests_per_second + + @property + def request_concurrency(self) -> StatusDistributionSummary: + """ + :return: Statistical distribution of concurrent requests throughout execution + """ + return self.metrics.request_concurrency + + @classmethod + def compile( + cls, + accumulator: GenerativeBenchmarkAccumulator, + scheduler_state: SchedulerState, + ) -> GenerativeBenchmark: + """ + Compile final benchmark results from accumulated execution state. + + :param accumulator: Accumulated benchmark state with request statistics + :param scheduler_state: Final scheduler state after execution completion + :return: Compiled generative benchmark instance with complete metrics + """ + return GenerativeBenchmark( + config=accumulator.config, + scheduler_state=scheduler_state, + scheduler_metrics=SchedulerMetrics.compile(accumulator, scheduler_state), + metrics=GenerativeMetrics.compile(accumulator), + requests=StatusBreakdown( + successful=accumulator.completed.get_sampled(), + incomplete=accumulator.incomplete.get_sampled(), + errored=accumulator.errored.get_sampled(), + total=None, + ), + ) diff --git a/src/guidellm/benchmark/schemas/generative/entrypoints.py b/src/guidellm/benchmark/schemas/generative/entrypoints.py new file mode 100644 index 00000000..3a77cab8 --- /dev/null +++ b/src/guidellm/benchmark/schemas/generative/entrypoints.py @@ -0,0 +1,375 @@ +""" +Configuration entrypoints for generative text benchmark execution. + +Defines parameter schemas and construction logic for creating benchmark runs from +scenario files or runtime arguments. Provides flexible configuration loading with +support for built-in scenarios, custom YAML/JSON files, and programmatic overrides. +Handles serialization of complex types including backends, processors, and profiles +for persistent storage and reproduction of benchmark configurations. +""" + +from __future__ import annotations + +import inspect +import json +from collections.abc import Callable +from pathlib import Path +from typing import Any, Literal + +import yaml +from pydantic import ( + AliasChoices, + AliasGenerator, + ConfigDict, + Field, + ValidationError, + ValidatorFunctionWrapHandler, + field_validator, + model_serializer, +) +from torch.utils.data import Sampler +from transformers import PreTrainedTokenizerBase + +from guidellm.backends import Backend, BackendType +from guidellm.benchmark.profiles import Profile, ProfileType +from guidellm.benchmark.scenarios import get_builtin_scenarios +from guidellm.benchmark.schemas.base import TransientPhaseConfig +from guidellm.data import DatasetPreprocessor, RequestFormatter +from guidellm.scheduler import StrategyType +from guidellm.schemas import StandardBaseModel + +__all__ = ["BenchmarkGenerativeTextArgs"] + + +class BenchmarkGenerativeTextArgs(StandardBaseModel): + """ + Configuration arguments for generative text benchmark execution. + + Defines all parameters for benchmark setup including target endpoint, data + sources, backend configuration, processing pipeline, output formatting, and + execution constraints. Supports loading from scenario files and merging with + runtime overrides for flexible benchmark construction from multiple sources. + + Example:: + + # Load from built-in scenario with overrides + args = BenchmarkGenerativeTextArgs.create( + scenario="chat", + target="http://localhost:8000/v1", + max_requests=1000 + ) + + # Create from keyword arguments only + args = BenchmarkGenerativeTextArgs( + target="http://localhost:8000/v1", + data=["path/to/dataset.json"], + profile="fixed", + rate=10.0 + ) + """ + + @classmethod + def create( + cls, scenario: Path | str | None, **kwargs: dict[str, Any] + ) -> BenchmarkGenerativeTextArgs: + """ + Create benchmark args from scenario file and keyword arguments. + + Loads base configuration from scenario file (built-in or custom) and merges + with provided keyword arguments. Arguments explicitly set via kwargs override + scenario values, while defaulted kwargs are ignored to preserve scenario + settings. + + :param scenario: Path to scenario file, built-in scenario name, or None + :param kwargs: Keyword arguments to override scenario values + :return: Configured benchmark args instance + :raises ValueError: If scenario is not found or file format is unsupported + """ + constructor_kwargs = {} + + if scenario is not None: + if isinstance(scenario, str) and scenario in ( + builtin_scenarios := get_builtin_scenarios() + ): + scenario_path = builtin_scenarios[scenario] + elif Path(scenario).exists() and Path(scenario).is_file(): + scenario_path = Path(scenario) + else: + raise ValueError(f"Scenario '{scenario}' not found.") + + with scenario_path.open() as file: + if scenario_path.suffix == ".json": + scenario_data = json.load(file) + elif scenario_path.suffix in {".yaml", ".yml"}: + scenario_data = yaml.safe_load(file) + else: + raise ValueError( + f"Unsupported scenario file format: {scenario_path.suffix}" + ) + if "args" in scenario_data: + # loading from a report file + scenario_data = scenario_data["args"] + constructor_kwargs.update(scenario_data) + + # Apply overrides from kwargs + constructor_kwargs.update(kwargs) + + return cls.model_validate(constructor_kwargs) + + @classmethod + def get_default(cls: type[BenchmarkGenerativeTextArgs], field: str) -> Any: + """ + Retrieve default value for a model field. + + Extracts the default value from field metadata, handling both static defaults + and factory functions. + + :param field: Field name to retrieve default value for + :return: Default value for the field + :raises ValueError: If field does not exist + """ + if field not in cls.model_fields: + raise ValueError(f"Field '{field}' not found in {cls.__name__}") + + field_info = cls.model_fields[field] + factory = field_info.default_factory + + if factory is None: + return field_info.default + + if len(inspect.signature(factory).parameters) == 0: + return factory() # type: ignore[call-arg] + else: + return factory({}) # type: ignore[call-arg] + + model_config = ConfigDict( + extra="ignore", + use_enum_values=True, + from_attributes=True, + arbitrary_types_allowed=True, + validate_by_alias=True, + validate_by_name=True, + alias_generator=AliasGenerator( + # Support field names with hyphens + validation_alias=lambda field_name: AliasChoices( + field_name, field_name.replace("_", "-") + ), + ), + ) + + # Required + target: str = Field(description="Target endpoint URL for benchmark execution") + data: list[Any] = Field( + description="List of dataset sources or data files", + default_factory=list, + min_length=1, + ) + # Benchmark configuration + profile: StrategyType | ProfileType | Profile = Field( + default="sweep", description="Benchmark profile or scheduling strategy type" + ) + rate: list[float] | None = Field( + default=None, description="Request rate(s) for rate-based scheduling" + ) + # Backend configuration + backend: BackendType | Backend = Field( + default="openai_http", description="Backend type or instance for execution" + ) + backend_kwargs: dict[str, Any] | None = Field( + default=None, description="Additional backend configuration arguments" + ) + model: str | None = Field(default=None, description="Model identifier for backend") + # Data configuration + processor: str | Path | PreTrainedTokenizerBase | None = Field( + default=None, description="Tokenizer path, name, or instance for processing" + ) + processor_args: dict[str, Any] | None = Field( + default=None, description="Additional tokenizer configuration arguments" + ) + data_args: list[dict[str, Any]] | None = Field( + default_factory=list, # type: ignore[arg-type] + description="Per-dataset configuration arguments", + ) + data_samples: int = Field( + default=-1, description="Number of samples to use from datasets (-1 for all)" + ) + data_column_mapper: ( + DatasetPreprocessor + | dict[str, str | list[str]] + | Literal["generative_column_mapper"] + ) = Field( + default="generative_column_mapper", + description="Column mapping preprocessor for dataset fields", + ) + data_request_formatter: RequestFormatter | dict[str, Any] | str = Field( + default="chat_completions", + description="Request formatting preprocessor or template name", + validation_alias=AliasChoices( + "data_request_formatter", + "data-request-formatter", + "request_type", + "request-type", + ), + ) + data_collator: Callable | Literal["generative"] | None = Field( + default="generative", description="Data collator for batch processing" + ) + data_sampler: Sampler[int] | Literal["shuffle"] | None = Field( + default=None, description="Data sampler for request ordering" + ) + data_num_workers: int | None = Field( + default=1, description="Number of workers for data loading" + ) + dataloader_kwargs: dict[str, Any] | None = Field( + default=None, description="Additional dataloader configuration arguments" + ) + random_seed: int = Field(default=42, description="Random seed for reproducibility") + # Output configuration + output_path: str | Path | None = Field( + default_factory=Path.cwd, description="Directory path for output files" + ) + output_formats: list[str] | dict[str, str | dict[str, Any]] | None = Field( + default_factory=lambda: ["console", "json", "csv"], + description="Output format names or configuration mappings", + ) + # Benchmarker configuration + sample_requests: int | None = Field( + default=10, + description="Number of requests to sample for detailed metrics (None for all)", + ) + warmup: int | float | dict | TransientPhaseConfig | None = Field( + default=None, + description=( + "Warmup phase config: time or requests before measurement starts " + "(overlapping requests count toward measurement)" + ), + ) + cooldown: int | float | dict | TransientPhaseConfig | None = Field( + default=None, + description=( + "Cooldown phase config: time or requests after measurement ends " + "(overlapping requests count toward measurement)" + ), + ) + rampup: int | float | dict | TransientPhaseConfig | None = Field( + default=None, + description=( + "Ramp-up phase config: time to gradually increase load " + "(Throughput/Concurrent strategies only, not Synchronous/Rate)" + ), + ) + prefer_response_metrics: bool = Field( + default=True, + description="Whether to prefer backend response metrics over request metrics", + ) + # Constraints configuration + max_seconds: int | float | None = Field( + default=None, description="Maximum benchmark execution time in seconds" + ) + max_requests: int | None = Field( + default=None, description="Maximum number of requests to execute" + ) + max_errors: int | None = Field( + default=None, description="Maximum number of errors before stopping" + ) + max_error_rate: float | None = Field( + default=None, description="Maximum error rate (0-1) before stopping" + ) + max_global_error_rate: float | None = Field( + default=None, description="Maximum global error rate (0-1) before stopping" + ) + + @field_validator("data", "data_args", "rate", mode="wrap") + @classmethod + def single_to_list( + cls, value: Any, handler: ValidatorFunctionWrapHandler + ) -> list[Any]: + """ + Ensures field is always a list. + + :param value: Input value for the 'data' field + :return: List of data sources + """ + try: + return handler(value) + except ValidationError as err: + # If validation fails, try wrapping the value in a list + if err.errors()[0]["type"] == "list_type": + return handler([value]) + else: + raise + + @model_serializer + def serialize_model(self) -> dict[str, Any]: + """ + Convert model to serializable dictionary format. + + Transforms complex types (Backend, Profile, Path, etc.) to JSON-compatible + primitives while preserving configuration semantics for storage and + reproduction. + + :return: Dictionary representation for JSON/YAML serialization + """ + return { + # target - serialize as is + "target": self.target, + "data": [ + item if isinstance(item, str | type(None)) else str(item) + for item in self.data + ], # data - for each item in the list, if not a str or None, save str(item) + "profile": ( + self.profile.type_ + if isinstance(self.profile, Profile) + else self.profile + ), # profile - if instance of Profile, then save as profile.type_ + "rate": self.rate, + "backend": ( + self.backend.type_ + if isinstance(self.backend, Backend) + else self.backend + ), # backend - if instance of Backend, then save as backend.type_ + "backend_kwargs": self.backend_kwargs, + "model": self.model, + "processor": ( + self.processor + if isinstance(self.processor, str) + else str(self.processor) + if self.processor is not None + else None + ), # processor - if not str, then save as str(processor) + "processor_args": self.processor_args, + "data_args": self.data_args, + "data_samples": self.data_samples, + "data_column_mapper": ( + self.data_column_mapper + if isinstance(self.data_column_mapper, dict | str) + else {} + ), # data_column_mapper - if not dict or str, then save as an empty dict + "data_request_formatter": ( + self.data_request_formatter + if isinstance(self.data_request_formatter, dict | str) + else {} + ), # data_request_formatter - if not dict or str, then save as empty dict + "data_collator": ( + self.data_collator if isinstance(self.data_collator, str) else None + ), # data_collator - if not str, then save as None + "data_sampler": ( + self.data_sampler if isinstance(self.data_sampler, str) else None + ), # data_sampler - if not str, then save as None + "data_num_workers": self.data_num_workers, + "dataloader_kwargs": self.dataloader_kwargs, + "random_seed": self.random_seed, + "output_path": ( + str(self.output_path) if self.output_path is not None else None + ), # output_path - if not None, then ensure it's a str + "output_formats": self.output_formats, + "sample_requests": self.sample_requests, + "warmup": self.warmup, + "cooldown": self.cooldown, + "prefer_response_metrics": self.prefer_response_metrics, + "max_seconds": self.max_seconds, + "max_requests": self.max_requests, + "max_errors": self.max_errors, + "max_error_rate": self.max_error_rate, + "max_global_error_rate": self.max_global_error_rate, + } diff --git a/src/guidellm/benchmark/schemas/generative/metrics.py b/src/guidellm/benchmark/schemas/generative/metrics.py new file mode 100644 index 00000000..f133007d --- /dev/null +++ b/src/guidellm/benchmark/schemas/generative/metrics.py @@ -0,0 +1,936 @@ +""" +Metrics schemas for generative AI benchmark results and performance analysis. + +This module defines comprehensive metric structures for tracking and analyzing +generative AI benchmark performance across multiple dimensions including request +statistics, token metrics, and domain-specific measurements for text, image, video, +and audio generation. It provides statistical summaries with distribution analysis +across successful, incomplete, and errored requests, along with scheduler-level +performance metrics for request processing and queueing behavior. +""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import Field + +from guidellm.benchmark.schemas.generative.accumulator import ( + GenerativeBenchmarkAccumulator, +) +from guidellm.scheduler import SchedulerState +from guidellm.schemas import ( + GenerativeRequestStats, + StandardBaseDict, + StatusBreakdown, + StatusDistributionSummary, +) + +__all__ = [ + "GenerativeAudioMetricsSummary", + "GenerativeImageMetricsSummary", + "GenerativeMetrics", + "GenerativeMetricsSummary", + "GenerativeTextMetricsSummary", + "GenerativeVideoMetricsSummary", + "SchedulerMetrics", + "StatusTypes", + "TimedMetricTypeAlias", +] + + +TimedMetricTypeAlias = ( + tuple[float, float, int | float | None, int | float | None] | None +) +"""Timed metric tuple containing start_time, end_time, input_value, and output_value.""" + +StatusTypes = Literal["successful", "incomplete", "errored"] +"""Request status category for metric compilation.""" + +# Constants for tuple indexing +_TIMED_METRIC_START_TIME_INDEX = 0 +_TIMED_METRIC_END_TIME_INDEX = 1 +_TIMED_METRIC_INPUT_VALUE_INDEX = 2 +_TIMED_METRIC_OUTPUT_VALUE_INDEX = 3 + + +class SchedulerMetrics(StandardBaseDict): + """ + Scheduler timing and performance statistics. + + Tracks overall benchmark timing, request counts by status, and detailed internal + scheduler performance metrics including queue times, processing delays, and + request execution statistics. Used to analyze scheduler efficiency and identify + bottlenecks in request processing pipelines. + """ + + # Overall timings for the scheduler + start_time: float = Field( + description="Unix timestamp when the benchmark run started" + ) + request_start_time: float = Field( + description="Unix timestamp when first request was made" + ) + measure_start_time: float = Field( + description="Unix timestamp when measurement period started" + ) + measure_end_time: float = Field( + description="Unix timestamp when measurement period ended" + ) + request_end_time: float = Field( + description="Unix timestamp when last request completed" + ) + end_time: float = Field(description="Unix timestamp when the benchmark run ended") + + # Request details tracked by the scheduler + requests_made: StatusBreakdown[int, int, int, int] = Field( + description="Request counts by status: successful, incomplete, errored, total" + ) + + # Scheduler internal performance timings + queued_time_avg: float = Field( + description="Avg time requests spent in the queue (seconds)" + ) + resolve_start_delay_avg: float = Field( + description="Avg delay before worker begins resolving req after dequeue (sec)" + ) + resolve_targeted_start_delay_avg: float = Field( + description="Avg delay to targeted resolve start time (seconds)" + ) + request_start_delay_avg: float = Field( + description="Avg delay before request starts after resolve (seconds)" + ) + request_targeted_start_delay_avg: float = Field( + description="Avg delay to targeted request start time (seconds)" + ) + request_time_avg: float = Field(description="Avg request execution time (seconds)") + resolve_end_delay_avg: float = Field( + description="Avg delay after request completes before resolve ends (seconds)" + ) + resolve_time_avg: float = Field( + description="Avg total resolve time including request (seconds)" + ) + finalized_delay_avg: float = Field( + description="Avg delay from resolve end to request finalization (seconds)" + ) + processed_delay_avg: float = Field( + description="Avg delay from finalization to processing completion (seconds)" + ) + + @classmethod + def compile( + cls, + accumulator: GenerativeBenchmarkAccumulator, + scheduler_state: SchedulerState, + ) -> SchedulerMetrics: + """ + Compile scheduler metrics from accumulator and scheduler state. + + :param accumulator: Benchmark accumulator containing timing and metric data + :param scheduler_state: Scheduler state with execution timing information + :return: Compiled scheduler metrics with performance statistics + """ + return SchedulerMetrics( + # Overall timings for the scheduler + start_time=scheduler_state.start_time, + request_start_time=accumulator.timings.request_start or -1.0, + measure_start_time=accumulator.timings.measure_start or -1.0, + measure_end_time=( + accumulator.timings.measure_end + if accumulator.timings.measure_end is not None + and accumulator.timings.measure_end != -1.0 + else accumulator.timings.request_end or -1.0 + ), # if no cooldown, measure_end isn't set, use request_end + request_end_time=accumulator.timings.request_end or -1.0, + end_time=scheduler_state.end_time or -1.0, + # Request details tracked by the scheduler + requests_made=accumulator.scheduler_metrics.requests_made, + # Scheduler internal performance timings + queued_time_avg=accumulator.scheduler_metrics.queued_time.mean or -1.0, + resolve_start_delay_avg=( + accumulator.scheduler_metrics.resolve_start_delay.mean or -1.0 + ), + resolve_targeted_start_delay_avg=( + accumulator.scheduler_metrics.resolve_targeted_start_delay.mean or -1.0 + ), + request_start_delay_avg=( + accumulator.scheduler_metrics.request_start_delay.mean or -1.0 + ), + request_targeted_start_delay_avg=( + accumulator.scheduler_metrics.request_targeted_start_delay.mean or -1.0 + ), + request_time_avg=accumulator.scheduler_metrics.request_time.mean or -1.0, + resolve_end_delay_avg=( + accumulator.scheduler_metrics.resolve_end_delay.mean or -1.0 + ), + resolve_time_avg=accumulator.scheduler_metrics.resolve_time.mean or -1.0, + finalized_delay_avg=( + accumulator.scheduler_metrics.finalized_delay.mean or -1.0 + ), + processed_delay_avg=( + accumulator.scheduler_metrics.processed_delay.mean or -1.0 + ), + ) + + +class GenerativeMetricsSummary(StandardBaseDict): + """ + Statistical summaries for input, output, and total metrics. + + Provides distribution summaries across successful, incomplete, and errored + requests for absolute values, per-second rates, and concurrency levels. + """ + + input: StatusDistributionSummary | None = Field( + description="Distribution of input metric values" + ) + input_per_second: StatusDistributionSummary | None = Field( + description="Distribution of input metric rates per second" + ) + input_concurrency: StatusDistributionSummary | None = Field( + description="Distribution of concurrent input metric values" + ) + + output: StatusDistributionSummary | None = Field( + description="Distribution of output metric values" + ) + output_per_second: StatusDistributionSummary | None = Field( + description="Distribution of output metric rates per second" + ) + output_concurrency: StatusDistributionSummary | None = Field( + description="Distribution of concurrent output metric values" + ) + + total: StatusDistributionSummary | None = Field( + description="Distribution of total metric values (input + output)" + ) + total_per_second: StatusDistributionSummary | None = Field( + description="Distribution of total metric rates per second" + ) + total_concurrency: StatusDistributionSummary | None = Field( + description="Distribution of concurrent total metric values" + ) + + @classmethod + def compile( + cls, + property_name: str, + successful: list[GenerativeRequestStats], + incomplete: list[GenerativeRequestStats], + errored: list[GenerativeRequestStats], + ) -> GenerativeMetricsSummary | None: + """ + Compile metrics summary from request statistics for a specific property. + + :param property_name: Name of the property to extract from request metrics + :param successful: Successfully completed request statistics + :param incomplete: Incomplete or cancelled request statistics + :param errored: Failed request statistics + :return: Compiled metrics summary or None if no data available + """ + successful_metrics = cls.extract_property_metrics_for_summary( + successful, property_name + ) + incomplete_metrics = cls.extract_property_metrics_for_summary( + incomplete, property_name + ) + errored_metrics = cls.extract_property_metrics_for_summary( + errored, property_name + ) + + return cls.compile_timed_metrics( + successful=successful_metrics, + incomplete=incomplete_metrics, + errored=errored_metrics, + ) + + @classmethod + def compile_timed_metrics( + cls, + successful: list[TimedMetricTypeAlias], + incomplete: list[TimedMetricTypeAlias], + errored: list[TimedMetricTypeAlias], + ) -> GenerativeMetricsSummary | None: + """ + Compile metrics summary from timed metric tuples. + + :param successful: Timed metrics from successful requests + :param incomplete: Timed metrics from incomplete requests + :param errored: Timed metrics from errored requests + :return: Compiled metrics summary or None if no data available + """ + + def _compile_metric_distributions( + metrics_by_status: dict[StatusTypes, list[TimedMetricTypeAlias]], + value_index: int, + ) -> tuple[ + StatusDistributionSummary | None, + StatusDistributionSummary | None, + StatusDistributionSummary | None, + dict[StatusTypes, list[float]], + dict[StatusTypes, list[tuple[float, float]]], + dict[StatusTypes, list[tuple[float, float, float]]], + ]: + """Helper to compile value, rate, and concurrency distributions.""" + value_lists: dict[StatusTypes, list[float]] = { + status: [ + float(metric[value_index] or 0.0) + for metric in metrics + if metric is not None + ] + for status, metrics in metrics_by_status.items() + } + value_dist = StatusDistributionSummary.from_values( + successful=value_lists["successful"], + incomplete=value_lists["incomplete"], + errored=value_lists["errored"], + ) + + if value_dist.total_sum == 0.0: + return None, None, None, value_lists, {}, {} + + rate_lists: dict[StatusTypes, list[tuple[float, float]]] = { + status: [ + ( # type: ignore[misc] + metric[_TIMED_METRIC_END_TIME_INDEX], + float(metric[value_index] or 0.0), + ) + for metric in metrics + if metric is not None + ] + for status, metrics in metrics_by_status.items() + } + rate_dist = StatusDistributionSummary.rate_distribution_from_timings( + successful=rate_lists["successful"], + incomplete=rate_lists["incomplete"], + errored=rate_lists["errored"], + ) + + concurrency_lists: dict[StatusTypes, list[tuple[float, float, float]]] = { + status: [ + ( # type: ignore[misc] + metric[_TIMED_METRIC_START_TIME_INDEX], + metric[_TIMED_METRIC_END_TIME_INDEX], + float(metric[value_index] or 0.0), + ) + for metric in metrics + if metric is not None + ] + for status, metrics in metrics_by_status.items() + } + concurrency_dist = ( + StatusDistributionSummary.concurrency_distribution_from_timings( + successful=concurrency_lists["successful"], + incomplete=concurrency_lists["incomplete"], + errored=concurrency_lists["errored"], + ) + ) + + return ( + value_dist, + rate_dist, + concurrency_dist, + value_lists, + rate_lists, + concurrency_lists, + ) + + metrics_by_status: dict[StatusTypes, list[TimedMetricTypeAlias]] = { + "successful": successful, + "incomplete": incomplete, + "errored": errored, + } + + # Calculate input distributions + ( + input_value_dist, + input_rate_dist, + input_concurrency_dist, + input_value_lists, + input_rate_lists, + input_concurrency_lists, + ) = _compile_metric_distributions( + metrics_by_status, _TIMED_METRIC_INPUT_VALUE_INDEX + ) + + # Calculate output distributions + ( + output_value_dist, + output_rate_dist, + output_concurrency_dist, + output_value_lists, + output_rate_lists, + output_concurrency_lists, + ) = _compile_metric_distributions( + metrics_by_status, _TIMED_METRIC_OUTPUT_VALUE_INDEX + ) + + # Calculate total distributions if both input and output have data + if input_value_dist is not None and output_value_dist is not None: + total_value_dist = StatusDistributionSummary.from_values( + successful=( + input_value_lists["successful"] + output_value_lists["successful"] + ), + incomplete=( + input_value_lists["incomplete"] + output_value_lists["incomplete"] + ), + errored=input_value_lists["errored"] + output_value_lists["errored"], + ) + total_rate_dist = StatusDistributionSummary.rate_distribution_from_timings( + successful=( + input_rate_lists["successful"] + output_rate_lists["successful"] + ), + incomplete=( + input_rate_lists["incomplete"] + output_rate_lists["incomplete"] + ), + errored=input_rate_lists["errored"] + output_rate_lists["errored"], + ) + total_concurrency_dist = ( + StatusDistributionSummary.concurrency_distribution_from_timings( + successful=( + input_concurrency_lists["successful"] + + output_concurrency_lists["successful"] + ), + incomplete=( + input_concurrency_lists["incomplete"] + + output_concurrency_lists["incomplete"] + ), + errored=( + input_concurrency_lists["errored"] + + output_concurrency_lists["errored"] + ), + ) + ) + else: + total_value_dist = None + total_rate_dist = None + total_concurrency_dist = None + + return GenerativeMetricsSummary( + input=input_value_dist, + input_per_second=input_rate_dist, + input_concurrency=input_concurrency_dist, + output=output_value_dist, + output_per_second=output_rate_dist, + output_concurrency=output_concurrency_dist, + total=total_value_dist, + total_per_second=total_rate_dist, + total_concurrency=total_concurrency_dist, + ) + + @classmethod + def extract_property_metrics_for_summary( + cls, stats_list: list[GenerativeRequestStats], property_name: str + ) -> list[TimedMetricTypeAlias]: + """ + Extract timed metrics for a specific property from request statistics. + + :param stats_list: List of request statistics to extract from + :param property_name: Name of the property to extract from metrics + :return: List of tuples containing + (start_time, end_time, input_value, output_value) + """ + return [ + ( + stats.request_start_time, + stats.request_end_time, + getattr(stats.input_metrics, property_name), + getattr(stats.output_metrics, property_name), + ) + for stats in stats_list + if ( + stats.request_start_time + and stats.request_end_time + and ( + getattr(stats.input_metrics, property_name) is not None + or getattr(stats.output_metrics, property_name) is not None + ) + ) + ] + + +class GenerativeTextMetricsSummary(StandardBaseDict): + """ + Text-specific metric summaries for generative benchmarks. + + Tracks token, word, and character-level metrics across input, output, and + total usage for text generation workloads. + """ + + tokens: GenerativeMetricsSummary | None = Field( + description="Token count metrics and distributions" + ) + words: GenerativeMetricsSummary | None = Field( + description="Word count metrics and distributions" + ) + characters: GenerativeMetricsSummary | None = Field( + description="Character count metrics and distributions" + ) + + @classmethod + def compile( + cls, + successful: list[GenerativeRequestStats], + incomplete: list[GenerativeRequestStats], + errored: list[GenerativeRequestStats], + ) -> GenerativeTextMetricsSummary: + """ + Compile text metrics summary from request statistics. + + :param successful: Successfully completed request statistics + :param incomplete: Incomplete/cancelled request statistics + :param errored: Failed request statistics + :return: Compiled text metrics summary + """ + return GenerativeTextMetricsSummary( + tokens=GenerativeMetricsSummary.compile( + property_name="text_tokens", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + words=GenerativeMetricsSummary.compile( + property_name="text_words", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + characters=GenerativeMetricsSummary.compile( + property_name="text_characters", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + ) + + +class GenerativeImageMetricsSummary(StandardBaseDict): + """ + Image-specific metric summaries for generative benchmarks. + + Tracks token, image count, pixel, and byte-level metrics across input, output, + and total usage for image generation workloads. + """ + + tokens: GenerativeMetricsSummary | None = Field( + description="Image token count metrics and distributions" + ) + images: GenerativeMetricsSummary | None = Field( + description="Image count metrics and distributions" + ) + pixels: GenerativeMetricsSummary | None = Field( + description="Pixel count metrics and distributions" + ) + bytes: GenerativeMetricsSummary | None = Field( + description="Byte size metrics and distributions" + ) + + @classmethod + def compile( + cls, + successful: list[GenerativeRequestStats], + incomplete: list[GenerativeRequestStats], + errored: list[GenerativeRequestStats], + ) -> GenerativeImageMetricsSummary: + """ + Compile image metrics summary from request statistics. + + :param successful: Successfully completed request statistics + :param incomplete: Incomplete/cancelled request statistics + :param errored: Failed request statistics + :return: Compiled image metrics summary + """ + return GenerativeImageMetricsSummary( + tokens=GenerativeMetricsSummary.compile( + property_name="image_tokens", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + images=GenerativeMetricsSummary.compile( + property_name="image_count", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + pixels=GenerativeMetricsSummary.compile( + property_name="image_pixels", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + bytes=GenerativeMetricsSummary.compile( + property_name="image_bytes", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + ) + + +class GenerativeVideoMetricsSummary(StandardBaseDict): + """ + Video-specific metric summaries for generative benchmarks. + + Tracks token, frame count, duration, and byte-level metrics across input, + output, and total usage for video generation workloads. + """ + + tokens: GenerativeMetricsSummary | None = Field( + description="Video token count metrics and distributions" + ) + frames: GenerativeMetricsSummary | None = Field( + description="Frame count metrics and distributions" + ) + seconds: GenerativeMetricsSummary | None = Field( + description="Duration metrics in seconds and distributions" + ) + bytes: GenerativeMetricsSummary | None = Field( + description="Byte size metrics and distributions" + ) + + @classmethod + def compile( + cls, + successful: list[GenerativeRequestStats], + incomplete: list[GenerativeRequestStats], + errored: list[GenerativeRequestStats], + ) -> GenerativeVideoMetricsSummary: + """ + Compile video metrics summary from request statistics. + + :param successful: Successfully completed request statistics + :param incomplete: Incomplete/cancelled request statistics + :param errored: Failed request statistics + :return: Compiled video metrics summary + """ + return GenerativeVideoMetricsSummary( + tokens=GenerativeMetricsSummary.compile( + property_name="video_tokens", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + frames=GenerativeMetricsSummary.compile( + property_name="video_frames", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + seconds=GenerativeMetricsSummary.compile( + property_name="video_seconds", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + bytes=GenerativeMetricsSummary.compile( + property_name="video_bytes", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + ) + + +class GenerativeAudioMetricsSummary(StandardBaseDict): + """ + Audio-specific metric summaries for generative benchmarks. + + Tracks token, sample count, duration, and byte-level metrics across input, + output, and total usage for audio generation workloads. + """ + + tokens: GenerativeMetricsSummary | None = Field( + description="Audio token count metrics and distributions" + ) + samples: GenerativeMetricsSummary | None = Field( + description="Sample count metrics and distributions" + ) + seconds: GenerativeMetricsSummary | None = Field( + description="Duration metrics in seconds and distributions" + ) + bytes: GenerativeMetricsSummary | None = Field( + description="Byte size metrics and distributions" + ) + + @classmethod + def compile( + cls, + successful: list[GenerativeRequestStats], + incomplete: list[GenerativeRequestStats], + errored: list[GenerativeRequestStats], + ) -> GenerativeAudioMetricsSummary: + """ + Compile audio metrics summary from request statistics. + + :param successful: Successfully completed request statistics + :param incomplete: Incomplete/cancelled request statistics + :param errored: Failed request statistics + :return: Compiled audio metrics summary + """ + return GenerativeAudioMetricsSummary( + tokens=GenerativeMetricsSummary.compile( + property_name="audio_tokens", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + samples=GenerativeMetricsSummary.compile( + property_name="audio_samples", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + seconds=GenerativeMetricsSummary.compile( + property_name="audio_seconds", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + bytes=GenerativeMetricsSummary.compile( + property_name="audio_bytes", + successful=successful, + incomplete=incomplete, + errored=errored, + ), + ) + + +class GenerativeMetrics(StandardBaseDict): + """ + Comprehensive metrics for generative AI benchmarks. + + Aggregates request statistics, token metrics, timing distributions, and + domain-specific measurements across text, image, video, and audio modalities. + Provides detailed statistical summaries including distribution analysis for + throughput, latency, concurrency, and resource utilization metrics across + successful, incomplete, and errored requests. + """ + + # Request stats + request_totals: StatusBreakdown[int, int, int, int] = Field( + description="Request counts by status: successful, incomplete, errored, total" + ) + requests_per_second: StatusDistributionSummary = Field( + description="Distribution of requests per second across benchmark execution" + ) + request_concurrency: StatusDistributionSummary = Field( + description="Distribution of concurrent request counts during execution" + ) + request_latency: StatusDistributionSummary = Field( + description="Distribution of request latencies for completed requests" + ) + request_streaming_iterations_count: StatusDistributionSummary = Field( + description="Distribution of stream iterations for completed requests" + ) + + # General token stats + prompt_token_count: StatusDistributionSummary = Field( + description="Distribution of prompt token counts by request status" + ) + output_token_count: StatusDistributionSummary = Field( + description="Distribution of output token counts by request status" + ) + total_token_count: StatusDistributionSummary = Field( + description="Distribution of total token counts by request status" + ) + time_to_first_token_ms: StatusDistributionSummary = Field( + description="Distribution of first token latencies in milliseconds" + ) + time_per_output_token_ms: StatusDistributionSummary = Field( + description="Distribution of average time per output token in milliseconds" + ) + inter_token_latency_ms: StatusDistributionSummary = Field( + description="Distribution of inter-token latencies in milliseconds" + ) + prompt_tokens_per_second: StatusDistributionSummary = Field( + description="Distribution of prompt token processing rates" + ) + output_tokens_per_second: StatusDistributionSummary = Field( + description="Distribution of output token generation rates" + ) + tokens_per_second: StatusDistributionSummary = Field( + description="Distribution of total token throughput including prompt and output" + ) + output_tokens_per_iteration: StatusDistributionSummary = Field( + description="Distribution of output tokens generated per streaming iteration" + ) + iter_tokens_per_iteration: StatusDistributionSummary = Field( + description=( + "Distribution of output tokens (without first) generated per " + "streaming iteration" + ) + ) + + # Domain specific stats + text: GenerativeTextMetricsSummary = Field( + description="Text-specific metrics for tokens, words, and characters" + ) + image: GenerativeImageMetricsSummary = Field( + description="Image-specific metrics for tokens, images, pixels, and bytes" + ) + video: GenerativeVideoMetricsSummary = Field( + description="Video-specific metrics for tokens, frames, duration, and bytes" + ) + audio: GenerativeAudioMetricsSummary = Field( + description="Audio-specific metrics for tokens, samples, duration, and bytes" + ) + + @classmethod + def compile(cls, accumulator: GenerativeBenchmarkAccumulator) -> GenerativeMetrics: + """ + Compile comprehensive generative metrics from benchmark accumulator. + + :param accumulator: Benchmark accumulator with completed request statistics + :return: Compiled generative metrics with all distributions and summaries + :raises ValueError: If measure_start and measure_end/request_end are not set + """ + start_time = accumulator.timings.measure_start + end_time = ( + accumulator.timings.measure_end + if accumulator.timings.measure_end != -1.0 + else accumulator.timings.request_end + ) + + if start_time is None or end_time is None: + raise ValueError( + "Cannot compile GenerativeMetrics: " + "measure_start and measure_end/request_end must be set" + ) + + successful = accumulator.completed.get_within_range(start_time, end_time) + incomplete = accumulator.incomplete.get_within_range(start_time, end_time) + errored = accumulator.errored.get_within_range(start_time, end_time) + + return GenerativeMetrics( + # Request stats + request_totals=StatusBreakdown( + successful=len(successful), + incomplete=len(incomplete), + errored=len(errored), + total=(len(successful) + len(incomplete) + len(errored)), + ), + requests_per_second=StatusDistributionSummary.rate_distribution_from_timings_function( + function=lambda req: req.request_end_time, + successful=successful, + incomplete=incomplete, + errored=errored, + start_time=start_time, + end_time=end_time, + ), + request_concurrency=StatusDistributionSummary.concurrency_distribution_from_timings_function( + function=( + lambda req: (req.request_start_time, req.request_end_time) + if req.request_start_time is not None + and req.request_end_time is not None + else None + ), + successful=successful, + incomplete=incomplete, + errored=errored, + start_time=start_time, + end_time=end_time, + ), + request_latency=StatusDistributionSummary.from_values_function( + function=lambda req: req.request_latency or 0.0, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + request_streaming_iterations_count=StatusDistributionSummary.from_values_function( + function=lambda req: req.info.timings.request_iterations or 0.0, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + # General token stats + prompt_token_count=StatusDistributionSummary.from_values_function( + function=lambda req: req.prompt_tokens or 0.0, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + output_token_count=StatusDistributionSummary.from_values_function( + function=lambda req: req.output_tokens or 0.0, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + total_token_count=StatusDistributionSummary.from_values_function( + function=lambda req: req.total_tokens or 0.0, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + time_to_first_token_ms=StatusDistributionSummary.from_values_function( + function=lambda req: req.time_to_first_token_ms or 0.0, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + time_per_output_token_ms=StatusDistributionSummary.from_values_function( + function=lambda req: ( + req.time_per_output_token_ms or 0.0, + req.output_tokens or 0.0, + ), + successful=successful, + incomplete=incomplete, + errored=errored, + ), + inter_token_latency_ms=StatusDistributionSummary.from_values_function( + function=lambda req: ( + req.inter_token_latency_ms or 0.0, + (req.output_tokens or 1.0) - 1.0, + ), + successful=successful, + incomplete=incomplete, + errored=errored, + ), + prompt_tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function( + function=lambda req: req.prompt_tokens_timing, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + output_tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function( + function=lambda req: req.output_tokens_timings, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function( + function=lambda req: req.total_tokens_timings, + successful=successful, + incomplete=incomplete, + errored=errored, + ), + output_tokens_per_iteration=StatusDistributionSummary.from_values_function( + function=lambda req: [ + tokens for (_timing, tokens) in req.output_tokens_timings + ], + successful=successful, + incomplete=incomplete, + errored=errored, + ), + iter_tokens_per_iteration=StatusDistributionSummary.from_values_function( + function=lambda req: [ + tokens for (_timing, tokens) in req.iter_tokens_timings + ], + successful=successful, + incomplete=incomplete, + errored=errored, + ), + # Domain-specific stats + text=GenerativeTextMetricsSummary.compile( + successful=successful, incomplete=incomplete, errored=errored + ), + image=GenerativeImageMetricsSummary.compile( + successful=successful, incomplete=incomplete, errored=errored + ), + video=GenerativeVideoMetricsSummary.compile( + successful=successful, incomplete=incomplete, errored=errored + ), + audio=GenerativeAudioMetricsSummary.compile( + successful=successful, incomplete=incomplete, errored=errored + ), + ) diff --git a/src/guidellm/benchmark/schemas/generative/report.py b/src/guidellm/benchmark/schemas/generative/report.py new file mode 100644 index 00000000..16cc654b --- /dev/null +++ b/src/guidellm/benchmark/schemas/generative/report.py @@ -0,0 +1,125 @@ +""" +Report container for multiple generative benchmark results with persistence. + +Provides data structures for aggregating multiple benchmark executions into a single +report with file I/O capabilities. Supports loading and saving benchmark collections +in JSON and YAML formats, enabling result persistence, sharing, and analysis across +different execution sessions. Core functionality includes benchmark grouping with +shared configuration parameters and flexible file path resolution. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import ClassVar, Literal + +import yaml +from pydantic import Field + +from guidellm.benchmark.schemas.generative.benchmark import GenerativeBenchmark +from guidellm.benchmark.schemas.generative.entrypoints import ( + BenchmarkGenerativeTextArgs, +) +from guidellm.schemas import StandardBaseModel + +__all__ = ["GenerativeBenchmarksReport"] + + +class GenerativeBenchmarksReport(StandardBaseModel): + """ + Container for multiple benchmark results with load/save functionality. + + Aggregates multiple generative benchmark executions into a single report, + providing persistence through JSON and YAML file formats. Enables result + collection, storage, and retrieval across different execution sessions with + automatic file type detection and path resolution. + + :cvar DEFAULT_FILE: Default filename used when saving to or loading from a directory + """ + + DEFAULT_FILE: ClassVar[str] = "benchmarks.json" + + args: BenchmarkGenerativeTextArgs = Field( + description="Benchmark arguments used for all benchmarks in the report" + ) + benchmarks: list[GenerativeBenchmark] = Field( + description="List of completed benchmarks in the report", + default_factory=list, + ) + + def save_file( + self, + path: str | Path | None = None, + type_: Literal["json", "yaml"] | None = None, + ) -> Path: + """ + Save report to file in JSON or YAML format. + + :param path: File path or directory for saving, defaults to current directory + with DEFAULT_FILE name + :param type_: File format override ('json' or 'yaml'), auto-detected from + extension if None + :return: Resolved path to the saved file + :raises ValueError: If file type is unsupported or cannot be determined + """ + file_path = GenerativeBenchmarksReport._resolve_path( + path if path is not None else Path.cwd() + ) + file_path.parent.mkdir(parents=True, exist_ok=True) + file_type = type_ or file_path.suffix.lower()[1:] + model_dict = self.model_dump() + + if file_type == "json": + save_str = json.dumps(model_dict) + elif file_type in ["yaml", "yml"]: + save_str = yaml.dump(model_dict) + else: + raise ValueError(f"Unsupported file type: {file_type} for {file_path}.") + + with file_path.open("w") as file: + file.write(save_str) + + return file_path + + @classmethod + def load_file( + cls, path: str | Path, type_: Literal["json", "yaml"] | None = None + ) -> GenerativeBenchmarksReport: + """ + Load report from JSON or YAML file. + + :param path: File path or directory containing DEFAULT_FILE to load from + :param type_: File format override ('json' or 'yaml'), auto-detected from + extension if None + :return: Loaded report instance with benchmarks and configuration + :raises ValueError: If file type is unsupported or cannot be determined + :raises FileNotFoundError: If specified file does not exist + """ + file_path = GenerativeBenchmarksReport._resolve_path(path) + file_type = type_ or file_path.suffix.lower()[1:] + + with file_path.open("r") as file: + if file_type == "json": + model_dict = json.loads(file.read()) + elif file_type in ["yaml", "yml"]: + model_dict = yaml.safe_load(file) + else: + raise ValueError(f"Unsupported file type: {file_type} for {file_path}.") + + return GenerativeBenchmarksReport.model_validate(model_dict) + + @classmethod + def _resolve_path(cls, path: str | Path) -> Path: + """ + Resolve input to file path, converting directories to DEFAULT_FILE location. + + :param path: String or Path to resolve, directories append DEFAULT_FILE + :return: Resolved file path + """ + resolved = Path(path) if not isinstance(path, Path) else path + + if resolved.is_dir(): + resolved = resolved / GenerativeBenchmarksReport.DEFAULT_FILE + + return resolved diff --git a/src/guidellm/data/__init__.py b/src/guidellm/data/__init__.py index 0bff1b64..9adbd3c8 100644 --- a/src/guidellm/data/__init__.py +++ b/src/guidellm/data/__init__.py @@ -9,6 +9,7 @@ DataDependentPreprocessor, DatasetPreprocessor, PreprocessorRegistry, + RequestFormatter, ) from .processor import ProcessorFactory from .schemas import GenerativeDatasetColumnType @@ -25,4 +26,5 @@ "GenerativeRequestCollator", "PreprocessorRegistry", "ProcessorFactory", + "RequestFormatter", ] diff --git a/src/guidellm/data/deserializers/synthetic.py b/src/guidellm/data/deserializers/synthetic.py index e1df911a..6e098462 100644 --- a/src/guidellm/data/deserializers/synthetic.py +++ b/src/guidellm/data/deserializers/synthetic.py @@ -17,7 +17,8 @@ DatasetDeserializer, DatasetDeserializerFactory, ) -from guidellm.utils import IntegerRangeSampler, StandardBaseModel +from guidellm.schemas import StandardBaseModel +from guidellm.utils import IntegerRangeSampler __all__ = [ "SyntheticTextDatasetConfig", diff --git a/src/guidellm/data/loaders.py b/src/guidellm/data/loaders.py index b4ee38da..4f96002e 100644 --- a/src/guidellm/data/loaders.py +++ b/src/guidellm/data/loaders.py @@ -2,7 +2,7 @@ import contextlib from collections.abc import Callable, Iterator -from typing import Any, Literal +from typing import Any, Literal, TypeVar import torch from torch.utils.data import Sampler @@ -17,7 +17,10 @@ __all__ = ["DataLoader", "DatasetsIterator"] -class DatasetsIterator(TorchIterableDataset): +DataT = TypeVar("DataT") + + +class DatasetsIterator(TorchIterableDataset[DataT]): def __init__( self, data: list[Any], @@ -60,7 +63,7 @@ def __init__( list(self.generator(data_samples)) if data_samples else None ) - def __iter__(self): + def __iter__(self) -> Iterator[DataT]: worker_info = torch.utils.data.get_worker_info() worker_modulus = worker_info.num_workers if worker_info is not None else 1 worker_index = worker_info.id if worker_info is not None else 0 @@ -77,7 +80,7 @@ def generator( max_items: int | None = None, modulus: int | None = None, offset: int | None = None, - ) -> Iterator[Any]: + ) -> Iterator[DataT]: gen_count = 0 with contextlib.suppress(StopIteration): @@ -102,7 +105,7 @@ def generator( # passed into the preprocessor, which is a type violation. # This should be fixed at some point. row = preprocessor(row) # type: ignore[assignment] - yield row + yield row # type: ignore[misc] except Exception as err: # noqa: BLE001 # Exception logged logger.error(f"Skipping data row due to error: {err}") gen_count -= 1 @@ -114,7 +117,7 @@ def generator( ) -class DataLoader(PyTorchDataLoader): +class DataLoader(PyTorchDataLoader[DataT]): def __init__( self, data: list[Any], @@ -128,7 +131,7 @@ def __init__( random_seed: int = 42, **kwargs: Any, ): - iterator = DatasetsIterator( + iterator: DatasetsIterator[DataT] = DatasetsIterator( data=data, data_args=data_args, data_samples=data_samples, diff --git a/src/guidellm/data/preprocessors/__init__.py b/src/guidellm/data/preprocessors/__init__.py index 664e196b..6d6e722d 100644 --- a/src/guidellm/data/preprocessors/__init__.py +++ b/src/guidellm/data/preprocessors/__init__.py @@ -3,6 +3,7 @@ GenerativeAudioTranslationRequestFormatter, GenerativeChatCompletionsRequestFormatter, GenerativeTextCompletionsRequestFormatter, + RequestFormatter, ) from .mappers import GenerativeColumnMapper from .preprocessor import ( @@ -22,4 +23,5 @@ "GenerativeColumnMapper", "GenerativeTextCompletionsRequestFormatter", "PreprocessorRegistry", + "RequestFormatter", ] diff --git a/src/guidellm/data/preprocessors/formatters.py b/src/guidellm/data/preprocessors/formatters.py index 5a869403..608128a6 100644 --- a/src/guidellm/data/preprocessors/formatters.py +++ b/src/guidellm/data/preprocessors/formatters.py @@ -1,6 +1,5 @@ from __future__ import annotations -from abc import ABCMeta from typing import Any from guidellm.data.preprocessors.preprocessor import ( @@ -14,10 +13,14 @@ "GenerativeAudioTranslationRequestFormatter", "GenerativeChatCompletionsRequestFormatter", "GenerativeTextCompletionsRequestFormatter", + "RequestFormatter", ] -class RequestFormatter(DatasetPreprocessor, metaclass=ABCMeta): +class RequestFormatter(DatasetPreprocessor): + def __init__(self, model: str, **_kwargs): + self.model = model + @staticmethod def encode_audio(*args, **kwargs): from guidellm.extras.audio import encode_audio @@ -47,7 +50,7 @@ def __init__( max_tokens: int | None = None, max_completion_tokens: int | None = None, ): - self.model: str | None = model + self.model: str = model self.extras = ( GenerationRequestArguments(**extras) if extras and isinstance(extras, dict) @@ -73,6 +76,7 @@ def __call__(self, columns: dict[str, list[Any]]) -> GenerationRequest: if self.stream: arguments.stream = True arguments.body["stream"] = True + arguments.body["stream_options"] = {"include_usage": True} # Handle output tokens if output_tokens := sum( @@ -158,9 +162,8 @@ def __call__( # noqa: C901, PLR0912, PLR0915 # Configure streaming if self.stream: arguments.stream = True - arguments.body.update( - {"stream": True, "stream_options": {"include_usage": True}} - ) + arguments.body["stream"] = True + arguments.body["stream_options"] = {"include_usage": True} # Handle output tokens if output_tokens := sum( @@ -334,6 +337,7 @@ def __call__( # noqa: C901 if self.stream: arguments.stream = True arguments.body["stream"] = True + arguments.body["stream_options"] = {"include_usage": True} # Handle output tokens if output_tokens := sum( diff --git a/src/guidellm/data/preprocessors/preprocessor.py b/src/guidellm/data/preprocessors/preprocessor.py index e95ad75d..43fe20e9 100644 --- a/src/guidellm/data/preprocessors/preprocessor.py +++ b/src/guidellm/data/preprocessors/preprocessor.py @@ -25,6 +25,6 @@ def setup_data( class PreprocessorRegistry( - RegistryMixin[DataDependentPreprocessor | type[DataDependentPreprocessor]] + RegistryMixin[type[DatasetPreprocessor] | type[DataDependentPreprocessor]] ): pass diff --git a/src/guidellm/data/processor.py b/src/guidellm/data/processor.py index 7962bfbf..e55eb123 100644 --- a/src/guidellm/data/processor.py +++ b/src/guidellm/data/processor.py @@ -1,11 +1,9 @@ from __future__ import annotations +from pathlib import Path from typing import Any -from transformers import ( # type: ignore[import] - AutoTokenizer, - PreTrainedTokenizerBase, -) +from transformers import AutoTokenizer, PreTrainedTokenizerBase # type: ignore[import] __all__ = ["ProcessorFactory"] @@ -13,7 +11,7 @@ class ProcessorFactory: def __init__( self, - processor: str | PreTrainedTokenizerBase, + processor: str | Path | PreTrainedTokenizerBase, processor_args: dict[str, Any] | None = None, ) -> None: self.processor = processor diff --git a/src/guidellm/mock_server/server.py b/src/guidellm/mock_server/server.py index ff9d5fcd..e85c6134 100644 --- a/src/guidellm/mock_server/server.py +++ b/src/guidellm/mock_server/server.py @@ -11,12 +11,13 @@ from __future__ import annotations import time +from typing import Any from sanic import Sanic, response from sanic.exceptions import NotFound from sanic.log import logger from sanic.request import Request -from sanic.response import HTTPResponse +from sanic.response import BaseHTTPResponse, HTTPResponse from guidellm.mock_server.config import MockServerConfig from guidellm.mock_server.handlers import ( @@ -65,16 +66,20 @@ def _setup_middleware(self): """Setup middleware for CORS, logging, etc.""" @self.app.middleware("request") - async def add_cors_headers(_request: Request): + async def add_cors_headers(_request: Request) -> None: """Add CORS headers to all requests.""" + return None # noqa: RET501 @self.app.middleware("response") - async def add_response_headers(_request: Request, resp: HTTPResponse): + async def add_response_headers( + _request: Any, resp: BaseHTTPResponse + ) -> HTTPResponse: """Add standard response headers.""" resp.headers["Access-Control-Allow-Origin"] = "*" resp.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS" resp.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization" resp.headers["Server"] = "guidellm-mock-server" + return resp # type: ignore[return-value] def _setup_routes(self): # noqa: C901 @self.app.get("/health") diff --git a/src/guidellm/preprocess/dataset.py b/src/guidellm/preprocess/dataset.py index 49ce7b09..033bf106 100644 --- a/src/guidellm/preprocess/dataset.py +++ b/src/guidellm/preprocess/dataset.py @@ -276,8 +276,8 @@ def process_dataset( processor_args, "dataset conversion.", ) - prompt_column = column_mappings.get("prompt_column") - output_column = column_mappings.get( + prompt_column = column_mappings.get("prompt_column") # type: ignore[attr-defined] + output_column = column_mappings.get( # type: ignore[attr-defined] "output_tokens_count_column", "output_tokens_count" ) @@ -304,7 +304,7 @@ def process_dataset( ) ) - dataset_iterator = iter(dataset) + dataset_iterator = iter(dataset) # type: ignore[call-overload] processed_prompts = [] prompt_handler = STRATEGY_HANDLERS[short_prompt_strategy] diff --git a/src/guidellm/presentation/__init__.py b/src/guidellm/presentation/__init__.py deleted file mode 100644 index 872188db..00000000 --- a/src/guidellm/presentation/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -from .builder import UIDataBuilder -from .data_models import ( - BenchmarkDatum, - Bucket, - Dataset, - Distribution, - Model, - RunInfo, - Server, - TokenDetails, - WorkloadDetails, -) -from .injector import create_report, inject_data - -__all__ = [ - "BenchmarkDatum", - "Bucket", - "Dataset", - "Distribution", - "Model", - "RunInfo", - "Server", - "TokenDetails", - "UIDataBuilder", - "WorkloadDetails", - "create_report", - "inject_data", -] diff --git a/src/guidellm/presentation/builder.py b/src/guidellm/presentation/builder.py deleted file mode 100644 index 6ea9c5c3..00000000 --- a/src/guidellm/presentation/builder.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from guidellm.benchmark import GenerativeBenchmark - -from guidellm.presentation.data_models import BenchmarkDatum, RunInfo, WorkloadDetails - - -class UIDataBuilder: - def __init__(self, benchmarks: list["GenerativeBenchmark"]): - self.benchmarks = benchmarks - - def build_run_info(self): - return RunInfo.from_benchmarks(self.benchmarks) - - def build_workload_details(self): - return WorkloadDetails.from_benchmarks(self.benchmarks) - - def build_benchmarks(self): - return [BenchmarkDatum.from_benchmark(b) for b in self.benchmarks] - - def to_dict(self) -> dict[str, Any]: - return { - "run_info": self.build_run_info().model_dump(), - "workload_details": self.build_workload_details().model_dump(), - "benchmarks": [b.model_dump() for b in self.build_benchmarks()], - } diff --git a/src/guidellm/presentation/data_models.py b/src/guidellm/presentation/data_models.py deleted file mode 100644 index deec925c..00000000 --- a/src/guidellm/presentation/data_models.py +++ /dev/null @@ -1,236 +0,0 @@ -import random -from collections import defaultdict -from math import ceil -from typing import TYPE_CHECKING - -from pydantic import BaseModel, computed_field - -if TYPE_CHECKING: - from guidellm.benchmark import GenerativeBenchmark - -from guidellm.utils import DistributionSummary - - -class Bucket(BaseModel): - value: float | int - count: int - - @staticmethod - def from_data( - data: list[float] | list[int], - bucket_width: float | None = None, - n_buckets: int | None = None, - ) -> tuple[list["Bucket"], float]: - if not data: - return [], 1.0 - - min_v = min(data) - max_v = max(data) - range_v = (1 + max_v) - min_v - - if bucket_width is None: - if n_buckets is None: - n_buckets = 10 - bucket_width = range_v / n_buckets - else: - n_buckets = ceil(range_v / bucket_width) - - bucket_counts: defaultdict[float | int, int] = defaultdict(int) - for val in data: - idx = int((val - min_v) // bucket_width) - if idx >= n_buckets: - idx = n_buckets - 1 - bucket_start = min_v + idx * bucket_width - bucket_counts[bucket_start] += 1 - - buckets = [ - Bucket(value=start, count=count) - for start, count in sorted(bucket_counts.items()) - ] - return buckets, bucket_width - - -class Model(BaseModel): - name: str - size: int - - -class Dataset(BaseModel): - name: str - - -class RunInfo(BaseModel): - model: Model - task: str - timestamp: float - dataset: Dataset - - @classmethod - def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]): - model = benchmarks[0].benchmarker.backend.get("model", "N/A") - timestamp = max( - bm.run_stats.start_time for bm in benchmarks if bm.start_time is not None - ) - return cls( - model=Model(name=model or "", size=0), - task="N/A", - timestamp=timestamp, - dataset=Dataset(name="N/A"), - ) - - -class Distribution(BaseModel): - statistics: DistributionSummary | None = None - buckets: list[Bucket] - bucket_width: float - - -class TokenDetails(BaseModel): - samples: list[str] - token_distributions: Distribution - - -class Server(BaseModel): - target: str - - -class RequestOverTime(BaseModel): - num_benchmarks: int - requests_over_time: Distribution - - -class WorkloadDetails(BaseModel): - prompts: TokenDetails - generations: TokenDetails - requests_over_time: RequestOverTime - rate_type: str - server: Server - - @classmethod - def from_benchmarks(cls, benchmarks: list["GenerativeBenchmark"]): - target = benchmarks[0].benchmarker.backend.get("target", "N/A") - rate_type = benchmarks[0].scheduler.strategy.type_ - successful_requests = [ - req for bm in benchmarks for req in bm.requests.successful - ] - sample_indices = random.sample( - range(len(successful_requests)), min(5, len(successful_requests)) - ) - sample_prompts = [ - req.request_args.replace("\n", " ").replace('"', "'") - if (req := successful_requests[i]).request_args - else "" - for i in sample_indices - ] - sample_outputs = [ - req.output.replace("\n", " ").replace('"', "'") - if (req := successful_requests[i]).output - else "" - for i in sample_indices - ] - - prompt_tokens = [ - float(req.prompt_tokens) if req.prompt_tokens is not None else -1 - for bm in benchmarks - for req in bm.requests.successful - ] - output_tokens = [ - float(req.output_tokens) if req.output_tokens is not None else -1 - for bm in benchmarks - for req in bm.requests.successful - ] - - prompt_token_buckets, _prompt_token_bucket_width = Bucket.from_data( - prompt_tokens, 1 - ) - output_token_buckets, _output_token_bucket_width = Bucket.from_data( - output_tokens, 1 - ) - - prompt_token_stats = DistributionSummary.from_values(prompt_tokens) - output_token_stats = DistributionSummary.from_values(output_tokens) - prompt_token_distributions = Distribution( - statistics=prompt_token_stats, buckets=prompt_token_buckets, bucket_width=1 - ) - output_token_distributions = Distribution( - statistics=output_token_stats, buckets=output_token_buckets, bucket_width=1 - ) - - min_start_time = benchmarks[0].start_time - - all_req_times = [ - req.info.timings.request_start - min_start_time - for bm in benchmarks - for req in bm.requests.successful - if req.info.timings.request_start is not None - ] - number_of_buckets = len(benchmarks) - request_over_time_buckets, bucket_width = Bucket.from_data( - all_req_times, None, number_of_buckets - ) - request_over_time_distribution = Distribution( - buckets=request_over_time_buckets, bucket_width=bucket_width - ) - return cls( - prompts=TokenDetails( - samples=sample_prompts, token_distributions=prompt_token_distributions - ), - generations=TokenDetails( - samples=sample_outputs, token_distributions=output_token_distributions - ), - requests_over_time=RequestOverTime( - requests_over_time=request_over_time_distribution, - num_benchmarks=number_of_buckets, - ), - rate_type=rate_type, - server=Server(target=target), - ) - - -class TabularDistributionSummary(DistributionSummary): - """ - Same fields as `DistributionSummary`, but adds a ready-to-serialize/iterate - `percentile_rows` helper. - """ - - @computed_field - def percentile_rows(self) -> list[dict[str, str | float]]: - rows = [ - {"percentile": name, "value": value} - for name, value in self.percentiles.model_dump().items() - ] - return list( - filter(lambda row: row["percentile"] in ["p50", "p90", "p95", "p99"], rows) - ) - - @classmethod - def from_distribution_summary( - cls, distribution: DistributionSummary - ) -> "TabularDistributionSummary": - return cls(**distribution.model_dump()) - - -class BenchmarkDatum(BaseModel): - requests_per_second: float - itl: TabularDistributionSummary - ttft: TabularDistributionSummary - throughput: TabularDistributionSummary - time_per_request: TabularDistributionSummary - - @classmethod - def from_benchmark(cls, bm: "GenerativeBenchmark"): - return cls( - requests_per_second=bm.metrics.requests_per_second.successful.mean, - itl=TabularDistributionSummary.from_distribution_summary( - bm.metrics.inter_token_latency_ms.successful - ), - ttft=TabularDistributionSummary.from_distribution_summary( - bm.metrics.time_to_first_token_ms.successful - ), - throughput=TabularDistributionSummary.from_distribution_summary( - bm.metrics.output_tokens_per_second.successful - ), - time_per_request=TabularDistributionSummary.from_distribution_summary( - bm.metrics.request_latency.successful - ), - ) diff --git a/src/guidellm/presentation/injector.py b/src/guidellm/presentation/injector.py deleted file mode 100644 index 1e78080e..00000000 --- a/src/guidellm/presentation/injector.py +++ /dev/null @@ -1,65 +0,0 @@ -import re -from pathlib import Path - -from loguru import logger - -from guidellm.settings import settings -from guidellm.utils.text import load_text - - -def create_report(js_data: dict, output_path: str | Path) -> Path: - """ - Creates a report from the dictionary and saves it to the output path. - - :param js_data: dict with match str and json data to inject - :type js_data: dict - :param output_path: the file to save the report to. - :type output_path: str - :return: the path to the saved report - :rtype: str - """ - - if not isinstance(output_path, Path): - output_path = Path(output_path) - - html_content = load_text(settings.report_generation.source) - report_content = inject_data( - js_data, - html_content, - ) - - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(report_content) - return output_path - - -def inject_data( - js_data: dict, - html: str, -) -> str: - """ - Injects the json data into the HTML, - replacing placeholders only within the section. - - :param js_data: the json data to inject - :type js_data: dict - :param html: the html to inject the data into - :type html: str - :return: the html with the json data injected - :rtype: str - """ - head_match = re.search(r"]*>(.*?)", html, re.DOTALL | re.IGNORECASE) - if not head_match: - logger.warning(" section missing, returning original HTML.") - - return html - - head_content = head_match.group(1) - - # Replace placeholders only inside the content - for placeholder, script in js_data.items(): - head_content = head_content.replace(placeholder, script) - - # Rebuild the HTML - new_head = f"{head_content}" - return html[: head_match.start()] + new_head + html[head_match.end() :] diff --git a/src/guidellm/scheduler/constraints.py b/src/guidellm/scheduler/constraints.py index e24419ea..bbf34fb4 100644 --- a/src/guidellm/scheduler/constraints.py +++ b/src/guidellm/scheduler/constraints.py @@ -21,9 +21,9 @@ SchedulerUpdateAction, SchedulerUpdateActionProgress, ) -from guidellm.schemas import RequestInfo +from guidellm.schemas import RequestInfo, StandardBaseModel from guidellm.settings import settings -from guidellm.utils import InfoMixin, RegistryMixin, StandardBaseModel +from guidellm.utils import InfoMixin, RegistryMixin __all__ = [ "Constraint", diff --git a/src/guidellm/scheduler/scheduler.py b/src/guidellm/scheduler/scheduler.py index 6da76438..cfdbc3b7 100644 --- a/src/guidellm/scheduler/scheduler.py +++ b/src/guidellm/scheduler/scheduler.py @@ -24,7 +24,7 @@ from guidellm.scheduler.strategies import SchedulingStrategy from guidellm.scheduler.worker_group import WorkerProcessGroup from guidellm.schemas import RequestInfo -from guidellm.utils.singleton import ThreadSafeSingletonMixin +from guidellm.utils import ThreadSafeSingletonMixin __all__ = ["Scheduler"] @@ -63,7 +63,6 @@ async def run( requests: Iterable[RequestT | MultiTurnRequestT[RequestT]], backend: BackendInterface[RequestT, ResponseT], strategy: SchedulingStrategy, - startup_duration: float, env: Environment[RequestT, ResponseT] | None, **constraints: Any | dict[str, Any] | Constraint, ) -> AsyncIterator[ @@ -86,7 +85,6 @@ async def run( multi-turn sequences with optional inter-request delays :param backend: Backend interface for request processing and response generation :param strategy: Scheduling strategy controlling request timing and distribution - :param startup_duration: Duration in seconds for requests to ramp up :param env: Environment interface for distributed coordination and synchronization. Defaults to NonDistributedEnvironment if None :param constraints: Runtime constraints for execution control (max_requests, @@ -123,7 +121,6 @@ async def run( requests=local_requests, backend=backend, strategy=local_strategy, - startup_duration=startup_duration, **local_constraints, ) await worker_group.create_processes() diff --git a/src/guidellm/scheduler/schemas.py b/src/guidellm/scheduler/schemas.py index 21567c67..b202b010 100644 --- a/src/guidellm/scheduler/schemas.py +++ b/src/guidellm/scheduler/schemas.py @@ -16,8 +16,8 @@ from pydantic import Field from typing_extensions import TypeAliasType, TypedDict -from guidellm.schemas import RequestInfo -from guidellm.utils import RegistryMixin, StandardBaseModel +from guidellm.schemas import RequestInfo, StandardBaseModel +from guidellm.utils import RegistryMixin from guidellm.utils.registry import RegistryObjT __all__ = [ diff --git a/src/guidellm/scheduler/strategies.py b/src/guidellm/scheduler/strategies.py index e1473b93..dfdd97f5 100644 --- a/src/guidellm/scheduler/strategies.py +++ b/src/guidellm/scheduler/strategies.py @@ -7,21 +7,24 @@ when they should be scheduled, and what constraints apply to concurrent processing. The scheduling system separates timing logic from strategy constraints, enabling flexible combination of timing behaviors with process and concurrency limits. + +Available strategies include synchronous (sequential), concurrent (fixed streams), +throughput (maximum load), constant-rate (steady intervals), and Poisson-distributed +(realistic variance) patterns for comprehensive benchmarking scenarios. """ from __future__ import annotations import asyncio import random -import time from abc import abstractmethod from multiprocessing import Lock, Value from typing import Annotated, ClassVar, Literal, TypeVar -from pydantic import Field, PrivateAttr +from pydantic import Field, NonNegativeFloat, NonNegativeInt, PositiveInt, PrivateAttr -from guidellm.schemas import RequestInfo -from guidellm.utils import InfoMixin, PydanticClassRegistryMixin +from guidellm.schemas import PydanticClassRegistryMixin, RequestInfo +from guidellm.utils import InfoMixin __all__ = [ "AsyncConstantStrategy", @@ -63,22 +66,15 @@ def __pydantic_schema_base_type__(cls) -> type[SchedulingStrategy]: return SchedulingStrategy type_: Literal["strategy"] = Field( - description="The type of scheduling strategy to schedule requests with", + description="Scheduling strategy type identifier for polymorphic dispatch", ) - worker_count: int = Field( - default=0, + worker_count: PositiveInt | None = Field( + default=None, description="Number of worker processes to use for this strategy", - ge=0, ) - max_concurrency: int | None = Field( + max_concurrency: PositiveInt | None = Field( default=None, description="Maximum number of concurrent requests to allow", - ge=0, - ) - startup_duration: float = Field( - default=0.0, - description="Duration in seconds for startup request distribution", - ge=0, ) _processes_lock = PrivateAttr(None) @@ -87,7 +83,7 @@ def __pydantic_schema_base_type__(cls) -> type[SchedulingStrategy]: _cached_processes_start_time: float | None = PrivateAttr(None) @property - def processes_limit(self) -> int | None: + def processes_limit(self) -> PositiveInt | None: """ Get the maximum number of worker processes supported by this strategy. @@ -96,7 +92,7 @@ def processes_limit(self) -> int | None: return None @property - def requests_limit(self) -> int | None: + def requests_limit(self) -> PositiveInt | None: """ Get the maximum number of concurrent requests supported by this strategy. @@ -105,21 +101,19 @@ def requests_limit(self) -> int | None: return None def init_processes_timings( - self, - worker_count: int, - max_concurrency: int, - startup_duration: float, + self, worker_count: PositiveInt, max_concurrency: PositiveInt ): """ Initialize shared timing state for multi-process coordination. + Sets up synchronized counters and locks for coordinating request timing + across distributed worker processes. + :param worker_count: Number of worker processes to coordinate :param max_concurrency: Maximum number of concurrent requests allowed - :param startup_duration: Duration in seconds for request startup ramping """ self.worker_count = worker_count self.max_concurrency = max_concurrency - self.startup_duration = startup_duration self._processes_request_index = Value("i", 0) self._processes_start_time = Value("d", -1.0) @@ -129,6 +123,9 @@ def init_processes_start(self, start_time: float): """ Set the synchronized start time for all worker processes. + Updates shared state with the benchmark start time to coordinate request + scheduling across all workers. + :param start_time: Unix timestamp when request processing should begin :raises RuntimeError: If called before init_processes_timings """ @@ -149,6 +146,9 @@ async def get_processes_start_time(self) -> float: """ Get the synchronized start time, waiting if not yet set. + Blocks until the main process sets the start time via init_processes_start, + enabling synchronized request scheduling across all workers. + :return: Unix timestamp when request processing began :raises RuntimeError: If called before init_processes_timings """ @@ -171,10 +171,13 @@ async def get_processes_start_time(self) -> float: return self._cached_processes_start_time - def next_request_index(self) -> int: + def next_request_index(self) -> PositiveInt: """ Get the next sequential request index across all worker processes. + Thread-safe counter providing globally unique indices for request timing + calculations in distributed environments. + :return: Globally unique request index for timing calculations :raises RuntimeError: If called before init_processes_timings """ @@ -193,11 +196,14 @@ def next_request_index(self) -> int: return self._processes_request_index.value @abstractmethod - async def next_request_time(self, offset: int) -> float: + async def next_request_time(self, worker_index: NonNegativeInt) -> float: """ Calculate the scheduled start time for the next request. - :param offset: Worker process offset for distributing request timing + Strategy-specific implementation determining when requests should be + processed based on timing patterns and worker distribution. + + :param worker_index: Worker process index for distributing request timing :return: Unix timestamp when the request should be processed """ @@ -206,12 +212,16 @@ def request_completed(self, request_info: RequestInfo): """ Handle request completion and update internal timing state. - :param request_info: Information about the completed request including - timing details and completion status + Strategy-specific handling of completed requests to maintain timing + coordination and schedule subsequent requests. + + :param request_info: Completed request metadata including timing details + and completion status """ StrategyT = TypeVar("StrategyT", bound=SchedulingStrategy) +"Type variable bound to SchedulingStrategy for generic strategy operations" @SchedulingStrategy.register("synchronous") @@ -234,27 +244,27 @@ def __str__(self) -> str: return "synchronous" @property - def processes_limit(self) -> int | None: + def processes_limit(self) -> PositiveInt: """ :return: Always 1 to enforce single-process constraint """ return 1 @property - def requests_limit(self) -> int | None: + def requests_limit(self) -> PositiveInt: """ :return: Always 1 to enforce single-request constraint """ return 1 - async def next_request_time(self, offset: int) -> float: + async def next_request_time(self, worker_index: NonNegativeInt) -> float: """ Calculate next request time based on previous completion. - :param offset: Unused for synchronous strategy + :param worker_index: Unused for synchronous strategy :return: Time of last completion or start time if first request """ - _ = offset # offset unused for synchronous strategy + _ = worker_index # unused for synchronous strategy if self._process_last_request_time is not None: return self._process_last_request_time @@ -282,9 +292,15 @@ class ConcurrentStrategy(SchedulingStrategy): """ type_: Literal["concurrent"] = "concurrent" # type: ignore[assignment] - streams: int = Field( + streams: PositiveInt = Field( description="Number of concurrent streams for scheduling requests", - gt=0, + ) + rampup_duration: NonNegativeFloat = Field( + default=0.0, + description=( + "Duration in seconds to spread initial requests up to max_concurrency " + "at the beginning of each strategy run" + ), ) _process_last_request_time: float | None = PrivateAttr(None) @@ -296,37 +312,48 @@ def __str__(self) -> str: return f"concurrent@{self.streams}" @property - def processes_limit(self) -> int: + def processes_limit(self) -> PositiveInt: """ :return: Number of streams as maximum worker processes """ return self.streams @property - def requests_limit(self) -> int: + def requests_limit(self) -> PositiveInt: """ :return: Number of streams as maximum concurrent requests """ return self.streams - async def next_request_time(self, offset: int) -> float: + async def next_request_time(self, worker_index: PositiveInt) -> float: """ Calculate next request time with stream-based distribution. - :param offset: Worker process offset for distributing initial requests + Initial requests are staggered across streams during rampup, subsequent + requests scheduled after previous completion within each stream. + + :param worker_index: Worker process index for distributing initial requests :return: Time of last completion or staggered start time if first request """ + _ = worker_index # unused + current_index = self.next_request_index() + start_time = await self.get_processes_start_time() + + if current_index < self.streams and self.rampup_duration > 0: + # linearly spread start times for first concurrent requests across rampup + return start_time + self.rampup_duration * (current_index / self.streams) + if self._process_last_request_time is not None: return self._process_last_request_time - start_time = await self.get_processes_start_time() - - return start_time + (offset / self.worker_count) + return start_time def request_completed(self, request_info: RequestInfo): """ Update timing state with completed request information. + Tracks completion time to schedule next request in the same stream. + :param request_info: Completed request metadata including timing """ if request_info.completed_at is not None: @@ -344,10 +371,16 @@ class ThroughputStrategy(SchedulingStrategy): """ type_: Literal["throughput"] = "throughput" # type: ignore[assignment] - max_concurrency: int | None = Field( + max_concurrency: PositiveInt | None = Field( default=None, description="Maximum number of concurrent requests to schedule", - gt=0, + ) + rampup_duration: NonNegativeFloat = Field( + default=0.0, + description=( + "Duration in seconds to spread initial requests up to max_concurrency " + "at the beginning of each strategy run" + ), ) def __str__(self) -> str: @@ -357,42 +390,44 @@ def __str__(self) -> str: return "throughput" @property - def processes_limit(self) -> int | None: + def processes_limit(self) -> PositiveInt | None: """ :return: Max concurrency if set, otherwise None for unlimited """ return self.max_concurrency @property - def requests_limit(self) -> int | None: + def requests_limit(self) -> PositiveInt | None: """ :return: Max concurrency if set, otherwise None for unlimited """ return self.max_concurrency - async def next_request_time(self, offset: int) -> float: + async def next_request_time(self, worker_index: int) -> float: """ Calculate next request time with optional startup ramping. - :param offset: Unused for throughput strategy + Spreads initial requests linearly during rampup period, then schedules + all subsequent requests immediately. + + :param worker_index: Unused for throughput strategy :return: Immediate start or ramped start time during startup period """ - _ = offset # offset unused for throughput strategy + _ = worker_index # unused for throughput strategy start_time = await self.get_processes_start_time() - if ( - self.max_concurrency is not None - and self.startup_duration > 0 - and (time.time() - start_time) < self.startup_duration - and (current_index := self.next_request_index()) <= self.max_concurrency - ): - # linearly ramp start times to spread max_concurrency requests evenly - # over startup_duration - return start_time + self.startup_duration * ( - current_index / self.max_concurrency + if self.max_concurrency is not None and self.rampup_duration > 0: + current_index = self.next_request_index() + delay = ( + self.rampup_duration + if current_index >= self.max_concurrency + else self.rampup_duration + * (current_index / float(self.max_concurrency)) ) - return start_time + self.startup_duration + return start_time + delay + else: + return start_time def request_completed(self, request_info: RequestInfo): """ @@ -404,7 +439,7 @@ def request_completed(self, request_info: RequestInfo): @SchedulingStrategy.register("constant") -class AsyncConstantStrategy(ThroughputStrategy): +class AsyncConstantStrategy(SchedulingStrategy): """ Constant-rate scheduling for predictable load patterns. @@ -415,9 +450,13 @@ class AsyncConstantStrategy(ThroughputStrategy): type_: Literal["constant"] = "constant" # type: ignore[assignment] rate: float = Field( - description="Rate for scheduling requests asynchronously in requests/second", + description="Request scheduling rate in requests per second", gt=0, ) + max_concurrency: PositiveInt | None = Field( + default=None, + description="Maximum number of concurrent requests to schedule", + ) def __str__(self) -> str: """ @@ -425,14 +464,31 @@ def __str__(self) -> str: """ return f"constant@{self.rate:.2f}" - async def next_request_time(self, offset: int) -> float: + @property + def processes_limit(self) -> PositiveInt | None: + """ + :return: Max concurrency if set, otherwise None for unlimited + """ + return self.max_concurrency + + @property + def requests_limit(self) -> PositiveInt | None: + """ + :return: Max concurrency if set, otherwise None for unlimited + """ + return self.max_concurrency + + async def next_request_time(self, worker_index: PositiveInt) -> float: """ Calculate next request time at fixed intervals. - :param offset: Unused for constant strategy + Schedules requests at uniform intervals determined by the configured rate, + independent of request completion times. + + :param worker_index: Unused for constant strategy :return: Start time plus constant interval based on request index """ - _ = offset # offset unused for throughput strategy + _ = worker_index # unused current_index = self.next_request_index() start_time = await self.get_processes_start_time() @@ -448,7 +504,7 @@ def request_completed(self, request_info: RequestInfo): @SchedulingStrategy.register("poisson") -class AsyncPoissonStrategy(ThroughputStrategy): +class AsyncPoissonStrategy(SchedulingStrategy): """ Poisson-distributed scheduling for realistic load simulation. @@ -459,12 +515,16 @@ class AsyncPoissonStrategy(ThroughputStrategy): type_: Literal["poisson"] = "poisson" # type: ignore[assignment] rate: float = Field( - description="Rate for scheduling requests asynchronously in requests/second", + description="Request scheduling rate in requests per second", gt=0, ) + max_concurrency: PositiveInt | None = Field( + default=None, + description="Maximum number of concurrent requests to schedule", + ) random_seed: int = Field( default=42, - description="Random seed to use for Poisson distribution", + description="Random seed for Poisson distribution reproducibility", ) _random: random.Random | None = PrivateAttr(None) @@ -476,20 +536,31 @@ def __str__(self) -> str: """ return f"poisson@{self.rate:.2f}" - def init_processes_timings( - self, - worker_count: int, - max_concurrency: int, - startup_duration: float, - ): + @property + def processes_limit(self) -> PositiveInt | None: + """ + :return: Max concurrency if set, otherwise None for unlimited + """ + return self.max_concurrency + + @property + def requests_limit(self) -> PositiveInt | None: + """ + :return: Max concurrency if set, otherwise None for unlimited + """ + return self.max_concurrency + + def init_processes_timings(self, worker_count: int, max_concurrency: int): """ Initialize Poisson-specific timing state. + Sets up shared offset value for coordinating exponentially distributed + request timing across worker processes. + :param worker_count: Number of worker processes to coordinate :param max_concurrency: Maximum number of concurrent requests allowed - :param startup_duration: Duration in seconds for request startup ramping """ - super().init_processes_timings(worker_count, max_concurrency, startup_duration) + super().init_processes_timings(worker_count, max_concurrency) if self._processes_lock is None: raise RuntimeError("_processes_lock is None in init_processes_timings") with self._processes_lock: @@ -499,6 +570,9 @@ def init_processes_start(self, start_time: float): """ Initialize the offset time for Poisson timing calculations. + Sets the initial timing offset from which exponentially distributed + intervals are calculated. + :param start_time: Unix timestamp when request processing should begin """ ThroughputStrategy.init_processes_start(self, start_time) @@ -513,14 +587,17 @@ def init_processes_start(self, start_time: float): with self._processes_lock: self._offset.value = start_time - async def next_request_time(self, offset: int) -> float: + async def next_request_time(self, worker_index: PositiveInt) -> float: """ Calculate next request time using exponential distribution. - :param offset: Unused for Poisson strategy + Generates inter-arrival times following exponential distribution, + accumulating delays to produce Poisson-distributed request arrivals. + + :param worker_index: Unused for Poisson strategy :return: Next arrival time based on Poisson process """ - _ = offset # offset unused for throughput strategy + _ = worker_index # unused _ = await self.get_processes_start_time() # ensure offset is initialized if self._random is None: diff --git a/src/guidellm/scheduler/worker.py b/src/guidellm/scheduler/worker.py index 977635fa..08072eaf 100644 --- a/src/guidellm/scheduler/worker.py +++ b/src/guidellm/scheduler/worker.py @@ -303,7 +303,7 @@ def _task_done(task): while True: await async_semaphore.acquire() request_time = await self.strategy.next_request_time( - offset=self.worker_index + worker_index=self.worker_index ) if ( @@ -408,9 +408,8 @@ async def _dequeue_next_request( async def _schedule_request( self, request: RequestT, request_info: RequestInfo, target_start: float ): - current_time = time.time() - request_info.timings.scheduled_at = current_time - if target_start > current_time: + request_info.timings.scheduled_at = request_info.timings.dequeued + if target_start > (current_time := time.time()): await asyncio.sleep(target_start - current_time) # Adapt delay so that scheduled at reflects the sleep time request_info.timings.scheduled_at = target_start diff --git a/src/guidellm/scheduler/worker_group.py b/src/guidellm/scheduler/worker_group.py index 2a0a51de..627fcc61 100644 --- a/src/guidellm/scheduler/worker_group.py +++ b/src/guidellm/scheduler/worker_group.py @@ -83,7 +83,6 @@ def __init__( requests: Iterable[RequestT | MultiTurnRequestT[RequestT]], backend: BackendInterface[RequestT, ResponseT], strategy: SchedulingStrategy, - startup_duration: float, **constraints: Constraint, ): """ @@ -92,13 +91,11 @@ def __init__( :param requests: Finite iterable of requests to process sequentially :param backend: Backend interface for processing requests :param strategy: Scheduling strategy for request timing and distribution - :param startup_duration: Duration in seconds for request startup ramping :param constraints: Named constraints for controlling execution behavior """ - self.requests = requests + self.requests = iter(requests) self.backend = backend self.strategy = strategy - self.startup_duration = startup_duration self.constraints = constraints # Multiprocessing contexts and primitives, created in create_processes @@ -216,9 +213,7 @@ async def create_processes(self): # Initialize worker processes self.processes = [] self.strategy.init_processes_timings( - worker_count=num_processes, - max_concurrency=max_conc, - startup_duration=self.startup_duration, + worker_count=num_processes, max_concurrency=max_conc ) for rank in range(num_processes): # Distribute any remainder across the first N ranks @@ -228,7 +223,7 @@ async def create_processes(self): worker = WorkerProcess[RequestT, ResponseT]( worker_index=rank, - messaging=self.messaging.create_worker_copy( + messaging=self.messaging.create_worker_copy( # type: ignore[arg-type] worker_index=rank, max_buffer_send_size=None, max_buffer_receive_size=per_proc_max_buffer_size, @@ -500,7 +495,7 @@ def requests_generator( try: count = 0 - for request in iter(requests): + for request in requests: count += 1 if hasattr(request, "request_id"): @@ -632,6 +627,8 @@ def _locked_update( ) def _update_state_request_counts(self, info: RequestInfo): + finalized = time.time() + if info.status == "queued": self._queued_request_ids.add(info.request_id) self._state.queued_requests = len(self._queued_request_ids) @@ -647,11 +644,13 @@ def _update_state_request_counts(self, info: RequestInfo): self._processing_request_ids.add(info.request_id) self._state.processing_requests = len(self._processing_request_ids) elif info.status == "completed": + info.timings.finalized = finalized self._processing_request_ids.remove(info.request_id) self._state.processing_requests = len(self._processing_request_ids) self._state.processed_requests += 1 self._state.successful_requests += 1 elif info.status in ("errored", "cancelled"): + info.timings.finalized = finalized if info.request_id in self._queued_request_ids: self._queued_request_ids.remove(info.request_id) self._state.queued_requests = len(self._queued_request_ids) diff --git a/src/guidellm/schemas/__init__.py b/src/guidellm/schemas/__init__.py index 42268f72..a7d2f527 100644 --- a/src/guidellm/schemas/__init__.py +++ b/src/guidellm/schemas/__init__.py @@ -9,6 +9,19 @@ from __future__ import annotations +from .base import ( + BaseModelT, + ErroredT, + IncompleteT, + PydanticClassRegistryMixin, + RegisterClassT, + ReloadableBaseModel, + StandardBaseDict, + StandardBaseModel, + StatusBreakdown, + SuccessfulT, + TotalT, +) from .info import RequestInfo, RequestTimings from .request import ( GenerationRequest, @@ -16,16 +29,37 @@ GenerativeRequestType, UsageMetrics, ) +from .request_stats import GenerativeRequestStats from .response import GenerationResponse -from .stats import GenerativeRequestStats +from .statistics import ( + DistributionSummary, + FunctionObjT, + Percentiles, + StatusDistributionSummary, +) __all__ = [ + "BaseModelT", + "DistributionSummary", + "ErroredT", + "FunctionObjT", "GenerationRequest", "GenerationRequestArguments", "GenerationResponse", "GenerativeRequestStats", "GenerativeRequestType", + "IncompleteT", + "Percentiles", + "PydanticClassRegistryMixin", + "RegisterClassT", + "ReloadableBaseModel", "RequestInfo", "RequestTimings", + "StandardBaseDict", + "StandardBaseModel", + "StatusBreakdown", + "StatusDistributionSummary", + "SuccessfulT", + "TotalT", "UsageMetrics", ] diff --git a/src/guidellm/utils/pydantic_utils.py b/src/guidellm/schemas/base.py similarity index 99% rename from src/guidellm/utils/pydantic_utils.py rename to src/guidellm/schemas/base.py index 05f5ad81..cd733b67 100644 --- a/src/guidellm/utils/pydantic_utils.py +++ b/src/guidellm/schemas/base.py @@ -19,11 +19,17 @@ from guidellm.utils.registry import RegistryMixin __all__ = [ + "BaseModelT", + "ErroredT", + "IncompleteT", "PydanticClassRegistryMixin", + "RegisterClassT", "ReloadableBaseModel", "StandardBaseDict", "StandardBaseModel", "StatusBreakdown", + "SuccessfulT", + "TotalT", ] diff --git a/src/guidellm/schemas/info.py b/src/guidellm/schemas/info.py index 4b5d188c..854756d4 100644 --- a/src/guidellm/schemas/info.py +++ b/src/guidellm/schemas/info.py @@ -14,7 +14,7 @@ from pydantic import Field, computed_field -from guidellm.utils import StandardBaseDict, StandardBaseModel +from guidellm.schemas.base import StandardBaseDict, StandardBaseModel __all__ = ["RequestInfo", "RequestTimings"] @@ -53,17 +53,23 @@ class RequestTimings(StandardBaseDict): default=None, description="Unix timestamp when the backend began processing the request", ) - first_iteration: float | None = Field( + first_request_iteration: float | None = Field( default=None, - description="Unix timestamp when the first iteration for a streaming began", ) - last_iteration: float | None = Field( + first_token_iteration: float | None = Field( default=None, - description="Unix timestamp when the last iteration for a streaming completed", ) - iterations: int | None = Field( + last_token_iteration: float | None = Field( default=None, - description="Total number of streaming update iterations performed", + ) + last_request_iteration: float | None = Field( + default=None, + ) + request_iterations: int = Field( + default=0, + ) + token_iterations: int = Field( + default=0, ) request_end: float | None = Field( default=None, @@ -78,6 +84,25 @@ class RequestTimings(StandardBaseDict): description="Unix timestamp when request was processed by the scheduler", ) + @property + def last_reported(self) -> float | None: + """ + Get the most recent timing measurement available. + + :return: The latest Unix timestamp from the timing fields, or None if none + """ + timing_fields = [ + self.queued, + self.dequeued, + self.scheduled_at, + self.resolve_start, + self.request_start, + self.request_end, + self.resolve_end, + ] + valid_timings = [field for field in timing_fields if field is not None] + return max(valid_timings) if valid_timings else None + class RequestInfo(StandardBaseModel): """ diff --git a/src/guidellm/schemas/request.py b/src/guidellm/schemas/request.py index 1f90d130..a5193474 100644 --- a/src/guidellm/schemas/request.py +++ b/src/guidellm/schemas/request.py @@ -14,7 +14,7 @@ from pydantic import Field, computed_field -from guidellm.utils import StandardBaseDict, StandardBaseModel +from guidellm.schemas.base import StandardBaseDict, StandardBaseModel __all__ = [ "GenerationRequest", @@ -73,7 +73,7 @@ def model_combine( Merge additional request arguments into the current instance. Combines method and stream fields by overwriting, while merging collection - fields like headers, params, json_body, and files by extending existing values. + fields like headers, params, body, and files by extending existing values. :param additional: Additional arguments to merge with current instance :return: Updated instance with merged arguments @@ -88,9 +88,10 @@ def model_combine( if (val := additional_dict.get(overwrite)) is not None: setattr(self, overwrite, val) - for combine in ("headers", "params", "json_body", "files"): + for combine in ("headers", "params", "body", "files"): if (val := additional_dict.get(combine)) is not None: - setattr(self, combine, {**getattr(self, combine, {}), **val}) + current = getattr(self, combine, None) or {} + setattr(self, combine, {**current, **val}) return self diff --git a/src/guidellm/schemas/request_stats.py b/src/guidellm/schemas/request_stats.py new file mode 100644 index 00000000..c3c7dee7 --- /dev/null +++ b/src/guidellm/schemas/request_stats.py @@ -0,0 +1,349 @@ +""" +Request statistics and metrics for generative AI benchmark analysis. + +Provides data structures for capturing and analyzing performance metrics from +generative AI workloads. The module contains request-level statistics including +token counts, latency measurements, and throughput calculations essential for +evaluating text generation benchmark performance. Computed properties enable +analysis of time-to-first-token, inter-token latency, and token generation rates. +""" + +from __future__ import annotations + +from typing import Literal + +import numpy as np +from pydantic import Field, computed_field + +from guidellm.schemas.base import StandardBaseDict +from guidellm.schemas.info import RequestInfo +from guidellm.schemas.request import GenerativeRequestType, UsageMetrics + +__all__ = ["GenerativeRequestStats"] + + +class GenerativeRequestStats(StandardBaseDict): + """ + Request statistics for generative AI text generation workloads. + + Captures comprehensive performance metrics for individual generative requests, + including token counts, timing measurements, and derived performance statistics. + Provides computed properties for latency analysis, throughput calculations, + and token generation metrics essential for benchmark evaluation. + + Example: + :: + stats = GenerativeRequestStats( + request_id="req_123", + request_type="text_completion", + info=request_info, + input_metrics=input_usage, + output_metrics=output_usage + ) + throughput = stats.output_tokens_per_second + """ + + type_: Literal["generative_request_stats"] = "generative_request_stats" + request_id: str = Field(description="Unique identifier for the request") + request_type: GenerativeRequestType | str = Field( + description="Type of generative request (text_completion or chat_completion)" + ) + request_args: str | None = Field( + default=None, description="Backend arguments used for this request" + ) + output: str | None = Field( + default=None, description="Generated text output from the request" + ) + info: RequestInfo = Field(description="Request metadata and timing information") + input_metrics: UsageMetrics = Field( + description="Token usage statistics for the input prompt" + ) + output_metrics: UsageMetrics = Field( + description="Token usage statistics for the generated output" + ) + + # Request stats + @computed_field # type: ignore[misc] + @property + def request_start_time(self) -> float | None: + """ + :return: Timestamp when the request started, or None if unavailable + """ + return ( + self.info.timings.request_start + if self.info.timings.request_start is not None + else self.info.timings.resolve_start + ) + + @computed_field # type: ignore[misc] + @property + def request_end_time(self) -> float: + """ + :return: Timestamp when the request ended, or None if unavailable + """ + if self.info.timings.resolve_end is None: + raise ValueError("resolve_end timings should be set but is None.") + + return ( + self.info.timings.request_end + if self.info.timings.request_end is not None + else self.info.timings.resolve_end + ) + + @computed_field # type: ignore[misc] + @property + def request_latency(self) -> float | None: + """ + End-to-end request processing latency in seconds. + + :return: Duration from request start to completion, or None if unavailable + """ + start = self.info.timings.request_start + end = self.info.timings.request_end + if start is None or end is None: + return None + + return end - start + + # General token stats + @computed_field # type: ignore[misc] + @property + def prompt_tokens(self) -> int | None: + """ + :return: Number of tokens in the input prompt, or None if unavailable + """ + return self.input_metrics.text_tokens + + @computed_field # type: ignore[misc] + @property + def input_tokens(self) -> int | None: + """ + :return: Number of tokens in the input prompt, or None if unavailable + """ + return self.input_metrics.total_tokens + + @computed_field # type: ignore[misc] + @property + def output_tokens(self) -> int | None: + """ + :return: Number of tokens in the generated output, or None if unavailable + """ + return self.output_metrics.total_tokens + + @computed_field # type: ignore[misc] + @property + def total_tokens(self) -> int | None: + """ + :return: Sum of prompt and output tokens, or None if both unavailable + """ + input_tokens = self.input_metrics.total_tokens + output_tokens = self.output_metrics.total_tokens + + if input_tokens is None and output_tokens is None: + return None + + return (input_tokens or 0) + (output_tokens or 0) + + @computed_field # type: ignore[misc] + @property + def time_to_first_token_ms(self) -> float | None: + """ + :return: Time to first token generation in milliseconds, or None if unavailable + """ + first_token = self.first_token_iteration + start = self.info.timings.request_start + if first_token is None or start is None: + return None + + return 1000 * (first_token - start) + + @computed_field # type: ignore[misc] + @property + def time_per_output_token_ms(self) -> float | None: + """ + Average time per output token in milliseconds including first token. + + :return: Average milliseconds per output token, or None if unavailable + """ + if ( + (start := self.info.timings.request_start) is None + or ( + (last_token := self.last_token_iteration or self.request_end_time) + is None + ) + or (output_tokens := self.output_tokens) is None + or output_tokens == 0 + ): + return None + + return 1000 * (last_token - start) / output_tokens + + @computed_field # type: ignore[misc] + @property + def inter_token_latency_ms(self) -> float | None: + """ + Average inter-token latency in milliseconds excluding first token. + + :return: Average milliseconds between token generations, or None if unavailable + """ + first_token = self.first_token_iteration + last_token = self.last_token_iteration + output_tokens = self.output_tokens + if ( + first_token is None + or last_token is None + or output_tokens is None + or output_tokens <= 1 + ): + return None + + return 1000 * (last_token - first_token) / (output_tokens - 1) + + @computed_field # type: ignore[misc] + @property + def tokens_per_second(self) -> float | None: + """ + :return: Total tokens per second throughput, or None if unavailable + """ + if not (latency := self.request_latency) or self.total_tokens is None: + return None + + return self.total_tokens / latency + + @computed_field # type: ignore[misc] + @property + def output_tokens_per_second(self) -> float | None: + """ + :return: Output token generation throughput, or None if unavailable + """ + if not (latency := self.request_latency) or self.output_tokens is None: + return None + + return self.output_tokens / latency + + @computed_field # type: ignore[misc] + @property + def iter_tokens_per_iteration(self) -> float | None: + """ + :return: Average tokens per iteration excluding first token, or None if + unavailable + """ + if ( + self.output_tokens is None + or self.output_tokens <= 1 + or self.token_iterations <= 1 + ): + return None + + return (self.output_tokens - 1.0) / ( + self.token_iterations - 1.0 + ) # subtract 1 for first token from the prompt, assume first iter is 1 token + + @computed_field # type: ignore[misc] + @property + def output_tokens_per_iteration(self) -> float | None: + """ + :return: Average output tokens per iteration, or None if unavailable + """ + if self.output_tokens is None or self.token_iterations < 1: + return None + + return self.output_tokens / self.token_iterations + + @property + def first_token_iteration(self) -> float | None: + """ + :return: Timestamp of first token generation, or None if unavailable + """ + return self.info.timings.first_token_iteration + + @property + def last_token_iteration(self) -> float | None: + """ + :return: Timestamp of last token generation, or None if unavailable + """ + return self.info.timings.last_token_iteration + + @property + def token_iterations(self) -> int: + """ + :return: Total number of token generation iterations + """ + return self.info.timings.token_iterations + + @property + def prompt_tokens_timing(self) -> tuple[float, float]: + """ + :return: Tuple of (timestamp, token_count) for prompt processing + :raises ValueError: If resolve_end timings are not set + """ + return ( + ( + self.first_token_iteration + if self.first_token_iteration is not None + else self.request_end_time + ), + self.prompt_tokens or 0.0, + ) + + @property + def output_tokens_timings(self) -> list[tuple[float, float]]: + """ + :return: List of (timestamp, token_count) tuples for output token generations + :raises ValueError: If resolve_end timings are not set + """ + if ( + self.first_token_iteration is None + or self.last_token_iteration is None + or self.token_iterations <= 1 + ): + # No iteration data, return single timing at end with all tokens + return [ + ( + ( + self.last_token_iteration + if self.last_token_iteration is not None + else self.request_end_time + ), + self.output_tokens or 0.0, + ) + ] + + # Return first token timing as 1 token plus per-iteration timings + return [ + (self.first_token_iteration, 1.0 * bool(self.output_tokens)) + ] + self.iter_tokens_timings + + @property + def iter_tokens_timings(self) -> list[tuple[float, float]]: + """ + :return: List of (timestamp, token_count) tuples for iterations excluding + first token + """ + if ( + self.first_token_iteration is None + or self.last_token_iteration is None + or (tok_per_iter := self.iter_tokens_per_iteration) is None + or self.token_iterations <= 1 + ): + return [] + + # evenly space the iterations since we don't have per-iteration timings + # / we don't know the individual token counts per iteration + iter_times = np.linspace( + self.first_token_iteration, + self.last_token_iteration, + num=self.token_iterations, + )[1:] # skip first iteration + + return [(iter_time, tok_per_iter) for iter_time in iter_times] + + @property + def total_tokens_timings(self) -> list[tuple[float, float]]: + """ + :return: List of (timestamp, token_count) tuples for all token generations + """ + prompt_timings = self.prompt_tokens_timing + output_timings = self.output_tokens_timings + + return ([prompt_timings] if prompt_timings else []) + output_timings diff --git a/src/guidellm/schemas/response.py b/src/guidellm/schemas/response.py index d4e53aa3..a02ae8ba 100644 --- a/src/guidellm/schemas/response.py +++ b/src/guidellm/schemas/response.py @@ -11,10 +11,10 @@ from pydantic import Field +from guidellm.schemas.base import StandardBaseModel from guidellm.schemas.info import RequestInfo from guidellm.schemas.request import GenerationRequest, UsageMetrics -from guidellm.schemas.stats import GenerativeRequestStats -from guidellm.utils import StandardBaseModel +from guidellm.schemas.request_stats import GenerativeRequestStats __all__ = ["GenerationResponse"] diff --git a/src/guidellm/schemas/statistics.py b/src/guidellm/schemas/statistics.py new file mode 100644 index 00000000..17f2f2dd --- /dev/null +++ b/src/guidellm/schemas/statistics.py @@ -0,0 +1,1018 @@ +""" +Statistical distribution analysis and summary calculations for benchmark metrics. + +Provides comprehensive statistical analysis tools including percentile calculations, +summary statistics, and status-based distributions. Supports value distributions, +time-based rate and concurrency distributions with weighted sampling, and probability +density functions for analyzing benchmark performance metrics and request patterns +across different status categories (successful, incomplete, errored). +""" + +from __future__ import annotations + +import math +from collections.abc import Callable, Sequence +from typing import Literal, TypeVar + +import numpy as np +from pydantic import Field + +from guidellm.schemas.base import StandardBaseModel, StatusBreakdown + +__all__ = [ + "DistributionSummary", + "FunctionObjT", + "Percentiles", + "StatusDistributionSummary", +] + +FunctionObjT = TypeVar("FunctionObjT") + + +class Percentiles(StandardBaseModel): + """ + Standard percentile values for probability distributions. + + Captures key percentile points from 0.1th to 99.9th percentile for comprehensive + distribution analysis, enabling assessment of central tendency, spread, and tail + behavior in benchmark metrics. + """ + + p001: float = Field(description="0.1th percentile value") + p01: float = Field(description="1st percentile value") + p05: float = Field(description="5th percentile value") + p10: float = Field(description="10th percentile value") + p25: float = Field(description="25th percentile value") + p50: float = Field(description="50th percentile (median) value") + p75: float = Field(description="75th percentile value") + p90: float = Field(description="90th percentile value") + p95: float = Field(description="95th percentile value") + p99: float = Field(description="99th percentile value") + p999: float = Field(description="99.9th percentile value") + + @classmethod + def from_pdf( + cls, pdf: np.ndarray, epsilon: float = 1e-6, validate: bool = True + ) -> Percentiles: + """ + Create percentiles from a probability density function. + + :param pdf: 2D array (N, 2) with values in column 0 and probabilities in + column 1 + :param epsilon: Tolerance for probability sum validation + :param validate: Whether to validate probabilities sum to 1 and are + non-negative + :return: Percentiles object with computed values + :raises ValueError: If PDF shape is invalid, probabilities are negative, + or probabilities don't sum to 1 + """ + expected_shape = (None, 2) + + if len(pdf.shape) != len(expected_shape) or pdf.shape[1] != expected_shape[1]: + raise ValueError( + "PDF must be a 2D array of shape (N, 2) where first column is values " + f"and second column is probabilities. Got {pdf.shape} instead." + ) + + percentile_probs = { + "p001": 0.001, + "p01": 0.01, + "p05": 0.05, + "p10": 0.1, + "p25": 0.25, + "p50": 0.5, + "p75": 0.75, + "p90": 0.9, + "p95": 0.95, + "p99": 0.99, + "p999": 0.999, + } + + if pdf.shape[0] == 0: + return Percentiles(**dict.fromkeys(percentile_probs.keys(), 0.0)) + + probabilities = pdf[:, 1] + + if validate: + if np.any(probabilities < 0): + raise ValueError("Probabilities must be non-negative.") + + prob_sum = np.sum(probabilities) + if abs(prob_sum - 1.0) > epsilon: + raise ValueError(f"Probabilities must sum to 1, got {prob_sum}.") + + cdf_probs = np.cumsum(probabilities) + + return Percentiles( + **{ + key: pdf[np.searchsorted(cdf_probs, value, side="left"), 0].item() + for key, value in percentile_probs.items() + } + ) + + +class DistributionSummary(StandardBaseModel): + """ + Comprehensive statistical summary of a probability distribution. + + Captures central tendency (mean, median, mode), spread (variance, std_dev), + extrema (min, max), and percentile information with optional probability density + function. Supports creation from raw values, PDFs, or time-based event data for + rate and concurrency analysis in benchmark metrics. + """ + + mean: float = Field(description="Mean/average value") + median: float = Field(description="Median (50th percentile) value") + mode: float = Field(description="Mode (most probable) value") + variance: float = Field(description="Variance of the distribution") + std_dev: float = Field(description="Standard deviation") + min: float = Field(description="Minimum value") + max: float = Field(description="Maximum value") + count: int = Field(description="Number of observations") + total_sum: float = Field(description="Sum of all values") + percentiles: Percentiles = Field(description="Standard percentile values") + pdf: list[tuple[float, float]] | None = Field( + description="Probability density function as (value, probability) pairs", + default=None, + ) + + @classmethod + def from_pdf( + cls, + pdf: np.ndarray, + count: int | None = None, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + validate: bool = True, + ) -> DistributionSummary: + """ + Create distribution summary from a probability density function. + + :param pdf: 2D array (N, 2) with values in column 0 and probabilities in + column 1 + :param count: Number of original observations; defaults to PDF length + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :param validate: Whether to validate probabilities sum to 1 and are non-negative + :return: Complete distribution summary with statistics + :raises ValueError: If PDF shape is invalid or probabilities are invalid + """ + expected_shape = (None, 2) + + if len(pdf.shape) != len(expected_shape) or pdf.shape[1] != expected_shape[1]: + raise ValueError( + "PDF must be a 2D array of shape (N, 2) where first column is values " + f"and second column is probabilities. Got {pdf.shape} instead." + ) + + if pdf.shape[0] == 0: + return DistributionSummary( + mean=0.0, + median=0.0, + mode=0.0, + variance=0.0, + std_dev=0.0, + min=0.0, + max=0.0, + count=0 if count is None else count, + total_sum=0.0, + percentiles=Percentiles.from_pdf(pdf, epsilon=epsilon), + pdf=None if include_pdf is False else [], + ) + + # Calculate stats + values = pdf[:, 0] + probabilities = pdf[:, 1] + + if validate: + # Fail if probabilities don't sum to 1 or are negative + if np.any(probabilities < 0): + raise ValueError("Probabilities must be non-negative.") + + prob_sum = np.sum(probabilities) + if not np.isclose(prob_sum, 1.0, atol=epsilon): + raise ValueError(f"Probabilities must sum to 1.0 (sum={prob_sum}).") + + # Fail if values are not sorted + if not np.all(values[:-1] <= values[1:]): + raise ValueError("Values in PDF must be sorted in ascending order.") + + percentiles = Percentiles.from_pdf(pdf, epsilon=epsilon, validate=False) + median = percentiles.p50 + mean = np.sum(values * probabilities).item() + mode = values[np.argmax(probabilities)].item() + variance = np.sum((values - mean) ** 2 * probabilities).item() + std_dev = math.sqrt(variance) + minimum = values[0].item() + maximum = values[-1].item() + + if count is None: + count = len(pdf) + + total_sum = mean * count + sampled_pdf = cls._sample_pdf(pdf, include_pdf) + + return DistributionSummary( + mean=mean, + median=median, + mode=mode, + variance=variance, + std_dev=std_dev, + min=minimum, + max=maximum, + count=count, + total_sum=total_sum, + percentiles=percentiles, + pdf=sampled_pdf, + ) + + @classmethod + def _sample_pdf( + cls, pdf: np.ndarray, include_pdf: bool | int + ) -> list[tuple[float, float]] | None: + """ + Sample PDF based on include_pdf parameter. + + :param pdf: PDF array to sample + :param include_pdf: False for None, True for full, int for sampled size + :return: Sampled PDF as list of tuples or None + """ + if include_pdf is False: + return None + if include_pdf is True: + return pdf.tolist() + if isinstance(include_pdf, int) and include_pdf > 0: + if len(pdf) <= include_pdf: + return pdf.tolist() + sample_indices = np.linspace(0, len(pdf) - 1, include_pdf, dtype=int) + return pdf[sample_indices].tolist() + return [] + + @classmethod + def from_values( + cls, + values: Sequence[float | tuple[float, float]] | np.ndarray, + count: int | None = None, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> DistributionSummary: + """ + Create distribution summary from raw values with optional weights. + + :param values: Values or (value, weight) tuples, or numpy array + :param count: Number of original observations; defaults to sum of weights + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Distribution summary computed from the values + :raises ValueError: If total weight is zero or invalid + """ + np_values = cls._to_weighted_ndarray(values, num_values_per_item=2) + + if np_values.shape[0] == 0: + return DistributionSummary.from_pdf( + pdf=np.empty((0, 2)), count=0, include_pdf=include_pdf, epsilon=epsilon + ) + + if count is None: + count = round(np.sum(np_values[:, 1]).item()) + + # Sort values and weights by values + sort_ind = np.argsort(np_values[:, 0]) + sorted_values = np_values[sort_ind, 0] + sorted_weights = np_values[sort_ind, 1] + + # Combine any duplicate values by summing their weights + unique_values, inverse_indices = np.unique(sorted_values, return_inverse=True) + combined_weights = np.zeros_like(unique_values, dtype=float) + np.add.at(combined_weights, inverse_indices, sorted_weights) + + # Remove any values with zero weight + nonzero_mask = combined_weights > 0 + final_values = unique_values[nonzero_mask] + final_weights = combined_weights[nonzero_mask] + + # Create PDF by normalizing weights and stacking + total_weight = np.sum(final_weights) + if total_weight <= epsilon: + # No valid weights to create PDF, overwrite to uniform distribution + final_weights = np.ones_like(final_values) + total_weight = np.sum(final_weights) + + probabilities = final_weights / total_weight + pdf = np.column_stack((final_values, probabilities)) + + return DistributionSummary.from_pdf( + pdf=pdf, + count=count, + include_pdf=include_pdf, + epsilon=epsilon, + validate=False, + ) + + @classmethod + def rate_distribution_from_timings( + cls, + event_times: Sequence[float | tuple[float, float]] | np.ndarray, + start_time: float | None = None, + end_time: float | None = None, + threshold: float | None = 1e-4, # 1/10th of a millisecond + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> DistributionSummary: + """ + Create rate distribution from event timestamps. + + Computes event rates over time intervals weighted by interval duration for + analyzing request throughput patterns. + + :param event_times: Event timestamps or (timestamp, weight) tuples + :param start_time: Analysis window start; filters earlier events + :param end_time: Analysis window end; filters later events + :param threshold: Time threshold for merging nearby events; 1/10th millisecond + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Distribution summary of event rates over time + """ + weighted_times = cls._to_weighted_ndarray(event_times, num_values_per_item=2) + + if start_time is not None: + # Filter out any times before start, insert start time with 0 weight + weighted_times = np.insert( + weighted_times[weighted_times[:, 0] >= start_time], + 0, + [start_time, 0.0], + axis=0, + ) + + if end_time is not None: + # Filter out any times after end, insert end time with 0 weight + weighted_times = np.append( + weighted_times[weighted_times[:, 0] <= end_time], + [[end_time, 0.0]], + axis=0, + ) + + # Sort by time for merging, merge any times within threshold + sort_ind = np.argsort(weighted_times[:, 0]) + weighted_times = weighted_times[sort_ind] + weighted_times = cls._merge_sorted_times_with_weights(weighted_times, threshold) + + if len(weighted_times) <= 1: + # No data to calculate rates from (need at least two times) + return cls.from_values( + [], + count=len(weighted_times), + include_pdf=include_pdf, + epsilon=epsilon, + ) + + times = weighted_times[:, 0] + occurrences = weighted_times[:, 1] + + # Calculate local duration for each event: ((times[i+1] - times[i-1])) / 2 + midpoints = (times[1:] + times[:-1]) / 2 + durations = np.empty_like(times) + durations[0] = midpoints[0] - times[0] + durations[1:-1] = midpoints[1:] - midpoints[:-1] + durations[-1] = np.clip(times[-1] - midpoints[-1], epsilon, None) + + # Calculate rate at each interval: occurences[i] / duration[i] + rates = occurrences / durations + count = round(np.sum(occurrences).item()) + + return cls.from_values( + np.column_stack((rates, durations)), + count=count, + include_pdf=include_pdf, + epsilon=epsilon, + ) + + @classmethod + def concurrency_distribution_from_timings( + cls, + event_intervals: ( + Sequence[tuple[float, float] | tuple[float, float, float]] | np.ndarray + ), + start_time: float | None = None, + end_time: float | None = None, + threshold: float | None = 1e-4, # 1/10th of a millisecond + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> DistributionSummary: + """ + Create concurrency distribution from event time intervals. + + Tracks overlapping events to compute concurrency levels over time for analyzing + request processing patterns and resource utilization. + + :param event_intervals: Event (start, end) or (start, end, weight) tuples + :param start_time: Analysis window start + :param end_time: Analysis window end + :param threshold: Time threshold for merging nearby transitions; + 1/10th millisecond + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Distribution summary of concurrency levels over time + """ + weighted_intervals = cls._to_weighted_ndarray( + event_intervals, num_values_per_item=3 + ) + + # If start_time, filter any intervals that end before start_time + if start_time is not None: + keep_mask = weighted_intervals[:, 1] >= start_time + weighted_intervals = weighted_intervals[keep_mask] + + # If end_time, filter any intervals that start after end_time + if end_time is not None: + keep_mask = weighted_intervals[:, 0] <= end_time + weighted_intervals = weighted_intervals[keep_mask] + + count = len(weighted_intervals) + + # Convert to concurrency changes at each time + add_occurences = ( + np.stack( + ( + weighted_intervals[:, 0], + weighted_intervals[:, 2], + ), + axis=1, + ) + if len(weighted_intervals) > 0 + else np.empty((0, 2)) + ) + remove_occurences = ( + np.stack( + ( + weighted_intervals[:, 1], + -1 * weighted_intervals[:, 2], + ), + axis=1, + ) + if len(weighted_intervals) > 0 + else np.empty((0, 2)) + ) + + # Combine add and remove occurences into weighted times + weighted_times = np.vstack((add_occurences, remove_occurences)) + + # Sort by the times and merge any times within threshold + weighted_times = weighted_times[np.argsort(weighted_times[:, 0])] + weighted_times = cls._merge_sorted_times_with_weights(weighted_times, threshold) + + # If start_time, ensure included (if any before, add final concurrency at start) + if start_time is not None and len(weighted_times) > 0: + start_ind = np.searchsorted(weighted_times[:, 0], start_time, side="left") + prior_delta = ( + np.sum(weighted_times[:start_ind, 1]) if start_ind > 0 else 0.0 + ) + weighted_times = np.insert( + weighted_times[start_ind:], 0, [start_time, prior_delta], axis=0 + ) + + # If end_time, ensure included (if any after, filter out) + if end_time is not None and len(weighted_times) > 0: + end_ind = np.searchsorted(weighted_times[:, 0], end_time, side="right") + weighted_times = np.append( + weighted_times[:end_ind], [[end_time, 0.0]], axis=0 + ) + + # Calculate concurrency from cumulative sum of changes over time + concurrencies = np.clip(np.cumsum(weighted_times[:, 1]), 0, None) + + if len(concurrencies) <= 1: + # No data to calculate concurrency from + return cls.from_values( + [] if count == 0 else [concurrencies[0].item()], + include_pdf=include_pdf, + epsilon=epsilon, + ) + + # Calculate durations equal to times[i+1] - times[i] + # The last concurrency level is not used since no following time point + durations = np.clip(np.diff(weighted_times[:, 0]), 0, None) + values = np.column_stack((concurrencies[:-1], durations)) + + return ( + cls.from_values( + values, + count=count, + include_pdf=include_pdf, + epsilon=epsilon, + ) + if np.any(durations > 0) + else cls.from_values( + [], + count=count, + include_pdf=include_pdf, + epsilon=epsilon, + ) + ) + + @classmethod + def _to_weighted_ndarray( + cls, + inputs: ( + Sequence[float | tuple[float, float] | tuple[float, float, float]] + | np.ndarray + ), + num_values_per_item: Literal[2, 3], + ) -> np.ndarray: + if not isinstance(inputs, np.ndarray): + # Convert list to structured numpy array with dims (N, num_dimensions) + # Fill in missing weights with 1.0 + return cls._sequence_to_weighted_ndarray(inputs, num_values_per_item) + + if len(inputs.shape) == 1: + # 1D array: reshape to (N, 1) and add weights column + inputs = inputs.reshape(-1, 1) + weights = np.ones((inputs.shape[0], 1), dtype=float) + + return ( + np.hstack((inputs, weights)) + if num_values_per_item == 2 # noqa: PLR2004 + else np.hstack((inputs, inputs, weights)) + ) + + if len(inputs.shape) == 2 and inputs.shape[1] == num_values_per_item - 1: # noqa: PLR2004 + # Add weights column of 1.0 + weights = np.ones((inputs.shape[0], 1), dtype=float) + + return np.hstack((inputs, weights)) + + if len(inputs.shape) == 2 and inputs.shape[1] == num_values_per_item: # noqa: PLR2004 + return inputs + + raise ValueError( + "inputs must be a numpy array of shape (N,), " + f"(N, {num_values_per_item - 1}), or (N, {num_values_per_item}). " + f"Got shape {inputs.shape}." + ) + + @classmethod + def _sequence_to_weighted_ndarray( + cls, + inputs: Sequence[float | tuple[float, float] | tuple[float, float, float]], + num_values_per_item: Literal[2, 3], + ) -> np.ndarray: + ndarray = np.empty((len(inputs), num_values_per_item), dtype=float) + scalar_types: tuple[type, ...] = (int, float, np.integer, np.floating) + + for ind, val in enumerate(inputs): + if isinstance(val, scalar_types): + ndarray[ind, :] = ( + (val, 1.0) if num_values_per_item == 2 else (val, val, 1.0) # noqa: PLR2004 + ) + elif isinstance(val, tuple) and len(val) == num_values_per_item: + ndarray[ind, :] = val + elif isinstance(val, tuple) and len(val) == num_values_per_item - 1: + ndarray[ind, :] = ( + (val[0], 1.0) if num_values_per_item == 2 else (val[0], val[1], 1.0) # noqa: PLR2004 + ) + else: + raise ValueError( + "Each item must be a float or a tuple of " + f"{num_values_per_item} or {num_values_per_item - 1} " + "elements." + ) + + return ndarray + + @classmethod + def _merge_sorted_times_with_weights( + cls, weighted_times: np.ndarray, threshold: float | None + ) -> np.ndarray: + # First remove any exact duplicate times and sum their weights + unique_times, inverse = np.unique(weighted_times[:, 0], return_inverse=True) + unique_weights = np.zeros_like(unique_times, dtype=float) + np.add.at(unique_weights, inverse, weighted_times[:, 1]) + weighted_times = np.column_stack((unique_times, unique_weights)) + + if threshold is None or threshold <= 0.0: + return weighted_times + + # Loop to merge times within threshold until no more merges possible + # (loop due to possible overlapping merge groups) + while weighted_times.shape[0] > 1: + times = weighted_times[:, 0] + weights = weighted_times[:, 1] + + # Find diffs between consecutive times, create mask for within-threshold + diffs = np.diff(times) + within = diffs <= threshold + if not np.any(within): + break + + # Start indices are marked by the transition from 0 to 1 in the mask + # End indices found by searching for last time within threshold from start + starts = np.where(np.diff(np.insert(within.astype(int), 0, 0)) == 1)[0] + start_end_times = times[starts] + threshold + ends = np.searchsorted(times, start_end_times, side="right") - 1 + + # Collapse overlapping or chained merge groups + if len(starts) > 1: + valid_mask = np.concatenate([[True], starts[1:] > ends[:-1]]) + starts, ends = starts[valid_mask], ends[valid_mask] + + # Update weights at start indices to sum of merged weights + cumsum = np.concatenate(([0.0], np.cumsum(weights))) + weighted_times[starts, 1] = cumsum[ends + 1] - cumsum[starts] + + # Calculate vectorized mask for removing merged entries + merged_events = np.zeros(len(weighted_times) + 1, dtype=int) + np.add.at(merged_events, starts, 1) + np.add.at(merged_events, ends + 1, -1) + remove_mask = np.cumsum(merged_events[:-1]) > 0 + remove_mask[starts] = False # Keep start indices + + # Remove merged entries, update weighted_times + weights = weights[~remove_mask] + times = times[~remove_mask] + weighted_times = np.column_stack((times, weights)) + + return weighted_times + + +class StatusDistributionSummary( + StatusBreakdown[ + DistributionSummary, + DistributionSummary, + DistributionSummary, + DistributionSummary, + ] +): + """ + Distribution summaries broken down by request status categories. + + Provides separate statistical analysis for successful, incomplete, and errored + requests with total aggregate statistics. Enables status-aware performance analysis + and SLO validation across different request outcomes in benchmark results. + """ + + @property + def count(self) -> int: + """ + :return: Total count of samples across all status categories + """ + return self.total.count + + @property + def total_sum(self) -> float: + """ + :return: Total sum of values across all status categories + """ + return self.total.total_sum + + @classmethod + def from_values( + cls, + successful: Sequence[float | tuple[float, float]] | np.ndarray, + incomplete: Sequence[float | tuple[float, float]] | np.ndarray, + errored: Sequence[float | tuple[float, float]] | np.ndarray, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> StatusDistributionSummary: + """ + Create status-broken-down distribution from values by status category. + + :param successful: Values or (value, weight) tuples for successful requests + :param incomplete: Values or (value, weight) tuples for incomplete requests + :param errored: Values or (value, weight) tuples for errored requests + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Status breakdown of distribution summaries + """ + total, successful_arr, incomplete_arr, errored_arr = cls._combine_status_arrays( + successful, incomplete, errored, num_values_per_item=2 + ) + + return StatusDistributionSummary( + total=DistributionSummary.from_values( + total, include_pdf=include_pdf, epsilon=epsilon + ), + successful=DistributionSummary.from_values( + successful_arr, include_pdf=include_pdf, epsilon=epsilon + ), + incomplete=DistributionSummary.from_values( + incomplete_arr, include_pdf=include_pdf, epsilon=epsilon + ), + errored=DistributionSummary.from_values( + errored_arr, include_pdf=include_pdf, epsilon=epsilon + ), + ) + + @classmethod + def from_values_function( + cls, + function: Callable[ + [FunctionObjT], + float | tuple[float, float] | Sequence[float | tuple[float, float]] | None, + ], + successful: Sequence[FunctionObjT], + incomplete: Sequence[FunctionObjT], + errored: Sequence[FunctionObjT], + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> StatusDistributionSummary: + """ + Create distribution summary by extracting values from objects via function. + + :param function: Function to extract value(s) from each object + :param successful: Successful request objects + :param incomplete: Incomplete request objects + :param errored: Errored request objects + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Status breakdown of distribution summaries + """ + + def _extract_values( + _objs: Sequence[FunctionObjT], + ) -> Sequence[float | tuple[float, float]]: + _outputs: list[float | tuple[float, float]] = [] + for _obj in _objs: + if (_result := function(_obj)) is None: + continue + if isinstance(_result, Sequence) and not isinstance(_result, tuple): + _outputs.extend(_result) + else: + _outputs.append(_result) + return _outputs + + return cls.from_values( + successful=_extract_values(successful), + incomplete=_extract_values(incomplete), + errored=_extract_values(errored), + include_pdf=include_pdf, + epsilon=epsilon, + ) + + @classmethod + def rate_distribution_from_timings( + cls, + successful: Sequence[float | tuple[float, float]] | np.ndarray, + incomplete: Sequence[float | tuple[float, float]] | np.ndarray, + errored: Sequence[float | tuple[float, float]] | np.ndarray, + start_time: float | None = None, + end_time: float | None = None, + threshold: float | None = 1e-4, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> StatusDistributionSummary: + """ + Create status-broken-down rate distribution from event timestamps. + + :param successful: Timestamps for successful request events + :param incomplete: Timestamps for incomplete request events + :param errored: Timestamps for errored request events + :param start_time: Analysis window start + :param end_time: Analysis window end + :param threshold: Time threshold for merging nearby events + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Status breakdown of rate distribution summaries + """ + total, successful_arr, incomplete_arr, errored_arr = cls._combine_status_arrays( + successful, incomplete, errored, num_values_per_item=2 + ) + + return StatusDistributionSummary( + total=DistributionSummary.rate_distribution_from_timings( + total, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + successful=DistributionSummary.rate_distribution_from_timings( + successful_arr, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + incomplete=DistributionSummary.rate_distribution_from_timings( + incomplete_arr, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + errored=DistributionSummary.rate_distribution_from_timings( + errored_arr, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + ) + + @classmethod + def rate_distribution_from_timings_function( + cls, + function: Callable[ + [FunctionObjT], + float | tuple[float, float] | Sequence[float | tuple[float, float]] | None, + ], + successful: Sequence[FunctionObjT], + incomplete: Sequence[FunctionObjT], + errored: Sequence[FunctionObjT], + start_time: float | None = None, + end_time: float | None = None, + threshold: float | None = 1e-4, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> StatusDistributionSummary: + """ + Create rate distribution by extracting timestamps from objects via function. + + :param function: Function to extract timestamp(s) from each object + :param successful: Successful request objects + :param incomplete: Incomplete request objects + :param errored: Errored request objects + :param start_time: Analysis window start + :param end_time: Analysis window end + :param threshold: Time threshold for merging nearby events + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Status breakdown of rate distribution summaries + """ + + def _extract_values( + _objs: Sequence[FunctionObjT], + ) -> Sequence[float | tuple[float, float]]: + _outputs: list[float | tuple[float, float]] = [] + for _obj in _objs: + if (_result := function(_obj)) is None: + continue + if isinstance(_result, Sequence) and not isinstance(_result, tuple): + _outputs.extend(_result) + else: + _outputs.append(_result) + return _outputs + + return cls.rate_distribution_from_timings( + successful=_extract_values(successful), + incomplete=_extract_values(incomplete), + errored=_extract_values(errored), + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ) + + @classmethod + def concurrency_distribution_from_timings( + cls, + successful: Sequence[tuple[float, float] | tuple[float, float, float]] + | np.ndarray, + incomplete: Sequence[tuple[float, float] | tuple[float, float, float]] + | np.ndarray, + errored: Sequence[tuple[float, float] | tuple[float, float, float]] + | np.ndarray, + start_time: float | None = None, + end_time: float | None = None, + threshold: float | None = 1e-4, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> StatusDistributionSummary: + """ + Create status-broken-down concurrency distribution from event intervals. + + :param successful: Event intervals for successful requests + :param incomplete: Event intervals for incomplete requests + :param errored: Event intervals for errored requests + :param start_time: Analysis window start + :param end_time: Analysis window end + :param threshold: Time threshold for merging nearby transitions + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Status breakdown of concurrency distribution summaries + """ + total, successful_arr, incomplete_arr, errored_arr = cls._combine_status_arrays( + successful, incomplete, errored, num_values_per_item=3 + ) + + return StatusDistributionSummary( + total=DistributionSummary.concurrency_distribution_from_timings( + total, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + successful=DistributionSummary.concurrency_distribution_from_timings( + successful_arr, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + incomplete=DistributionSummary.concurrency_distribution_from_timings( + incomplete_arr, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + errored=DistributionSummary.concurrency_distribution_from_timings( + errored_arr, + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ), + ) + + @classmethod + def concurrency_distribution_from_timings_function( + cls, + function: Callable[ + [FunctionObjT], + tuple[float, float] + | tuple[float, float, float] + | Sequence[tuple[float, float] | tuple[float, float, float]] + | None, + ], + successful: Sequence[FunctionObjT], + incomplete: Sequence[FunctionObjT], + errored: Sequence[FunctionObjT], + start_time: float | None = None, + end_time: float | None = None, + threshold: float | None = 1e-4, + include_pdf: bool | int = False, + epsilon: float = 1e-6, + ) -> StatusDistributionSummary: + """ + Create concurrency distribution by extracting intervals from objects. + + :param function: Function to extract time interval(s) from each object + :param successful: Successful request objects + :param incomplete: Incomplete request objects + :param errored: Errored request objects + :param start_time: Analysis window start + :param end_time: Analysis window end + :param threshold: Time threshold for merging nearby transitions + :param include_pdf: Whether to include PDF; True for full, int for sampled size + :param epsilon: Tolerance for probability validation + :return: Status breakdown of concurrency distribution summaries + """ + + def _extract_values( + _objs: Sequence[FunctionObjT], + ) -> Sequence[tuple[float, float] | tuple[float, float, float]]: + _outputs: list[tuple[float, float] | tuple[float, float, float]] = [] + for _obj in _objs: + if (_result := function(_obj)) is None: + continue + if isinstance(_result, Sequence) and not isinstance(_result, tuple): + _outputs.extend(_result) + else: + _outputs.append(_result) + return _outputs + + return cls.concurrency_distribution_from_timings( + successful=_extract_values(successful), + incomplete=_extract_values(incomplete), + errored=_extract_values(errored), + start_time=start_time, + end_time=end_time, + threshold=threshold, + include_pdf=include_pdf, + epsilon=epsilon, + ) + + @classmethod + def _combine_status_arrays( + cls, + successful: Sequence[float | tuple[float, float] | tuple[float, float, float]] + | np.ndarray, + incomplete: Sequence[float | tuple[float, float] | tuple[float, float, float]] + | np.ndarray, + errored: Sequence[float | tuple[float, float] | tuple[float, float, float]] + | np.ndarray, + num_values_per_item: Literal[2, 3], + ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + successful_array = DistributionSummary._to_weighted_ndarray( # noqa: SLF001 + successful, num_values_per_item=num_values_per_item + ) + incomplete_array = DistributionSummary._to_weighted_ndarray( # noqa: SLF001 + incomplete, num_values_per_item=num_values_per_item + ) + errored_array = DistributionSummary._to_weighted_ndarray( # noqa: SLF001 + errored, num_values_per_item=num_values_per_item + ) + total_array = np.concatenate( + (successful_array, incomplete_array, errored_array), axis=0 + ) + return total_array, successful_array, incomplete_array, errored_array diff --git a/src/guidellm/schemas/stats.py b/src/guidellm/schemas/stats.py deleted file mode 100644 index 67f1d26c..00000000 --- a/src/guidellm/schemas/stats.py +++ /dev/null @@ -1,228 +0,0 @@ -""" -Request statistics and metrics for generative AI benchmark analysis. - -Provides data structures for capturing and analyzing performance metrics from -generative AI workloads. Contains request-level statistics including token counts, -latency measurements, and throughput calculations for text generation benchmarks. -""" - -from __future__ import annotations - -from typing import Literal - -from pydantic import Field, computed_field - -from guidellm.schemas.info import RequestInfo -from guidellm.schemas.request import GenerativeRequestType, UsageMetrics -from guidellm.utils import StandardBaseDict - -__all__ = ["GenerativeRequestStats"] - - -class GenerativeRequestStats(StandardBaseDict): - """ - Request statistics for generative AI text generation workloads. - - Captures comprehensive performance metrics for individual generative requests, - including token counts, timing measurements, and derived performance statistics. - Provides computed properties for latency analysis, throughput calculations, - and token generation metrics essential for benchmark evaluation. - - Example: - :: - stats = GenerativeRequestStats( - request_id="req_123", - request_type="text_completion", - info=request_info, - input_metrics=input_usage, - output_metrics=output_usage - ) - throughput = stats.output_tokens_per_second - """ - - type_: Literal["generative_request_stats"] = "generative_request_stats" - request_id: str = Field(description="Unique identifier for the request") - request_type: GenerativeRequestType | str = Field( - description="Type of generative request: text or chat completion" - ) - request_args: str | None = Field( - default=None, description="Arguments passed to the backend for this request" - ) - output: str | None = Field( - description="Generated text output, if request completed successfully" - ) - info: RequestInfo = Field( - description="Metadata and timing information for the request" - ) - input_metrics: UsageMetrics = Field( - description="Usage statistics for the input prompt" - ) - output_metrics: UsageMetrics = Field( - description="Usage statistics for the generated output" - ) - - # Request stats - @computed_field # type: ignore[misc] - @property - def request_latency(self) -> float | None: - """ - End-to-end request processing latency in seconds. - - :return: Duration from request start to completion, or None if unavailable. - """ - if not self.info.timings.request_end or not self.info.timings.request_start: - return None - - return self.info.timings.request_end - self.info.timings.request_start - - # General token stats - @computed_field # type: ignore[misc] - @property - def prompt_tokens(self) -> int | None: - """ - Number of tokens in the input prompt. - - :return: Input prompt token count, or None if unavailable. - """ - return self.input_metrics.text_tokens - - @computed_field # type: ignore[misc] - @property - def input_tokens(self) -> int | None: - """ - Number of tokens in the input prompt. - - :return: Input prompt token count, or None if unavailable. - """ - return self.input_metrics.total_tokens - - @computed_field # type: ignore[misc] - @property - def output_tokens(self) -> int | None: - """ - Number of tokens in the generated output. - - :return: Generated output token count, or None if unavailable. - """ - return self.output_metrics.total_tokens - - @computed_field # type: ignore[misc] - @property - def total_tokens(self) -> int | None: - """ - Total token count including prompt and output tokens. - - :return: Sum of prompt and output tokens, or None if either is unavailable. - """ - input_tokens = self.input_metrics.total_tokens - output_tokens = self.output_metrics.total_tokens - - if input_tokens is None and output_tokens is None: - return None - - return (input_tokens or 0) + (output_tokens or 0) - - @computed_field # type: ignore[misc] - @property - def time_to_first_token_ms(self) -> float | None: - """ - Time to first token generation in milliseconds. - - :return: Latency from request start to first token, or None if unavailable. - """ - if ( - not self.info.timings.first_iteration - or not self.info.timings.request_start - or self.info.timings.first_iteration == self.info.timings.last_iteration - ): - return None - - return 1000 * ( - self.info.timings.first_iteration - self.info.timings.request_start - ) - - @computed_field # type: ignore[misc] - @property - def time_per_output_token_ms(self) -> float | None: - """ - Average time per output token in milliseconds. - - Includes time for first token and all subsequent tokens. - - :return: Average milliseconds per output token, or None if unavailable. - """ - if ( - not self.info.timings.request_start - or not self.info.timings.last_iteration - or not self.output_metrics.total_tokens - ): - return None - - return ( - 1000 - * (self.info.timings.last_iteration - self.info.timings.request_start) - / self.output_metrics.total_tokens - ) - - @computed_field # type: ignore[misc] - @property - def inter_token_latency_ms(self) -> float | None: - """ - Average inter-token latency in milliseconds. - - Measures time between token generations, excluding first token. - - :return: Average milliseconds between tokens, or None if unavailable. - """ - if ( - not self.info.timings.first_iteration - or not self.info.timings.last_iteration - or not self.output_metrics.total_tokens - or self.output_metrics.total_tokens <= 1 - ): - return None - - return ( - 1000 - * (self.info.timings.last_iteration - self.info.timings.first_iteration) - / (self.output_metrics.total_tokens - 1) - ) - - @computed_field # type: ignore[misc] - @property - def tokens_per_second(self) -> float | None: - """ - Overall token throughput including prompt and output tokens. - - :return: Total tokens per second, or None if unavailable. - """ - if not (latency := self.request_latency) or self.total_tokens is None: - return None - - return self.total_tokens / latency - - @computed_field # type: ignore[misc] - @property - def output_tokens_per_second(self) -> float | None: - """ - Output token generation throughput. - - :return: Output tokens per second, or None if unavailable. - """ - if not (latency := self.request_latency) or self.output_tokens is None: - return None - - return self.output_tokens / latency - - @computed_field # type: ignore[misc] - @property - def output_tokens_per_iteration(self) -> float | None: - """ - Average output tokens generated per iteration. - - :return: Output tokens per iteration, or None if unavailable. - """ - if self.output_tokens is None or not self.info.timings.iterations: - return None - - return self.output_tokens / self.info.timings.iterations diff --git a/src/guidellm/settings.py b/src/guidellm/settings.py index c3552d5b..293416d7 100644 --- a/src/guidellm/settings.py +++ b/src/guidellm/settings.py @@ -162,7 +162,7 @@ class Settings(BaseSettings): preferred_output_tokens_source: Literal["request", "response"] = "response" preferred_backend: Literal["openai"] = "openai" preferred_route: Literal["text_completions", "chat_completions"] = ( - "text_completions" + "chat_completions" ) openai: OpenAISettings = OpenAISettings() diff --git a/src/guidellm/utils/__init__.py b/src/guidellm/utils/__init__.py index 89312771..0874c291 100644 --- a/src/guidellm/utils/__init__.py +++ b/src/guidellm/utils/__init__.py @@ -13,6 +13,7 @@ all_defined, safe_add, safe_divide, + safe_format_number, safe_format_timestamp, safe_getattr, safe_multiply, @@ -28,23 +29,9 @@ SendMessageT, ) from .mixins import InfoMixin -from .pydantic_utils import ( - PydanticClassRegistryMixin, - ReloadableBaseModel, - StandardBaseDict, - StandardBaseModel, - StatusBreakdown, -) from .random import IntegerRangeSampler from .registry import RegistryMixin, RegistryObjT from .singleton import SingletonMixin, ThreadSafeSingletonMixin -from .statistics import ( - DistributionSummary, - Percentiles, - RunningStats, - StatusDistributionSummary, - TimeRunningStats, -) from .synchronous import ( wait_for_sync_barrier, wait_for_sync_event, @@ -67,11 +54,9 @@ "SUPPORTED_TYPES", "AutoImporterMixin", "Colors", - "Colors", "Console", "ConsoleUpdateStep", "DefaultGroupHandler", - "DistributionSummary", "Encoder", "EncodingTypesAlias", "EndlessTextCreator", @@ -82,25 +67,15 @@ "InterProcessMessagingPipe", "InterProcessMessagingQueue", "MessageEncoding", - "MessageEncoding", - "Percentiles", - "PydanticClassRegistryMixin", "RegistryMixin", "RegistryObjT", - "ReloadableBaseModel", - "RunningStats", "SendMessageT", "SerializationTypesAlias", "Serializer", "SingletonMixin", - "StandardBaseDict", - "StandardBaseModel", - "StatusBreakdown", - "StatusDistributionSummary", "StatusIcons", "StatusStyles", "ThreadSafeSingletonMixin", - "TimeRunningStats", "all_defined", "camelize_str", "check_load_processor", @@ -114,6 +89,7 @@ "recursive_key_update", "safe_add", "safe_divide", + "safe_format_number", "safe_format_timestamp", "safe_getattr", "safe_multiply", diff --git a/src/guidellm/utils/cli.py b/src/guidellm/utils/cli.py index 9af6841a..104b40b1 100644 --- a/src/guidellm/utils/cli.py +++ b/src/guidellm/utils/cli.py @@ -1,3 +1,4 @@ +import contextlib import json from typing import Any @@ -51,7 +52,12 @@ def parse_json(ctx, param, value): # noqa: ARG001 return result if "{" not in value and "}" not in value: - # Treat it as a plain string if it doesn't look like JSON. + # Treat it as a primitive if it doesn't look like JSON. + try: + value = int(value) + except ValueError: + with contextlib.suppress(ValueError): + value = float(value) return value try: diff --git a/src/guidellm/utils/console.py b/src/guidellm/utils/console.py index 54e90cf7..bdb2da86 100644 --- a/src/guidellm/utils/console.py +++ b/src/guidellm/utils/console.py @@ -1,8 +1,18 @@ +""" +Console utilities for rich terminal output and status updates. + +Provides an extended Rich console with custom formatting for status messages, +progress tracking, and tabular data display. Includes predefined color schemes, +status levels, icons, and styles for consistent terminal output across the +application. Supports multi-step operations with spinners and context managers +for clean progress reporting. +""" + from __future__ import annotations -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from dataclasses import dataclass -from typing import Any, Literal +from typing import Annotated, Any, Literal from rich.console import Console as RichConsole from rich.padding import Padding @@ -14,11 +24,41 @@ "Console", "ConsoleUpdateStep", "StatusIcons", + "StatusLevel", "StatusStyles", ] +StatusLevel = Annotated[ + Literal[ + "debug", + "info", + "warning", + "error", + "critical", + "notset", + "success", + ], + "Status level for console messages indicating severity or state", +] + class Colors: + """ + Color constants for console styling. + + Provides standardized color schemes for different message types and branding. + Colors are defined using Rich console color names or hex values. + + :cvar info: Color for informational messages + :cvar progress: Color for progress indicators + :cvar success: Color for successful operations + :cvar warning: Color for warning messages + :cvar error: Color for error messages + :cvar primary: Primary brand color + :cvar secondary: Secondary brand color + :cvar tertiary: Tertiary brand color + """ + # Core states info: str = "light_steel_blue" progress: str = "dark_slate_gray1" @@ -32,7 +72,10 @@ class Colors: tertiary: str = "#008080" -StatusIcons: Mapping[str, str] = { +StatusIcons: Annotated[ + Mapping[str, str], + "Mapping of status levels to unicode icon characters for visual indicators", +] = { "debug": "…", "info": "ℹ", "warning": "⚠", @@ -42,7 +85,10 @@ class Colors: "success": "✔", } -StatusStyles: Mapping[str, str] = { +StatusStyles: Annotated[ + Mapping[str, str], + "Mapping of status levels to Rich console style strings for colored output", +] = { "debug": "dim", "info": f"bold {Colors.info}", "warning": f"bold {Colors.warning}", @@ -55,95 +101,119 @@ class Colors: @dataclass class ConsoleUpdateStep: + """ + Context manager for multi-step progress operations with spinner. + + Displays animated spinner during operation execution and allows dynamic + status updates. Automatically stops spinner on exit and prints final + status message. Designed for use with Python's `with` statement. + + Example: + :: + console = Console() + with console.print_update_step("Processing data") as step: + step.update("Loading files", "info") + # ... do work ... + step.finish("Completed successfully", status_level="success") + + :param console: The Console instance to use for output + :param title: Initial progress message to display + :param details: Optional additional details to show after completion + :param status_level: Initial status level determining style and icon + :param spinner: Spinner animation style name from Rich's spinner set + """ + console: Console title: str details: Any | None = None - status_level: Literal[ - "debug", - "info", - "warning", - "error", - "critical", - "notset", - "success", - ] = "info" + status_level: StatusLevel = "info" spinner: str = "dots" _status: Status | None = None - def __enter__(self): + def __enter__(self) -> ConsoleUpdateStep: if self.console.quiet: return self + style = StatusStyles.get(self.status_level, "bold") self._status = self.console.status( - f"[{StatusStyles.get(self.status_level, 'bold')}]{self.title}[/]", + f"[{style}]{self.title}[/]", spinner=self.spinner, ) self._status.__enter__() return self - def update( - self, - title: str, - status_level: Literal[ - "debug", - "info", - "warning", - "error", - "critical", - "notset", - "success", - ] - | None = None, - ): + def update(self, title: str, status_level: StatusLevel | None = None): + """ + Update the progress message and optionally the status level. + + :param title: New progress message to display + :param status_level: Optional new status level to apply + """ self.title = title if status_level is not None: self.status_level = status_level + if self._status: - self._status.update( - status=f"[{StatusStyles.get(self.status_level, 'bold')}]{title}[/]" - ) + style = StatusStyles.get(self.status_level, "bold") + self._status.update(status=f"[{style}]{title}[/]") def finish( self, title: str, details: Any | None = None, - status_level: Literal[ - "debug", - "info", - "warning", - "error", - "critical", - "notset", - "success", - ] = "info", + status_level: StatusLevel = "info", ): + """ + Stop the spinner and print the final status message. + + :param title: Final completion message to display + :param details: Optional additional information to show below message + :param status_level: Status level for final message styling + """ self.title = title self.status_level = status_level + if self._status: self._status.stop() + self.console.print_update(title, details, status_level) def __exit__(self, exc_type, exc_val, exc_tb): if self._status: - return self._status.__exit__(exc_type, exc_val, exc_tb) - return False + self._status.__exit__(exc_type, exc_val, exc_tb) class Console(RichConsole): + """ + Extended Rich console with custom formatting and status reporting. + + Enhances Rich's Console with specialized methods for status messages, + progress tracking with spinners, and formatted table output. Provides + consistent styling through predefined status levels, icons, and colors. + Supports quiet mode to suppress non-critical output. + + Example: + :: + console = Console() + console.print_update("Starting process", status="info") + with console.print_update_step("Loading data") as step: + step.update("Processing items") + step.finish("Complete", status_level="success") + """ + def print_update( self, title: str, - details: str | None = None, - status: Literal[ - "debug", - "info", - "warning", - "error", - "critical", - "notset", - "success", - ] = "info", - ) -> None: + details: Any | None = None, + status: StatusLevel = "info", + ): + """ + Print a status message with icon and optional details. + + :param title: Main status message to display + :param details: Optional additional details shown indented below message + :param status: Status level determining icon and styling + """ icon = StatusIcons.get(status, "•") style = StatusStyles.get(status, "bold") line = Text.assemble(f"{icon} ", (title, style)) @@ -151,6 +221,11 @@ def print_update( self.print_update_details(details) def print_update_details(self, details: Any | None): + """ + Print additional details indented below a status message. + + :param details: Content to display, converted to string and styled dimly + """ if details: block = Padding( Text.from_markup(str(details)), @@ -162,18 +237,19 @@ def print_update_details(self, details: Any | None): def print_update_step( self, title: str, - status: Literal[ - "debug", - "info", - "warning", - "error", - "critical", - "notset", - "success", - ] = "info", + status: StatusLevel = "info", details: Any | None = None, spinner: str = "dots", ) -> ConsoleUpdateStep: + """ + Create a context manager for multi-step progress with spinner. + + :param title: Initial progress message to display + :param status: Initial status level for styling + :param details: Optional details to show after completion + :param spinner: Spinner animation style name + :return: ConsoleUpdateStep context manager for progress tracking + """ return ConsoleUpdateStep( console=self, title=title, @@ -181,3 +257,310 @@ def print_update_step( status_level=status, spinner=spinner, ) + + def print_tables( + self, + header_cols_groups: Sequence[Sequence[str | list[str]]], + value_cols_groups: Sequence[Sequence[str | list[str]]], + title: str | None = None, + widths: Sequence[int] | None = None, + ): + """ + Print multiple tables with uniform column widths. + + :param header_cols_groups: List of header column groups for each table + :param value_cols_groups: List of value column groups for each table + :param title: Optional title to display before tables + :param widths: Optional minimum column widths to enforce + """ + if title is not None: + self.print_update(title, None, "info") + + # Format all groups to determine uniform widths + widths = widths or None + headers = [] + values = [] + + # Process all tables to get consistent widths + for value_cols in value_cols_groups: + formatted, widths = self._format_table_columns(value_cols, widths) + values.append(formatted) + for header_cols in header_cols_groups: + formatted, widths = self._format_table_headers(header_cols, widths) + headers.append(formatted) + + # Print each table + for ind, (header, value) in enumerate(zip(headers, values, strict=False)): + is_last = ind == len(headers) - 1 + self.print_table( + header, + value, + widths=widths, + apply_formatting=False, + print_bottom_divider=is_last, + ) + + def print_table( + self, + header_cols: Sequence[str | list[str]], + value_cols: Sequence[str | list[str]], + title: str | None = None, + widths: Sequence[int] | None = None, + apply_formatting: bool = True, + print_bottom_divider: bool = True, + ): + """ + Print a formatted table with headers and values. + + :param header_cols: List of header columns, each string or list of strings + :param value_cols: List of value columns, each string or list of strings + :param title: Optional title to display before table + :param widths: Optional minimum column widths to enforce + :param apply_formatting: Whether to calculate widths and format columns + :param print_bottom_divider: Whether to print bottom border line + """ + if title is not None: + self.print_update(title, None, "info") + + # Format data + values: list[list[str]] + headers: list[list[str]] + final_widths: list[int] + + if apply_formatting: + values, final_widths = self._format_table_columns(value_cols, widths) + headers, final_widths = self._format_table_headers( + header_cols, final_widths + ) + else: + values = [col if isinstance(col, list) else [col] for col in value_cols] + headers = [col if isinstance(col, list) else [col] for col in header_cols] + final_widths = list(widths) if widths else [] + + # Print table structure + self.print_table_divider(final_widths, "=") + self.print_table_headers(headers, final_widths) + self.print_table_divider(final_widths, "-") + self.print_table_values(values, final_widths) + + if print_bottom_divider: + self.print_table_divider(final_widths, "=") + + def print_table_divider(self, widths: Sequence[int], char: str): + """ + Print a horizontal divider line across table columns. + + :param widths: Column widths for divider line + :param char: Character to use for divider line (e.g., '=', '-') + """ + self.print_table_row( + [""] * len(widths), + widths=widths, + spacer=char, + cell_style="bold", + divider_style="bold", + edge_style="bold", + ) + + def print_table_headers(self, headers: Sequence[list[str]], widths: Sequence[int]): + """ + Print header rows with support for column spanning. + + :param headers: List of header columns, each containing header row values + :param widths: Column widths for proper alignment + """ + if not headers or not headers[0]: + return + + for row_idx in range(len(headers[0])): + # Calculate widths for this header row, accounting for merged cells. + row_widths = list(widths) + for col_idx in range(len(headers)): + if not headers[col_idx][row_idx]: + continue + + # Find span end + span_end = col_idx + 1 + while span_end < len(headers) and not headers[span_end][row_idx]: + row_widths[span_end] = 0 + span_end += 1 + + # Set combined width for the first cell in span + row_widths[col_idx] = sum( + widths[col] for col in range(col_idx, span_end) + ) + + # Print the header row + self.print_table_row( + values=[headers[col][row_idx] for col in range(len(headers))], + widths=row_widths, + cell_style="bold", + divider_style="bold", + edge_style="bold", + ) + + def print_table_values(self, values: Sequence[list[str]], widths: Sequence[int]): + """ + Print all data rows in the table. + + :param values: List of value columns, each containing row values + :param widths: Column widths for proper alignment + """ + if not values: + return + + for row_idx in range(len(values[0])): + # Print the value row + self.print_table_row( + values=[values[col][row_idx] for col in range(len(values))], + widths=widths, + divider="|", + edge_style="bold", + ) + + def print_table_row( + self, + values: Sequence[str], + widths: Sequence[int] | None = None, + spacer: str = " ", + divider: str = "|", + cell_style: str = "", + value_style: str = "", + divider_style: str = "", + edge_style: str = "", + ): + """ + Print a single table row with custom styling. + + :param values: Cell values for the row + :param widths: Column widths, defaults to value lengths + :param spacer: Character for padding cells + :param divider: Character separating columns + :param cell_style: Rich style string for entire cells + :param value_style: Rich style string for cell values only + :param divider_style: Rich style string for column dividers + :param edge_style: Rich style string for table edges + """ + widths = widths or [len(val) for val in values] + + # Build styled cells + cells = [] + for val, width in zip(values, widths, strict=True): + cell = val.ljust(width, spacer) + if value_style and val: + cell = cell.replace(val, f"[{value_style}]{val}[/{value_style}]") + if cell_style: + cell = f"[{cell_style}]{cell}[/{cell_style}]" + cells.append(cell) + + # Build and print row + edge = f"[{edge_style}]{divider}[/{edge_style}]" if edge_style else divider + inner = ( + f"[{divider_style}]{divider}[/{divider_style}]" + if divider_style + else divider + ) + line = edge + inner.join(cells) + edge + self.print(line, overflow="ignore", crop=False) + + def _format_table_headers( + self, + headers: Sequence[str | list[str]], + col_widths: Sequence[int] | None = None, + spacer: str = " ", + min_padding: int = 1, + ) -> tuple[list[list[str]], list[int]]: + formatted, header_widths = self._format_table_columns( + headers, col_widths, spacer, min_padding + ) + + if not formatted or not formatted[0]: + return formatted, [] + + # Merge identical adjacent headers row by row + widths = list(col_widths) if col_widths else header_widths + for row_idx in range(len(formatted[0])): + last_value = None + start_col = -1 + + for col_idx in range(len(formatted) + 1): + cur_value = ( + formatted[col_idx][row_idx] if col_idx < len(formatted) else None + ) + + # Check if we should continue merging + if ( + col_idx < len(formatted) + and cur_value != "" + and cur_value == last_value + and ( + row_idx == 0 + or headers[start_col][row_idx - 1] + == headers[col_idx][row_idx - 1] + ) + ): + continue + + # Finalize previous + if start_col >= 0: + # Clear merged cells to keep only the first + for col in range(start_col + 1, col_idx): + formatted[col][row_idx] = "" + + # Adjust widths of columns in the merged span, if needed + if (required := len(formatted[start_col][row_idx])) > ( + current := sum(widths[col] for col in range(start_col, col_idx)) + ): + diff = required - current + cols_count = col_idx - start_col + per_col = diff // cols_count + extra = diff % cols_count + + for col in range(start_col, col_idx): + widths[col] += per_col + if extra > 0: + widths[col] += 1 + extra -= 1 + + # Start new merge + last_value = cur_value + start_col = col_idx + + return formatted, widths + + def _format_table_columns( + self, + columns: Sequence[str | list[str]], + col_widths: Sequence[int] | None = None, + spacer: str = " ", + min_padding: int = 1, + ) -> tuple[list[list[str]], list[int]]: + if not columns: + return [], [] + + # Normalize to list of lists + max_rows = max(len(col) if isinstance(col, list) else 1 for col in columns) + + formatted = [] + for col in columns: + col_list = col if isinstance(col, list) else [col] + # Pad to max height + col_list = col_list + [""] * (max_rows - len(col_list)) + # Add cell padding + padding = spacer * min_padding + col_list = [ + f"{padding}{item}{padding}" if item else "" for item in col_list + ] + formatted.append(col_list) + + # Calculate widths + widths = [max(len(row) for row in col) for col in formatted] + + # Apply minimum widths if provided + if col_widths is not None: + widths = [ + max(width, min_w) + for width, min_w in zip(widths, col_widths, strict=True) + ] + + return formatted, widths diff --git a/src/guidellm/utils/functions.py b/src/guidellm/utils/functions.py index ed4a2075..0633616c 100644 --- a/src/guidellm/utils/functions.py +++ b/src/guidellm/utils/functions.py @@ -15,6 +15,7 @@ "all_defined", "safe_add", "safe_divide", + "safe_format_number", "safe_format_timestamp", "safe_getattr", "safe_multiply", @@ -115,7 +116,7 @@ def safe_add( def safe_format_timestamp( - timestamp: float | None, format_: str = "%H:%M:%S", default: str = "N/A" + timestamp: float | int | None, format_: str = "%H:%M:%S", default: str = "N/A" ) -> str: """ Safely format a timestamp with error handling and validation. @@ -132,3 +133,27 @@ def safe_format_timestamp( return datetime.fromtimestamp(timestamp).strftime(format_) except (ValueError, OverflowError, OSError): return default + + +def safe_format_number( + number: int | float | None, precision: int = 1, default: str = "--" +) -> str: + """ + Safely format a number with specified precision and default handling. + + :param number: Number to format, or None + :param precision: Number of decimal places for formatting floats + :param default: Value to return if number is None + :return: Formatted number string or default value + """ + if number is None: + return default + + if isinstance(number, int): + return str(number) + + try: + format_str = f"{{:.{precision}f}}" + return format_str.format(number) + except (ValueError, TypeError): + return default diff --git a/src/guidellm/utils/statistics.py b/src/guidellm/utils/statistics.py deleted file mode 100644 index a8403c72..00000000 --- a/src/guidellm/utils/statistics.py +++ /dev/null @@ -1,1047 +0,0 @@ -""" -Statistical analysis utilities for distribution calculations and running metrics. - -Provides comprehensive statistical computation tools for analyzing numerical -distributions, percentiles, and streaming data. Includes specialized support for -request timing analysis, concurrency measurement, and rate calculations. Integrates -with Pydantic for serializable statistical models and supports both weighted and -unweighted distributions with cumulative distribution function (CDF) generation. -""" - -from __future__ import annotations - -import math -import time as timer -from collections import defaultdict -from typing import Any, Literal - -import numpy as np -from pydantic import Field, computed_field - -from guidellm.utils.pydantic_utils import StandardBaseModel, StatusBreakdown - -__all__ = [ - "DistributionSummary", - "Percentiles", - "RunningStats", - "StatusDistributionSummary", - "TimeRunningStats", -] - - -class Percentiles(StandardBaseModel): - """ - Standard percentiles model for statistical distribution analysis. - - Provides complete percentile coverage from 0.1th to 99.9th percentiles for - statistical distribution characterization. Used as a component within - DistributionSummary to provide detailed distribution shape analysis. - """ - - p001: float = Field( - description="The 0.1th percentile of the distribution.", - ) - p01: float = Field( - description="The 1st percentile of the distribution.", - ) - p05: float = Field( - description="The 5th percentile of the distribution.", - ) - p10: float = Field( - description="The 10th percentile of the distribution.", - ) - p25: float = Field( - description="The 25th percentile of the distribution.", - ) - p50: float = Field( - description="The 50th percentile of the distribution.", - ) - p75: float = Field( - description="The 75th percentile of the distribution.", - ) - p90: float = Field( - description="The 90th percentile of the distribution.", - ) - p95: float = Field( - description="The 95th percentile of the distribution.", - ) - p99: float = Field( - description="The 99th percentile of the distribution.", - ) - p999: float = Field( - description="The 99.9th percentile of the distribution.", - ) - - -class DistributionSummary(StandardBaseModel): - """ - Comprehensive statistical summary for numerical value distributions. - - Calculates and stores complete statistical metrics including central tendency, - dispersion, extremes, and percentiles for any numerical distribution. Supports - both weighted and unweighted data with optional cumulative distribution function - generation. Primary statistical analysis tool for request timing, performance - metrics, and benchmark result characterization. - - Example: - :: - # Create from simple values - summary = DistributionSummary.from_values([1.0, 2.0, 3.0, 4.0, 5.0]) - print(f"Mean: {summary.mean}, P95: {summary.percentiles.p95}") - - # Create from request timings for concurrency analysis - requests = [(0.0, 1.0), (0.5, 2.0), (1.0, 2.5)] - concurrency = DistributionSummary.from_request_times( - requests, "concurrency" - ) - """ - - mean: float = Field( - description="The mean/average of the distribution.", - ) - median: float = Field( - description="The median of the distribution.", - ) - mode: float = Field( - description="The mode of the distribution.", - ) - variance: float = Field( - description="The variance of the distribution.", - ) - std_dev: float = Field( - description="The standard deviation of the distribution.", - ) - min: float = Field( - description="The minimum value of the distribution.", - ) - max: float = Field( - description="The maximum value of the distribution.", - ) - count: int = Field( - description="The number of values in the distribution.", - ) - total_sum: float = Field( - description="The total sum of the values in the distribution.", - ) - percentiles: Percentiles = Field( - description="The percentiles of the distribution.", - ) - cumulative_distribution_function: list[tuple[float, float]] | None = Field( - description="The cumulative distribution function (CDF) of the distribution.", - default=None, - ) - - @staticmethod - def from_distribution_function( - distribution: list[tuple[float, float]], - include_cdf: bool = False, - ) -> DistributionSummary: - """ - Create statistical summary from weighted distribution or probability function. - - Converts weighted numerical values or probability distribution function (PDF) - into comprehensive statistical summary. Normalizes weights to probabilities - and calculates all statistical metrics including percentiles. - - :param distribution: List of (value, weight) or (value, probability) tuples - representing the distribution - :param include_cdf: Whether to include cumulative distribution function - in the output - :return: DistributionSummary instance with calculated statistical metrics - """ - values, weights = zip(*distribution, strict=True) if distribution else ([], []) - values = np.array(values) # type: ignore[assignment] - weights = np.array(weights) # type: ignore[assignment] - - # create the PDF - probabilities = weights / np.sum(weights) # type: ignore[operator] - pdf = np.column_stack((values, probabilities)) - pdf = pdf[np.argsort(pdf[:, 0])] - values = pdf[:, 0] # type: ignore[assignment] - probabilities = pdf[:, 1] - - # calculate the CDF - cumulative_probabilities = np.cumsum(probabilities) - cdf = np.column_stack((values, cumulative_probabilities)) - - # calculate statistics - mean = np.sum(values * probabilities).item() # type: ignore[attr-defined] - median = cdf[np.argmax(cdf[:, 1] >= 0.5), 0].item() if len(cdf) > 0 else 0 # noqa: PLR2004 - mode = values[np.argmax(probabilities)].item() if len(values) > 0 else 0 # type: ignore[call-overload] - variance = np.sum((values - mean) ** 2 * probabilities).item() # type: ignore[attr-defined] - std_dev = math.sqrt(variance) - minimum = values[0].item() if len(values) > 0 else 0 - maximum = values[-1].item() if len(values) > 0 else 0 - count = len(values) - total_sum = np.sum(values).item() # type: ignore[attr-defined] - - return DistributionSummary( - mean=mean, - median=median, - mode=mode, - variance=variance, - std_dev=std_dev, - min=minimum, - max=maximum, - count=count, - total_sum=total_sum, - percentiles=( - Percentiles( - p001=cdf[np.argmax(cdf[:, 1] >= 0.001), 0].item(), # noqa: PLR2004 - p01=cdf[np.argmax(cdf[:, 1] >= 0.01), 0].item(), # noqa: PLR2004 - p05=cdf[np.argmax(cdf[:, 1] >= 0.05), 0].item(), # noqa: PLR2004 - p10=cdf[np.argmax(cdf[:, 1] >= 0.1), 0].item(), # noqa: PLR2004 - p25=cdf[np.argmax(cdf[:, 1] >= 0.25), 0].item(), # noqa: PLR2004 - p50=cdf[np.argmax(cdf[:, 1] >= 0.50), 0].item(), # noqa: PLR2004 - p75=cdf[np.argmax(cdf[:, 1] >= 0.75), 0].item(), # noqa: PLR2004 - p90=cdf[np.argmax(cdf[:, 1] >= 0.9), 0].item(), # noqa: PLR2004 - p95=cdf[np.argmax(cdf[:, 1] >= 0.95), 0].item(), # noqa: PLR2004 - p99=cdf[np.argmax(cdf[:, 1] >= 0.99), 0].item(), # noqa: PLR2004 - p999=cdf[np.argmax(cdf[:, 1] >= 0.999), 0].item(), # noqa: PLR2004 - ) - if len(cdf) > 0 - else Percentiles( - p001=0, - p01=0, - p05=0, - p10=0, - p25=0, - p50=0, - p75=0, - p90=0, - p95=0, - p99=0, - p999=0, - ) - ), - cumulative_distribution_function=cdf.tolist() if include_cdf else None, - ) - - @staticmethod - def from_values( - values: list[float], - weights: list[float] | None = None, - include_cdf: bool = False, - ) -> DistributionSummary: - """ - Create statistical summary from numerical values with optional weights. - - Wrapper around from_distribution_function for simple value lists. If weights - are not provided, all values are equally weighted. Enables statistical - analysis of any numerical dataset. - - :param values: Numerical values representing the distribution - :param weights: Optional weights for each value. If not provided, all values - are equally weighted - :param include_cdf: Whether to include cumulative distribution function in - the output DistributionSummary - :return: DistributionSummary instance with calculated statistical metrics - :raises ValueError: If values and weights lists have different lengths - """ - if weights is None: - weights = [1.0] * len(values) - - if len(values) != len(weights): - raise ValueError( - "The length of values and weights must be the same.", - ) - - return DistributionSummary.from_distribution_function( - distribution=list(zip(values, weights, strict=True)), - include_cdf=include_cdf, - ) - - @staticmethod - def from_request_times( - requests: list[tuple[float, float]], - distribution_type: Literal["concurrency", "rate"], - weights: list[float] | None = None, - include_cdf: bool = False, - epsilon: float = 1e-6, - ) -> DistributionSummary: - """ - Create statistical summary from request timing data. - - Analyzes request start/end times to calculate concurrency or rate - distributions. Converts timing events into statistical metrics for - performance analysis and load characterization. - - :param requests: List of (start_time, end_time) tuples for each request - :param distribution_type: Type of analysis - "concurrency" for simultaneous - requests or "rate" for completion rates - :param include_cdf: Whether to include cumulative distribution function - :param epsilon: Threshold for merging close timing events - :return: DistributionSummary with timing-based statistical metrics - :raises ValueError: If distribution_type is not "concurrency" or "rate" - """ - if not weights: - weights = [1.0] * len(requests) - - if len(requests) != len(weights): - raise ValueError( - "The length of requests and weights must be the same.", - ) - - # First convert to timing events based on type - events = DistributionSummary._convert_to_timing_events( - requests, distribution_type, weights - ) - - # Combine any events within epsilon of each other for stability - flattened_events = DistributionSummary._combine_events(events, epsilon) - - # Convert events to value distribution function - distribution: dict[float, float] = defaultdict(float) - - if distribution_type == "concurrency": - # For concurrency, convert to active concurrency over time - active = 0.0 - for ind in range(len(flattened_events)): - time, change = flattened_events[ind] - active += change - flattened_events[ind] = (time, active) - - # Then convert to distribution by weighting each concurrency - # by duration to next event (last event is 0 concurrency) - for ind in range(len(flattened_events) - 1): - time, value = flattened_events[ind] - next_time = flattened_events[ind + 1][0] - duration = next_time - time - distribution[value] += duration - elif distribution_type == "rate": - # For rate, convert to distribution by converting each value - # to a rate (value/duration) weighted by duration from previous - # (first event is 0 rate) - for ind in range(1, len(flattened_events)): - time, value = flattened_events[ind] - prev_time = flattened_events[ind - 1][0] - duration = time - prev_time - rate = value / duration if duration > 0 else 0.0 - distribution[rate] += duration - else: - raise ValueError( - f"Invalid distribution_type '{distribution_type}'. " - "Must be 'concurrency' or 'rate'." - ) - - return DistributionSummary.from_distribution_function( - distribution=sorted(distribution.items()), - include_cdf=include_cdf, - ) - - @staticmethod - def _convert_to_timing_events( - requests: list[tuple[float, float]], - distribution_type: Literal["concurrency", "rate"], - weights: list[float], - ) -> list[tuple[float, float]]: - events: list[tuple[float, float]] = [] - - if distribution_type == "concurrency": - # For concurrency, each request adds to concurrency at start - # and subtracts at end - for (start, end), weight in zip(requests, weights, strict=False): - events.append((start, weight)) - events.append((end, -1 * weight)) - elif distribution_type == "rate": - # For rate, each request is added at the end time only - global_start = min(start for start, _ in requests) if requests else 0.0 - events.append((global_start, 0.0)) - for (_, end), weight in zip(requests, weights, strict=False): - events.append((end, weight)) - else: - raise ValueError( - f"Invalid distribution_type '{distribution_type}'. " - "Must be 'concurrency' or 'rate'." - ) - return events - - @staticmethod - def _combine_events( - events: list[tuple[float, float]], - epsilon: float, - ) -> list[tuple[float, float]]: - sorted_events = sorted(events, key=lambda event: event[0]) - flattened_events: list[tuple[float, float]] = ( - [sorted_events.pop(0)] if sorted_events else [] - ) - last_time = flattened_events[0][0] if flattened_events else 0.0 - - for time, val in sorted_events: - if abs(time - last_time) <= epsilon: - last_val = flattened_events[-1][1] - flattened_events[-1] = (last_time, last_val + val) - else: - last_time = time - flattened_events.append((time, val)) - return flattened_events - - @staticmethod - def from_iterable_request_times( - requests: list[tuple[float, float]], - first_iter_times: list[float], - iter_counts: list[int], - first_iter_counts: list[int] | None = None, - include_cdf: bool = False, - epsilon: float = 1e-6, - ) -> DistributionSummary: - """ - Create statistical summary from iterative request timing data. - - Analyzes autoregressive or streaming requests with multiple iterations - between start and end times. Calculates rate distributions based on - iteration timing patterns for LLM token generation analysis. - - :param requests: List of (start_time, end_time) tuples for each request - :param first_iter_times: Times when first iteration was received for - each request - :param iter_counts: Total iteration counts for each request from first - iteration to end - :param first_iter_counts: Iteration counts for first iteration (defaults - to 1 for each request) - :param include_cdf: Whether to include cumulative distribution function - :param epsilon: Threshold for merging close timing events - :return: DistributionSummary with iteration rate statistical metrics - :raises ValueError: If input lists have mismatched lengths - """ - - if first_iter_counts is None: - first_iter_counts = [1] * len(requests) - - if ( - len(requests) != len(first_iter_times) - or len(requests) != len(iter_counts) - or len(requests) != len(first_iter_counts) - ): - raise ValueError( - "requests, first_iter_times, iter_counts, and first_iter_counts must" - "be the same length." - f"Given {len(requests)}, {len(first_iter_times)}, {len(iter_counts)}, " - f"{len(first_iter_counts)}", - ) - - # first break up the requests into individual iterable events - events = defaultdict(int) - global_start = min(start for start, _ in requests) if requests else 0 - global_end = max(end for _, end in requests) if requests else 0 - events[global_start] = 0 - events[global_end] = 0 - - for (_, end), first_iter, first_iter_count, total_count in zip( - requests, first_iter_times, first_iter_counts, iter_counts, strict=True - ): - events[first_iter] += first_iter_count - - if total_count > 1: - iter_latency = (end - first_iter) / (total_count - 1) - for ind in range(1, total_count): - events[first_iter + ind * iter_latency] += 1 - - # combine any events that are very close together - flattened_events: list[tuple[float, int]] = [] - - for time, count in sorted(events.items()): - last_time, last_count = ( - flattened_events[-1] if flattened_events else (None, None) - ) - - if ( - last_time is not None - and last_count is not None - and abs(last_time - time) <= epsilon - ): - flattened_events[-1] = (last_time, last_count + count) - else: - flattened_events.append((time, count)) - - # convert to value distribution function - distribution: dict[float, float] = defaultdict(float) - - for ind in range(len(flattened_events) - 1): - start_time, count = flattened_events[ind] - end_time, _ = flattened_events[ind + 1] - duration = end_time - start_time - rate = count / duration - distribution[rate] += duration - - distribution_list = sorted(distribution.items()) - - return DistributionSummary.from_distribution_function( - distribution=distribution_list, - include_cdf=include_cdf, - ) - - -class StatusDistributionSummary( - StatusBreakdown[ - DistributionSummary, - DistributionSummary, - DistributionSummary, - DistributionSummary, - ] -): - """ - Status-grouped statistical summary for request processing analysis. - - Provides comprehensive statistical analysis grouped by request status (total, - successful, incomplete, errored). Enables performance analysis across different - request outcomes for benchmarking and monitoring applications. Each status - category maintains complete DistributionSummary metrics. - - Example: - :: - status_summary = StatusDistributionSummary.from_values( - value_types=["successful", "error", "successful"], - values=[1.5, 10.0, 2.1] - ) - print(f"Success mean: {status_summary.successful.mean}") - print(f"Error rate: {status_summary.errored.count}") - """ - - @staticmethod - def from_values( - value_types: list[Literal["successful", "incomplete", "error"]], - values: list[float], - weights: list[float] | None = None, - include_cdf: bool = False, - ) -> StatusDistributionSummary: - """ - Create status-grouped statistical summary from values and status types. - - Groups numerical values by request status and calculates complete - statistical summaries for each category. Enables performance analysis - across different request outcomes. - - :param value_types: Status type for each value ("successful", "incomplete", - or "error") - :param values: Numerical values representing the distribution - :param weights: Optional weights for each value (defaults to equal weighting) - :param include_cdf: Whether to include cumulative distribution functions - :return: StatusDistributionSummary with statistics grouped by status - :raises ValueError: If input lists have mismatched lengths or invalid - status types - """ - if any( - type_ not in {"successful", "incomplete", "error"} for type_ in value_types - ): - raise ValueError( - "value_types must be one of 'successful', 'incomplete', or 'error'. " - f"Got {value_types} instead.", - ) - - if weights is None: - weights = [1.0] * len(values) - - if len(value_types) != len(values) or len(value_types) != len(weights): - raise ValueError( - "The length of value_types, values, and weights must be the same.", - ) - - _, successful_values, successful_weights = ( - zip(*successful, strict=True) - if ( - successful := list( - filter( - lambda val: val[0] == "successful", - zip(value_types, values, weights, strict=True), - ) - ) - ) - else ([], [], []) - ) - _, incomplete_values, incomplete_weights = ( - zip(*incomplete, strict=True) - if ( - incomplete := list( - filter( - lambda val: val[0] == "incomplete", - zip(value_types, values, weights, strict=True), - ) - ) - ) - else ([], [], []) - ) - _, errored_values, errored_weights = ( - zip(*errored, strict=True) - if ( - errored := list( - filter( - lambda val: val[0] == "error", - zip(value_types, values, weights, strict=True), - ) - ) - ) - else ([], [], []) - ) - - return StatusDistributionSummary( - total=DistributionSummary.from_values( - values, - weights, - include_cdf=include_cdf, - ), - successful=DistributionSummary.from_values( - successful_values, # type: ignore[arg-type] - successful_weights, # type: ignore[arg-type] - include_cdf=include_cdf, - ), - incomplete=DistributionSummary.from_values( - incomplete_values, # type: ignore[arg-type] - incomplete_weights, # type: ignore[arg-type] - include_cdf=include_cdf, - ), - errored=DistributionSummary.from_values( - errored_values, # type: ignore[arg-type] - errored_weights, # type: ignore[arg-type] - include_cdf=include_cdf, - ), - ) - - @staticmethod - def from_request_times( - request_types: list[Literal["successful", "incomplete", "error"]], - requests: list[tuple[float, float]], - distribution_type: Literal["concurrency", "rate"], - weights: list[float] | None = None, - include_cdf: bool = False, - epsilon: float = 1e-6, - ) -> StatusDistributionSummary: - """ - Create status-grouped statistical summary from request timing data. - - Analyzes request timings grouped by status to calculate concurrency or - rate distributions for each outcome category. Enables comparative - performance analysis across successful, incomplete, and errored requests. - - :param request_types: Status type for each request ("successful", - "incomplete", or "error") - :param requests: List of (start_time, end_time) tuples for each request - :param distribution_type: Analysis type - "concurrency" or "rate" - :param include_cdf: Whether to include cumulative distribution functions - :param epsilon: Threshold for merging close timing events - :return: StatusDistributionSummary with timing statistics by status - :raises ValueError: If input lists have mismatched lengths or invalid types - """ - if distribution_type not in {"concurrency", "rate"}: - raise ValueError( - f"Invalid distribution_type '{distribution_type}'. " - "Must be 'concurrency' or 'rate'." - ) - - if any( - type_ not in {"successful", "incomplete", "error"} - for type_ in request_types - ): - raise ValueError( - "request_types must be one of 'successful', 'incomplete', or 'error'. " - f"Got {request_types} instead.", - ) - - if len(request_types) != len(requests): - raise ValueError( - "The length of request_types and requests must be the same. " - f"Got {len(request_types)} and {len(requests)} instead.", - ) - - if weights is None: - weights = [1.0] * len(requests) - - if len(requests) != len(weights): - raise ValueError( - "The length of requests and weights must be the same." - f"Got {len(requests)} and {len(weights)} instead.", - ) - - _, successful_requests, successful_weights = ( - zip(*successful, strict=False) - if ( - successful := list( - filter( - lambda val: val[0] == "successful", - zip(request_types, requests, weights, strict=False), - ) - ) - ) - else ([], [], []) - ) - _, incomplete_requests, incomplete_weights = ( - zip(*incomplete, strict=False) - if ( - incomplete := list( - filter( - lambda val: val[0] == "incomplete", - zip(request_types, requests, weights, strict=False), - ) - ) - ) - else ([], [], []) - ) - _, errored_requests, errored_weights = ( - zip(*errored, strict=False) - if ( - errored := list( - filter( - lambda val: val[0] == "error", - zip(request_types, requests, weights, strict=False), - ) - ) - ) - else ([], [], []) - ) - - return StatusDistributionSummary( - total=DistributionSummary.from_request_times( - requests, - distribution_type=distribution_type, - weights=weights, - include_cdf=include_cdf, - epsilon=epsilon, - ), - successful=DistributionSummary.from_request_times( - successful_requests, # type: ignore[arg-type] - distribution_type=distribution_type, - weights=successful_weights, # type: ignore[arg-type] - include_cdf=include_cdf, - epsilon=epsilon, - ), - incomplete=DistributionSummary.from_request_times( - incomplete_requests, # type: ignore[arg-type] - distribution_type=distribution_type, - weights=incomplete_weights, # type: ignore[arg-type] - include_cdf=include_cdf, - epsilon=epsilon, - ), - errored=DistributionSummary.from_request_times( - errored_requests, # type: ignore[arg-type] - distribution_type=distribution_type, - weights=errored_weights, # type: ignore[arg-type] - include_cdf=include_cdf, - epsilon=epsilon, - ), - ) - - @staticmethod - def from_iterable_request_times( - request_types: list[Literal["successful", "incomplete", "error"]], - requests: list[tuple[float, float]], - first_iter_times: list[float], - iter_counts: list[int] | None = None, - first_iter_counts: list[int] | None = None, - include_cdf: bool = False, - epsilon: float = 1e-6, - ) -> StatusDistributionSummary: - """ - Create status-grouped statistical summary from iterative request timing data. - - Analyzes autoregressive request timings grouped by status to calculate - iteration rate distributions for each outcome category. Enables comparative - analysis of token generation or streaming response performance across - different request statuses. - - :param request_types: Status type for each request ("successful", - "incomplete", or "error") - :param requests: List of (start_time, end_time) tuples for each request - :param first_iter_times: Times when first iteration was received for - each request - :param iter_counts: Total iteration counts for each request (defaults to 1) - :param first_iter_counts: Iteration counts for first iteration (defaults - to 1) - :param include_cdf: Whether to include cumulative distribution functions - :param epsilon: Threshold for merging close timing events - :return: StatusDistributionSummary with iteration statistics by status - :raises ValueError: If input lists have mismatched lengths or invalid types - """ - if any( - type_ not in {"successful", "incomplete", "error"} - for type_ in request_types - ): - raise ValueError( - "request_types must be one of 'successful', 'incomplete', or 'error'. " - f"Got {request_types} instead.", - ) - - if iter_counts is None: - iter_counts = [1] * len(requests) - - if first_iter_counts is None: - first_iter_counts = [1] * len(requests) - - if ( - len(request_types) != len(requests) - or len(requests) != len(first_iter_times) - or len(requests) != len(iter_counts) - or len(requests) != len(first_iter_counts) - ): - raise ValueError( - "request_types, requests, first_iter_times, iter_counts, and " - "first_iter_counts must be the same length." - f"Given {len(request_types)}, {len(requests)}, " - f"{len(first_iter_times)}, {len(iter_counts)}, " - f"{len(first_iter_counts)}", - ) - - ( - _, - successful_requests, - successful_first_iter_times, - successful_iter_counts, - successful_first_iter_counts, - ) = ( - zip(*successful, strict=True) - if ( - successful := list( - filter( - lambda val: val[0] == "successful", - zip( - request_types, - requests, - first_iter_times, - iter_counts, - first_iter_counts, - strict=True, - ), - ) - ) - ) - else ([], [], [], [], []) - ) - ( - _, - incomplete_requests, - incomplete_first_iter_times, - incomplete_iter_counts, - incomplete_first_iter_counts, - ) = ( - zip(*incomplete, strict=True) - if ( - incomplete := list( - filter( - lambda val: val[0] == "incomplete", - zip( - request_types, - requests, - first_iter_times, - iter_counts, - first_iter_counts, - strict=True, - ), - ) - ) - ) - else ([], [], [], [], []) - ) - ( - _, - errored_requests, - errored_first_iter_times, - errored_iter_counts, - errored_first_iter_counts, - ) = ( - zip(*errored, strict=True) - if ( - errored := list( - filter( - lambda val: val[0] == "error", - zip( - request_types, - requests, - first_iter_times, - iter_counts, - first_iter_counts, - strict=True, - ), - ) - ) - ) - else ([], [], [], [], []) - ) - - return StatusDistributionSummary( - total=DistributionSummary.from_iterable_request_times( - requests, - first_iter_times, - iter_counts, - first_iter_counts, - include_cdf=include_cdf, - epsilon=epsilon, - ), - successful=DistributionSummary.from_iterable_request_times( - successful_requests, # type: ignore[arg-type] - successful_first_iter_times, # type: ignore[arg-type] - successful_iter_counts, # type: ignore[arg-type] - successful_first_iter_counts, # type: ignore[arg-type] - include_cdf=include_cdf, - epsilon=epsilon, - ), - incomplete=DistributionSummary.from_iterable_request_times( - incomplete_requests, # type: ignore[arg-type] - incomplete_first_iter_times, # type: ignore[arg-type] - incomplete_iter_counts, # type: ignore[arg-type] - incomplete_first_iter_counts, # type: ignore[arg-type] - include_cdf=include_cdf, - epsilon=epsilon, - ), - errored=DistributionSummary.from_iterable_request_times( - errored_requests, # type: ignore[arg-type] - errored_first_iter_times, # type: ignore[arg-type] - errored_iter_counts, # type: ignore[arg-type] - errored_first_iter_counts, # type: ignore[arg-type] - include_cdf=include_cdf, - epsilon=epsilon, - ), - ) - - -class RunningStats(StandardBaseModel): - """ - Real-time statistics tracking for streaming numerical data. - - Maintains mean, rate, and cumulative statistics for continuous data streams - without storing individual values. Optimized for memory efficiency in - long-running monitoring applications. Supports arithmetic operators for - convenient value addition and provides computed properties for derived metrics. - - Example: - :: - stats = RunningStats() - stats += 10.5 # Add value using operator - stats.update(20.0, count=3) # Add value with custom count - print(f"Mean: {stats.mean}, Rate: {stats.rate}") - """ - - start_time: float = Field( - default_factory=timer.time, - description=( - "The time the running statistics object was created. " - "This is used to calculate the rate of the statistics." - ), - ) - count: int = Field( - default=0, - description="The number of values added to the running statistics.", - ) - total: float = Field( - default=0.0, - description="The total sum of the values added to the running statistics.", - ) - last: float = Field( - default=0.0, - description="The last value added to the running statistics.", - ) - - @computed_field # type: ignore[misc] - @property - def mean(self) -> float: - """ - :return: The mean of the running statistics (total / count). - If count is 0, return 0.0. - """ - if self.count == 0: - return 0.0 - return self.total / self.count - - @computed_field # type: ignore[misc] - @property - def rate(self) -> float: - """ - :return: The rate of the running statistics - (total / (time.time() - start_time)). - If count is 0, return 0.0. - """ - if self.count == 0: - return 0.0 - return self.total / (timer.time() - self.start_time) - - def __add__(self, value: Any) -> float: - """ - Add value using + operator and return current mean. - - :param value: Numerical value to add to the running statistics - :return: Updated mean after adding the value - :raises ValueError: If value is not numeric (int or float) - """ - if not isinstance(value, int | float): - raise ValueError( - f"Value must be an int or float, got {type(value)} instead.", - ) - - self.update(value) - - return self.mean - - def __iadd__(self, value: Any) -> RunningStats: - """ - Add value using += operator and return updated instance. - - :param value: Numerical value to add to the running statistics - :return: Self reference for method chaining - :raises ValueError: If value is not numeric (int or float) - """ - if not isinstance(value, int | float): - raise ValueError( - f"Value must be an int or float, got {type(value)} instead.", - ) - - self.update(value) - - return self - - def update(self, value: float, count: int = 1) -> None: - """ - Update running statistics with new value and count. - - :param value: Numerical value to add to the running statistics - :param count: Number of occurrences to count for this value (defaults to 1) - """ - self.count += count - self.total += value - self.last = value - - -class TimeRunningStats(RunningStats): - """ - Specialized running statistics for time-based measurements. - - Extends RunningStats with time-specific computed properties for millisecond - conversions. Designed for tracking latency, duration, and timing metrics in - performance monitoring applications. - - Example: - :: - time_stats = TimeRunningStats() - time_stats += 0.125 # Add 125ms in seconds - print(f"Mean: {time_stats.mean_ms}ms, Total: {time_stats.total_ms}ms") - """ - - @computed_field # type: ignore[misc] - @property - def total_ms(self) -> float: - """ - :return: The total time multiplied by 1000.0 to convert to milliseconds. - """ - return self.total * 1000.0 - - @computed_field # type: ignore[misc] - @property - def last_ms(self) -> float: - """ - :return: The last time multiplied by 1000.0 to convert to milliseconds. - """ - return self.last * 1000.0 - - @computed_field # type: ignore[misc] - @property - def mean_ms(self) -> float: - """ - :return: The mean time multiplied by 1000.0 to convert to milliseconds. - """ - return self.mean * 1000.0 - - @computed_field # type: ignore[misc] - @property - def rate_ms(self) -> float: - """ - :return: The rate of the running statistics multiplied by 1000.0 - to convert to milliseconds. - """ - return self.rate * 1000.0 diff --git a/tests/unit/backends/test_backend.py b/tests/unit/backends/test_backend.py index e5530917..89af636f 100644 --- a/tests/unit/backends/test_backend.py +++ b/tests/unit/backends/test_backend.py @@ -10,40 +10,29 @@ import pytest -from guidellm.backends.backend import Backend, BackendType -from guidellm.schemas import ( - GenerationRequest, - RequestInfo, -) -from guidellm.schemas.request import GenerationRequestArguments +from guidellm.backends import Backend +from guidellm.schemas import GenerationRequest, GenerationRequestArguments, RequestInfo from guidellm.utils import RegistryMixin from tests.unit.testing_utils import async_timeout -def test_backend_type(): - """Test that BackendType is defined correctly as a Literal type.""" - assert BackendType is not None - # BackendType should be a literal type containing "openai_http" - assert "openai_http" in str(BackendType) - - class TestBackend: """Test cases for Backend base class.""" @pytest.fixture( params=[ {"type_": "openai_http"}, - {"type_": "openai_http"}, # Test multiple instances with same type - ] + ], + ids=["openai_http_type"], ) def valid_instances(self, request): """Fixture providing valid Backend instances.""" constructor_args = request.param - class TestBackend(Backend): + class TestBackendImpl(Backend): @property def info(self) -> dict[str, Any]: - return {"type": self.type_} + return {"type": self.type_, "test": "backend"} async def process_startup(self): pass @@ -59,24 +48,31 @@ async def resolve( ) -> AsyncIterator[tuple[Any, Any]]: yield request, request_info - async def default_model(self) -> str | None: + async def default_model(self) -> str: return "test-model" - instance = TestBackend(**constructor_args) + instance = TestBackendImpl(**constructor_args) return instance, constructor_args @pytest.mark.smoke def test_class_signatures(self): """Test Backend inheritance and type relationships.""" + # Test inheritance assert issubclass(Backend, RegistryMixin) + # Check that Backend implements BackendInterface methods assert hasattr(Backend, "resolve") assert hasattr(Backend, "process_startup") assert hasattr(Backend, "process_shutdown") assert hasattr(Backend, "validate") + assert hasattr(Backend, "info") + + # Check registry methods exist assert hasattr(Backend, "create") assert hasattr(Backend, "register") assert hasattr(Backend, "get_registered_object") + assert hasattr(Backend, "is_registered") + assert hasattr(Backend, "registered_objects") # Check properties exist assert hasattr(Backend, "processes_limit") @@ -104,7 +100,7 @@ def test_initialization(self, valid_instances): def test_invalid_initialization_values(self, field, value): """Test Backend with invalid field values.""" - class TestBackend(Backend): + class TestBackendImpl(Backend): @property def info(self) -> dict[str, Any]: return {} @@ -121,14 +117,41 @@ async def validate(self): async def resolve(self, request, request_info, history=None): yield request, request_info - async def default_model(self) -> str | None: + async def default_model(self) -> str: return "test-model" data = {field: value} # Backend itself doesn't validate types, but we test that it accepts the value - backend = TestBackend(**data) + backend = TestBackendImpl(**data) assert getattr(backend, field) == value + @pytest.mark.sanity + def test_invalid_initialization_missing(self): + """Test Backend initialization without required field.""" + + class TestBackendImpl(Backend): + @property + def info(self) -> dict[str, Any]: + return {} + + async def process_startup(self): + pass + + async def process_shutdown(self): + pass + + async def validate(self): + pass + + async def resolve(self, request, request_info, history=None): + yield request, request_info + + async def default_model(self) -> str: + return "test-model" + + with pytest.raises(TypeError): + TestBackendImpl() # type: ignore + @pytest.mark.smoke def test_default_properties(self, valid_instances): """Test Backend default property implementations.""" @@ -136,36 +159,79 @@ def test_default_properties(self, valid_instances): assert instance.processes_limit is None assert instance.requests_limit is None + @pytest.mark.smoke + def test_info_property(self, valid_instances): + """Test Backend info property.""" + instance, constructor_args = valid_instances + info = instance.info + assert isinstance(info, dict) + assert info["type"] == constructor_args["type_"] + assert "test" in info + @pytest.mark.smoke @pytest.mark.asyncio @async_timeout(5.0) - async def test_default_model_abstract(self): + async def test_default_model(self, valid_instances): """Test that default_model is abstract and must be implemented.""" - # Backend itself is abstract and cannot be instantiated + instance, _ = valid_instances + # Test that it returns a string + model = await instance.default_model() + assert isinstance(model, str) + assert model == "test-model" + + # Test that Backend itself is abstract and cannot be instantiated with pytest.raises(TypeError): Backend("openai_http") # type: ignore - @pytest.mark.regression + @pytest.mark.smoke + @pytest.mark.asyncio + @async_timeout(5.0) + async def test_process_startup(self, valid_instances): + """Test Backend.process_startup lifecycle method.""" + instance, _ = valid_instances + # Should not raise any exceptions + await instance.process_startup() + + @pytest.mark.smoke + @pytest.mark.asyncio + @async_timeout(5.0) + async def test_process_shutdown(self, valid_instances): + """Test Backend.process_shutdown lifecycle method.""" + instance, _ = valid_instances + # Should not raise any exceptions + await instance.process_shutdown() + + @pytest.mark.smoke @pytest.mark.asyncio @async_timeout(5.0) - async def test_interface_compatibility(self, valid_instances): - """Test that Backend is compatible with BackendInterface.""" + async def test_validate(self, valid_instances): + """Test Backend.validate lifecycle method.""" instance, _ = valid_instances + # Should not raise any exceptions + await instance.validate() - # Test that Backend uses the correct generic types + @pytest.mark.smoke + @pytest.mark.asyncio + @async_timeout(5.0) + async def test_resolve(self, valid_instances): + """Test Backend.resolve method.""" + instance, _ = valid_instances request = GenerationRequest( request_type="text_completions", arguments=GenerationRequestArguments() ) request_info = RequestInfo(request_id="test-id") # Test resolve method + results = [] async for response, info in instance.resolve(request, request_info): - assert response == request - assert info == request_info - break # Only test first iteration + results.append((response, info)) + + assert len(results) == 1 + assert results[0][0] == request + assert results[0][1] == request_info @pytest.mark.smoke - def test_create_method_valid(self): + def test_create(self): """Test Backend.create class method with valid backend.""" # Mock a registered backend mock_backend_class = Mock() @@ -182,12 +248,12 @@ def test_create_method_valid(self): assert result == mock_backend_instance @pytest.mark.sanity - def test_create_method_invalid(self): + def test_create_invalid(self): """Test Backend.create class method with invalid backend type.""" with pytest.raises( ValueError, match="Backend type 'invalid_type' is not registered" ): - Backend.create("invalid_type") + Backend.create("invalid_type") # type: ignore @pytest.mark.regression def test_docstring_example_pattern(self): @@ -199,6 +265,7 @@ def __init__(self, api_key: str): super().__init__("mock_backend") # type: ignore [arg-type] self.api_key = api_key + @property def info(self) -> dict[str, Any]: return {"api_key": "***"} @@ -214,7 +281,7 @@ async def validate(self): async def resolve(self, request, request_info, history=None): yield request, request_info - async def default_model(self) -> str | None: + async def default_model(self) -> str: return "my-model" # Register the backend @@ -226,10 +293,6 @@ async def default_model(self) -> str | None: assert backend.api_key == "secret" assert backend.type_ == "mock_backend" - -class TestBackendRegistry: - """Test cases for Backend registry functionality.""" - @pytest.mark.smoke def test_openai_backend_registered(self): """Test that OpenAI HTTP backend is registered.""" @@ -240,14 +303,6 @@ def test_openai_backend_registered(self): assert isinstance(backend, OpenAIHTTPBackend) assert backend.type_ == "openai_http" - @pytest.mark.sanity - def test_backend_create_invalid_type(self): - """Test Backend.create with invalid type raises appropriate error.""" - with pytest.raises( - ValueError, match="Backend type 'invalid_type' is not registered" - ): - Backend.create("invalid_type") - @pytest.mark.smoke def test_backend_registry_functionality(self): """Test that backend registry functions work.""" @@ -265,7 +320,7 @@ def test_backend_registry_functionality(self): assert backend.model == "gpt-4" @pytest.mark.smoke - def test_backend_is_registered(self): + def test_is_registered(self): """Test Backend.is_registered method.""" # Test with a known registered backend assert Backend.is_registered("openai_http") @@ -274,16 +329,17 @@ def test_backend_is_registered(self): assert not Backend.is_registered("unknown_backend") @pytest.mark.regression - def test_backend_registration_decorator(self): + def test_registration_decorator(self): """Test that backend registration decorator works.""" # Create a test backend class - @Backend.register("test_backend") - class TestBackend(Backend): + @Backend.register("test_decorator_backend") + class TestDecoratorBackend(Backend): def __init__(self, test_param="default"): - super().__init__("test_backend") # type: ignore + super().__init__("test_decorator_backend") # type: ignore self._test_param = test_param + @property def info(self): return {"test_param": self._test_param} @@ -303,12 +359,12 @@ async def default_model(self): return "test-model" # Test that it's registered and can be created - backend = Backend.create("test_backend", test_param="custom") - assert isinstance(backend, TestBackend) - assert backend.info() == {"test_param": "custom"} + backend = Backend.create("test_decorator_backend", test_param="custom") + assert isinstance(backend, TestDecoratorBackend) + assert backend.info == {"test_param": "custom"} @pytest.mark.smoke - def test_backend_registered_objects(self): + def test_registered_objects(self): """Test Backend.registered_objects method returns registered backends.""" # Should include at least the openai_http backend registered = Backend.registered_objects() diff --git a/tests/unit/backends/test_objects.py b/tests/unit/backends/test_objects.py deleted file mode 100644 index 0e808d57..00000000 --- a/tests/unit/backends/test_objects.py +++ /dev/null @@ -1,362 +0,0 @@ -""" -Unit tests for GenerationRequest, GenerationResponse, RequestTimings. -""" - -from __future__ import annotations - -import uuid - -import pytest -from pydantic import ValidationError - -from guidellm.schemas import ( - GenerationRequest, - GenerationResponse, - RequestInfo, - RequestTimings, -) -from guidellm.schemas.request import GenerationRequestArguments -from guidellm.utils import StandardBaseModel - - -class TestGenerationRequest: - """Test cases for GenerationRequest model.""" - - @pytest.fixture( - params=[ - { - "request_type": "text_completions", - "arguments": GenerationRequestArguments(), - }, - { - "request_type": "chat_completions", - "arguments": GenerationRequestArguments(body={"temperature": 0.7}), - }, - { - "request_id": "custom-id", - "request_type": "text_completions", - "arguments": GenerationRequestArguments(body={"prompt": "test"}), - }, - ] - ) - def valid_instances(self, request): - """Fixture providing valid GenerationRequest instances.""" - constructor_args = request.param - instance = GenerationRequest(**constructor_args) - return instance, constructor_args - - @pytest.mark.smoke - def test_class_signatures(self): - """Test GenerationRequest inheritance and type relationships.""" - assert issubclass(GenerationRequest, StandardBaseModel) - assert hasattr(GenerationRequest, "model_dump") - assert hasattr(GenerationRequest, "model_validate") - - # Check all expected fields are defined - fields = GenerationRequest.model_fields - expected_fields = [ - "request_id", - "request_type", - "arguments", - "input_metrics", - "output_metrics", - ] - for field in expected_fields: - assert field in fields - - @pytest.mark.smoke - def test_initialization(self, valid_instances): - """Test GenerationRequest initialization.""" - instance, constructor_args = valid_instances - assert isinstance(instance, GenerationRequest) - assert instance.arguments == constructor_args["arguments"] - - # Check defaults - expected_request_type = constructor_args.get("request_type", "text_completions") - assert instance.request_type == expected_request_type - - if "request_id" in constructor_args: - assert instance.request_id == constructor_args["request_id"] - else: - assert isinstance(instance.request_id, str) - # Should be valid UUID - uuid.UUID(instance.request_id) - - @pytest.mark.sanity - def test_invalid_initialization_values(self): - """Test GenerationRequest with invalid field values.""" - # Invalid request_type (not a string) - with pytest.raises(ValidationError): - GenerationRequest(request_type=123, arguments=GenerationRequestArguments()) - - @pytest.mark.sanity - def test_invalid_initialization_missing(self): - """Test GenerationRequest initialization without required field.""" - with pytest.raises(ValidationError): - GenerationRequest() # Missing required 'request_type' field - - @pytest.mark.smoke - def test_auto_id_generation(self): - """Test that request_id is auto-generated if not provided.""" - request1 = GenerationRequest( - request_type="text_completions", arguments=GenerationRequestArguments() - ) - request2 = GenerationRequest( - request_type="text_completions", arguments=GenerationRequestArguments() - ) - - assert request1.request_id != request2.request_id - assert len(request1.request_id) > 0 - assert len(request2.request_id) > 0 - - # Should be valid UUIDs - uuid.UUID(request1.request_id) - uuid.UUID(request2.request_id) - - @pytest.mark.regression - def test_content_types(self): - """Test GenerationRequest with different argument types.""" - # Basic arguments - request1 = GenerationRequest( - request_type="text_completions", arguments=GenerationRequestArguments() - ) - assert isinstance(request1.arguments, GenerationRequestArguments) - - # Arguments with body - request2 = GenerationRequest( - request_type="chat_completions", - arguments=GenerationRequestArguments(body={"prompt": "test"}), - ) - assert request2.arguments.body == {"prompt": "test"} - - # Arguments with headers - request3 = GenerationRequest( - request_type="text_completions", - arguments=GenerationRequestArguments( - headers={"Authorization": "Bearer token"} - ), - ) - assert request3.arguments.headers == {"Authorization": "Bearer token"} - - @pytest.mark.sanity - def test_marshalling(self, valid_instances): - """Test GenerationRequest serialization and deserialization.""" - instance, constructor_args = valid_instances - data_dict = instance.model_dump() - assert isinstance(data_dict, dict) - assert "arguments" in data_dict - - # Test reconstruction - reconstructed = GenerationRequest.model_validate(data_dict) - assert reconstructed.arguments == instance.arguments - assert reconstructed.request_type == instance.request_type - assert reconstructed.request_id == instance.request_id - - -class TestGenerationResponse: - """Test cases for GenerationResponse model.""" - - @pytest.fixture( - params=[ - { - "request_id": "test-123", - "request_args": "model=gpt-3.5-turbo", - }, - { - "request_id": "test-456", - "request_args": "model=gpt-4", - "text": "Generated text", - }, - ] - ) - def valid_instances(self, request): - """Fixture providing valid GenerationResponse instances.""" - constructor_args = request.param - instance = GenerationResponse(**constructor_args) - return instance, constructor_args - - @pytest.mark.smoke - def test_class_signatures(self): - """Test GenerationResponse inheritance and type relationships.""" - assert issubclass(GenerationResponse, StandardBaseModel) - assert hasattr(GenerationResponse, "model_dump") - assert hasattr(GenerationResponse, "model_validate") - - # Check all expected fields and properties are defined - fields = GenerationResponse.model_fields - expected_fields = [ - "request_id", - "request_args", - "text", - "input_metrics", - "output_metrics", - ] - for field in expected_fields: - assert field in fields - - # Check methods exist - assert hasattr(GenerationResponse, "compile_stats") - - @pytest.mark.smoke - def test_initialization(self, valid_instances): - """Test GenerationResponse initialization.""" - instance, constructor_args = valid_instances - assert isinstance(instance, GenerationResponse) - assert instance.request_id == constructor_args["request_id"] - assert instance.request_args == constructor_args["request_args"] - - # Check defaults for optional fields - if "text" not in constructor_args: - assert instance.text is None - - # Check default metrics - assert hasattr(instance, "input_metrics") - assert hasattr(instance, "output_metrics") - - @pytest.mark.sanity - def test_invalid_initialization_values(self): - """Test GenerationResponse with invalid field values.""" - # Invalid iterations type - with pytest.raises(ValidationError): - GenerationResponse(request_id="test", request_args={}, iterations="not_int") - - @pytest.mark.sanity - def test_invalid_initialization_missing(self): - """Test GenerationResponse initialization without required fields.""" - with pytest.raises(ValidationError): - GenerationResponse() # Missing required fields - - with pytest.raises(ValidationError): - GenerationResponse(request_id="test") # Missing request_args - - @pytest.mark.smoke - def test_compile_stats_method(self): - """Test compile_stats method functionality.""" - from guidellm.schemas.request import GenerationRequestArguments - - response = GenerationResponse( - request_id="test-123", request_args="test_args", text="Generated response" - ) - - request = GenerationRequest( - request_id="test-123", - request_type="text_completions", - arguments=GenerationRequestArguments(), - ) - - request_info = RequestInfo(request_id="test-123") - - # Test that compile_stats works - stats = response.compile_stats(request, request_info) - assert stats is not None - assert hasattr(stats, "request_id") - assert stats.request_id == "test-123" - - @pytest.mark.sanity - def test_marshalling(self, valid_instances): - """Test GenerationResponse serialization and deserialization.""" - instance, constructor_args = valid_instances - data_dict = instance.model_dump() - assert isinstance(data_dict, dict) - assert data_dict["request_id"] == constructor_args["request_id"] - assert data_dict["request_args"] == constructor_args["request_args"] - - # Test reconstruction - reconstructed = GenerationResponse.model_validate(data_dict) - assert reconstructed.request_id == instance.request_id - assert reconstructed.request_args == instance.request_args - if hasattr(instance, "text"): - assert reconstructed.text == instance.text - - -class TestRequestTimings: - """Test cases for RequestTimings model.""" - - @pytest.fixture( - params=[ - {}, - {"first_iteration": 1234567890.0}, - {"last_iteration": 1234567895.0}, - { - "first_iteration": 1234567890.0, - "last_iteration": 1234567895.0, - }, - ] - ) - def valid_instances(self, request): - """Fixture providing valid RequestTimings instances.""" - constructor_args = request.param - instance = RequestTimings(**constructor_args) - return instance, constructor_args - - @pytest.mark.smoke - def test_class_signatures(self): - """Test RequestTimings inheritance and type relationships.""" - assert issubclass(RequestTimings, RequestTimings) - assert hasattr(RequestTimings, "model_dump") - assert hasattr(RequestTimings, "model_validate") - - # Check inherited fields from RequestTimings - fields = RequestTimings.model_fields - expected_inherited_fields = ["request_start", "request_end"] - for field in expected_inherited_fields: - assert field in fields - - # Check own fields - expected_own_fields = ["first_iteration", "last_iteration"] - for field in expected_own_fields: - assert field in fields - - @pytest.mark.smoke - def test_initialization(self, valid_instances): - """Test RequestTimings initialization.""" - instance, constructor_args = valid_instances - assert isinstance(instance, RequestTimings) - assert isinstance(instance, RequestTimings) - - # Check field values - expected_first = constructor_args.get("first_iteration") - expected_last = constructor_args.get("last_iteration") - assert instance.first_iteration == expected_first - assert instance.last_iteration == expected_last - - @pytest.mark.sanity - def test_invalid_initialization_values(self): - """Test RequestTimings with invalid field values.""" - # Invalid timestamp type - with pytest.raises(ValidationError): - RequestTimings(first_iteration="not_float") - - with pytest.raises(ValidationError): - RequestTimings(last_iteration="not_float") - - @pytest.mark.smoke - def test_optional_fields(self): - """Test that all timing fields are optional.""" - # Should be able to create with no fields - timings1 = RequestTimings() - assert timings1.first_iteration is None - assert timings1.last_iteration is None - - # Should be able to create with only one field - timings2 = RequestTimings(first_iteration=123.0) - assert timings2.first_iteration == 123.0 - assert timings2.last_iteration is None - - timings3 = RequestTimings(last_iteration=456.0) - assert timings3.first_iteration is None - assert timings3.last_iteration == 456.0 - - @pytest.mark.sanity - def test_marshalling(self, valid_instances): - """Test RequestTimings serialization and deserialization.""" - instance, constructor_args = valid_instances - data_dict = instance.model_dump() - assert isinstance(data_dict, dict) - - # Test reconstruction - reconstructed = RequestTimings.model_validate(data_dict) - assert reconstructed.first_iteration == instance.first_iteration - assert reconstructed.last_iteration == instance.last_iteration - assert reconstructed.request_start == instance.request_start - assert reconstructed.request_end == instance.request_end diff --git a/tests/unit/backends/test_openai_backend.py b/tests/unit/backends/test_openai_backend.py index b91e83e7..47acbac4 100644 --- a/tests/unit/backends/test_openai_backend.py +++ b/tests/unit/backends/test_openai_backend.py @@ -4,35 +4,23 @@ from __future__ import annotations +import asyncio from unittest.mock import Mock, patch import httpx import pytest -from guidellm.backends.backend import Backend -from guidellm.backends.openai import OpenAIHTTPBackend +from guidellm.backends import Backend, OpenAIHTTPBackend from guidellm.schemas import ( GenerationRequest, + GenerationRequestArguments, GenerationResponse, RequestInfo, RequestTimings, ) -from guidellm.schemas.request import GenerationRequestArguments, UsageMetrics from tests.unit.testing_utils import async_timeout -def test_usage_metrics(): - """Test that UsageMetrics is defined correctly.""" - metrics = UsageMetrics() - assert hasattr(metrics, "text_tokens") - assert hasattr(metrics, "text_characters") - assert hasattr(metrics, "total_tokens") - - metrics_with_values = UsageMetrics(text_tokens=10, text_characters=50) - assert metrics_with_values.text_tokens == 10 - assert metrics_with_values.text_characters == 50 - - class TestOpenAIHTTPBackend: """Test cases for OpenAIHTTPBackend.""" @@ -71,6 +59,9 @@ def test_class_signatures(self): assert hasattr(OpenAIHTTPBackend, "resolve") assert hasattr(OpenAIHTTPBackend, "default_model") assert hasattr(OpenAIHTTPBackend, "available_models") + # Check that inherited properties exist + assert hasattr(OpenAIHTTPBackend, "processes_limit") + assert hasattr(OpenAIHTTPBackend, "requests_limit") @pytest.mark.smoke def test_initialization(self, valid_instances): @@ -104,6 +95,23 @@ def test_invalid_initialization_values(self, field, value): backend = OpenAIHTTPBackend(**base_args) assert getattr(backend, field) == value + @pytest.mark.sanity + def test_invalid_validate_backend_parameter(self): + """Test OpenAIHTTPBackend with invalid validate_backend parameter.""" + # Invalid dict without url + with pytest.raises(ValueError, match="validate_backend must be"): + OpenAIHTTPBackend( + target="http://localhost:8000", + validate_backend={"method": "GET"}, + ) + + # Invalid type (number) + with pytest.raises(ValueError, match="validate_backend must be"): + OpenAIHTTPBackend( + target="http://localhost:8000", + validate_backend=123, # type: ignore[arg-type] + ) + @pytest.mark.smoke def test_factory_registration(self): """Test that OpenAIHTTPBackend is registered with Backend factory.""" @@ -118,13 +126,15 @@ def test_initialization_minimal(self): backend = OpenAIHTTPBackend(target="http://localhost:8000") assert backend.target == "http://localhost:8000" - assert backend.model is None + assert backend.model == "" assert backend.timeout == 60.0 assert backend.http2 is True assert backend.follow_redirects is True assert backend.verify is False assert backend._in_process is False assert backend._async_client is None + assert backend.processes_limit is None + assert backend.requests_limit is None @pytest.mark.smoke def test_initialization_full(self): @@ -153,6 +163,47 @@ def test_initialization_full(self): assert backend.api_routes["health"] == "custom/health" assert backend.api_routes["models"] == "custom/models" assert backend.response_handlers == response_handlers + assert backend.processes_limit is None + assert backend.requests_limit is None + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("validate_backend", "expected_validate_backend"), + [ + (True, {"method": "GET", "url": "http://test/health"}), + (False, None), + ("health", {"method": "GET", "url": "http://test/health"}), + ( + "http://custom/endpoint", + {"method": "GET", "url": "http://custom/endpoint"}, + ), + ( + {"url": "http://custom/url", "method": "POST"}, + {"url": "http://custom/url", "method": "POST"}, + ), + ( + {"url": "http://custom/url"}, + {"url": "http://custom/url", "method": "GET"}, + ), + ], + ids=[ + "bool_true", + "bool_false", + "str_api_route", + "str_custom_url", + "dict_with_method", + "dict_without_method", + ], + ) + def test_validate_backend_parameter( + self, validate_backend, expected_validate_backend + ): + """Test validate_backend parameter with various input types.""" + backend = OpenAIHTTPBackend( + target="http://test", + validate_backend=validate_backend, + ) + assert backend.validate_backend == expected_validate_backend @pytest.mark.sanity def test_target_normalization(self): @@ -272,7 +323,7 @@ async def test_default_model(self): # Test when not in process backend2 = OpenAIHTTPBackend(target="http://test") result2 = await backend2.default_model() - assert result2 is None + assert result2 == "" # Test when in process but no model set backend3 = OpenAIHTTPBackend(target="http://test") @@ -314,6 +365,27 @@ async def test_validate_without_model(self): with patch.object(backend._async_client, "request", return_value=mock_response): await backend.validate() # Should not raise + @pytest.mark.regression + @pytest.mark.asyncio + @async_timeout(10.0) + async def test_validate_not_in_process(self): + """Test validate method when backend is not started.""" + backend = OpenAIHTTPBackend(target="http://test") + + with pytest.raises(RuntimeError, match="Backend not started up"): + await backend.validate() + + @pytest.mark.regression + @pytest.mark.asyncio + @async_timeout(10.0) + async def test_validate_disabled(self): + """Test validate method when validation is disabled.""" + backend = OpenAIHTTPBackend(target="http://test", validate_backend=False) + await backend.process_startup() + + # Should not raise and should not make any requests + await backend.validate() + @pytest.mark.regression @pytest.mark.asyncio @async_timeout(10.0) @@ -359,6 +431,55 @@ async def test_resolve_not_implemented_history(self): async for _ in backend.resolve(request, request_info, history): pass + @pytest.mark.regression + @pytest.mark.asyncio + @async_timeout(10.0) + async def test_resolve_invalid_request_type(self): + """Test resolve method raises error for invalid request type.""" + backend = OpenAIHTTPBackend(target="http://test") + await backend.process_startup() + + request = GenerationRequest( + request_type="invalid_type", + arguments=GenerationRequestArguments(body={"prompt": "test"}), + ) + request_info = RequestInfo( + request_id="test-id", + status="pending", + scheduler_node_id=1, + scheduler_process_id=1, + scheduler_start_time=123.0, + request_timings=RequestTimings(), + ) + + with pytest.raises(ValueError, match="Unsupported request type"): + async for _ in backend.resolve(request, request_info): + pass + + @pytest.mark.regression + @pytest.mark.asyncio + @async_timeout(10.0) + async def test_resolve_not_in_process(self): + """Test resolve method raises error when backend is not started.""" + backend = OpenAIHTTPBackend(target="http://test") + + request = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(body={"prompt": "test"}), + ) + request_info = RequestInfo( + request_id="test-id", + status="pending", + scheduler_node_id=1, + scheduler_process_id=1, + scheduler_start_time=123.0, + request_timings=RequestTimings(), + ) + + with pytest.raises(RuntimeError, match="Backend not started up"): + async for _ in backend.resolve(request, request_info): + pass + @pytest.mark.regression @pytest.mark.asyncio @async_timeout(10.0) @@ -383,7 +504,10 @@ async def test_resolve_text_completions(self): ) # Mock response handler - from guidellm.backends.response_handlers import GenerationResponseHandler + from guidellm.backends.response_handlers import ( + GenerationResponseHandler, + GenerationResponseHandlerFactory, + ) mock_handler = Mock(spec=GenerationResponseHandler) mock_response = GenerationResponse( @@ -393,7 +517,7 @@ async def test_resolve_text_completions(self): with ( patch.object( - backend, "_resolve_response_handler", return_value=mock_handler + GenerationResponseHandlerFactory, "create", return_value=mock_handler ), patch.object(backend._async_client, "request") as mock_request, ): @@ -439,7 +563,10 @@ async def test_resolve_chat_completions(self): ) # Mock response handler - from guidellm.backends.response_handlers import GenerationResponseHandler + from guidellm.backends.response_handlers import ( + GenerationResponseHandler, + GenerationResponseHandlerFactory, + ) mock_handler = Mock(spec=GenerationResponseHandler) mock_response = GenerationResponse( @@ -449,7 +576,7 @@ async def test_resolve_chat_completions(self): with ( patch.object( - backend, "_resolve_response_handler", return_value=mock_handler + GenerationResponseHandlerFactory, "create", return_value=mock_handler ), patch.object(backend._async_client, "request") as mock_request, ): @@ -467,3 +594,125 @@ async def test_resolve_chat_completions(self): assert len(responses) == 1 final_response = responses[0][0] assert final_response.request_id == "test-id" + + @pytest.mark.regression + @pytest.mark.asyncio + @async_timeout(10.0) + async def test_resolve_with_files(self): + """Test resolve method with file uploads.""" + backend = OpenAIHTTPBackend(target="http://test") + await backend.process_startup() + + request = GenerationRequest( + request_type="audio_transcriptions", + arguments=GenerationRequestArguments( + body={"model": "whisper-1"}, + files={"file": ["audio.mp3", b"audio_data", "audio/mpeg"]}, + ), + ) + request_info = RequestInfo( + request_id="test-id", + status="pending", + scheduler_node_id=1, + scheduler_process_id=1, + scheduler_start_time=123.0, + request_timings=RequestTimings(), + ) + + # Mock response handler + from guidellm.backends.response_handlers import ( + GenerationResponseHandler, + GenerationResponseHandlerFactory, + ) + + mock_handler = Mock(spec=GenerationResponseHandler) + mock_response = GenerationResponse( + request_id="test-id", request_args="test args" + ) + mock_handler.compile_non_streaming.return_value = mock_response + + with ( + patch.object( + GenerationResponseHandlerFactory, "create", return_value=mock_handler + ), + patch.object(backend._async_client, "request") as mock_request, + ): + mock_http_response = Mock() + mock_http_response.json.return_value = {"text": "transcribed text"} + mock_http_response.raise_for_status = Mock() + mock_request.return_value = mock_http_response + + responses = [] + async for response, info in backend.resolve(request, request_info): + responses.append((response, info)) + + assert len(responses) == 1 + # Verify that files were passed correctly + call_kwargs = mock_request.call_args[1] + assert call_kwargs["files"] is not None + assert call_kwargs["data"] is not None + assert call_kwargs["json"] is None + + @pytest.mark.regression + @pytest.mark.asyncio + @async_timeout(10.0) + async def test_resolve_streaming_cancelled(self): + """Test resolve method handles asyncio.CancelledError during streaming.""" + backend = OpenAIHTTPBackend(target="http://test") + await backend.process_startup() + + request = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments( + body={"prompt": "test"}, + stream=True, + ), + ) + request_info = RequestInfo( + request_id="test-id", + status="pending", + scheduler_node_id=1, + scheduler_process_id=1, + scheduler_start_time=123.0, + request_timings=RequestTimings(), + ) + + # Mock response handler + from guidellm.backends.response_handlers import ( + GenerationResponseHandler, + GenerationResponseHandlerFactory, + ) + + mock_handler = Mock(spec=GenerationResponseHandler) + mock_handler.add_streaming_line.return_value = 1 + mock_response = GenerationResponse( + request_id="test-id", request_args="test args" + ) + mock_handler.compile_streaming.return_value = mock_response + + # Create a mock stream that raises CancelledError + class MockStream: + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + pass + + def raise_for_status(self): + pass + + async def aiter_lines(self): + yield "data: chunk1" + raise asyncio.CancelledError + + mock_stream = MockStream() + + with ( + patch.object( + GenerationResponseHandlerFactory, "create", return_value=mock_handler + ), + patch.object(backend._async_client, "stream", return_value=mock_stream), + pytest.raises(asyncio.CancelledError), + ): + async for _response, _info in backend.resolve(request, request_info): + pass diff --git a/tests/unit/backends/test_response_handlers.py b/tests/unit/backends/test_response_handlers.py new file mode 100644 index 00000000..f4be83ff --- /dev/null +++ b/tests/unit/backends/test_response_handlers.py @@ -0,0 +1,723 @@ +from __future__ import annotations + +import pytest + +from guidellm.backends import ( + AudioResponseHandler, + ChatCompletionsResponseHandler, + GenerationResponseHandler, + GenerationResponseHandlerFactory, + TextCompletionsResponseHandler, +) +from guidellm.schemas import GenerationRequest, GenerationRequestArguments +from guidellm.utils import RegistryMixin + + +@pytest.fixture +def generation_request(): + """Create a basic GenerationRequest for testing.""" + return GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments( + method="POST", + url="http://test.com", + body={"prompt": "Test prompt"}, + ), + ) + + +class TestGenerationResponseHandler: + @pytest.mark.smoke + def test_class_signatures(self): + """Test GenerationResponseHandler is a Protocol with correct methods.""" + + # Verify it's a Protocol by checking its methods + assert hasattr(GenerationResponseHandler, "compile_non_streaming") + assert hasattr(GenerationResponseHandler, "add_streaming_line") + assert hasattr(GenerationResponseHandler, "compile_streaming") + + +class TestGenerationResponseHandlerFactory: + @pytest.mark.smoke + def test_class_signatures(self): + """Test that GenerationResponseHandlerFactory has correct inheritance.""" + assert issubclass(GenerationResponseHandlerFactory, RegistryMixin) + assert hasattr(GenerationResponseHandlerFactory, "register") + assert hasattr(GenerationResponseHandlerFactory, "create") + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("request_type", "handler_overrides", "expected_class"), + [ + ("text_completions", None, TextCompletionsResponseHandler), + ("chat_completions", None, ChatCompletionsResponseHandler), + ("audio_transcriptions", None, AudioResponseHandler), + ("audio_translations", None, AudioResponseHandler), + ( + "text_completions", + {"text_completions": ChatCompletionsResponseHandler}, + ChatCompletionsResponseHandler, + ), + ], + ids=[ + "text_completions", + "chat_completions", + "audio_transcriptions", + "audio_translations", + "override_text_completions", + ], + ) + def test_create( + self, + request_type, + handler_overrides, + expected_class, + ): + """Test create method with various request types and overrides.""" + handler = GenerationResponseHandlerFactory.create( + request_type, handler_overrides + ) + assert isinstance(handler, expected_class) + + @pytest.mark.sanity + def test_create_invalid_request_type(self): + """Test create method with invalid request type.""" + with pytest.raises(ValueError, match="No response handler registered"): + GenerationResponseHandlerFactory.create("invalid_type") + + +class TestTextCompletionsResponseHandler: + @pytest.fixture( + params=[{}], + ids=["default"], + ) + def valid_instances(self, request): + """Create instance of TextCompletionsResponseHandler.""" + return TextCompletionsResponseHandler() + + @pytest.mark.smoke + def test_class_signatures(self): + """Test TextCompletionsResponseHandler class signatures.""" + handler = TextCompletionsResponseHandler() + assert hasattr(handler, "compile_non_streaming") + assert hasattr(handler, "add_streaming_line") + assert hasattr(handler, "compile_streaming") + assert hasattr(handler, "extract_line_data") + assert hasattr(handler, "extract_choices_and_usage") + assert hasattr(handler, "extract_metrics") + assert hasattr(handler, "streaming_texts") + assert hasattr(handler, "streaming_usage") + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test TextCompletionsResponseHandler initialization.""" + instance = valid_instances + assert isinstance(instance, TextCompletionsResponseHandler) + assert instance.streaming_texts == [] + assert instance.streaming_usage is None + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "response", + "expected_text", + "expected_input_tokens", + "expected_output_tokens", + ), + [ + ( + { + "choices": [{"text": "Hello, world!"}], + "usage": {"prompt_tokens": 5, "completion_tokens": 3}, + }, + "Hello, world!", + 5, + 3, + ), + ( + { + "choices": [{"text": "Test response"}], + "usage": { + "prompt_tokens": 10, + "completion_tokens": 5, + "prompt_tokens_details": {"prompt_tokens": 10}, + "completion_tokens_details": {"completion_tokens": 5}, + }, + }, + "Test response", + 10, + 5, + ), + ({"choices": [{"text": ""}], "usage": {}}, "", None, None), + ({"choices": [], "usage": {}}, "", None, None), + ({}, "", None, None), + ], + ) + def test_non_streaming( + self, + valid_instances, + generation_request, + response, + expected_text, + expected_input_tokens, + expected_output_tokens, + ): + """Test compile_non_streaming method.""" + instance: TextCompletionsResponseHandler = valid_instances + + result = instance.compile_non_streaming(generation_request, response) + + assert result.text == expected_text + assert result.input_metrics.text_tokens == expected_input_tokens + assert result.output_metrics.text_tokens == expected_output_tokens + assert result.output_metrics.text_words == len(expected_text.split()) + assert result.output_metrics.text_characters == len(expected_text) + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "lines", + "expected_text", + "expected_input_tokens", + "expected_output_tokens", + ), + [ + ( + [ + 'data: {"choices": [{"text": "Hello"}], "usage": {}}', + "", + 'data: {"choices": [{"text": ", "}], "usage": {}}', + ( + 'data: {"choices": [{"text": "world!"}], ' + '"usage": {"prompt_tokens": 5, "completion_tokens": 3}}' + ), + "data: [DONE]", + ], + "Hello, world!", + 5, + 3, + ), + ( + [ + 'data: {"choices": [{"text": "Test"}], "usage": {}}', + "", + "data: [DONE]", + ], + "Test", + None, + None, + ), + (["", "data: [DONE]"], "", None, None), + ], + ) + def test_streaming( + self, + valid_instances, + generation_request, + lines, + expected_text, + expected_input_tokens, + expected_output_tokens, + ): + """Test streaming with add_streaming_line and compile_streaming.""" + instance: TextCompletionsResponseHandler = valid_instances + + updated_count = 0 + for line in lines: + result = instance.add_streaming_line(line) + if result == 1: + updated_count += 1 + elif result is None: + break + + response = instance.compile_streaming(generation_request) + assert response.text == expected_text + assert response.input_metrics.text_tokens == expected_input_tokens + assert response.output_metrics.text_tokens == expected_output_tokens + assert response.output_metrics.text_words == len(expected_text.split()) + assert response.output_metrics.text_characters == len(expected_text) + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("line", "expected_output"), + [ + ('data: {"choices": [{"text": "Test"}]}', {"choices": [{"text": "Test"}]}), + ("data: [DONE]", None), + ("", {}), + ("invalid line", {}), + ('data: {"test": "value"}', {"test": "value"}), + ], + ) + def test_extract_line_data(self, valid_instances, line, expected_output): + """Test extract_line_data method.""" + instance = valid_instances + result = instance.extract_line_data(line) + assert result == expected_output + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("response", "expected_choices", "expected_usage"), + [ + ( + {"choices": [{"text": "Hello"}], "usage": {"prompt_tokens": 5}}, + [{"text": "Hello"}], + {"prompt_tokens": 5}, + ), + ( + {"choices": [], "usage": {}}, + [], + {}, + ), + ( + {}, + [], + {}, + ), + ], + ) + def test_extract_choices_and_usage( + self, valid_instances, response, expected_choices, expected_usage + ): + """Test extract_choices_and_usage method.""" + instance = valid_instances + choices, usage = instance.extract_choices_and_usage(response) + assert choices == expected_choices + assert usage == expected_usage + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("usage", "text", "expected_input_tokens", "expected_output_tokens"), + [ + ( + {"prompt_tokens": 5, "completion_tokens": 3}, + "Hello world", + 5, + 3, + ), + ( + { + "prompt_tokens_details": {"prompt_tokens": 10, "image_tokens": 2}, + "completion_tokens_details": {"completion_tokens": 5}, + }, + "Test response", + 10, + 5, + ), + ( + None, + "Hello world", + None, + None, + ), + ( + {}, + "", + None, + None, + ), + ], + ) + def test_extract_metrics( + self, + valid_instances, + usage, + text, + expected_input_tokens, + expected_output_tokens, + ): + """Test extract_metrics method.""" + instance = valid_instances + input_metrics, output_metrics = instance.extract_metrics(usage, text) + + assert input_metrics.text_tokens == expected_input_tokens + assert output_metrics.text_tokens == expected_output_tokens + assert output_metrics.text_words == (len(text.split()) if text else 0) + assert output_metrics.text_characters == len(text) + + +class TestChatCompletionsResponseHandler: + @pytest.fixture( + params=[{}], + ids=["default"], + ) + def valid_instances(self, request): + """Create instance of ChatCompletionsResponseHandler.""" + return ChatCompletionsResponseHandler() + + @pytest.mark.smoke + def test_class_signatures(self): + """Test ChatCompletionsResponseHandler class signatures.""" + handler = ChatCompletionsResponseHandler() + assert issubclass( + ChatCompletionsResponseHandler, TextCompletionsResponseHandler + ) + assert hasattr(handler, "compile_non_streaming") + assert hasattr(handler, "add_streaming_line") + assert hasattr(handler, "compile_streaming") + assert hasattr(handler, "streaming_texts") + assert hasattr(handler, "streaming_usage") + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test ChatCompletionsResponseHandler initialization.""" + instance: ChatCompletionsResponseHandler = valid_instances + assert isinstance(instance, ChatCompletionsResponseHandler) + assert instance.streaming_texts == [] + assert instance.streaming_usage is None + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "response", + "expected_text", + "expected_input_tokens", + "expected_output_tokens", + ), + [ + ( + { + "choices": [{"message": {"content": "Hello, world!"}}], + "usage": {"prompt_tokens": 5, "completion_tokens": 3}, + }, + "Hello, world!", + 5, + 3, + ), + ( + { + "choices": [{"message": {"content": "Test response"}}], + "usage": { + "prompt_tokens": 10, + "completion_tokens": 5, + }, + }, + "Test response", + 10, + 5, + ), + ( + {"choices": [{"message": {"content": ""}}], "usage": {}}, + "", + None, + None, + ), + ( + {"choices": [], "usage": {}}, + "", + None, + None, + ), + ], + ) + def test_non_streaming( + self, + valid_instances, + generation_request, + response, + expected_text, + expected_input_tokens, + expected_output_tokens, + ): + """Test compile_non_streaming method for chat completions.""" + instance: ChatCompletionsResponseHandler = valid_instances + + result = instance.compile_non_streaming(generation_request, response) + + assert result.text == expected_text + assert result.input_metrics.text_tokens == expected_input_tokens + assert result.output_metrics.text_tokens == expected_output_tokens + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("lines", "expected_text", "expected_input_tokens", "expected_output_tokens"), + [ + ( + [ + 'data: {"choices": [{"delta": {"content": "Hello"}}], "usage": {}}', + "", + 'data: {"choices": [{"delta": {"content": ", "}}], "usage": {}}', + ( + 'data: {"choices": [{"delta": {"content": "world!"}}], ' + '"usage": {"prompt_tokens": 5, "completion_tokens": 3}}' + ), + "data: [DONE]", + ], + "Hello, world!", + 5, + 3, + ), + ( + [ + 'data: {"choices": [{"delta": {"content": "Test"}}], "usage": {}}', + "", + "data: [DONE]", + ], + "Test", + None, + None, + ), + ( + ["", "data: [DONE]"], + "", + None, + None, + ), + ], + ) + def test_streaming( + self, + valid_instances, + generation_request, + lines, + expected_text, + expected_input_tokens, + expected_output_tokens, + ): + """Test streaming pathway for chat completions.""" + instance = ChatCompletionsResponseHandler() + + updated_count = 0 + for line in lines: + result = instance.add_streaming_line(line) + if result == 1: + updated_count += 1 + elif result is None: + break + + response = instance.compile_streaming(generation_request) + assert response.text == expected_text + assert response.input_metrics.text_tokens == expected_input_tokens + assert response.output_metrics.text_tokens == expected_output_tokens + + +class TestAudioResponseHandler: + @pytest.fixture( + params=[{}], + ids=["default"], + ) + def valid_instances(self, request): + """Create instance of AudioResponseHandler.""" + return AudioResponseHandler() + + @pytest.mark.smoke + def test_class_signatures(self): + """Test AudioResponseHandler class signatures.""" + handler = AudioResponseHandler() + assert hasattr(handler, "compile_non_streaming") + assert hasattr(handler, "add_streaming_line") + assert hasattr(handler, "compile_streaming") + assert hasattr(handler, "extract_metrics") + assert hasattr(handler, "streaming_buffer") + assert hasattr(handler, "streaming_texts") + assert hasattr(handler, "streaming_usage") + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test AudioResponseHandler initialization.""" + instance = valid_instances + assert isinstance(instance, AudioResponseHandler) + assert isinstance(instance.streaming_buffer, bytearray) + assert len(instance.streaming_buffer) == 0 + assert instance.streaming_texts == [] + assert instance.streaming_usage is None + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "response", + "expected_text", + "expected_audio_tokens", + "expected_output_tokens", + "expected_seconds", + ), + [ + ( + { + "text": "Hello, world!", + "usage": {"input_tokens": 1000, "output_tokens": 3}, + }, + "Hello, world!", + 1000, + 3, + 0, + ), + ( + { + "text": "Test transcription", + "usage": { + "audio_tokens": 500, + "output_tokens": 5, + "seconds": 10, + }, + }, + "Test transcription", + 500, + 5, + 10, + ), + ( + {"text": "", "usage": {}}, + "", + None, + None, + None, + ), + ], + ) + def test_non_streaming( + self, + valid_instances, + generation_request, + response, + expected_text, + expected_audio_tokens, + expected_output_tokens, + expected_seconds, + ): + """Test compile_non_streaming method for audio.""" + instance: AudioResponseHandler = valid_instances + + result = instance.compile_non_streaming(generation_request, response) + + assert result.text == expected_text + assert result.input_metrics.audio_tokens == expected_audio_tokens + assert result.output_metrics.text_tokens == expected_output_tokens + assert result.output_metrics.text_words == len(expected_text.split()) + assert result.output_metrics.text_characters == len(expected_text) + assert result.input_metrics.audio_seconds == expected_seconds + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "lines", + "expected_text", + "expected_audio_tokens", + "expected_output_tokens", + "expected_seconds", + ), + [ + ( + [ + '{"text": "Hello", "usage": {}}', + ( + '{"text": " world!", ' + '"usage": {"audio_tokens": 1000, ' + '"output_tokens": 3, "seconds": 5}}' + ), + "data: [DONE]", + ], + "Hello world!", + 1000, + 3, + 5, + ), + ( + [ + '{"text": "Test", "usage": {}}', + "data: [DONE]", + ], + "Test", + None, + None, + None, + ), + ( + ["data: [DONE]"], + "", + None, + None, + None, + ), + ], + ) + def test_streaming( + self, + valid_instances, + generation_request, + lines, + expected_text, + expected_audio_tokens, + expected_output_tokens, + expected_seconds, + ): + """Test streaming pathway for audio.""" + instance: AudioResponseHandler = valid_instances + + updated_count = 0 + for line in lines: + result = instance.add_streaming_line(line) + if result == 1: + updated_count += 1 + elif result is None: + break + + response = instance.compile_streaming(generation_request) + assert response.text == expected_text + assert response.input_metrics.audio_tokens == expected_audio_tokens + assert response.output_metrics.text_tokens == expected_output_tokens + assert response.input_metrics.audio_seconds == expected_seconds + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "usage", + "text", + "expected_audio_tokens", + "expected_output_tokens", + "expected_seconds", + ), + [ + ( + {"input_tokens": 1000, "output_tokens": 5}, + "Hello world", + 1000, + 5, + 0, + ), + ( + { + "audio_tokens": 500, + "output_tokens": 3, + "seconds": 10, + }, + "Test", + 500, + 3, + 10, + ), + ( + { + "input_token_details": { + "audio_tokens": 800, + "text_tokens": 5, + "seconds": 10, + }, + "output_token_details": {"text_tokens": 10}, + }, + "Hello world test", + 800, + 10, + 10, + ), + (None, "Hello", None, None, None), + ({}, "", None, None, None), + ], + ) + def test_extract_metrics( + self, + valid_instances, + usage, + text, + expected_audio_tokens, + expected_output_tokens, + expected_seconds, + ): + """Test extract_metrics method for audio.""" + instance: AudioResponseHandler = valid_instances + input_metrics, output_metrics = instance.extract_metrics(usage, text) + + assert input_metrics.audio_tokens == expected_audio_tokens + assert input_metrics.audio_seconds == expected_seconds + assert output_metrics.text_tokens == expected_output_tokens + assert output_metrics.text_words == (len(text.split()) if text else 0) + assert output_metrics.text_characters == len(text) diff --git a/tests/unit/benchmark/test_output.py b/tests/unit/benchmark/test_output.py deleted file mode 100644 index 416a9b2b..00000000 --- a/tests/unit/benchmark/test_output.py +++ /dev/null @@ -1,185 +0,0 @@ -import csv -import json -from pathlib import Path -from unittest.mock import patch - -import pytest -import yaml -from pydantic import ValidationError - -from guidellm.benchmark import ( - GenerativeBenchmarksReport, -) -from guidellm.benchmark.output import ( - GenerativeBenchmarkerConsole, - GenerativeBenchmarkerCSV, -) -from guidellm.benchmark.schemas import BenchmarkGenerativeTextArgs -from tests.unit.mock_benchmark import mock_generative_benchmark - - -def test_generative_benchmark_initilization(): - args = BenchmarkGenerativeTextArgs(target="http://localhost:8000", data=["test"]) - report = GenerativeBenchmarksReport(args=args) - assert len(report.benchmarks) == 0 - - mock_benchmark = mock_generative_benchmark() - report_with_benchmarks = GenerativeBenchmarksReport( - args=args, benchmarks=[mock_benchmark] - ) - assert len(report_with_benchmarks.benchmarks) == 1 - assert report_with_benchmarks.benchmarks[0] == mock_benchmark - - -def test_generative_benchmark_invalid_initilization(): - with pytest.raises(ValidationError): - GenerativeBenchmarksReport(benchmarks="invalid_type") # type: ignore[arg-type] - - -def test_generative_benchmark_marshalling(): - args = BenchmarkGenerativeTextArgs(target="http://localhost:8000", data=["test"]) - mock_benchmark = mock_generative_benchmark() - report = GenerativeBenchmarksReport(args=args, benchmarks=[mock_benchmark]) - - serialized = report.model_dump() - deserialized = GenerativeBenchmarksReport.model_validate(serialized) - deserialized_benchmark = deserialized.benchmarks[0] - - # model_dump as workaround for duplicate fields for computed fields. - assert mock_benchmark.model_dump() == deserialized_benchmark.model_dump() - - -def test_file_json(): - args = BenchmarkGenerativeTextArgs(target="http://localhost:8000", data=["test"]) - mock_benchmark = mock_generative_benchmark() - report = GenerativeBenchmarksReport(args=args, benchmarks=[mock_benchmark]) - - mock_path = Path("mock_report.json") - report.save_file(mock_path) - - with mock_path.open("r") as file: - saved_data = json.load(file) - assert saved_data == report.model_dump() - - loaded_report = GenerativeBenchmarksReport.load_file(mock_path) - loaded_benchmark = loaded_report.benchmarks[0] - - # model_dump as workaround for duplicate fields for computed fields. - assert mock_benchmark.model_dump() == loaded_benchmark.model_dump() - - mock_path.unlink() - - -def test_file_yaml(): - args = BenchmarkGenerativeTextArgs(target="http://localhost:8000", data=["test"]) - mock_benchmark = mock_generative_benchmark() - report = GenerativeBenchmarksReport(args=args, benchmarks=[mock_benchmark]) - - mock_path = Path("mock_report.yaml") - report.save_file(mock_path) - - with mock_path.open("r") as file: - saved_data = yaml.safe_load(file) - assert saved_data == report.model_dump() - - loaded_report = GenerativeBenchmarksReport.load_file(mock_path) - loaded_benchmark = loaded_report.benchmarks[0] - - # model_dump as workaround for duplicate fields for computed fields. - assert mock_benchmark.model_dump() == loaded_benchmark.model_dump() - - mock_path.unlink() - - -@pytest.mark.asyncio -async def test_file_csv(): - args = BenchmarkGenerativeTextArgs(target="http://localhost:8000", data=["test"]) - mock_benchmark = mock_generative_benchmark() - report = GenerativeBenchmarksReport(args=args, benchmarks=[mock_benchmark]) - - mock_path = Path("mock_report.csv") - csv_benchmarker = GenerativeBenchmarkerCSV(output_path=mock_path) - await csv_benchmarker.finalize(report) - - with mock_path.open("r") as file: # noqa: ASYNC230 # This is a test. - reader = csv.reader(file) - headers = next(reader) - rows = list(reader) - - assert "Type" in headers - assert "Profile" in headers - assert len(rows) == 1 - - mock_path.unlink() - - -def test_console_benchmarks_profile_str(): - console = GenerativeBenchmarkerConsole() - mock_benchmark = mock_generative_benchmark() - profile_str = console._get_profile_str(mock_benchmark) - # The profile string should contain the profile type information - assert "synchronous" in profile_str - - -def test_console_print_section_header(): - console = GenerativeBenchmarkerConsole() - with patch.object(console.console, "print") as mock_print: - console._print_section_header("Test Header") - mock_print.assert_called_once() - - -def test_console_print_labeled_line(): - console = GenerativeBenchmarkerConsole() - with patch.object(console.console, "print") as mock_print: - console._print_labeled_line("Label", "Value") - mock_print.assert_called_once() - - -def test_console_print_line(): - console = GenerativeBenchmarkerConsole() - with patch.object(console.console, "print") as mock_print: - console._print_line("Test Line") - mock_print.assert_called_once() - - -def test_console_print_table(): - console = GenerativeBenchmarkerConsole() - headers = ["Header1", "Header2"] - rows = [["Row1Col1", "Row1Col2"], ["Row2Col1", "Row2Col2"]] - with ( - patch.object(console, "_print_section_header") as mock_header, - patch.object(console, "_print_table_divider") as mock_divider, - patch.object(console, "_print_table_row") as mock_row, - ): - console._print_table(headers, rows, "Test Table") - mock_header.assert_called_once() - mock_divider.assert_called() - mock_row.assert_called() - - -def test_console_print_benchmarks_metadata(): - console = GenerativeBenchmarkerConsole() - mock_benchmark = mock_generative_benchmark() - with ( - patch.object(console, "_print_section_header") as mock_header, - patch.object(console, "_print_labeled_line") as mock_labeled, - ): - console._print_benchmarks_metadata([mock_benchmark]) - mock_header.assert_called_once() - mock_labeled.assert_called() - - -def test_console_print_benchmarks_info(): - console = GenerativeBenchmarkerConsole() - mock_benchmark = mock_generative_benchmark() - with patch.object(console, "_print_table") as mock_table: - console._print_benchmarks_info([mock_benchmark]) - mock_table.assert_called_once() - - -def test_console_print_benchmarks_stats(): - console = GenerativeBenchmarkerConsole() - mock_benchmark = mock_generative_benchmark() - with patch.object(console, "_print_table") as mock_table: - console._print_benchmarks_stats([mock_benchmark]) - mock_table.assert_called_once() diff --git a/tests/unit/mock_benchmark.py b/tests/unit/mock_benchmark.py index 0546d28f..9da4227d 100644 --- a/tests/unit/mock_benchmark.py +++ b/tests/unit/mock_benchmark.py @@ -1,7 +1,7 @@ """Mock benchmark objects for unit testing.""" from guidellm.benchmark import ( - BenchmarkSchedulerStats, + BenchmarkSchedulerMetrics, GenerativeBenchmark, GenerativeMetrics, ) @@ -113,7 +113,7 @@ def mock_generative_benchmark() -> GenerativeBenchmark: ), env_args=StandardBaseDict(), extras=StandardBaseDict(), - run_stats=BenchmarkSchedulerStats( + run_stats=BenchmarkSchedulerMetrics( start_time=1, end_time=2, requests_made=StatusBreakdown( diff --git a/tests/unit/presentation/test_data_models.py b/tests/unit/presentation/test_data_models.py deleted file mode 100644 index c1663c43..00000000 --- a/tests/unit/presentation/test_data_models.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -from guidellm.presentation.data_models import Bucket - - -@pytest.mark.smoke -def test_bucket_from_data(): - buckets, bucket_width = Bucket.from_data([8, 8, 8, 8, 8, 8], 1) - assert len(buckets) == 1 - assert buckets[0].value == 8.0 - assert buckets[0].count == 6 - assert bucket_width == 1 - - buckets, bucket_width = Bucket.from_data([8, 8, 8, 8, 8, 7], 1) - assert len(buckets) == 2 - assert buckets[0].value == 7.0 - assert buckets[0].count == 1 - assert buckets[1].value == 8.0 - assert buckets[1].count == 5 - assert bucket_width == 1 diff --git a/tests/unit/presentation/test_injector.py b/tests/unit/presentation/test_injector.py deleted file mode 100644 index da269815..00000000 --- a/tests/unit/presentation/test_injector.py +++ /dev/null @@ -1,87 +0,0 @@ -from pathlib import Path - -import pytest -from pydantic import BaseModel - -from guidellm.presentation.injector import create_report, inject_data -from guidellm.settings import settings - - -class ExampleModel(BaseModel): - name: str - version: str - - -@pytest.mark.smoke -def test_inject_data(): - html = "" - expected_html = ( - "" - ) - js_data = { - "window.runInfo = {};": "window.runInfo =" - '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };' - } - result = inject_data( - js_data, - html, - ) - assert result == expected_html - - -@pytest.mark.smoke -def test_create_report_to_file(tmpdir): - js_data = { - "window.runInfo = {};": "window.runInfo =" - '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };' - } - html_content = "" - expected_html_content = ( - "" - ) - - mock_html_path = tmpdir.join("template.html") - mock_html_path.write(html_content) - settings.report_generation.source = str(mock_html_path) - - output_path = tmpdir.join("output.html") - result_path = create_report(js_data, str(output_path)) - result_content = result_path.read_text() - - assert result_path == output_path - assert result_content == expected_html_content - - -@pytest.mark.smoke -def test_create_report_with_file_nested_in_dir(tmpdir): - js_data = { - "window.runInfo = {};": "window.runInfo =" - '{ "model": { "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8" } };' - } - html_content = "" - expected_html_content = ( - "" - ) - - output_dir = tmpdir.mkdir("output_dir") - mock_html_path = tmpdir.join("template.html") - mock_html_path.write(html_content) - settings.report_generation.source = str(mock_html_path) - - output_path = Path(output_dir) / "report.html" - result_path = create_report(js_data, str(output_path)) - - with Path(result_path).open("r") as file: - result_content = file.read() - - assert result_path == output_path - assert result_content == expected_html_content diff --git a/tests/unit/scheduler/test_constraints.py b/tests/unit/scheduler/test_constraints.py index 64dcd1e2..8bf571ab 100644 --- a/tests/unit/scheduler/test_constraints.py +++ b/tests/unit/scheduler/test_constraints.py @@ -22,8 +22,8 @@ SerializableConstraintInitializer, UnserializableConstraintInitializer, ) -from guidellm.schemas import RequestInfo -from guidellm.utils import InfoMixin, StandardBaseModel +from guidellm.schemas import RequestInfo, StandardBaseModel +from guidellm.utils import InfoMixin class TestConstraint: diff --git a/tests/unit/scheduler/test_objects.py b/tests/unit/scheduler/test_objects.py index 140af94d..c89c6181 100644 --- a/tests/unit/scheduler/test_objects.py +++ b/tests/unit/scheduler/test_objects.py @@ -20,8 +20,7 @@ SchedulerUpdateAction, SchedulerUpdateActionProgress, ) -from guidellm.schemas import RequestInfo, RequestTimings -from guidellm.utils import StandardBaseModel +from guidellm.schemas import RequestInfo, RequestTimings, StandardBaseModel def test_request_t(): diff --git a/tests/unit/scheduler/test_scheduler.py b/tests/unit/scheduler/test_scheduler.py index 43ec8f10..fe50a6bc 100644 --- a/tests/unit/scheduler/test_scheduler.py +++ b/tests/unit/scheduler/test_scheduler.py @@ -109,7 +109,6 @@ def test_class_signatures(self): "requests", "backend", "strategy", - "startup_duration", "env", "constraints", ] @@ -163,7 +162,6 @@ async def test_run_basic_functionality( requests=requests, backend=backend, strategy=strategy, - startup_duration=0.1, env=env, **constraint_args, ): @@ -190,7 +188,6 @@ async def test_run_with_errors(self, valid_instances): requests=requests, backend=backend, strategy=strategy, - startup_duration=0.1, env=env, max_number=MaxNumberConstraint(max_num=10), ): @@ -235,7 +232,6 @@ async def test_run_constraint_variations(self, valid_instances): requests=requests, backend=backend, strategy=strategy, - startup_duration=0.1, env=env, max_number=MaxNumberConstraint(max_num=5), max_duration=5.0, # Should be converted to constraint diff --git a/tests/unit/scheduler/test_strategies.py b/tests/unit/scheduler/test_strategies.py index 894b6bba..7f01fc89 100644 --- a/tests/unit/scheduler/test_strategies.py +++ b/tests/unit/scheduler/test_strategies.py @@ -187,8 +187,8 @@ class TestConcurrentStrategy: params=[ {"streams": 1}, {"streams": 4}, - {"streams": 8, "startup_duration": 2.0}, - {"streams": 2, "startup_duration": 0.0}, + {"streams": 8, "rampup_duration": 2.0}, + {"streams": 2, "rampup_duration": 0.0}, ] ) def valid_instances(self, request): @@ -212,7 +212,7 @@ def test_initialization(self, valid_instances: tuple[ConcurrentStrategy, dict]): [ ("streams", 0), ("streams", -1), - ("startup_duration", -1.0), + ("rampup_duration", -1.0), ], ) def test_invalid_initialization(self, field, value): @@ -288,8 +288,8 @@ class TestThroughputStrategy: params=[ {}, {"max_concurrency": 10}, - {"startup_duration": 5.0}, - {"max_concurrency": 5, "startup_duration": 2.0}, + {"rampup_duration": 5.0}, + {"max_concurrency": 5, "rampup_duration": 2.0}, ] ) def valid_instances(self, request): @@ -313,7 +313,7 @@ def test_initialization(self, valid_instances: tuple[ThroughputStrategy, dict]): [ ("max_concurrency", 0), ("max_concurrency", -1), - ("startup_duration", -1.0), + ("rampup_duration", -1.0), ], ) def test_invalid_initialization(self, field, value): diff --git a/tests/unit/scheduler/test_worker_group.py b/tests/unit/scheduler/test_worker_group.py index 8f54cf9c..b8fb3d6c 100644 --- a/tests/unit/scheduler/test_worker_group.py +++ b/tests/unit/scheduler/test_worker_group.py @@ -208,7 +208,9 @@ def test_initialization(self, valid_instances): # Core attributes assert isinstance(instance.backend, MockBackend) - assert instance.requests == constructor_args["requests"] + # requests is now an iterator, not a list + assert hasattr(instance.requests, "__iter__") + assert hasattr(instance.requests, "__next__") assert isinstance(instance.strategy, type(constructor_args["strategy"])) assert isinstance(instance.constraints, dict) @@ -233,9 +235,8 @@ def test_initialization(self, valid_instances): ("requests", "expected_error"), [ (None, TypeError), - ([], TypeError), ], - ids=["no_requests", "empty_requests"], + ids=["no_requests"], ) def test_invalid_initialization_values(self, requests, expected_error): """Test WorkerProcessGroup with invalid initialization values.""" diff --git a/tests/unit/presentation/__init__.py b/tests/unit/schemas/__init__.py similarity index 100% rename from tests/unit/presentation/__init__.py rename to tests/unit/schemas/__init__.py diff --git a/tests/unit/utils/test_pydantic_utils.py b/tests/unit/schemas/test_base.py similarity index 68% rename from tests/unit/utils/test_pydantic_utils.py rename to tests/unit/schemas/test_base.py index b1278f51..95217c21 100644 --- a/tests/unit/utils/test_pydantic_utils.py +++ b/tests/unit/schemas/test_base.py @@ -1,5 +1,5 @@ """ -Unit tests for the pydantic_utils module. +Unit tests for the base pydantic utilities module. """ from __future__ import annotations @@ -10,18 +10,16 @@ import pytest from pydantic import BaseModel, Field, ValidationError -from guidellm.utils import ( +from guidellm.schemas import ( + BaseModelT, + ErroredT, + IncompleteT, PydanticClassRegistryMixin, + RegisterClassT, ReloadableBaseModel, StandardBaseDict, StandardBaseModel, StatusBreakdown, -) -from guidellm.utils.pydantic_utils import ( - BaseModelT, - ErroredT, - IncompleteT, - RegisterClassT, SuccessfulT, TotalT, ) @@ -159,13 +157,37 @@ def test_reload_schema(self): class TestModel(ReloadableBaseModel): name: str - # Mock the model_rebuild method to simulate schema reload - with mock.patch.object(TestModel, "model_rebuild") as mock_rebuild: + # Test standard reload with mocked pathways + with ( + mock.patch.object(TestModel, "model_rebuild") as mock_rebuild, + mock.patch.object(TestModel, "reload_parent_schemas") as mock_parents, + ): TestModel.reload_schema() mock_rebuild.assert_called_once_with(force=True) + mock_parents.assert_called_once() + + # Test without parent reloading with mocked pathways + with ( + mock.patch.object(TestModel, "model_rebuild") as mock_rebuild, + mock.patch.object(TestModel, "reload_parent_schemas") as mock_parents, + ): + TestModel.reload_schema(parents=False) + mock_rebuild.assert_called_once_with(force=True) + mock_parents.assert_not_called() + + # Test parent reloading separately with mocked pathways + class ParentModel(ReloadableBaseModel): + child: TestModel + + with mock.patch.object(ParentModel, "model_rebuild") as mock_parent_rebuild: + TestModel.reload_parent_schemas() + # Schema rebuild may or may not be triggered depending on structure + assert mock_parent_rebuild.call_count >= 0 @pytest.mark.sanity - def test_marshalling(self, valid_instances): + def test_marshalling( + self, valid_instances: tuple[ReloadableBaseModel, dict[str, str]] + ): """Test ReloadableBaseModel serialization and deserialization.""" instance, constructor_args = valid_instances data_dict = instance.model_dump() @@ -176,6 +198,66 @@ def test_marshalling(self, valid_instances): assert isinstance(recreated, instance.__class__) assert recreated.name == constructor_args["name"] + @pytest.mark.sanity + def test_json_serialization(self, valid_instances): + """Test ReloadableBaseModel JSON serialization.""" + instance, constructor_args = valid_instances + json_data = instance.model_dump_json() + assert isinstance(json_data, str) + assert constructor_args["name"] in json_data + + recreated = instance.__class__.model_validate_json(json_data) + assert isinstance(recreated, instance.__class__) + assert recreated.name == constructor_args["name"] + + @pytest.mark.sanity + def test_extra_fields_ignored(self): + """Test that ReloadableBaseModel ignores extra fields.""" + + class TestModel(ReloadableBaseModel): + name: str + + # Extra fields should be ignored + instance = TestModel(name="test", extra_field="ignored") + assert instance.name == "test" + assert not hasattr(instance, "extra_field") + + @pytest.mark.sanity + def test_from_attributes(self): + """Test ReloadableBaseModel from_attributes configuration.""" + + class TestModel(ReloadableBaseModel): + name: str + value: int = 10 + + class SourceObject: + def __init__(self): + self.name = "test_name" + self.value = 42 + + source = SourceObject() + instance = TestModel.model_validate(source) + assert instance.name == "test_name" + assert instance.value == 42 + + @pytest.mark.sanity + def test_arbitrary_types_allowed(self): + """Test ReloadableBaseModel arbitrary_types_allowed configuration.""" + + class CustomType: + def __init__(self, val: str): + self.val = val + + class TestModel(ReloadableBaseModel): + name: str + custom: CustomType + + custom_obj = CustomType("test") + instance = TestModel(name="test", custom=custom_obj) + assert instance.name == "test" + assert isinstance(instance.custom, CustomType) + assert instance.custom.val == "test" + class TestStandardBaseModel: """Test suite for StandardBaseModel.""" @@ -258,27 +340,6 @@ class TestModel(StandardBaseModel): with pytest.raises(ValidationError): TestModel() # type: ignore[call-arg] - @pytest.mark.smoke - def test_get_default(self): - """Test StandardBaseModel.get_default method.""" - - class TestModel(StandardBaseModel): - field_str: str = Field(description="Test string field") - field_int: int = Field(default=42, description="Test integer field") - - default_value = TestModel.get_default("field_int") - assert default_value == 42 - - @pytest.mark.sanity - def test_get_default_invalid(self): - """Test StandardBaseModel.get_default with invalid field.""" - - class TestModel(StandardBaseModel): - field_str: str = Field(description="Test string field") - - with pytest.raises(KeyError): - TestModel.get_default("nonexistent_field") - @pytest.mark.sanity def test_marshalling(self, valid_instances): """Test StandardBaseModel serialization and deserialization.""" @@ -293,6 +354,50 @@ def test_marshalling(self, valid_instances): assert recreated.field_str == constructor_args["field_str"] assert recreated.field_int == constructor_args["field_int"] + @pytest.mark.sanity + def test_json_serialization(self, valid_instances): + """Test StandardBaseModel JSON serialization.""" + instance, constructor_args = valid_instances + json_data = instance.model_dump_json() + assert isinstance(json_data, str) + assert constructor_args["field_str"] in json_data + + recreated = instance.__class__.model_validate_json(json_data) + assert isinstance(recreated, instance.__class__) + assert recreated.field_str == constructor_args["field_str"] + assert recreated.field_int == constructor_args["field_int"] + + @pytest.mark.sanity + def test_extra_fields_ignored(self): + """Test that StandardBaseModel ignores extra fields.""" + + class TestModel(StandardBaseModel): + field_str: str = Field(description="Test string field") + field_int: int = Field(default=10, description="Test integer field") + + # Extra fields should be ignored + instance = TestModel(field_str="test", extra_field="ignored") + assert instance.field_str == "test" + assert not hasattr(instance, "extra_field") + + @pytest.mark.sanity + def test_from_attributes(self): + """Test StandardBaseModel from_attributes configuration.""" + + class TestModel(StandardBaseModel): + field_str: str = Field(description="Test string field") + field_int: int = Field(default=10, description="Test integer field") + + class SourceObject: + def __init__(self): + self.field_str = "test_value" + self.field_int = 99 + + source = SourceObject() + instance = TestModel.model_validate(source) + assert instance.field_str == "test_value" + assert instance.field_int == 99 + class TestStandardBaseDict: """Test suite for StandardBaseDict.""" @@ -395,6 +500,41 @@ def test_marshalling(self, valid_instances): assert hasattr(recreated, key) assert getattr(recreated, key) == value + @pytest.mark.sanity + def test_json_serialization(self, valid_instances): + """Test StandardBaseDict JSON serialization.""" + instance, constructor_args = valid_instances + json_data = instance.model_dump_json() + assert isinstance(json_data, str) + assert constructor_args["field_str"] in json_data + + recreated = instance.__class__.model_validate_json(json_data) + assert isinstance(recreated, instance.__class__) + assert recreated.field_str == constructor_args["field_str"] + + # Check extra fields are preserved after JSON deserialization + for key in constructor_args: + if key != "field_str": + assert hasattr(recreated, key) + + @pytest.mark.sanity + def test_arbitrary_types_allowed(self): + """Test StandardBaseDict arbitrary_types_allowed configuration.""" + + class CustomType: + def __init__(self, val: str): + self.val = val + + class TestModel(StandardBaseDict): + field_str: str = Field(description="Test string field") + + custom_obj = CustomType("test") + instance = TestModel(field_str="test", custom=custom_obj) + assert instance.field_str == "test" + assert hasattr(instance, "custom") + assert isinstance(instance.custom, CustomType) + assert instance.custom.val == "test" + class TestStatusBreakdown: """Test suite for StatusBreakdown.""" @@ -471,6 +611,22 @@ def test_marshalling(self, valid_instances): assert recreated.incomplete == constructor_args["incomplete"] assert recreated.total == constructor_args["total"] + @pytest.mark.sanity + def test_json_serialization(self): + """Test StatusBreakdown JSON serialization with various types.""" + instance: StatusBreakdown = StatusBreakdown( + successful=100, errored=5, incomplete=10, total=115 + ) + json_data = instance.model_dump_json() + assert isinstance(json_data, str) + assert "100" in json_data + + recreated = StatusBreakdown.model_validate_json(json_data) + assert recreated.successful == 100 + assert recreated.errored == 5 + assert recreated.incomplete == 10 + assert recreated.total == 115 + class TestPydanticClassRegistryMixin: """Test suite for PydanticClassRegistryMixin.""" @@ -517,6 +673,8 @@ def test_class_signatures(self): assert hasattr(PydanticClassRegistryMixin, "register_decorator") assert hasattr(PydanticClassRegistryMixin, "__get_pydantic_core_schema__") assert hasattr(PydanticClassRegistryMixin, "__pydantic_generate_base_schema__") + assert hasattr(PydanticClassRegistryMixin, "__pydantic_schema_base_type__") + assert hasattr(PydanticClassRegistryMixin, "__new__") assert hasattr(PydanticClassRegistryMixin, "auto_populate_registry") assert hasattr(PydanticClassRegistryMixin, "registered_classes") @@ -587,6 +745,25 @@ class TestSubModel(TestBaseModel): with pytest.raises(ValidationError): TestSubModel() # type: ignore[call-arg] + @pytest.mark.sanity + def test_base_class_direct_instantiation(self): + """Test that base class cannot be instantiated directly.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + with pytest.raises(TypeError) as exc_info: + TestBaseModel(test_type="test") + + assert "only children of" in str(exc_info.value) + @pytest.mark.smoke def test_register_decorator(self): """Test PydanticClassRegistryMixin.register_decorator method.""" @@ -610,6 +787,108 @@ class TestSubModel(TestBaseModel): assert "TestSubModel" in TestBaseModel.registry # type: ignore[misc] assert TestBaseModel.registry["TestSubModel"] is TestSubModel # type: ignore[misc] + @pytest.mark.smoke + def test_get_pydantic_core_schema(self): + """Test PydanticClassRegistryMixin.__get_pydantic_core_schema__ method.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + @TestBaseModel.register("test_sub") + class TestSubModel(TestBaseModel): + test_type: str = "test_sub" + value: str + + # Create a mock handler + mock_handler = mock.Mock() + mock_handler.return_value = {"type": "model"} + + # Test schema generation for base type + schema = TestBaseModel.__get_pydantic_core_schema__(TestBaseModel, mock_handler) + assert schema is not None + assert schema["type"] == "tagged-union" + + @pytest.mark.sanity + def test_get_pydantic_core_schema_no_registry(self): + """Test __get_pydantic_core_schema__ without registry.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + # Ensure registry is empty + TestBaseModel.registry = None + + # Create a mock handler + mock_handler = mock.Mock() + + # Test schema generation without registry + with mock.patch.object( + TestBaseModel, "__pydantic_generate_base_schema__" + ) as mock_base: + TestBaseModel.__get_pydantic_core_schema__(TestBaseModel, mock_handler) + mock_base.assert_called_once_with(mock_handler) + + @pytest.mark.sanity + def test_get_pydantic_core_schema_subclass(self): + """Test __get_pydantic_core_schema__ for a subclass.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + @TestBaseModel.register("test_sub") + class TestSubModel(TestBaseModel): + test_type: str = "test_sub" + value: str + + # Create a mock handler + mock_handler = mock.Mock() + mock_handler.return_value = {"type": "model"} + + # Test schema generation for subclass (not base type) + schema = TestBaseModel.__get_pydantic_core_schema__(TestSubModel, mock_handler) + assert schema is not None + mock_handler.assert_called_once_with(TestBaseModel) + + @pytest.mark.smoke + def test_pydantic_generate_base_schema(self): + """Test PydanticClassRegistryMixin.__pydantic_generate_base_schema__.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + mock_handler = mock.Mock() + schema = TestBaseModel.__pydantic_generate_base_schema__(mock_handler) + assert schema is not None + assert schema["type"] == "any" + @pytest.mark.sanity def test_register_decorator_with_name(self): """Test PydanticClassRegistryMixin.register_decorator with custom name.""" @@ -633,6 +912,31 @@ class TestSubModel(TestBaseModel): assert "custom_name" in TestBaseModel.registry # type: ignore[misc] assert TestBaseModel.registry["custom_name"] is TestSubModel # type: ignore[misc] + @pytest.mark.sanity + def test_register_decorator_with_multiple_names(self): + """Test PydanticClassRegistryMixin.register_decorator with list of names.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + @TestBaseModel.register(["name_one", "name_two"]) + class TestSubModel(TestBaseModel): + test_type: str = "name_one" + value: str + + assert TestBaseModel.registry is not None # type: ignore[misc] + assert "name_one" in TestBaseModel.registry # type: ignore[misc] + assert "name_two" in TestBaseModel.registry # type: ignore[misc] + assert TestBaseModel.registry["name_one"] is TestSubModel # type: ignore[misc] + assert TestBaseModel.registry["name_two"] is TestSubModel # type: ignore[misc] + @pytest.mark.sanity def test_register_decorator_invalid_type(self): """Test PydanticClassRegistryMixin.register_decorator with invalid type.""" @@ -675,11 +979,38 @@ def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: mock.patch( "guidellm.utils.registry.RegistryMixin.auto_populate_registry", return_value=True, - ), + ) as mock_parent_auto, ): result = TestBaseModel.auto_populate_registry() assert result is True mock_reload.assert_called_once() + mock_parent_auto.assert_called_once() + + @pytest.mark.sanity + def test_auto_populate_registry_already_populated(self): + """Test auto_populate_registry when already populated.""" + + class TestBaseModel(PydanticClassRegistryMixin): + schema_discriminator: ClassVar[str] = "test_type" + test_type: str + registry_auto_discovery: ClassVar[bool] = True + + @classmethod + def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: + if cls.__name__ == "TestBaseModel": + return cls + return TestBaseModel + + with ( + mock.patch.object(TestBaseModel, "reload_schema") as mock_reload, + mock.patch( + "guidellm.utils.registry.RegistryMixin.auto_populate_registry", + return_value=False, + ), + ): + result = TestBaseModel.auto_populate_registry() + assert result is False + mock_reload.assert_called_once() @pytest.mark.smoke def test_registered_classes(self): @@ -850,153 +1181,33 @@ class ContainerModel(BaseModel): assert isinstance(recreated.models[0], TestSubModelA) assert isinstance(recreated.models[1], TestSubModelB) - @pytest.mark.smoke - def test_register_preserves_pydantic_metadata(self): # noqa: C901 - """Test that registered Pydantic classes retain docs, types, and methods.""" + @pytest.mark.regression + def test_json_serialization(self): + """Test PydanticClassRegistryMixin JSON serialization.""" class TestBaseModel(PydanticClassRegistryMixin): - schema_discriminator: ClassVar[str] = "model_type" - model_type: str + schema_discriminator: ClassVar[str] = "test_type" + test_type: str @classmethod def __pydantic_schema_base_type__(cls) -> type[TestBaseModel]: if cls.__name__ == "TestBaseModel": return cls - return TestBaseModel - @TestBaseModel.register("documented_model") - class DocumentedModel(TestBaseModel): - """This is a documented Pydantic model with methods and type hints.""" - - model_type: str = "documented_model" - value: int = Field(description="An integer value for the model") - - def get_value(self) -> int: - """Get the stored value. - - :return: The stored integer value - """ - return self.value - - def set_value(self, new_value: int) -> None: - """Set a new value. - - :param new_value: The new integer value to set - """ - self.value = new_value - - @classmethod - def from_string(cls, value_str: str) -> DocumentedModel: - """Create instance from string. - - :param value_str: String representation of value - :return: New DocumentedModel instance - """ - return cls(value=int(value_str)) - - @staticmethod - def validate_value(value: int) -> bool: - """Validate that a value is positive. - - :param value: Value to validate - :return: True if positive, False otherwise - """ - return value > 0 - - def model_post_init(self, __context) -> None: - """Post-initialization processing. - - :param __context: Validation context - """ - if self.value < 0: - raise ValueError("Value must be non-negative") - - # Check that the class was registered - assert TestBaseModel.is_registered("documented_model") - registered_class = TestBaseModel.get_registered_object("documented_model") - assert registered_class is DocumentedModel - - # Check that the class retains its documentation - assert registered_class.__doc__ is not None - assert "documented Pydantic model with methods" in registered_class.__doc__ - - # Check that methods retain their documentation - assert registered_class.get_value.__doc__ is not None - assert "Get the stored value" in registered_class.get_value.__doc__ - assert registered_class.set_value.__doc__ is not None - assert "Set a new value" in registered_class.set_value.__doc__ - assert registered_class.from_string.__doc__ is not None - assert "Create instance from string" in registered_class.from_string.__doc__ - assert registered_class.validate_value.__doc__ is not None - assert ( - "Validate that a value is positive" - in registered_class.validate_value.__doc__ - ) - assert registered_class.model_post_init.__doc__ is not None - assert ( - "Post-initialization processing" in registered_class.model_post_init.__doc__ - ) - - # Check that methods are callable and work correctly - instance = DocumentedModel(value=42) - assert isinstance(instance, DocumentedModel) - assert instance.get_value() == 42 - instance.set_value(100) - assert instance.get_value() == 100 - assert instance.model_type == "documented_model" - - # Check class methods work - instance2 = DocumentedModel.from_string("123") - assert instance2.get_value() == 123 - assert instance2.model_type == "documented_model" - - # Check static methods work - assert DocumentedModel.validate_value(10) is True - assert DocumentedModel.validate_value(-5) is False - - # Check that Pydantic functionality is preserved - data_dict = instance.model_dump() - assert data_dict["value"] == 100 - assert data_dict["model_type"] == "documented_model" - - recreated = DocumentedModel.model_validate(data_dict) - assert isinstance(recreated, DocumentedModel) - assert recreated.value == 100 - assert recreated.model_type == "documented_model" + @TestBaseModel.register("test_sub") + class TestSubModel(TestBaseModel): + test_type: str = "test_sub" + value: str - # Test field validation - with pytest.raises(ValidationError): - DocumentedModel(value="not_an_int") - - # Test post_init validation - with pytest.raises(ValueError, match="Value must be non-negative"): - DocumentedModel(value=-10) - - # Check that Pydantic field metadata is preserved - value_field = DocumentedModel.model_fields["value"] - assert value_field.description == "An integer value for the model" - - # Check that type annotations are preserved (if accessible) - import inspect - - if hasattr(inspect, "get_annotations"): - # Python 3.10+ - try: - annotations = inspect.get_annotations(DocumentedModel.get_value) - return_ann = annotations.get("return") - assert return_ann is int or return_ann == "int" - except (AttributeError, NameError): - # Fallback for older Python or missing annotations - pass - - # Check that the class name is preserved - assert DocumentedModel.__name__ == "DocumentedModel" - assert DocumentedModel.__qualname__.endswith("DocumentedModel") - - # Verify that the class is still properly integrated with the registry system - all_registered = TestBaseModel.registered_classes() - assert DocumentedModel in all_registered - - # Test that the registered class is the same as the original - assert registered_class is DocumentedModel + instance = TestSubModel(value="test_value") + json_data = instance.model_dump_json() + assert isinstance(json_data, str) + assert "test_sub" in json_data + assert "test_value" in json_data + + # Deserialize through base class + recreated = TestBaseModel.model_validate_json(json_data) # type: ignore[assignment] + assert isinstance(recreated, TestSubModel) + assert recreated.test_type == "test_sub" + assert recreated.value == "test_value" diff --git a/tests/unit/schemas/test_info.py b/tests/unit/schemas/test_info.py new file mode 100644 index 00000000..91d9258a --- /dev/null +++ b/tests/unit/schemas/test_info.py @@ -0,0 +1,464 @@ +""" +Unit tests for RequestInfo and RequestTimings. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from guidellm.schemas import ( + RequestInfo, + RequestTimings, + StandardBaseDict, + StandardBaseModel, +) + + +class TestRequestTimings: + """Test cases for RequestTimings model.""" + + @pytest.fixture( + params=[ + {}, + {"targeted_start": 1234567880.0}, + {"queued": 1234567885.0, "dequeued": 1234567886.0}, + {"scheduled_at": 1234567887.0, "resolve_start": 1234567888.0}, + { + "request_start": 1234567890.0, + "first_request_iteration": 1234567891.0, + "first_token_iteration": 1234567892.0, + }, + { + "last_token_iteration": 1234567895.0, + "last_request_iteration": 1234567896.0, + }, + { + "request_end": 1234567900.0, + "resolve_end": 1234567905.0, + "finalized": 1234567910.0, + }, + { + "request_iterations": 5, + "token_iterations": 10, + }, + { + "queued": 1234567885.0, + "scheduled_at": 1234567887.0, + "request_start": 1234567890.0, + "request_end": 1234567900.0, + "request_iterations": 3, + "token_iterations": 7, + }, + ], + ids=[ + "empty", + "targeted_start", + "queue_lifecycle", + "scheduling", + "request_start_tokens", + "request_end_tokens", + "completion_lifecycle", + "iteration_counts", + "full_lifecycle", + ], + ) + def valid_instances(self, request): + """Fixture providing valid RequestTimings instances.""" + constructor_args = request.param + instance = RequestTimings(**constructor_args) + return instance, constructor_args + + @pytest.mark.smoke + def test_class_signatures(self): + """Test RequestTimings inheritance and type relationships.""" + assert issubclass(RequestTimings, StandardBaseDict) + assert hasattr(RequestTimings, "model_dump") + assert hasattr(RequestTimings, "model_validate") + + # Check fields are defined + fields = RequestTimings.model_fields + expected_fields = [ + "targeted_start", + "queued", + "dequeued", + "scheduled_at", + "resolve_start", + "request_start", + "first_request_iteration", + "first_token_iteration", + "last_token_iteration", + "last_request_iteration", + "request_iterations", + "token_iterations", + "request_end", + "resolve_end", + "finalized", + ] + for field in expected_fields: + assert field in fields + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test RequestTimings initialization.""" + instance, constructor_args = valid_instances + assert isinstance(instance, RequestTimings) + + # Check field values + for key, expected_value in constructor_args.items(): + assert getattr(instance, key) == expected_value + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("field", "value"), + [ + ("targeted_start", "not_float"), + ("queued", "not_float"), + ("dequeued", [123]), + ("scheduled_at", {}), + ("resolve_start", "invalid"), + ("request_start", "invalid"), + ("first_request_iteration", "not_float"), + ("first_token_iteration", "invalid"), + ("last_token_iteration", []), + ("last_request_iteration", "not_float"), + ("request_iterations", "not_int"), + ("request_iterations", 3.14), + ("token_iterations", "not_int"), + ("token_iterations", 2.5), + ("request_end", []), + ("resolve_end", {}), + ("finalized", "invalid"), + ], + ) + def test_invalid_initialization_values(self, field, value): + """Test RequestTimings with invalid field values.""" + with pytest.raises(ValidationError): + RequestTimings(**{field: value}) + + @pytest.mark.smoke + def test_last_reported_property(self): + """Test RequestTimings.last_reported property.""" + timings = RequestTimings() + assert timings.last_reported is None + + # Set timing values and verify last_reported updates + timings.queued = 1234567890.0 + assert timings.last_reported == 1234567890.0 + + timings.dequeued = 1234567891.0 + assert timings.last_reported == 1234567891.0 + + timings.scheduled_at = 1234567895.0 + assert timings.last_reported == 1234567895.0 + + timings.resolve_start = 1234567896.0 + assert timings.last_reported == 1234567896.0 + + timings.request_start = 1234567897.0 + assert timings.last_reported == 1234567897.0 + + timings.request_end = 1234567900.0 + assert timings.last_reported == 1234567900.0 + + timings.resolve_end = 1234567905.0 + assert timings.last_reported == 1234567905.0 + + # Fields not included in last_reported should not affect it + timings.targeted_start = 1234567999.0 + assert timings.last_reported == 1234567905.0 + + timings.finalized = 1234568000.0 + assert timings.last_reported == 1234567905.0 + + timings.first_request_iteration = 1234568001.0 + assert timings.last_reported == 1234567905.0 + + @pytest.mark.sanity + def test_marshalling(self, valid_instances): + """Test RequestTimings serialization and deserialization.""" + instance, constructor_args = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + + # Verify all expected fields are in the dict + expected_fields = [ + "targeted_start", + "queued", + "dequeued", + "scheduled_at", + "resolve_start", + "request_start", + "first_request_iteration", + "first_token_iteration", + "last_token_iteration", + "last_request_iteration", + "request_iterations", + "token_iterations", + "request_end", + "resolve_end", + "finalized", + ] + for field in expected_fields: + assert field in data_dict + + # Test reconstruction + reconstructed = RequestTimings.model_validate(data_dict) + for key, expected_value in constructor_args.items(): + assert getattr(reconstructed, key) == expected_value + + # Verify fields not in constructor_args have correct defaults + for field in expected_fields: + if field not in constructor_args: + value = getattr(reconstructed, field) + if field in ["request_iterations", "token_iterations"]: + assert value == 0 + else: + assert value is None + + +class TestRequestInfo: + """Test cases for RequestInfo model.""" + + @pytest.fixture( + params=[ + {}, + {"request_id": "test-123", "status": "queued"}, + { + "request_id": "test-456", + "status": "completed", + "scheduler_node_id": 1, + }, + { + "status": "in_progress", + "scheduler_node_id": 2, + "scheduler_process_id": 3, + }, + { + "status": "errored", + "error": "Test error message", + }, + { + "request_id": "test-789", + "status": "pending", + "scheduler_node_id": 0, + "scheduler_process_id": 1, + "scheduler_start_time": 1234567890.0, + }, + ], + ids=[ + "empty", + "basic", + "with_node_id", + "with_process_ids", + "with_error", + "full_fields", + ], + ) + def valid_instances(self, request): + """Fixture providing valid RequestInfo instances.""" + constructor_args = request.param + instance = RequestInfo(**constructor_args) + return instance, constructor_args + + @pytest.mark.smoke + def test_class_signatures(self): + """Test RequestInfo inheritance and type relationships.""" + assert issubclass(RequestInfo, StandardBaseModel) + assert hasattr(RequestInfo, "model_dump") + assert hasattr(RequestInfo, "model_validate") + assert hasattr(RequestInfo, "model_copy") + + # Check fields + fields = RequestInfo.model_fields + expected_fields = [ + "request_id", + "status", + "scheduler_node_id", + "scheduler_process_id", + "scheduler_start_time", + "timings", + "error", + ] + for field in expected_fields: + assert field in fields + + # Check computed properties + assert hasattr(RequestInfo, "started_at") + assert hasattr(RequestInfo, "completed_at") + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test RequestInfo initialization.""" + instance, constructor_args = valid_instances + assert isinstance(instance, RequestInfo) + + # Check field values + for key, expected_value in constructor_args.items(): + assert getattr(instance, key) == expected_value + + # Check defaults + if "request_id" not in constructor_args: + assert isinstance(instance.request_id, str) + assert len(instance.request_id) > 0 + if "status" not in constructor_args: + assert instance.status == "queued" + if "scheduler_node_id" not in constructor_args: + assert instance.scheduler_node_id == -1 + if "scheduler_process_id" not in constructor_args: + assert instance.scheduler_process_id == -1 + if "scheduler_start_time" not in constructor_args: + assert instance.scheduler_start_time == -1 + if "timings" not in constructor_args: + assert isinstance(instance.timings, RequestTimings) + if "error" not in constructor_args: + assert instance.error is None + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("field", "value"), + [ + ("status", "invalid_status"), + ("status", 123), + ("status", None), + ("scheduler_node_id", "not_int"), + ("scheduler_node_id", 3.14), + ("scheduler_process_id", "not_int"), + ("scheduler_process_id", []), + ("scheduler_start_time", "not_float"), + ("scheduler_start_time", {}), + ("timings", "not_timings"), + ("timings", 123), + ("timings", []), + ], + ) + def test_invalid_initialization_values(self, field, value): + """Test RequestInfo with invalid field values.""" + with pytest.raises(ValidationError): + RequestInfo(**{field: value}) + + @pytest.mark.smoke + def test_auto_id_generation(self): + """Test that request_id is auto-generated if not provided.""" + import uuid + + info1 = RequestInfo() + info2 = RequestInfo() + + assert info1.request_id != info2.request_id + assert len(info1.request_id) > 0 + assert len(info2.request_id) > 0 + + # Should be valid UUIDs + uuid.UUID(info1.request_id) + uuid.UUID(info2.request_id) + + @pytest.mark.smoke + def test_status_values(self): + """Test RequestInfo with different status values.""" + valid_statuses = [ + "queued", + "pending", + "in_progress", + "completed", + "errored", + "cancelled", + ] + + for status in valid_statuses: + info = RequestInfo(status=status) + assert info.status == status + + @pytest.mark.smoke + def test_started_at_property(self): + """Test RequestInfo.started_at computed property.""" + info = RequestInfo() + assert info.started_at is None + + # Set request_start + info.timings.request_start = 1234567890.0 + assert info.started_at == 1234567890.0 + + # Set resolve_start (should be used if request_start is None) + info2 = RequestInfo() + info2.timings.resolve_start = 1234567895.0 + assert info2.started_at == 1234567895.0 + + # Both set - should prefer request_start + info3 = RequestInfo() + info3.timings.resolve_start = 1234567895.0 + info3.timings.request_start = 1234567900.0 + assert info3.started_at == 1234567900.0 + + @pytest.mark.smoke + def test_completed_at_property(self): + """Test RequestInfo.completed_at computed property.""" + info = RequestInfo() + assert info.completed_at is None + + # Set request_end + info.timings.request_end = 1234567900.0 + assert info.completed_at == 1234567900.0 + + # Set resolve_end (should be used if request_end is None) + info2 = RequestInfo() + info2.timings.resolve_end = 1234567905.0 + assert info2.completed_at == 1234567905.0 + + # Both set - should prefer request_end + info3 = RequestInfo() + info3.timings.resolve_end = 1234567905.0 + info3.timings.request_end = 1234567910.0 + assert info3.completed_at == 1234567910.0 + + @pytest.mark.smoke + def test_model_copy(self): + """Test RequestInfo.model_copy creates independent copies.""" + info = RequestInfo(request_id="test-123") + info.timings.request_start = 1234567890.0 + + # Create copy + copied = info.model_copy() + + # Verify it's a different instance + assert copied is not info + assert copied.timings is not info.timings + + # Verify values are the same + assert copied.request_id == info.request_id + assert copied.timings.request_start == info.timings.request_start + + # Modify original and verify copy is independent + info.timings.request_end = 1234567900.0 + assert copied.timings.request_end is None + + @pytest.mark.sanity + def test_marshalling(self, valid_instances): + """Test RequestInfo serialization and deserialization.""" + instance, constructor_args = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + + # Verify all expected fields are in the dict + expected_fields = [ + "request_id", + "status", + "scheduler_node_id", + "scheduler_process_id", + "scheduler_start_time", + "timings", + "error", + ] + for field in expected_fields: + assert field in data_dict + + # Verify timings is a dict + assert isinstance(data_dict["timings"], dict) + + # Test reconstruction + reconstructed = RequestInfo.model_validate(data_dict) + assert isinstance(reconstructed, RequestInfo) + assert isinstance(reconstructed.timings, RequestTimings) + + for key, expected_value in constructor_args.items(): + assert getattr(reconstructed, key) == expected_value diff --git a/tests/unit/schemas/test_request.py b/tests/unit/schemas/test_request.py new file mode 100644 index 00000000..91fb979b --- /dev/null +++ b/tests/unit/schemas/test_request.py @@ -0,0 +1,713 @@ +""" +Unit tests for GenerationRequest, GenerationRequestArguments, and UsageMetrics. +""" + +from __future__ import annotations + +import typing +import uuid + +import pytest +from pydantic import ValidationError + +from guidellm.schemas import ( + GenerationRequest, + GenerationRequestArguments, + StandardBaseDict, + StandardBaseModel, + UsageMetrics, +) +from guidellm.schemas.request import GenerativeRequestType + + +@pytest.mark.smoke +def test_generative_request_type(): + """Test that GenerativeRequestType is defined correctly.""" + assert hasattr(typing, "get_args") + args = typing.get_args(GenerativeRequestType) + assert len(args) == 4 + assert "text_completions" in args + assert "chat_completions" in args + assert "audio_transcriptions" in args + assert "audio_translations" in args + + +class TestGenerationRequestArguments: + """Test cases for GenerationRequestArguments model.""" + + @pytest.fixture( + params=[ + {}, + {"method": "POST", "body": {"prompt": "test"}}, + { + "method": "GET", + "headers": {"Authorization": "Bearer token"}, + "params": {"key": "value"}, + }, + { + "method": "POST", + "stream": True, + "headers": {"Content-Type": "application/json"}, + "params": {"limit": 10}, + "body": {"prompt": "hello"}, + "files": {"file": "data.txt"}, + }, + ], + ids=["empty", "method_body", "method_headers_params", "all_fields"], + ) + def valid_instances(self, request): + """Fixture providing valid GenerationRequestArguments instances.""" + constructor_args = request.param + instance = GenerationRequestArguments(**constructor_args) + return instance, constructor_args + + @pytest.mark.smoke + def test_class_signatures(self): + """Test GenerationRequestArguments inheritance and type relationships.""" + assert issubclass(GenerationRequestArguments, StandardBaseDict) + assert hasattr(GenerationRequestArguments, "model_dump") + assert hasattr(GenerationRequestArguments, "model_validate") + assert hasattr(GenerationRequestArguments, "model_combine") + + # Check fields + fields = GenerationRequestArguments.model_fields + expected_fields = ["method", "stream", "headers", "params", "body", "files"] + for field in expected_fields: + assert field in fields + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test GenerationRequestArguments initialization.""" + instance, constructor_args = valid_instances + assert isinstance(instance, GenerationRequestArguments) + + # Check field values + for key, expected_value in constructor_args.items(): + assert getattr(instance, key) == expected_value + + # Check defaults for fields not provided + for field in ["method", "stream", "headers", "params", "body", "files"]: + if field not in constructor_args: + assert getattr(instance, field) is None + + @pytest.mark.sanity + def test_invalid_initialization_values(self): + """Test GenerationRequestArguments with invalid field values.""" + # Invalid method type + with pytest.raises(ValidationError): + GenerationRequestArguments(method=123) + + # Invalid stream type + with pytest.raises(ValidationError): + GenerationRequestArguments(stream="not_bool") + + # Invalid headers type + with pytest.raises(ValidationError): + GenerationRequestArguments(headers="not_dict") + + # Invalid params type + with pytest.raises(ValidationError): + GenerationRequestArguments(params="not_dict") + + # Invalid body type + with pytest.raises(ValidationError): + GenerationRequestArguments(body="not_dict") + + @pytest.mark.sanity + def test_invalid_initialization_missing(self): + """Test GenerationRequestArguments initialization without any fields.""" + # Should succeed since all fields are optional + instance = GenerationRequestArguments() + assert isinstance(instance, GenerationRequestArguments) + assert instance.method is None + assert instance.stream is None + assert instance.headers is None + assert instance.params is None + assert instance.body is None + assert instance.files is None + + @pytest.mark.smoke + @pytest.mark.parametrize( + ( + "base_kwargs", + "additional_kwargs", + "expected_method", + "expected_headers", + "expected_body", + ), + [ + ( + { + "method": "POST", + "headers": {"Auth": "token1"}, + "body": {"key1": "val1"}, + }, + { + "method": "GET", + "headers": {"Type": "json"}, + "body": {"key2": "val2"}, + }, + "GET", + {"Auth": "token1", "Type": "json"}, + {"key1": "val1", "key2": "val2"}, + ), + ( + {"method": "POST"}, + {"stream": True, "headers": {"Auth": "token"}}, + "POST", + {"Auth": "token"}, + None, + ), + ( + {"params": {"page": 1}, "files": {"file1": "data"}}, + {"params": {"limit": 10}, "files": {"file2": "more"}}, + None, + None, + None, + ), + ], + ids=["overwrite_and_merge", "partial_merge", "params_and_files"], + ) + def test_model_combine( + self, + base_kwargs, + additional_kwargs, + expected_method, + expected_headers, + expected_body, + ): + """Test GenerationRequestArguments.model_combine method.""" + base_args = GenerationRequestArguments(**base_kwargs) + additional_args = GenerationRequestArguments(**additional_kwargs) + + # Combine args + result = base_args.model_combine(additional_args) + + # Check method and stream (overwrite behavior) + if expected_method is not None: + assert result.method == expected_method + elif "method" in base_kwargs: + assert result.method == base_kwargs["method"] + + if "stream" in additional_kwargs: + assert result.stream == additional_kwargs["stream"] + elif "stream" in base_kwargs: + assert result.stream == base_kwargs.get("stream") + + # Check headers (merge behavior) + if expected_headers is not None: + assert result.headers == expected_headers + + # Check body (merge behavior) + if expected_body is not None: + assert result.body == expected_body + + # Check params merge + if "params" in base_kwargs and "params" in additional_kwargs: + expected_params = {**base_kwargs["params"], **additional_kwargs["params"]} + assert result.params == expected_params + + # Check files merge + if "files" in base_kwargs and "files" in additional_kwargs: + expected_files = {**base_kwargs["files"], **additional_kwargs["files"]} + assert result.files == expected_files + + @pytest.mark.smoke + def test_model_combine_with_dict(self): + """Test GenerationRequestArguments.model_combine with dict input.""" + base_args = GenerationRequestArguments( + method="POST", + headers={"Authorization": "Bearer token1"}, + ) + + additional_dict = { + "method": "GET", + "headers": {"Content-Type": "application/json"}, + } + + # Combine with dict + result = base_args.model_combine(additional_dict) + + # Method should be overwritten + assert result.method == "GET" + + # Headers should be merged + assert result.headers == { + "Authorization": "Bearer token1", + "Content-Type": "application/json", + } + + @pytest.mark.sanity + def test_marshalling(self, valid_instances): + """Test GenerationRequestArguments serialization and deserialization.""" + instance, constructor_args = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + + # Test reconstruction + reconstructed = GenerationRequestArguments.model_validate(data_dict) + for key, expected_value in constructor_args.items(): + assert getattr(reconstructed, key) == expected_value + + +class TestUsageMetrics: + """Test cases for UsageMetrics model.""" + + @pytest.fixture( + params=[ + {}, + {"text_tokens": 100, "text_words": 50}, + {"image_tokens": 200, "image_count": 5, "image_pixels": 1024}, + {"audio_tokens": 150, "audio_seconds": 30.5}, + { + "video_tokens": 75, + "video_frames": 300, + "video_seconds": 10.0, + "video_bytes": 5000000, + }, + { + "text_tokens": 100, + "image_tokens": 50, + "video_tokens": 25, + "audio_tokens": 25, + }, + ], + ids=[ + "empty", + "text_metrics", + "image_metrics", + "audio_metrics", + "video_metrics", + "all_token_types", + ], + ) + def valid_instances(self, request): + """Fixture providing valid UsageMetrics instances.""" + constructor_args = request.param + instance = UsageMetrics(**constructor_args) + return instance, constructor_args + + @pytest.mark.smoke + def test_class_signatures(self): + """Test UsageMetrics inheritance and type relationships.""" + assert issubclass(UsageMetrics, StandardBaseDict) + assert hasattr(UsageMetrics, "model_dump") + assert hasattr(UsageMetrics, "model_validate") + assert hasattr(UsageMetrics, "add_text_metrics") + + # Check fields + fields = UsageMetrics.model_fields + expected_fields = [ + "text_tokens", + "text_words", + "text_characters", + "image_tokens", + "image_count", + "image_pixels", + "image_bytes", + "video_tokens", + "video_frames", + "video_seconds", + "video_bytes", + "audio_tokens", + "audio_samples", + "audio_seconds", + "audio_bytes", + ] + for field in expected_fields: + assert field in fields + + # Check computed property + assert hasattr(UsageMetrics, "total_tokens") + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test UsageMetrics initialization.""" + instance, constructor_args = valid_instances + assert isinstance(instance, UsageMetrics) + + # Check field values + for key, expected_value in constructor_args.items(): + assert getattr(instance, key) == expected_value + + # Check defaults for fields not provided + all_fields = [ + "text_tokens", + "text_words", + "text_characters", + "image_tokens", + "image_count", + "image_pixels", + "image_bytes", + "video_tokens", + "video_frames", + "video_seconds", + "video_bytes", + "audio_tokens", + "audio_samples", + "audio_seconds", + "audio_bytes", + ] + for field in all_fields: + if field not in constructor_args: + assert getattr(instance, field) is None + + @pytest.mark.sanity + def test_invalid_initialization_values(self): + """Test UsageMetrics with invalid field values.""" + # Invalid token count type + with pytest.raises(ValidationError): + UsageMetrics(text_tokens="not_int") + + # Invalid seconds type + with pytest.raises(ValidationError): + UsageMetrics(audio_seconds="not_float") + + # Invalid image count type + with pytest.raises(ValidationError): + UsageMetrics(image_count=1.5) + + # Invalid video frames type + with pytest.raises(ValidationError): + UsageMetrics(video_frames="invalid") + + @pytest.mark.sanity + def test_invalid_initialization_missing(self): + """Test UsageMetrics initialization without any fields.""" + # Should succeed since all fields are optional + metrics = UsageMetrics() + assert isinstance(metrics, UsageMetrics) + assert metrics.text_tokens is None + assert metrics.image_tokens is None + assert metrics.video_tokens is None + assert metrics.audio_tokens is None + + @pytest.mark.smoke + def test_optional_fields(self): + """Test that all usage metric fields are optional.""" + # Should be able to create with no fields + metrics = UsageMetrics() + assert metrics.text_tokens is None + assert metrics.image_tokens is None + assert metrics.audio_tokens is None + assert metrics.video_tokens is None + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("metrics_kwargs", "expected_total"), + [ + ({}, None), + ({"text_tokens": 100}, 100), + ({"text_tokens": 100, "image_tokens": 50}, 150), + ( + { + "text_tokens": 100, + "image_tokens": 50, + "video_tokens": 25, + "audio_tokens": 25, + }, + 200, + ), + ({"image_tokens": 50, "video_tokens": 25}, 75), + ], + ids=[ + "no_tokens", + "text_only", + "text_and_image", + "all_modalities", + "image_and_video", + ], + ) + def test_total_tokens_property(self, metrics_kwargs, expected_total): + """Test UsageMetrics.total_tokens computed property.""" + metrics = UsageMetrics(**metrics_kwargs) + assert metrics.total_tokens == expected_total + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("initial_chars", "initial_words", "text", "expected_chars", "expected_words"), + [ + (None, None, "Hello world", 11, 2), + (0, 0, "Hello world", 11, 2), + (11, 2, "Test message", 23, 4), + (0, 0, "One", 3, 1), + (10, 5, "", 10, 5), + ], + ids=[ + "first_text", + "from_zero", + "accumulate", + "single_word", + "empty_string", + ], + ) + def test_add_text_metrics( + self, + initial_chars, + initial_words, + text, + expected_chars, + expected_words, + ): + """Test UsageMetrics.add_text_metrics method.""" + metrics = UsageMetrics( + text_characters=initial_chars, + text_words=initial_words, + ) + metrics.add_text_metrics(text) + assert metrics.text_characters == expected_chars + assert metrics.text_words == expected_words + + @pytest.mark.sanity + def test_marshalling(self, valid_instances): + """Test UsageMetrics serialization and deserialization.""" + instance, constructor_args = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + + # Test reconstruction + reconstructed = UsageMetrics.model_validate(data_dict) + for key, expected_value in constructor_args.items(): + assert getattr(reconstructed, key) == expected_value + + +class TestGenerationRequest: + """Test cases for GenerationRequest model.""" + + @pytest.fixture( + params=[ + { + "request_type": "text_completions", + "arguments": GenerationRequestArguments(), + }, + { + "request_type": "chat_completions", + "arguments": GenerationRequestArguments(body={"temperature": 0.7}), + }, + { + "request_id": "custom-id", + "request_type": "text_completions", + "arguments": GenerationRequestArguments(body={"prompt": "test"}), + }, + { + "request_type": "audio_transcriptions", + "arguments": GenerationRequestArguments( + method="POST", + files={"file": "audio.mp3"}, + ), + "input_metrics": UsageMetrics(audio_seconds=30.0), + "output_metrics": UsageMetrics(text_tokens=100), + }, + ], + ids=[ + "minimal", + "with_body", + "custom_id", + "with_metrics", + ], + ) + def valid_instances(self, request): + """Fixture providing valid GenerationRequest instances.""" + constructor_args = request.param + instance = GenerationRequest(**constructor_args) + return instance, constructor_args + + @pytest.mark.smoke + def test_class_signatures(self): + """Test GenerationRequest inheritance and type relationships.""" + assert issubclass(GenerationRequest, StandardBaseModel) + assert hasattr(GenerationRequest, "model_dump") + assert hasattr(GenerationRequest, "model_validate") + + # Check all expected fields are defined + fields = GenerationRequest.model_fields + expected_fields = [ + "request_id", + "request_type", + "arguments", + "input_metrics", + "output_metrics", + ] + for field in expected_fields: + assert field in fields + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Test GenerationRequest initialization.""" + instance, constructor_args = valid_instances + assert isinstance(instance, GenerationRequest) + assert instance.arguments == constructor_args["arguments"] + + # Check request_type + expected_request_type = constructor_args.get("request_type", "text_completions") + assert instance.request_type == expected_request_type + + # Check request_id + if "request_id" in constructor_args: + assert instance.request_id == constructor_args["request_id"] + else: + assert isinstance(instance.request_id, str) + # Should be valid UUID + uuid.UUID(instance.request_id) + + # Check metrics defaults + if "input_metrics" in constructor_args: + assert instance.input_metrics == constructor_args["input_metrics"] + else: + assert isinstance(instance.input_metrics, UsageMetrics) + + if "output_metrics" in constructor_args: + assert instance.output_metrics == constructor_args["output_metrics"] + else: + assert isinstance(instance.output_metrics, UsageMetrics) + + @pytest.mark.sanity + def test_invalid_initialization_values(self): + """Test GenerationRequest with invalid field values.""" + # Invalid request_type (not a string) + with pytest.raises(ValidationError): + GenerationRequest( + request_type=123, + arguments=GenerationRequestArguments(), + ) + + # Invalid arguments type + with pytest.raises(ValidationError): + GenerationRequest( + request_type="text_completions", + arguments="not_a_dict", + ) + + # Invalid input_metrics type + with pytest.raises(ValidationError): + GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(), + input_metrics="invalid", + ) + + # Invalid output_metrics type + with pytest.raises(ValidationError): + GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(), + output_metrics=123, + ) + + @pytest.mark.sanity + def test_invalid_initialization_missing(self): + """Test GenerationRequest initialization without required field.""" + # Missing required 'request_type' field + with pytest.raises(ValidationError): + GenerationRequest() + + # Missing required 'arguments' field + with pytest.raises(ValidationError): + GenerationRequest(request_type="text_completions") + + @pytest.mark.smoke + def test_auto_id_generation(self): + """Test that request_id is auto-generated if not provided.""" + request1 = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + request2 = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + + assert request1.request_id != request2.request_id + assert len(request1.request_id) > 0 + assert len(request2.request_id) > 0 + + # Should be valid UUIDs + uuid.UUID(request1.request_id) + uuid.UUID(request2.request_id) + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("request_type", "expected_type"), + [ + ("text_completions", "text_completions"), + ("chat_completions", "chat_completions"), + ("audio_transcriptions", "audio_transcriptions"), + ("audio_translations", "audio_translations"), + ("custom_type", "custom_type"), + ], + ids=[ + "text_completions", + "chat_completions", + "audio_transcriptions", + "audio_translations", + "custom_type", + ], + ) + def test_request_types(self, request_type, expected_type): + """Test GenerationRequest with different request types.""" + request = GenerationRequest( + request_type=request_type, + arguments=GenerationRequestArguments(), + ) + assert request.request_type == expected_type + + @pytest.mark.regression + def test_content_types(self): + """Test GenerationRequest with different argument types.""" + # Basic arguments + request1 = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + assert isinstance(request1.arguments, GenerationRequestArguments) + + # Arguments with body + request2 = GenerationRequest( + request_type="chat_completions", + arguments=GenerationRequestArguments(body={"prompt": "test"}), + ) + assert request2.arguments.body == {"prompt": "test"} + + # Arguments with headers + request3 = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments( + headers={"Authorization": "Bearer token"} + ), + ) + assert request3.arguments.headers == {"Authorization": "Bearer token"} + + @pytest.mark.smoke + def test_metrics_defaults(self): + """Test that input_metrics and output_metrics are initialized.""" + request = GenerationRequest( + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + + assert isinstance(request.input_metrics, UsageMetrics) + assert isinstance(request.output_metrics, UsageMetrics) + assert request.input_metrics.total_tokens is None + assert request.output_metrics.total_tokens is None + + @pytest.mark.sanity + def test_marshalling(self, valid_instances): + """Test GenerationRequest serialization and deserialization.""" + instance, constructor_args = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + assert "arguments" in data_dict + assert "input_metrics" in data_dict + assert "output_metrics" in data_dict + + # Test reconstruction + reconstructed = GenerationRequest.model_validate(data_dict) + assert reconstructed.arguments == instance.arguments + assert reconstructed.request_type == instance.request_type + assert reconstructed.request_id == instance.request_id + assert reconstructed.input_metrics.model_dump() == ( + instance.input_metrics.model_dump() + ) + assert reconstructed.output_metrics.model_dump() == ( + instance.output_metrics.model_dump() + ) diff --git a/tests/unit/schemas/test_request_stats.py b/tests/unit/schemas/test_request_stats.py new file mode 100644 index 00000000..9867d76a --- /dev/null +++ b/tests/unit/schemas/test_request_stats.py @@ -0,0 +1,669 @@ +from __future__ import annotations + +import asyncio +from typing import Any + +import numpy as np +import pytest +from pydantic import ValidationError + +from guidellm.schemas import ( + GenerativeRequestStats, + RequestInfo, + StandardBaseDict, + UsageMetrics, +) +from tests.unit.testing_utils import async_timeout + + +class TestGenerativeRequestStats: + """High-coverage, concise tests for GenerativeRequestStats.""" + + @pytest.fixture( + params=[ + "short_completion", + "long_chat", + "single_token_output", + "no_iterations_fallback", + ], + ) + def valid_instances( + self, request: pytest.FixtureRequest + ) -> tuple[GenerativeRequestStats, dict[str, Any]]: + """ + Generate realistic test instances with statistically sampled metrics. + + Returns tuple of (GenerativeRequestStats instance, expected values dict). + """ + case_id = request.param + rng = np.random.default_rng(hash(case_id) % (2**32)) + + # Define realistic scenarios based on common generative AI patterns + if case_id == "short_completion": + # Quick completion with moderate tokens + request_type = "text_completions" + prompt_tokens = 30 + output_tokens = 20 + request_start = 0.0 + # Time to first token: ~200ms (typical for small models) + ttft_ms = rng.uniform(150, 250) + first_iter = request_start + ttft_ms / 1000.0 + # Inter-token latency: ~50ms per token + token_iters = 6 + inter_token_ms = rng.uniform(40, 60) + last_iter = first_iter + (output_tokens - 1) * inter_token_ms / 1000.0 + request_end = last_iter + rng.uniform(0.05, 0.15) + resolve_end = request_end + + elif case_id == "long_chat": + # Long conversation with many tokens + request_type = "chat_completions" + prompt_tokens = 400 + output_tokens = 256 + request_start = 10.0 + # Longer TTFT for larger context + ttft_ms = rng.uniform(2000, 3000) + first_iter = request_start + ttft_ms / 1000.0 + # Consistent generation speed + token_iters = 30 + inter_token_ms = rng.uniform(45, 55) + last_iter = first_iter + (output_tokens - 1) * inter_token_ms / 1000.0 + request_end = last_iter + rng.uniform(0.1, 0.3) + resolve_end = request_end + + elif case_id == "single_token_output": + # Edge case: single token generation + request_type = "text_completions" + prompt_tokens = 5 + output_tokens = 1 + request_start = 5.0 + ttft_ms = rng.uniform(80, 120) + first_iter = request_start + ttft_ms / 1000.0 + last_iter = first_iter + token_iters = 1 + request_end = last_iter + rng.uniform(0.05, 0.1) + resolve_end = request_end + + else: # no_iterations_fallback + # Fallback scenario with missing timing data + request_type = "text_completions" + prompt_tokens = 12 + output_tokens = 7 + request_start = None + first_iter = None + last_iter = None + token_iters = 0 + request_end = 50.0 + resolve_end = 50.5 + + # Build timings object via RequestInfo + info = RequestInfo(request_id=case_id, status="completed") + info.timings.request_start = request_start + info.timings.resolve_start = resolve_end if request_start is None else None + info.timings.first_token_iteration = first_iter + info.timings.last_token_iteration = last_iter + info.timings.token_iterations = token_iters + info.timings.request_end = request_end + info.timings.resolve_end = resolve_end + + stats = GenerativeRequestStats( + request_id=case_id, + request_type=request_type, + info=info, + input_metrics=UsageMetrics(text_tokens=prompt_tokens), + output_metrics=UsageMetrics(text_tokens=output_tokens), + ) + + # Compute expected properties from the generated timings + if request_start is not None and request_end is not None: + expected_latency = request_end - request_start + else: + expected_latency = None + + expected_time_to_first_ms = ( + None + if first_iter is None or request_start is None + else (first_iter - request_start) * 1000.0 + ) + + expected_time_per_output_ms = ( + None + if request_start is None or last_iter is None or output_tokens in (None, 0) + else (last_iter - request_start) * 1000.0 / output_tokens + ) + + if ( + first_iter is None + or last_iter is None + or output_tokens is None + or output_tokens <= 1 + ): + expected_inter_token_ms = None + else: + expected_inter_token_ms = ( + (last_iter - first_iter) * 1000.0 / (output_tokens - 1) + ) + + total_tokens = (prompt_tokens or 0) + (output_tokens or 0) + expected_tokens_per_sec = ( + None + if expected_latency is None or expected_latency == 0 + else total_tokens / expected_latency + ) + expected_output_tokens_per_sec = ( + None + if expected_latency is None + or expected_latency == 0 + or output_tokens is None + else output_tokens / expected_latency + ) + + expected_iter_tokens_per_iter = ( + None + if output_tokens is None or output_tokens <= 1 or token_iters <= 1 + else (output_tokens - 1.0) / (token_iters - 1.0) + ) + expected_output_tokens_per_iter = ( + None + if output_tokens is None or token_iters < 1 + else output_tokens / token_iters + ) + + # Prompt timing chooses first token ts, or request_end_time fallback + request_end_time_fallback = ( + request_end if request_end is not None else resolve_end + ) + expected_prompt_timing = ( + None + if resolve_end is None + else ( + (first_iter if first_iter is not None else request_end_time_fallback), + float(prompt_tokens or 0), + ) + ) + + # Output timings (first token + evenly spaced iterations) or single at end + if resolve_end is None: + expected_output_timings = None + elif first_iter is None or last_iter is None or token_iters <= 1: + expected_output_timings = [ + ( + (last_iter if last_iter is not None else request_end_time_fallback), + float(output_tokens or 0), + ) + ] + else: + # first token as 1, then spread the remaining (token_iters - 1) slots + tok_per_iter = expected_iter_tokens_per_iter or 0.0 + iter_times = np.linspace(first_iter, last_iter, num=token_iters)[1:] + expected_output_timings = [(first_iter, 1.0 * bool(output_tokens))] + [ + (float(tim), float(tok_per_iter)) for tim in iter_times + ] + + expected_total_timings = ( + [] if expected_prompt_timing is None else [expected_prompt_timing] + ) + ([] if expected_output_timings is None else list(expected_output_timings)) + + expected: dict[str, Any] = { + "request_start_time": ( + request_start if request_start is not None else resolve_end + ), + "request_end_time": ( + request_end if request_end is not None else resolve_end + ), + "request_latency": expected_latency, + "prompt_tokens": prompt_tokens, + "input_tokens": prompt_tokens, + "output_tokens": output_tokens, + "total_tokens": total_tokens, + "time_to_first_token_ms": expected_time_to_first_ms, + "time_per_output_token_ms": expected_time_per_output_ms, + "inter_token_latency_ms": expected_inter_token_ms, + "tokens_per_second": expected_tokens_per_sec, + "output_tokens_per_second": expected_output_tokens_per_sec, + "iter_tokens_per_iteration": expected_iter_tokens_per_iter, + "output_tokens_per_iteration": expected_output_tokens_per_iter, + "prompt_tokens_timing": expected_prompt_timing, + "output_tokens_timings": ( + None + if expected_output_timings is None + else list(expected_output_timings) + ), + "total_tokens_timings": expected_total_timings, + "first_token_iteration": first_iter, + "last_token_iteration": last_iter, + "token_iterations": token_iters, + } + return stats, expected + + @pytest.mark.smoke + def test_class_signatures(self): + """Validate public surface, inheritance, and key properties.""" + assert issubclass(GenerativeRequestStats, StandardBaseDict) + assert hasattr(GenerativeRequestStats, "model_dump") + assert hasattr(GenerativeRequestStats, "model_validate") + + # fields exposed + fields = GenerativeRequestStats.model_fields + for field_name in ( + "type_", + "request_id", + "request_type", + "request_args", + "output", + "info", + "input_metrics", + "output_metrics", + ): + assert field_name in fields + + # computed properties + for prop_name in ( + "request_start_time", + "request_end_time", + "request_latency", + "prompt_tokens", + "input_tokens", + "output_tokens", + "total_tokens", + "time_to_first_token_ms", + "time_per_output_token_ms", + "inter_token_latency_ms", + "tokens_per_second", + "output_tokens_per_second", + "iter_tokens_per_iteration", + "output_tokens_per_iteration", + ): + assert hasattr(GenerativeRequestStats, prop_name) + + # regular properties + for prop_name in ( + "first_token_iteration", + "last_token_iteration", + "token_iterations", + "prompt_tokens_timing", + "output_tokens_timings", + "iter_tokens_timings", + "total_tokens_timings", + ): + assert hasattr(GenerativeRequestStats, prop_name) + + @pytest.mark.smoke + def test_initialization(self, valid_instances): + """Initialization from realistic inputs.""" + instance, expected = valid_instances + assert isinstance(instance, GenerativeRequestStats) + assert instance.type_ == "generative_request_stats" + assert instance.request_id + assert instance.request_type in ("text_completions", "chat_completions") + + # Basic fields echo + assert instance.prompt_tokens == expected["prompt_tokens"] + assert instance.output_tokens == expected["output_tokens"] + + @pytest.mark.sanity + def test_invalid_initialization_missing(self): + """Missing required fields should fail validation.""" + with pytest.raises(ValidationError): + GenerativeRequestStats() # type: ignore[call-arg] + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("field_name", "bad_value"), + [ + ("request_id", None), + ("request_id", 123), + ("request_type", None), + ("request_type", 456), + ("info", None), + ("info", "not_request_info"), + ("input_metrics", None), + ("input_metrics", "not_usage_metrics"), + ("output_metrics", None), + ("output_metrics", "not_usage_metrics"), + ], + ) + def test_invalid_initialization_values(self, field_name: str, bad_value: Any): + """Type/None mismatches should raise.""" + info = RequestInfo(request_id="bad-1", status="completed") + info.timings.resolve_end = 1.0 + base = { + "request_id": "ok", + "request_type": "text_completions", + "info": info, + "input_metrics": UsageMetrics(text_tokens=1), + "output_metrics": UsageMetrics(text_tokens=1), + } + base[field_name] = bad_value + with pytest.raises(ValidationError): + GenerativeRequestStats(**base) # type: ignore[arg-type] + + @pytest.mark.regression + def test_computed_properties_match_expected(self, valid_instances): + """All computed properties should match precomputed expectations.""" + instance, expected = valid_instances + + # direct scalar comparisons + for key in ( + "request_start_time", + "request_end_time", + "request_latency", + "prompt_tokens", + "input_tokens", + "output_tokens", + "total_tokens", + "time_to_first_token_ms", + "time_per_output_token_ms", + "inter_token_latency_ms", + "tokens_per_second", + "output_tokens_per_second", + "iter_tokens_per_iteration", + "output_tokens_per_iteration", + "first_token_iteration", + "last_token_iteration", + "token_iterations", + ): + got = getattr(instance, key) + exp = expected[key] + if isinstance(exp, float): + # tolerant float compare + assert (got is None and exp is None) or pytest.approx( + exp, rel=1e-6, abs=1e-6 + ) == got + else: + assert got == exp + + # prompt timing (all valid_instances have resolve_end set) + got_ts, got_count = instance.prompt_tokens_timing + exp_ts, exp_count = expected["prompt_tokens_timing"] + assert pytest.approx(exp_ts, rel=1e-6, abs=1e-6) == got_ts + assert got_count == exp_count + + # output timings (all valid_instances have resolve_end set) + exp_output_timings = expected["output_tokens_timings"] + got_output_timings = instance.output_tokens_timings + assert len(got_output_timings) == len(exp_output_timings) + for (got_t, got_v), (exp_t, exp_v) in zip( + got_output_timings, exp_output_timings, strict=False + ): + assert pytest.approx(exp_t, rel=1e-6, abs=1e-6) == got_t + assert pytest.approx(exp_v, rel=1e-6, abs=1e-6) == got_v + + # total timings (prompt + output) + got_total_timings = instance.total_tokens_timings + assert len(got_total_timings) == len(expected["total_tokens_timings"]) + + @pytest.mark.sanity + def test_error_when_no_resolve_end_for_end_time(self): + """Accessing end-dependent fields without resolve_end should raise.""" + info = RequestInfo(request_id="no-end", status="completed") + # request_end set but resolve_end missing -> property raises by design + info.timings.request_end = 2.0 + instance = GenerativeRequestStats( + request_id="no-end", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=3), + output_metrics=UsageMetrics(text_tokens=2), + ) + with pytest.raises(ValueError): + _ = instance.request_end_time + with pytest.raises(ValueError): + _ = instance.prompt_tokens_timing + with pytest.raises(ValueError): + _ = instance.output_tokens_timings + + @pytest.mark.sanity + def test_none_paths_for_latency_and_rates(self): + """Ensure None is returned when required timing parts are missing.""" + info = RequestInfo(request_id="none-lat", status="completed") + info.timings.resolve_end = 1.0 # minimal to avoid property error + instance = GenerativeRequestStats( + request_id="none-lat", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=4), + output_metrics=UsageMetrics(text_tokens=6), + ) + assert instance.request_latency is None + assert instance.tokens_per_second is None + assert instance.output_tokens_per_second is None + assert instance.time_to_first_token_ms is None + assert instance.time_per_output_token_ms is None + assert instance.inter_token_latency_ms is None + + @pytest.mark.smoke + def test_marshalling(self, valid_instances): + """model_dump / model_validate round-trip.""" + instance, _ = valid_instances + dumped = instance.model_dump() + assert dumped["type_"] == "generative_request_stats" + rebuilt = GenerativeRequestStats.model_validate(dumped) + assert rebuilt.request_id == instance.request_id + assert rebuilt.request_type == instance.request_type + + @pytest.mark.sanity + def test_optional_fields(self): + """Test optional fields request_args and output.""" + info = RequestInfo(request_id="opt-test", status="completed") + info.timings.resolve_end = 10.0 + + # Without optional fields + instance = GenerativeRequestStats( + request_id="opt-test", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=10), + ) + assert instance.request_args is None + assert instance.output is None + + # With optional fields + instance_with_opts = GenerativeRequestStats( + request_id="opt-test-2", + request_type="chat_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=10), + request_args="temperature=0.7", + output="Generated response text", + ) + assert instance_with_opts.request_args == "temperature=0.7" + assert instance_with_opts.output == "Generated response text" + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("output_tokens_value", "token_iters_value", "expect_iter_tok_per_iter"), + [ + (0, 5, None), + (1, 5, None), + (2, 1, None), + (10, 2, (10 - 1) / (2 - 1)), + ], + ids=["zero_tokens", "single_token", "single_iter", "basic_case"], + ) + def test_iter_token_formulas( + self, + output_tokens_value: int, + token_iters_value: int, + expect_iter_tok_per_iter: float | None, + ): + """Edge coverage for iteration-derived metrics.""" + info = RequestInfo(request_id="iter-calc", status="completed") + info.timings.resolve_end = 9.0 + info.timings.token_iterations = token_iters_value + stats = GenerativeRequestStats( + request_id="iter-calc", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=3), + output_metrics=UsageMetrics(text_tokens=output_tokens_value), + ) + assert ( + stats.iter_tokens_per_iteration is None + if expect_iter_tok_per_iter is None + else pytest.approx(expect_iter_tok_per_iter, rel=1e-6, abs=1e-6) + == stats.iter_tokens_per_iteration + ) + + @pytest.mark.sanity + def test_iter_tokens_timings_property(self): + """Test iter_tokens_timings property with various scenarios.""" + info = RequestInfo(request_id="iter-time", status="completed") + info.timings.resolve_end = 10.0 + info.timings.first_token_iteration = 1.0 + info.timings.last_token_iteration = 5.0 + info.timings.token_iterations = 5 + + # With valid iteration data + stats = GenerativeRequestStats( + request_id="iter-time", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=10), + ) + iter_timings = stats.iter_tokens_timings + assert len(iter_timings) == 4 # token_iterations - 1 + # Verify evenly spaced + assert all( + isinstance(tim, float) and isinstance(tok, float) + for tim, tok in iter_timings + ) + + # Without iteration data (returns empty list) + info_no_iter = RequestInfo(request_id="no-iter", status="completed") + info_no_iter.timings.resolve_end = 10.0 + info_no_iter.timings.token_iterations = 0 + stats_no_iter = GenerativeRequestStats( + request_id="no-iter", + request_type="text_completions", + info=info_no_iter, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=10), + ) + assert stats_no_iter.iter_tokens_timings == [] + + @pytest.mark.sanity + def test_total_tokens_timings_property(self): + """Test total_tokens_timings combines prompt and output timings.""" + info = RequestInfo(request_id="total-time", status="completed") + info.timings.resolve_end = 10.0 + info.timings.request_start = 0.0 + info.timings.request_end = 10.0 + info.timings.first_token_iteration = 1.0 + info.timings.last_token_iteration = 5.0 + info.timings.token_iterations = 3 + + stats = GenerativeRequestStats( + request_id="total-time", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=6), + ) + total_timings = stats.total_tokens_timings + # Should have prompt timing + output timings + assert len(total_timings) > 0 + # First should be prompt timing + prompt_timing = stats.prompt_tokens_timing + if prompt_timing: + assert total_timings[0] == prompt_timing + + @pytest.mark.sanity + def test_zero_division_edge_cases(self): + """Test edge cases that could cause zero division errors.""" + info = RequestInfo(request_id="zero-div", status="completed") + info.timings.resolve_end = 10.0 + info.timings.request_start = 10.0 # Same as end + info.timings.request_end = 10.0 + + stats = GenerativeRequestStats( + request_id="zero-div", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=0), + ) + + # Zero latency should give None for rate calculations + assert stats.request_latency == 0.0 + assert stats.tokens_per_second is None # Division by zero avoided + assert stats.output_tokens_per_second is None + + # Zero output tokens should give None for time_per_output_token_ms + info_zero_tokens = RequestInfo(request_id="zero-tokens", status="completed") + info_zero_tokens.timings.resolve_end = 10.0 + info_zero_tokens.timings.request_start = 0.0 + info_zero_tokens.timings.request_end = 10.0 + info_zero_tokens.timings.last_token_iteration = 5.0 + + stats_zero_tokens = GenerativeRequestStats( + request_id="zero-tokens", + request_type="text_completions", + info=info_zero_tokens, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(text_tokens=0), + ) + assert stats_zero_tokens.time_per_output_token_ms is None + + @pytest.mark.regression + def test_total_tokens_with_none_values(self): + """Test total_tokens calculation when input or output tokens are None.""" + info = RequestInfo(request_id="none-tokens", status="completed") + info.timings.resolve_end = 10.0 + + # Both None returns None + stats_both_none = GenerativeRequestStats( + request_id="none-tokens-1", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(), + output_metrics=UsageMetrics(), + ) + assert stats_both_none.total_tokens is None + + # One None, one with value + stats_input_none = GenerativeRequestStats( + request_id="none-tokens-2", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(), + output_metrics=UsageMetrics(text_tokens=10), + ) + assert stats_input_none.total_tokens == 10 + + @pytest.mark.regression + def test_output_tokens_per_second_with_none_output(self): + """Test output_tokens_per_second when output_tokens is None.""" + info = RequestInfo(request_id="none-output", status="completed") + info.timings.resolve_end = 10.0 + info.timings.request_start = 0.0 + info.timings.request_end = 10.0 + + stats = GenerativeRequestStats( + request_id="none-output", + request_type="text_completions", + info=info, + input_metrics=UsageMetrics(text_tokens=5), + output_metrics=UsageMetrics(), # No text_tokens + ) + # Should return None when output_tokens is None, not crash + assert stats.output_tokens_per_second is None + + @pytest.mark.sanity + @pytest.mark.asyncio + @async_timeout(0.2) # ensure no accidental indefinite waits if expanded later + async def test_async_context_usage(self, valid_instances): + """Light async smoke to satisfy async-timeout policy.""" + instance, expected = valid_instances + await asyncio.sleep(0) # yield + assert instance.request_id + # simple float compare in async path + exp = expected["output_tokens_per_iteration"] + got = instance.output_tokens_per_iteration + if exp is None: + assert got is None + else: + assert pytest.approx(exp, rel=1e-6, abs=1e-6) == got diff --git a/tests/unit/schemas/test_response.py b/tests/unit/schemas/test_response.py new file mode 100644 index 00000000..28626df1 --- /dev/null +++ b/tests/unit/schemas/test_response.py @@ -0,0 +1,277 @@ +""" +Unit tests for GenerationResponse. +""" + +from __future__ import annotations + +from typing import Any + +import pytest +from pydantic import ValidationError + +from guidellm.schemas import ( + GenerationRequest, + GenerationRequestArguments, + GenerationResponse, + GenerativeRequestStats, + RequestInfo, + StandardBaseModel, + UsageMetrics, +) + + +class TestGenerationResponse: + """Test cases for GenerationResponse model.""" + + @pytest.fixture( + params=[ + { + "request_id": "test-123", + "request_args": "model=gpt-3.5-turbo", + }, + { + "request_id": "test-456", + "request_args": "model=gpt-4", + "text": "Generated text", + }, + { + "request_id": "test-789", + "request_args": None, + "text": "Another response", + "input_metrics": UsageMetrics(text_tokens=50), + "output_metrics": UsageMetrics(text_tokens=25), + }, + ], + ids=["minimal", "with_text", "with_metrics"], + ) + def valid_instances(self, request): + """Fixture providing valid GenerationResponse instances.""" + constructor_args = request.param + instance = GenerationResponse(**constructor_args) + return instance, constructor_args + + @pytest.mark.smoke + def test_class_signatures(self): + """Test GenerationResponse inheritance and type relationships.""" + assert issubclass(GenerationResponse, StandardBaseModel) + assert hasattr(GenerationResponse, "model_dump") + assert hasattr(GenerationResponse, "model_validate") + + # Check all expected fields and properties are defined + fields = GenerationResponse.model_fields + expected_fields = [ + "request_id", + "request_args", + "text", + "input_metrics", + "output_metrics", + ] + for field in expected_fields: + assert field in fields + + # Check methods exist + assert hasattr(GenerationResponse, "compile_stats") + + @pytest.mark.smoke + def test_initialization( + self, valid_instances: tuple[GenerationResponse, dict[str, str]] + ): + """Test GenerationResponse initialization.""" + instance, constructor_args = valid_instances + assert isinstance(instance, GenerationResponse) + assert instance.request_id == constructor_args["request_id"] + assert instance.request_args == constructor_args["request_args"] + + # Check defaults for optional fields + if "text" not in constructor_args: + assert instance.text is None + else: + assert instance.text == constructor_args["text"] + + # Check metrics (either provided or default) + assert hasattr(instance, "input_metrics") + assert hasattr(instance, "output_metrics") + assert isinstance(instance.input_metrics, UsageMetrics) + assert isinstance(instance.output_metrics, UsageMetrics) + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("field", "value"), + [ + ("request_id", None), + ("request_id", 123), + ("request_id", {}), + ("request_args", 123), + ("request_args", []), + ("text", 123), + ("text", []), + ("input_metrics", "invalid"), + ("output_metrics", "invalid"), + ], + ) + def test_invalid_initialization_values(self, field: str, value: Any): + """Test GenerationResponse with invalid field values.""" + data = {"request_id": "test-id", "request_args": "test_args"} + data[field] = value + with pytest.raises(ValidationError): + GenerationResponse(**data) + + @pytest.mark.sanity + def test_invalid_initialization_missing(self): + """Test GenerationResponse initialization without required fields.""" + with pytest.raises(ValidationError): + GenerationResponse() + + with pytest.raises(ValidationError): + GenerationResponse(request_id="test") + + with pytest.raises(ValidationError): + GenerationResponse(request_args="test") + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("prefer_response", "expected_tokens"), + [ + (True, 75), + (False, 60), + ], + ) + def test_compile_stats(self, prefer_response: bool, expected_tokens: int): + """Test compile_stats method functionality.""" + response = GenerationResponse( + request_id="test-123", + request_args="test_args", + text="Generated response", + input_metrics=UsageMetrics(text_tokens=50), + output_metrics=UsageMetrics(text_tokens=25), + ) + + request = GenerationRequest( + request_id="test-123", + request_type="text_completions", + arguments=GenerationRequestArguments(), + input_metrics=UsageMetrics(text_tokens=40), + output_metrics=UsageMetrics(text_tokens=20), + ) + + request_info = RequestInfo(request_id="test-123", status="completed") + + stats = response.compile_stats(request, request_info, prefer_response) + assert isinstance(stats, GenerativeRequestStats) + assert stats.request_id == "test-123" + assert stats.request_type == "text_completions" + assert stats.output == "Generated response" + + total_tokens = ( + stats.input_metrics.text_tokens + stats.output_metrics.text_tokens + ) + assert total_tokens == expected_tokens + + @pytest.mark.smoke + def test_compile_stats_with_defaults(self): + """Test compile_stats with default metrics.""" + response = GenerationResponse( + request_id="test-123", + request_args="test_args", + text="Generated response", + ) + + request = GenerationRequest( + request_id="test-123", + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + + request_info = RequestInfo(request_id="test-123", status="completed") + + stats = response.compile_stats(request, request_info) + assert isinstance(stats, GenerativeRequestStats) + assert stats.request_id == "test-123" + assert stats.output == "Generated response" + + @pytest.mark.smoke + def test_compile_stats_failed_request(self): + """Test compile_stats with failed request status.""" + response = GenerationResponse( + request_id="test-123", + request_args="test_args", + text=None, + output_metrics=UsageMetrics(text_tokens=25), + ) + + request = GenerationRequest( + request_id="test-123", + request_type="text_completions", + arguments=GenerationRequestArguments(), + output_metrics=UsageMetrics(text_tokens=20), + ) + + request_info = RequestInfo(request_id="test-123", status="errored") + + stats = response.compile_stats(request, request_info) + assert isinstance(stats, GenerativeRequestStats) + assert stats.request_id == "test-123" + assert stats.info.status == "errored" + + @pytest.mark.sanity + def test_compile_stats_mismatched_request_id(self): + """Test compile_stats with mismatched request IDs.""" + response = GenerationResponse( + request_id="test-123", + request_args="test_args", + ) + + request = GenerationRequest( + request_id="test-456", + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + + request_info = RequestInfo(request_id="test-123") + + with pytest.raises(ValueError, match="Mismatched request IDs"): + response.compile_stats(request, request_info) + + @pytest.mark.sanity + def test_compile_stats_mismatched_info_id(self): + """Test compile_stats with mismatched info request ID.""" + response = GenerationResponse( + request_id="test-123", + request_args="test_args", + ) + + request = GenerationRequest( + request_id="test-123", + request_type="text_completions", + arguments=GenerationRequestArguments(), + ) + + request_info = RequestInfo(request_id="test-456") + + with pytest.raises(ValueError, match="Mismatched request IDs"): + response.compile_stats(request, request_info) + + @pytest.mark.sanity + def test_marshalling( + self, valid_instances: tuple[GenerationResponse, dict[str, str]] + ): + """Test GenerationResponse serialization and deserialization.""" + instance, constructor_args = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + assert data_dict["request_id"] == constructor_args["request_id"] + assert data_dict["request_args"] == constructor_args["request_args"] + + # Test reconstruction + reconstructed = GenerationResponse.model_validate(data_dict) + assert reconstructed.request_id == instance.request_id + assert reconstructed.request_args == instance.request_args + assert reconstructed.text == instance.text + assert ( + reconstructed.input_metrics.model_dump() + == instance.input_metrics.model_dump() + ) + assert ( + reconstructed.output_metrics.model_dump() + == instance.output_metrics.model_dump() + ) diff --git a/tests/unit/schemas/test_statistics.py b/tests/unit/schemas/test_statistics.py new file mode 100644 index 00000000..fe95ad97 --- /dev/null +++ b/tests/unit/schemas/test_statistics.py @@ -0,0 +1,1720 @@ +from __future__ import annotations + +import math +from typing import Literal, TypeVar + +import numpy as np +import pytest +from pydantic import BaseModel, ValidationError + +from guidellm.schemas import DistributionSummary, Percentiles, StatusDistributionSummary +from guidellm.schemas.statistics import FunctionObjT + + +def test_function_obj_type(): + """Test that FunctionObjT is filled out correctly as a TypeVar.""" + assert isinstance(FunctionObjT, type(TypeVar("test"))) + assert FunctionObjT.__name__ == "FunctionObjT" + assert FunctionObjT.__bound__ is None + assert FunctionObjT.__constraints__ == () + + +def generate_pdf( + distribution: str | None, distribution_args: dict, size: int +) -> np.ndarray: + if distribution is None: + return np.empty((0, 2)) + + if distribution == "normal": + mean = distribution_args.get("loc", 0.0) + std_dev = distribution_args.get("scale", 1.0) + x_values = np.linspace(mean - 4 * std_dev, mean + 4 * std_dev, size) + pdf_values = (1.0 / np.sqrt(2 * np.pi * std_dev**2)) * np.exp( + -1.0 * ((x_values - mean) ** 2) / (2 * std_dev**2) + ) + elif distribution == "uniform": + low = distribution_args.get("low", 0.0) + high = distribution_args.get("high", 1.0) + x_values = np.linspace(low, high, size) + pdf_values = np.full_like(x_values, 1.0 / (high - low)) + elif distribution == "exponential": + scale = distribution_args.get("scale", 1.0) + x_values = np.linspace(0, 10 * scale, size) + pdf_values = (1 / scale) * np.exp(-x_values / scale) + elif distribution == "poisson": + lam = distribution_args.get("lam", 1.0) + x_values = np.arange(0, 20) + pdf_values = (lam**x_values * np.exp(-lam)) / np.array( + [math.factorial(x) for x in x_values] + ) + else: + raise ValueError(f"Unsupported distribution type: {distribution}") + + return np.column_stack((x_values, pdf_values / np.sum(pdf_values))) + + +@pytest.fixture( + params=[ + {"distribution": None, "distribution_args": {}}, + { + "distribution": "normal", + "distribution_args": {"loc": 5.0, "scale": 1.0}, + }, + { + "distribution": "normal", + "distribution_args": {"loc": 100.0, "scale": 15.0}, + }, + {"distribution": "uniform", "distribution_args": {"low": 3.4, "high": 9.8}}, + { + "distribution": "exponential", + "distribution_args": {"scale": 1.0}, + }, + { + "distribution": "poisson", + "distribution_args": {"lam": 5.0}, + }, + ] +) +def probability_distributions( + request, +) -> tuple[str | None, np.ndarray, np.ndarray, dict[str, float]]: + """ + Create various probability distributions for testing. + + :return: A tuple containing the distribution type, the generated values, + the pdf, and the correct distribution statistics. + """ + distribution_type: str | None = request.param["distribution"] + distribution_args: dict[str, float] = request.param["distribution_args"] + + num_samples = 10000 + rng = np.random.default_rng(seed=42) + percentile_probs = { + "p001": 0.001, + "p01": 0.01, + "p05": 0.05, + "p10": 0.1, + "p25": 0.25, + "p50": 0.5, + "p75": 0.75, + "p90": 0.9, + "p95": 0.95, + "p99": 0.99, + "p999": 0.999, + } + + if distribution_type is None: + # Empty / 0's distribution + return ( + None, + [], + np.empty((0, 2)), + { + "mean": 0.0, + "median": 0.0, + "mode": 0.0, + "variance": 0.0, + "std_dev": 0.0, + "min": 0.0, + "max": 0.0, + "count": 0, + "total_sum": 0.0, + "percentiles": dict.fromkeys(percentile_probs.keys(), 0.0), + }, + ) + + rng = np.random.default_rng(seed=42) + samples = getattr(rng, distribution_type)(**distribution_args, size=num_samples) + pdf = np.column_stack( + (np.sort(samples), np.zeros_like(samples) + 1.0 / num_samples) + ) + + return ( + distribution_type, + samples, + pdf, + { + "mean": float(np.mean(samples)), + "median": float(np.median(samples)), + "variance": float(np.var(samples)), + "std_dev": float(np.std(samples)), + "min": float(np.min(samples)), + "max": float(np.max(samples)), + "count": int(len(samples)), + "total_sum": float(np.sum(samples)), + "percentiles": { + key: float(np.percentile(samples, per * 100)) + for key, per in percentile_probs.items() + }, + }, + ) + + +def concurrency_distributions( + concurrency_type: Literal[ + "sequential", + "parallel", + "constant_rate", + "burst", + "triangular_ramp", + "normal_dist", + ], + num_requests: int = 100, + start_time: float = 0.0, + end_time: float = 100.0, +) -> tuple[ + Literal["sequential", "parallel", "constant_rate", "burst", "triangular_ramp"], + np.ndarray, + dict[str, float], +]: + if concurrency_type == "sequential": + timings = np.linspace(start_time, end_time, num_requests + 1) + requests = np.column_stack((timings[:-1], timings[1:])) + + return ( + concurrency_type, + requests, + { + "start_time": None, + "end_time": None, + "mean_concurrency": 1.0, + "median_concurrency": 1.0, + "std_dev_concurrency": 0.0, + }, + ) + + if concurrency_type == "parallel": + requests = np.column_stack( + (np.ones(num_requests) * start_time, np.ones(num_requests) * end_time) + ) + + return ( + concurrency_type, + requests, + { + "start_time": None, + "end_time": None, + "mean_concurrency": num_requests, + "median_concurrency": num_requests, + "std_dev_concurrency": 0.0, + }, + ) + + if concurrency_type == "constant_rate": + request_duration = (end_time - start_time) / 10 + timings = np.linspace(start_time, end_time - request_duration, num_requests) + requests = np.column_stack((timings, timings + request_duration)) + request_delay = timings[1] - timings[0] + rate = 1 / request_delay + concurrency = rate * request_duration + + return ( + concurrency_type, + requests, + { + "start_time": request_delay * concurrency, + "end_time": end_time - request_delay * concurrency, + "mean_concurrency": concurrency, + "median_concurrency": concurrency, + "std_dev_concurrency": 0.0, + }, + ) + + if concurrency_type == "burst": + request_length = (end_time - start_time) / 10 + requests = np.column_stack( + ( + np.repeat(start_time, num_requests), + np.repeat(start_time + request_length, num_requests), + ) + ) + + fraction_active = request_length / (end_time - start_time) + mean_concurrency_windowed = num_requests * fraction_active + median_concurrency_windowed = 0.0 if fraction_active < 0.5 else num_requests + variance = ( + fraction_active * (num_requests - mean_concurrency_windowed) ** 2 + + (1 - fraction_active) * mean_concurrency_windowed**2 + ) + std_dev_concurrency_windowed = variance**0.5 + + return ( + concurrency_type, + requests, + { + "start_time": start_time, + "end_time": end_time, + "mean_concurrency": mean_concurrency_windowed, + "median_concurrency": median_concurrency_windowed, + "std_dev_concurrency": std_dev_concurrency_windowed, + }, + ) + + if concurrency_type == "triangular_ramp": + max_concurrency = num_requests + ramp_up_time = (end_time - start_time) / 2 + request_duration = ramp_up_time + timings = np.linspace(start_time, start_time + ramp_up_time, max_concurrency) + requests = np.column_stack((timings, timings + request_duration)) + + return ( + concurrency_type, + requests, + { + "start_time": None, + "end_time": None, + "mean_concurrency": max_concurrency / 2, + "median_concurrency": max_concurrency / 2, + "std_dev_concurrency": max_concurrency / (2 * math.sqrt(3)), + }, + ) + + return None + + +class TestPercentiles: + @pytest.fixture + def valid_instances( + self, + probability_distributions: tuple[ + str | None, np.ndarray, np.ndarray, dict[str, float] + ], + ) -> tuple[Percentiles, str | None, np.ndarray, np.ndarray, dict[str, float]]: + dist_type, samples, pdf, stats = probability_distributions + instance = Percentiles( + p001=stats["percentiles"]["p001"], + p01=stats["percentiles"]["p01"], + p05=stats["percentiles"]["p05"], + p10=stats["percentiles"]["p10"], + p25=stats["percentiles"]["p25"], + p50=stats["percentiles"]["p50"], + p75=stats["percentiles"]["p75"], + p90=stats["percentiles"]["p90"], + p95=stats["percentiles"]["p95"], + p99=stats["percentiles"]["p99"], + p999=stats["percentiles"]["p999"], + ) + return instance, dist_type, samples, pdf, stats + + @pytest.mark.smoke + def test_class_signatures(self): + assert issubclass(Percentiles, BaseModel) + assert "p001" in Percentiles.model_fields + assert "p01" in Percentiles.model_fields + assert "p05" in Percentiles.model_fields + assert "p10" in Percentiles.model_fields + assert "p25" in Percentiles.model_fields + assert "p50" in Percentiles.model_fields + assert "p75" in Percentiles.model_fields + assert "p90" in Percentiles.model_fields + assert "p95" in Percentiles.model_fields + assert "p99" in Percentiles.model_fields + assert "p999" in Percentiles.model_fields + assert hasattr(Percentiles, "from_pdf") + + @pytest.mark.smoke + def test_initialization( + self, + valid_instances: tuple[ + DistributionSummary, Percentiles, np.ndarray, np.ndarray, dict[str, float] + ], + ): + instance, _dist_type, _samples, _pdf, stats = valid_instances + assert isinstance(instance, Percentiles) + assert instance.p001 == stats["percentiles"]["p001"], "p001 percentile mismatch" + assert instance.p01 == stats["percentiles"]["p01"], "p01 percentile mismatch" + assert instance.p05 == stats["percentiles"]["p05"], "p05 percentile mismatch" + assert instance.p10 == stats["percentiles"]["p10"], "p10 percentile mismatch" + assert instance.p25 == stats["percentiles"]["p25"], "p25 percentile mismatch" + assert instance.p50 == stats["percentiles"]["p50"], "p50 percentile mismatch" + assert instance.p75 == stats["percentiles"]["p75"], "p75 percentile mismatch" + assert instance.p90 == stats["percentiles"]["p90"], "p90 percentile mismatch" + assert instance.p95 == stats["percentiles"]["p95"], "p95 percentile mismatch" + assert instance.p99 == stats["percentiles"]["p99"], "p99 percentile mismatch" + assert instance.p999 == stats["percentiles"]["p999"], "p999 percentile mismatch" + + @pytest.mark.sanity + @pytest.mark.parametrize( + "missing_field", + ["p001", "p01", "p05", "p10", "p25", "p50", "p75", "p90", "p95", "p99", "p999"], + ) + def test_invalid_initialization(self, missing_field): + test_kwargs = { + "p001": 0.1, + "p01": 1.0, + "p05": 5.0, + "p10": 10.0, + "p25": 25.0, + "p50": 50.0, + "p75": 75.0, + "p90": 90.0, + "p95": 95.0, + "p99": 99.0, + "p999": 99.9, + } + del test_kwargs[missing_field] + + with pytest.raises(ValidationError): + Percentiles(**test_kwargs) + + @pytest.mark.smoke + def test_from_pdf(self, valid_instances): + _instance, _dist_type, _values, pdf, stats = valid_instances + + tolerance = 0.1 * abs(stats["std_dev"]) # within 10% of standard deviation + percentiles = Percentiles.from_pdf(pdf) + assert percentiles.p001 == pytest.approx( + stats["percentiles"]["p001"], abs=tolerance + ), "p001 percentile mismatch" + assert percentiles.p01 == pytest.approx( + stats["percentiles"]["p01"], abs=tolerance + ), "p01 percentile mismatch" + assert percentiles.p05 == pytest.approx( + stats["percentiles"]["p05"], abs=tolerance + ), "p05 percentile mismatch" + assert percentiles.p10 == pytest.approx( + stats["percentiles"]["p10"], abs=tolerance + ), "p10 percentile mismatch" + assert percentiles.p25 == pytest.approx( + stats["percentiles"]["p25"], abs=tolerance + ), "p25 percentile mismatch" + assert percentiles.p50 == pytest.approx( + stats["percentiles"]["p50"], abs=tolerance + ), "p50 percentile mismatch" + assert percentiles.p75 == pytest.approx( + stats["percentiles"]["p75"], abs=tolerance + ), "p75 percentile mismatch" + assert percentiles.p90 == pytest.approx( + stats["percentiles"]["p90"], abs=tolerance + ), "p90 percentile mismatch" + assert percentiles.p95 == pytest.approx( + stats["percentiles"]["p95"], abs=tolerance + ), "p95 percentile mismatch" + assert percentiles.p99 == pytest.approx( + stats["percentiles"]["p99"], abs=tolerance + ), "p99 percentile mismatch" + assert percentiles.p999 == pytest.approx( + stats["percentiles"]["p999"], abs=(tolerance * 2) + ), "p999 percentile mismatch" + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("values", "expected_count"), + [ + ([], 0), + ([5.0], 1), + ([3.0, 7.0], 2), + ], + ) + def test_from_pdf_small_distributions(self, values, expected_count): + """Test Percentiles.from_pdf with edge cases of 0, 1, 2 values.""" + if len(values) == 0: + pdf = np.empty((0, 2)) + else: + pdf = np.column_stack((values, np.ones(len(values)) / len(values))) + + percentiles = Percentiles.from_pdf(pdf) + assert isinstance(percentiles, Percentiles) + + if expected_count == 0: + # All percentiles should be 0 for empty distribution + assert percentiles.p001 == 0.0 + assert percentiles.p50 == 0.0 + assert percentiles.p999 == 0.0 + elif expected_count == 1: + # All percentiles should be the single value + assert percentiles.p001 == values[0] + assert percentiles.p50 == values[0] + assert percentiles.p999 == values[0] + elif expected_count == 2: + # Percentiles should be one of the two values + assert percentiles.p001 in values + assert percentiles.p50 in values + assert percentiles.p999 in values + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("pdf", "error_match"), + [ + (np.array([1, 2, 3]), "must be a 2D array"), + (np.array([[1, 2, 3]]), "must be a 2D array"), + (np.array([[1.0, -0.5], [2.0, 0.5]]), "must be non-negative"), + (np.array([[1.0, 0.3], [2.0, 0.5]]), "must sum to 1"), + ], + ) + def test_from_pdf_invalid(self, pdf, error_match): + with pytest.raises(ValueError, match=error_match): + Percentiles.from_pdf(pdf) + + @pytest.mark.smoke + def test_marshalling(self, valid_instances): + instance, _dist_type, _values, _pdf, stats = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + for param in stats["percentiles"]: + assert param in data_dict + assert data_dict[param] == getattr(instance, param) + + recreated = Percentiles.model_validate(data_dict) + assert isinstance(recreated, Percentiles) + for param in stats["percentiles"]: + assert getattr(recreated, param) == getattr(instance, param) + + +class TestDistributionSummary: + @pytest.fixture + def valid_instances( + self, + probability_distributions: tuple[ + str | None, np.ndarray, np.ndarray, dict[str, float] + ], + ) -> tuple[ + DistributionSummary, str | None, np.ndarray, np.ndarray, dict[str, float] + ]: + dist_type, samples, pdf, stats = probability_distributions + instance = DistributionSummary( + mean=stats["mean"], + median=stats["median"], + mode=0.0, + variance=stats["variance"], + std_dev=stats["std_dev"], + min=stats["min"], + max=stats["max"], + count=stats["count"], + total_sum=stats["total_sum"], + percentiles=Percentiles(**stats["percentiles"]), + pdf=pdf, + ) + + return instance, dist_type, samples, pdf, stats + + @pytest.mark.smoke + def test_class_signatures(self): + assert issubclass(DistributionSummary, BaseModel) + assert "mean" in DistributionSummary.model_fields + assert "median" in DistributionSummary.model_fields + assert "mode" in DistributionSummary.model_fields + assert "variance" in DistributionSummary.model_fields + assert "std_dev" in DistributionSummary.model_fields + assert "min" in DistributionSummary.model_fields + assert "max" in DistributionSummary.model_fields + assert "count" in DistributionSummary.model_fields + assert "total_sum" in DistributionSummary.model_fields + assert "percentiles" in DistributionSummary.model_fields + assert "pdf" in DistributionSummary.model_fields + assert hasattr(DistributionSummary, "from_pdf") + assert hasattr(DistributionSummary, "from_values") + assert hasattr(DistributionSummary, "rate_distribution_from_timings") + assert hasattr(DistributionSummary, "concurrency_distribution_from_timings") + + @pytest.mark.smoke + def test_initialization( + self, + valid_instances: tuple[ + DistributionSummary, str | None, np.ndarray, np.ndarray, dict[str, float] + ], + ): + instance, _dist_type, _samples, _pdf, stats = valid_instances + assert instance.mean == stats["mean"] + assert instance.median == stats["median"] + assert instance.variance == stats["variance"] + assert instance.std_dev == stats["std_dev"] + assert instance.min == stats["min"] + assert instance.max == stats["max"] + assert instance.count == stats["count"] + assert instance.total_sum == stats["total_sum"] + assert isinstance(instance.percentiles, Percentiles) + for param in stats["percentiles"]: + assert getattr(instance.percentiles, param) == stats["percentiles"][param] + assert instance.pdf is None or isinstance(instance.pdf, list) + + @pytest.mark.sanity + @pytest.mark.parametrize( + "missing_field", + [ + "mean", + "median", + "mode", + "variance", + "std_dev", + "min", + "max", + "count", + "total_sum", + "percentiles", + ], + ) + def test_invalid_initialization(self, missing_field): + test_kwargs = { + "mean": 50.0, + "median": 50.0, + "mode": 50.0, + "variance": 835.0, + "std_dev": math.sqrt(835.0), + "min": 0.0, + "max": 100.0, + "count": 1001, + "total_sum": 50050.0, + "percentiles": Percentiles( + p001=0.1, + p01=1.0, + p05=5.0, + p10=10.0, + p25=25.0, + p50=50.0, + p75=75.0, + p90=90.0, + p95=95.0, + p99=99.0, + p999=99.9, + ), + } + del test_kwargs[missing_field] + + with pytest.raises(ValidationError): + DistributionSummary(**test_kwargs) + + @pytest.mark.smoke + @pytest.mark.parametrize("include_pdf", [False, True]) + def test_from_pdf( + self, + valid_instances: tuple[ + DistributionSummary, str | None, np.ndarray, np.ndarray, dict[str, float] + ], + include_pdf: bool | int, + ): + _instance, _dist_type, values, pdf, stats = valid_instances + + tolerance = 0.1 * abs(stats["std_dev"]) # within 10% of standard deviation + summary = DistributionSummary.from_pdf(pdf, include_pdf=include_pdf) + assert summary.mean == pytest.approx(stats["mean"], abs=tolerance), ( + "mean mismatch" + ) + assert summary.median == pytest.approx(stats["median"], abs=tolerance), ( + "median mismatch" + ) + assert summary.variance == pytest.approx(stats["variance"], abs=tolerance), ( + "variance mismatch" + ) + assert summary.std_dev == pytest.approx(stats["std_dev"], abs=tolerance), ( + "std_dev mismatch" + ) + assert summary.min == pytest.approx(stats["min"], abs=tolerance), "min mismatch" + assert summary.max == pytest.approx(stats["max"], abs=tolerance), "max mismatch" + assert summary.count == stats["count"], "count mismatch" + assert summary.total_sum == pytest.approx(stats["total_sum"], abs=tolerance), ( + "total_sum mismatch" + ) + assert isinstance(summary.percentiles, Percentiles) + for param in stats["percentiles"]: + assert getattr(summary.percentiles, param) == pytest.approx( + stats["percentiles"][param], + abs=tolerance if param != "p999" else (tolerance * 2), + ), f"{param} percentile mismatch" + + if include_pdf is False: + assert summary.pdf is None + elif include_pdf is True: + assert summary.pdf is not None + assert isinstance(summary.pdf, list) + assert len(summary.pdf) == len(pdf) + + @pytest.mark.sanity + @pytest.mark.parametrize("sample_size", [50, 100, 500]) + def test_from_pdf_with_sampling(self, sample_size: int): + """Test DistributionSummary.from_pdf with integer sampling.""" + # Create a large PDF + values = np.linspace(0, 100, 1000) + probabilities = np.ones(1000) / 1000 + pdf = np.column_stack((values, probabilities)) + + summary = DistributionSummary.from_pdf(pdf, include_pdf=sample_size) + assert isinstance(summary, DistributionSummary) + assert summary.pdf is not None + assert len(summary.pdf) == sample_size + + # Verify sampled PDF still represents the distribution reasonably + sampled_values = [val for val, _prob in summary.pdf] + assert min(sampled_values) >= 0 + assert max(sampled_values) <= 100 + + # Verify stats are accurate regardless of sampling + assert summary.mean == pytest.approx(50.0, abs=1.0) + assert summary.median == pytest.approx(50.0, abs=1.0) + assert summary.min == pytest.approx(0.0, abs=1.0) + assert summary.max == pytest.approx(100.0, abs=1.0) + + @pytest.mark.smoke + @pytest.mark.parametrize("include_pdf", [False, True]) + def test_from_values( + self, + valid_instances: tuple[ + DistributionSummary, str | None, np.ndarray, np.ndarray, dict[str, float] + ], + include_pdf: bool | int, + ): + _instance, _dist_type, values, _pdf, stats = valid_instances + + tolerance = 0.1 * abs(stats["std_dev"]) # within 10% of standard deviation + summary = DistributionSummary.from_values(values, include_pdf=include_pdf) + assert summary.mean == pytest.approx(stats["mean"], abs=tolerance), ( + "mean mismatch" + ) + assert summary.median == pytest.approx(stats["median"], abs=tolerance), ( + "median mismatch" + ) + assert summary.variance == pytest.approx(stats["variance"], abs=tolerance), ( + "variance mismatch" + ) + assert summary.std_dev == pytest.approx(stats["std_dev"], abs=tolerance), ( + "std_dev mismatch" + ) + assert summary.min == pytest.approx(stats["min"], abs=tolerance), "min mismatch" + assert summary.max == pytest.approx(stats["max"], abs=tolerance), "max mismatch" + assert summary.count == stats["count"], "count mismatch" + assert summary.total_sum == pytest.approx(stats["total_sum"], abs=tolerance), ( + "total_sum mismatch" + ) + assert isinstance(summary.percentiles, Percentiles) + for param in stats["percentiles"]: + assert getattr(summary.percentiles, param) == pytest.approx( + stats["percentiles"][param], + abs=tolerance if param != "p999" else (tolerance * 2), + ), f"{param} percentile mismatch" + + if include_pdf is False: + assert summary.pdf is None + elif include_pdf is True: + assert summary.pdf is not None + assert isinstance(summary.pdf, list) + assert len(summary.pdf) > 0 if len(values) > 0 else len(summary.pdf) == 0 + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("limit_start_time", "limit_end_time", "include_pdf"), + [ + (False, False, False), + (True, False, True), + (False, True, False), + (True, True, True), + ], + ) + def test_rate_distribution_from_timings( + self, + valid_instances: tuple[ + DistributionSummary, str | None, np.ndarray, np.ndarray, dict[str, float] + ], + limit_start_time: bool, + limit_end_time: bool, + include_pdf: bool | int, + ): + _instance, dist_type, _values, pdf, stats = valid_instances + + if dist_type in ("exponential", "poisson"): + pytest.skip( + f"Skipping rate distribution test for {dist_type} distribution " + "due to inherent variability and incompatibility with rate assumptions." + ) + + rng = np.random.default_rng(seed=42) + + if len(pdf) > 0: + # The PDF gives the expected distribution for the rates + # So, we can use it to sample individual, instantaneous rates + # and convert those to timings by inverting and accumulating + sampled_rates = rng.choice(pdf[:, 0], size=100000, p=pdf[:, 1]) + delta_times = 1.0 / np.clip(sampled_rates, a_min=1e-6, a_max=None) + timings = np.cumsum(delta_times) + else: + timings = np.array([]) + + # Now, compute the rate distribution from the timings and compare + start_time = stats["mean"] if limit_start_time and len(timings) > 0 else None + end_time = ( + np.max(timings) - stats["mean"] + if limit_end_time and len(timings) > 0 + else None + ) + distribution = DistributionSummary.rate_distribution_from_timings( + timings, start_time=start_time, end_time=end_time, include_pdf=include_pdf + ) + + # Check expected nearly exact values (mean and count) + expected_rate = ( + len(timings) / (timings[-1] - timings[0]) if len(timings) > 1 else 0.0 + ) + assert distribution.mean == pytest.approx(expected_rate, rel=10e-4), ( + "expected mean rate mismatch" + ) + expected_count = len(timings) + if start_time and len(timings) > 0: + expected_count -= len(timings[timings < start_time]) + if end_time and len(timings) > 0: + expected_count -= len(timings[timings > end_time]) + assert distribution.count == expected_count, "expected count mismatch" + + # Loosely validate against original stats (randomness in sampling) + tolerance = 0.5 * abs(stats["std_dev"]) # within 10% of standard deviation + assert distribution.mean == pytest.approx(stats["mean"], abs=tolerance), ( + "mean mismatch" + ) + assert distribution.median == pytest.approx(stats["median"], abs=tolerance), ( + "median mismatch" + ) + assert distribution.std_dev == pytest.approx(stats["std_dev"], abs=tolerance), ( + "std_dev mismatch" + ) + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("concurrency_type", "include_pdf"), + [ + ("sequential", False), + ("parallel", True), + ("constant_rate", False), + ("burst", True), + ("triangular_ramp", False), + ], + ) + def test_concurrency_distribution_from_timings(self, concurrency_type, include_pdf): + ( + _concurrency_type, + requests, + stats, + ) = concurrency_distributions(concurrency_type, num_requests=1000) + + distribution = DistributionSummary.concurrency_distribution_from_timings( + requests, + start_time=stats["start_time"], + end_time=stats["end_time"], + include_pdf=include_pdf, + ) + + assert distribution.mean == pytest.approx( + stats["mean_concurrency"], rel=1e-2 + ), "mean concurrency mismatch" + assert distribution.median == pytest.approx( + stats["median_concurrency"], rel=1e-2 + ), "median concurrency mismatch" + assert distribution.std_dev == pytest.approx( + stats["std_dev_concurrency"], rel=1e-2 + ), "std_dev concurrency mismatch" + + @pytest.mark.smoke + def test_marshalling(self, valid_instances): + instance, _dist_type, _values, _pdf, stats = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + for param in [ + "mean", + "median", + "mode", + "variance", + "std_dev", + "min", + "max", + "count", + "total_sum", + "percentiles", + "pdf", + ]: + assert param in data_dict + if param == "percentiles": + for p_param in stats["percentiles"]: + assert ( + getattr(instance.percentiles, p_param) + == data_dict["percentiles"][p_param] + ) + else: + assert data_dict[param] == getattr(instance, param) + + recreated = DistributionSummary.model_validate(data_dict) + assert isinstance(recreated, DistributionSummary) + for param in [ + "mean", + "median", + "mode", + "variance", + "std_dev", + "min", + "max", + "count", + "total_sum", + "percentiles", + "pdf", + ]: + if param == "percentiles": + for p_param in stats["percentiles"]: + assert getattr(recreated.percentiles, p_param) == getattr( + instance.percentiles, p_param + ) + else: + assert getattr(recreated, param) == getattr(instance, param) + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("values", "error_type"), + [ + ("not_a_list", ValueError), + ({"invalid": "dict"}, ValueError), + (None, TypeError), # None raises TypeError instead of ValueError + ], + ) + def test_from_values_invalid_input(self, values, error_type): + """Test DistributionSummary.from_values with invalid input types.""" + with pytest.raises(error_type): + DistributionSummary.from_values(values) + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("pdf", "error_match"), + [ + (np.array([1, 2, 3]), "must be a 2D array"), + (np.array([[1, 2, 3]]), "must be a 2D array"), + (np.array([[1.0, -0.5], [2.0, 0.5]]), "must be non-negative"), + (np.array([[1.0, 0.3], [2.0, 0.5]]), "must sum to 1"), + ], + ) + def test_from_pdf_invalid(self, pdf, error_match): + """Test DistributionSummary.from_pdf with invalid PDFs.""" + with pytest.raises(ValueError, match=error_match): + DistributionSummary.from_pdf(pdf) + + @pytest.mark.sanity + def test_from_values_with_weights(self): + """Test DistributionSummary.from_values with weighted values.""" + # Values with weights: (value, weight) + values = [(1.0, 2.0), (2.0, 1.0), (3.0, 1.0)] + summary = DistributionSummary.from_values(values) + + assert isinstance(summary, DistributionSummary) + # Count is sum of weights: 2 + 1 + 1 = 4 + assert summary.count == 4 + # Mean should be weighted: (1*2 + 2*1 + 3*1) / (2+1+1) = 7/4 = 1.75 + assert summary.mean == pytest.approx(1.75, abs=0.01) + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("values", "expected_count"), + [ + ([], 0), + ([5.0], 1), + ([3.0, 7.0], 2), + ], + ) + def test_from_values_small_distributions(self, values, expected_count): + """Test DistributionSummary.from_values with edge cases of 0, 1, 2 values.""" + summary = DistributionSummary.from_values(values) + assert isinstance(summary, DistributionSummary) + assert summary.count == expected_count + + if expected_count == 0: + assert summary.mean == 0.0 + assert summary.median == 0.0 + assert summary.std_dev == 0.0 + assert summary.min == 0.0 + assert summary.max == 0.0 + elif expected_count == 1: + assert summary.mean == values[0] + assert summary.median == values[0] + assert summary.std_dev == 0.0 + assert summary.min == values[0] + assert summary.max == values[0] + elif expected_count == 2: + assert summary.mean == pytest.approx(np.mean(values), abs=1e-6) + # For 2 values, median from PDF is the first value at CDF >= 0.5 + assert summary.median in values + assert summary.min == min(values) + assert summary.max == max(values) + + @pytest.mark.sanity + def test_rate_distribution_empty_timings(self): + """Test rate_distribution_from_timings with empty input.""" + summary = DistributionSummary.rate_distribution_from_timings([]) + assert summary.count == 0 + assert summary.mean == 0.0 + + @pytest.mark.sanity + def test_concurrency_distribution_empty_intervals(self): + """Test concurrency_distribution_from_timings with empty input.""" + summary = DistributionSummary.concurrency_distribution_from_timings([]) + assert summary.count == 0 + assert summary.mean == 0.0 + + @pytest.mark.sanity + def test_rate_distribution_single_event(self): + """Test rate_distribution_from_timings with single event.""" + summary = DistributionSummary.rate_distribution_from_timings([1.0]) + # Single event results in no rates (need at least 2 for intervals) + assert summary.count == 0 + assert summary.mean == 0.0 + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("timings", "expected_behavior"), + [ + ([1.0, 1.0, 1.0], "duplicate_times"), + ([1.0, 1.00001, 1.00002], "within_threshold"), + ([5.0, 3.0, 1.0, 4.0, 2.0], "unsorted"), + ], + ) + def test_rate_distribution_edge_cases(self, timings, expected_behavior): + """Test rate_distribution_from_timings with edge cases.""" + summary = DistributionSummary.rate_distribution_from_timings( + timings, threshold=1e-4 + ) + assert isinstance(summary, DistributionSummary) + assert summary.count >= 0 + + if expected_behavior == "duplicate_times": + # All duplicates merge to single time, no rate distribution + assert summary.count in {0, 1} + elif expected_behavior == "within_threshold": + # Times within threshold should merge + assert summary.count <= len(timings) + elif expected_behavior == "unsorted": + # Should handle unsorted input correctly + assert summary.count > 0 + + @pytest.mark.sanity + def test_rate_distribution_with_weights(self): + """Test rate_distribution_from_timings with weighted timestamps.""" + # Events with weights: (timestamp, weight) + weighted_times = [(1.0, 2.0), (5.0, 1.0), (10.0, 3.0)] + summary = DistributionSummary.rate_distribution_from_timings(weighted_times) + assert isinstance(summary, DistributionSummary) + # Total count should be sum of weights + assert summary.count == 6 + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("start_time", "end_time", "num_events"), + [ + (5.0, None, 50), + (None, 95.0, 50), + (5.0, 95.0, 50), + ], + ) + def test_rate_distribution_time_windows( + self, start_time: float | None, end_time: float | None, num_events: int + ): + """Test rate_distribution_from_timings with time window filtering.""" + rng = np.random.default_rng(seed=42) + timings = sorted(rng.uniform(0, 100, num_events)) + + summary = DistributionSummary.rate_distribution_from_timings( + timings, start_time=start_time, end_time=end_time + ) + assert isinstance(summary, DistributionSummary) + + # Count should be less than or equal to original when filtered + if start_time or end_time: + assert summary.count <= num_events + else: + assert summary.count == num_events + + @pytest.mark.sanity + def test_concurrency_with_weighted_intervals(self): + """Test concurrency_distribution_from_timings with weighted intervals.""" + # Intervals with weights: (start, end, weight) + intervals = [(0.0, 10.0, 2.0), (5.0, 15.0, 1.0)] + summary = DistributionSummary.concurrency_distribution_from_timings(intervals) + + assert isinstance(summary, DistributionSummary) + assert summary.count == 2 + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("intervals", "expected_behavior"), + [ + ([(1.0, 1.0)], "zero_duration"), + ([(1.0, 5.0), (2.0, 6.0), (3.0, 7.0)], "overlapping"), + ([(1.0, 10.0), (2.0, 8.0), (3.0, 6.0)], "nested"), + ([(1.0, 3.0), (5.0, 7.0), (9.0, 11.0)], "non_overlapping"), + ], + ) + def test_concurrency_distribution_edge_cases(self, intervals, expected_behavior): + """Test concurrency_distribution_from_timings with various interval patterns.""" + summary = DistributionSummary.concurrency_distribution_from_timings(intervals) + assert isinstance(summary, DistributionSummary) + assert summary.count == len(intervals) + + if expected_behavior == "zero_duration": + # Zero duration intervals should be handled + assert summary.mean == 0.0 or summary.count == 1 + elif expected_behavior == "overlapping": + # Overlapping intervals should show concurrency > 1 at some points + assert summary.max >= 1.0 + elif expected_behavior == "nested": + # Nested intervals should show max concurrency + assert summary.max >= 2.0 + elif expected_behavior == "non_overlapping": + # Non-overlapping intervals should have mean concurrency closer to 1 + assert summary.mean >= 0.0 + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("start_time", "end_time"), + [ + (5.0, None), + (None, 75.0), + (5.0, 75.0), + ], + ) + def test_concurrency_distribution_time_windows( + self, start_time: float | None, end_time: float | None + ): + """Test concurrency_distribution_from_timings with time window filtering.""" + rng = np.random.default_rng(seed=42) + num_intervals = 50 + starts = rng.uniform(0, 80, num_intervals) + intervals = [(start, start + rng.uniform(5, 25)) for start in starts] + + summary = DistributionSummary.concurrency_distribution_from_timings( + intervals, start_time=start_time, end_time=end_time + ) + assert isinstance(summary, DistributionSummary) + assert summary.count <= num_intervals + + @pytest.mark.regression + @pytest.mark.parametrize( + ("distribution", "dist_params", "sample_size"), + [ + ("normal", {"loc": 50.0, "scale": 10.0}, 10000), + ("uniform", {"low": 0.0, "high": 100.0}, 10000), + ("exponential", {"scale": 5.0}, 10000), + ], + ) + def test_statistical_accuracy_against_numpy( + self, distribution: str, dist_params: dict, sample_size: int + ): + """Validate statistical calculations against numpy's implementations.""" + rng = np.random.default_rng(seed=42) + + # Generate samples using numpy's distribution + samples = getattr(rng, distribution)(**dist_params, size=sample_size) + + # Create distribution summary + summary = DistributionSummary.from_values(samples) + + # Validate against numpy's statistics + tolerance = 0.05 * abs(np.std(samples)) # 5% of std dev + assert summary.mean == pytest.approx(np.mean(samples), abs=tolerance) + assert summary.median == pytest.approx(np.median(samples), abs=tolerance) + assert summary.variance == pytest.approx(np.var(samples), abs=tolerance * 2) + assert summary.std_dev == pytest.approx(np.std(samples), abs=tolerance) + assert summary.min == pytest.approx(np.min(samples), abs=tolerance) + assert summary.max == pytest.approx(np.max(samples), abs=tolerance) + assert summary.count == sample_size + assert summary.total_sum == pytest.approx( + np.sum(samples), abs=tolerance * sample_size + ) + + # Validate percentiles + for percentile_name, percentile_value in { + "p001": 0.1, + "p01": 1.0, + "p05": 5.0, + "p10": 10.0, + "p25": 25.0, + "p50": 50.0, + "p75": 75.0, + "p90": 90.0, + "p95": 95.0, + "p99": 99.0, + "p999": 99.9, + }.items(): + expected = np.percentile(samples, percentile_value) + actual = getattr(summary.percentiles, percentile_name) + # Use larger tolerance for extreme percentiles (p001, p999) + percentile_tolerance = ( + tolerance * 3 if percentile_name in {"p001", "p999"} else tolerance + ) + assert actual == pytest.approx(expected, abs=percentile_tolerance) + + @pytest.mark.regression + def test_pdf_normalization_accuracy(self): + """Test that PDF creation maintains accurate probability normalization.""" + rng = np.random.default_rng(seed=42) + samples = rng.normal(loc=100.0, scale=15.0, size=5000) + + summary = DistributionSummary.from_values(samples, include_pdf=True) + + assert summary.pdf is not None + # Extract probabilities and verify they sum to approximately 1 + probabilities = np.array([prob for _val, prob in summary.pdf]) + assert np.sum(probabilities) == pytest.approx(1.0, abs=1e-6) + + # Verify all probabilities are non-negative + assert np.all(probabilities >= 0.0) + + # Verify PDF values are sorted + values = np.array([val for val, _prob in summary.pdf]) + assert np.all(values[:-1] <= values[1:]) + + @pytest.mark.regression + def test_weighted_statistics_accuracy(self): + """Test that weighted statistics are calculated correctly.""" + # Known weighted distribution + values_weights = [ + (10.0, 1.0), + (20.0, 2.0), + (30.0, 3.0), + (40.0, 2.0), + (50.0, 1.0), + ] + + summary = DistributionSummary.from_values(values_weights) + + # Manual calculation for verification + values = np.array([10.0, 20.0, 30.0, 40.0, 50.0]) + weights = np.array([1.0, 2.0, 3.0, 2.0, 1.0]) + + expected_mean = np.sum(values * weights) / np.sum(weights) + expected_count = int(np.sum(weights)) + expected_total = np.sum(values * weights) + + assert summary.count == expected_count + assert summary.mean == pytest.approx(expected_mean, abs=1e-6) + assert summary.total_sum == pytest.approx(expected_total, abs=1e-6) + + # Verify variance calculation + expected_variance = np.sum(weights * (values - expected_mean) ** 2) / np.sum( + weights + ) + assert summary.variance == pytest.approx(expected_variance, abs=1e-6) + + +class TestStatusDistributionSummary: + @pytest.fixture( + params=[ + { + "successful": [1.0, 2.0, 3.0], + "incomplete": [4.0, 5.0], + "errored": [6.0], + }, + { + "successful": np.array([10.0, 20.0, 30.0, 40.0]), + "incomplete": np.array([50.0]), + "errored": np.array([]), + }, + { + "successful": [], + "incomplete": [], + "errored": [], + }, + ] + ) + def valid_instances( + self, + request, + ) -> tuple[StatusDistributionSummary, dict[str, list[float] | np.ndarray]]: + """Fixture providing test data for StatusDistributionSummary.""" + test_data = request.param + instance = StatusDistributionSummary.from_values( + successful=test_data["successful"], + incomplete=test_data["incomplete"], + errored=test_data["errored"], + ) + return instance, test_data + + @pytest.mark.smoke + def test_class_signatures(self): + """Test StatusDistributionSummary class structure and methods.""" + assert hasattr(StatusDistributionSummary, "from_values") + assert hasattr(StatusDistributionSummary, "from_values_function") + assert hasattr(StatusDistributionSummary, "rate_distribution_from_timings") + assert hasattr( + StatusDistributionSummary, "rate_distribution_from_timings_function" + ) + assert hasattr( + StatusDistributionSummary, "concurrency_distribution_from_timings" + ) + assert hasattr( + StatusDistributionSummary, "concurrency_distribution_from_timings_function" + ) + assert "total" in StatusDistributionSummary.model_fields + assert "successful" in StatusDistributionSummary.model_fields + assert "incomplete" in StatusDistributionSummary.model_fields + assert "errored" in StatusDistributionSummary.model_fields + # Properties are checked via hasattr, actual property nature tested separately + assert hasattr(StatusDistributionSummary, "count") + assert hasattr(StatusDistributionSummary, "total_sum") + + @pytest.mark.smoke + def test_initialization( + self, + valid_instances: tuple[ + StatusDistributionSummary, dict[str, list[float] | np.ndarray] + ], + ): + """Test StatusDistributionSummary initialization.""" + instance, test_data = valid_instances + assert isinstance(instance, StatusDistributionSummary) + assert isinstance(instance.total, DistributionSummary) + assert isinstance(instance.successful, DistributionSummary) + assert isinstance(instance.incomplete, DistributionSummary) + assert isinstance(instance.errored, DistributionSummary) + + # Verify counts match expected + successful_count = ( + len(test_data["successful"]) + if isinstance(test_data["successful"], list) + else test_data["successful"].shape[0] + ) + incomplete_count = ( + len(test_data["incomplete"]) + if isinstance(test_data["incomplete"], list) + else test_data["incomplete"].shape[0] + ) + errored_count = ( + len(test_data["errored"]) + if isinstance(test_data["errored"], list) + else test_data["errored"].shape[0] + ) + + assert instance.successful.count == successful_count + assert instance.incomplete.count == incomplete_count + assert instance.errored.count == errored_count + assert ( + instance.total.count == successful_count + incomplete_count + errored_count + ) + + @pytest.mark.sanity + @pytest.mark.parametrize( + ("field", "value"), + [ + ("successful", "invalid_string"), + ("incomplete", 123), + ("errored", [1, 2, 3]), + ("total", {"dict": "value"}), + ], + ) + def test_invalid_initialization(self, field, value): + """Test StatusDistributionSummary with invalid field types.""" + test_kwargs = { + "successful": DistributionSummary.from_values([1.0, 2.0]), + "incomplete": DistributionSummary.from_values([3.0]), + "errored": DistributionSummary.from_values([]), + "total": DistributionSummary.from_values([1.0, 2.0, 3.0]), + } + test_kwargs[field] = value + + with pytest.raises(ValidationError): + StatusDistributionSummary(**test_kwargs) + + @pytest.mark.smoke + @pytest.mark.parametrize("include_pdf", [False, True]) + def test_from_values( + self, + valid_instances: tuple[ + StatusDistributionSummary, dict[str, list[float] | np.ndarray] + ], + include_pdf: bool | int, + ): + """Test creating StatusDistributionSummary from values.""" + _instance, test_data = valid_instances + + summary = StatusDistributionSummary.from_values( + successful=test_data["successful"], + incomplete=test_data["incomplete"], + errored=test_data["errored"], + include_pdf=include_pdf, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert isinstance(summary.total, DistributionSummary) + assert isinstance(summary.successful, DistributionSummary) + assert isinstance(summary.incomplete, DistributionSummary) + assert isinstance(summary.errored, DistributionSummary) + + if include_pdf is False: + assert summary.total.pdf is None + assert summary.successful.pdf is None + assert summary.incomplete.pdf is None + assert summary.errored.pdf is None + elif include_pdf is True: + assert summary.total.pdf is not None or summary.total.count == 0 + assert summary.successful.pdf is not None or summary.successful.count == 0 + assert summary.incomplete.pdf is not None or summary.incomplete.count == 0 + assert summary.errored.pdf is not None or summary.errored.count == 0 + + @pytest.mark.smoke + @pytest.mark.parametrize( + ("limit_start_time", "limit_end_time", "include_pdf"), + [ + (False, False, False), + (True, False, True), + (False, True, False), + (True, True, True), + ], + ) + def test_rate_distribution_from_timings( + self, + limit_start_time: bool, + limit_end_time: bool, + include_pdf: bool | int, + ): + """Test creating rate distribution from timings by status.""" + rng = np.random.default_rng(seed=42) + successful_times = rng.uniform(0, 100, 50).tolist() + incomplete_times = rng.uniform(0, 100, 20).tolist() + errored_times = rng.uniform(0, 100, 10).tolist() + + start_time = 25.0 if limit_start_time else None + end_time = 75.0 if limit_end_time else None + + summary = StatusDistributionSummary.rate_distribution_from_timings( + successful=successful_times, + incomplete=incomplete_times, + errored=errored_times, + start_time=start_time, + end_time=end_time, + include_pdf=include_pdf, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert isinstance(summary.total, DistributionSummary) + assert isinstance(summary.successful, DistributionSummary) + assert isinstance(summary.incomplete, DistributionSummary) + assert isinstance(summary.errored, DistributionSummary) + + # Verify counts are reasonable + assert summary.total.count >= 0 + assert summary.successful.count >= 0 + assert summary.incomplete.count >= 0 + assert summary.errored.count >= 0 + + @pytest.mark.smoke + @pytest.mark.parametrize( + "include_pdf", + [ + False, + True, + ], + ) + def test_concurrency_distribution_from_timings(self, include_pdf: bool | int): + """Test creating concurrency distribution from intervals by status.""" + rng = np.random.default_rng(seed=42) + num_successful = 30 + num_incomplete = 10 + num_errored = 5 + + # Generate realistic intervals (start, end) + successful_starts = rng.uniform(0, 80, num_successful) + successful_intervals = [ + (start, start + rng.uniform(1, 20)) for start in successful_starts + ] + + incomplete_starts = rng.uniform(0, 80, num_incomplete) + incomplete_intervals = [ + (start, start + rng.uniform(1, 20)) for start in incomplete_starts + ] + + errored_starts = rng.uniform(0, 80, num_errored) + errored_intervals = [ + (start, start + rng.uniform(1, 20)) for start in errored_starts + ] + + summary = StatusDistributionSummary.concurrency_distribution_from_timings( + successful=successful_intervals, + incomplete=incomplete_intervals, + errored=errored_intervals, + include_pdf=include_pdf, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert isinstance(summary.total, DistributionSummary) + assert isinstance(summary.successful, DistributionSummary) + assert isinstance(summary.incomplete, DistributionSummary) + assert isinstance(summary.errored, DistributionSummary) + + # Verify counts match + assert summary.successful.count == num_successful + assert summary.incomplete.count == num_incomplete + assert summary.errored.count == num_errored + assert summary.total.count == num_successful + num_incomplete + num_errored + + @pytest.mark.smoke + def test_marshalling(self, valid_instances): + """Test StatusDistributionSummary serialization and deserialization.""" + instance, _test_data = valid_instances + data_dict = instance.model_dump() + assert isinstance(data_dict, dict) + assert "total" in data_dict + assert "successful" in data_dict + assert "incomplete" in data_dict + assert "errored" in data_dict + + # Verify each status has distribution summary data + for status in ["total", "successful", "incomplete", "errored"]: + assert isinstance(data_dict[status], dict) + assert "mean" in data_dict[status] + assert "median" in data_dict[status] + assert "count" in data_dict[status] + + recreated = StatusDistributionSummary.model_validate(data_dict) + assert isinstance(recreated, StatusDistributionSummary) + assert recreated.total.count == instance.total.count + assert recreated.successful.count == instance.successful.count + assert recreated.incomplete.count == instance.incomplete.count + assert recreated.errored.count == instance.errored.count + + @pytest.mark.smoke + def test_count_property(self, valid_instances): + """Test StatusDistributionSummary.count property.""" + instance, test_data = valid_instances + assert isinstance(instance.count, int) + assert instance.count == instance.total.count + + successful_count = ( + len(test_data["successful"]) + if isinstance(test_data["successful"], list) + else test_data["successful"].shape[0] + ) + incomplete_count = ( + len(test_data["incomplete"]) + if isinstance(test_data["incomplete"], list) + else test_data["incomplete"].shape[0] + ) + errored_count = ( + len(test_data["errored"]) + if isinstance(test_data["errored"], list) + else test_data["errored"].shape[0] + ) + + expected_count = successful_count + incomplete_count + errored_count + assert instance.count == expected_count + + @pytest.mark.smoke + def test_total_sum_property(self, valid_instances): + """Test StatusDistributionSummary.total_sum property.""" + instance, _test_data = valid_instances + assert isinstance(instance.total_sum, float) + assert instance.total_sum == instance.total.total_sum + + @pytest.mark.smoke + @pytest.mark.parametrize("include_pdf", [False, True]) + def test_from_values_function(self, include_pdf: bool | int): + """Test creating StatusDistributionSummary from values via function.""" + + class MockObject: + def __init__(self, value: float): + self.value = value + + def extract_value(obj: MockObject) -> float: + return obj.value + + successful_objs = [MockObject(1.0), MockObject(2.0), MockObject(3.0)] + incomplete_objs = [MockObject(4.0), MockObject(5.0)] + errored_objs = [MockObject(6.0)] + + summary = StatusDistributionSummary.from_values_function( + function=extract_value, + successful=successful_objs, + incomplete=incomplete_objs, + errored=errored_objs, + include_pdf=include_pdf, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert summary.successful.count == 3 + assert summary.incomplete.count == 2 + assert summary.errored.count == 1 + assert summary.total.count == 6 + + # Verify means + assert summary.successful.mean == pytest.approx(2.0, abs=0.01) + assert summary.incomplete.mean == pytest.approx(4.5, abs=0.01) + assert summary.errored.mean == pytest.approx(6.0, abs=0.01) + + @pytest.mark.smoke + @pytest.mark.parametrize("include_pdf", [False, True]) + def test_rate_distribution_from_timings_function(self, include_pdf: bool | int): + """Test creating rate distribution from timings via function extraction.""" + + class MockEvent: + def __init__(self, timestamp: float): + self.timestamp = timestamp + + def extract_timestamp(event: MockEvent) -> float: + return event.timestamp + + rng = np.random.default_rng(seed=42) + successful_events = [MockEvent(ts) for ts in rng.uniform(0, 100, 30)] + incomplete_events = [MockEvent(ts) for ts in rng.uniform(0, 100, 15)] + errored_events = [MockEvent(ts) for ts in rng.uniform(0, 100, 5)] + + summary = StatusDistributionSummary.rate_distribution_from_timings_function( + function=extract_timestamp, + successful=successful_events, + incomplete=incomplete_events, + errored=errored_events, + include_pdf=include_pdf, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert isinstance(summary.total, DistributionSummary) + assert isinstance(summary.successful, DistributionSummary) + assert isinstance(summary.incomplete, DistributionSummary) + assert isinstance(summary.errored, DistributionSummary) + + # Verify counts are reasonable + assert summary.successful.count >= 0 + assert summary.incomplete.count >= 0 + assert summary.errored.count >= 0 + + @pytest.mark.smoke + @pytest.mark.parametrize("include_pdf", [False, True]) + def test_concurrency_distribution_from_timings_function( + self, include_pdf: bool | int + ): + """Test creating concurrency distribution from intervals via function.""" + + class MockRequest: + def __init__(self, start: float, end: float): + self.start = start + self.end = end + + def extract_interval(request: MockRequest) -> tuple[float, float]: + return (request.start, request.end) + + rng = np.random.default_rng(seed=42) + num_successful = 20 + num_incomplete = 10 + num_errored = 5 + + successful_starts = rng.uniform(0, 80, num_successful) + successful_requests = [ + MockRequest(start, start + rng.uniform(1, 20)) + for start in successful_starts + ] + + incomplete_starts = rng.uniform(0, 80, num_incomplete) + incomplete_requests = [ + MockRequest(start, start + rng.uniform(1, 20)) + for start in incomplete_starts + ] + + errored_starts = rng.uniform(0, 80, num_errored) + errored_requests = [ + MockRequest(start, start + rng.uniform(1, 20)) for start in errored_starts + ] + + summary = ( + StatusDistributionSummary.concurrency_distribution_from_timings_function( + function=extract_interval, + successful=successful_requests, + incomplete=incomplete_requests, + errored=errored_requests, + include_pdf=include_pdf, + ) + ) + + assert isinstance(summary, StatusDistributionSummary) + assert isinstance(summary.total, DistributionSummary) + assert isinstance(summary.successful, DistributionSummary) + assert isinstance(summary.incomplete, DistributionSummary) + assert isinstance(summary.errored, DistributionSummary) + + # Verify counts match + assert summary.successful.count == num_successful + assert summary.incomplete.count == num_incomplete + assert summary.errored.count == num_errored + assert summary.total.count == num_successful + num_incomplete + num_errored + + @pytest.mark.sanity + def test_from_values_function_with_none_returns(self): + """Test from_values_function when function returns None for some objects.""" + + class MockObject: + def __init__(self, value: float | None): + self.value = value + + def extract_value(obj: MockObject) -> float | None: + return obj.value + + successful_objs = [MockObject(1.0), MockObject(None), MockObject(3.0)] + incomplete_objs = [MockObject(4.0), MockObject(None)] + errored_objs = [MockObject(None), MockObject(6.0)] + + summary = StatusDistributionSummary.from_values_function( + function=extract_value, + successful=successful_objs, + incomplete=incomplete_objs, + errored=errored_objs, + ) + + assert isinstance(summary, StatusDistributionSummary) + # Only non-None values should be counted + assert summary.successful.count == 2 + assert summary.incomplete.count == 1 + assert summary.errored.count == 1 + assert summary.total.count == 4 + + @pytest.mark.sanity + def test_from_values_function_with_sequence_returns(self): + """Test from_values_function when function returns sequences.""" + + class MockObject: + def __init__(self, values: list[float]): + self.values = values + + def extract_values(obj: MockObject) -> list[float]: + return obj.values + + successful_objs = [MockObject([1.0, 2.0]), MockObject([3.0])] + incomplete_objs = [MockObject([4.0, 5.0, 6.0])] + errored_objs = [MockObject([])] + + summary = StatusDistributionSummary.from_values_function( + function=extract_values, + successful=successful_objs, + incomplete=incomplete_objs, + errored=errored_objs, + ) + + assert isinstance(summary, StatusDistributionSummary) + # Count should be total number of values extracted + assert summary.successful.count == 3 + assert summary.incomplete.count == 3 + assert summary.errored.count == 0 + assert summary.total.count == 6 + + @pytest.mark.sanity + def test_rate_distribution_with_weighted_timestamps(self): + """Test rate_distribution_from_timings with weighted timestamps by status.""" + weighted_successful = [(1.0, 2.0), (5.0, 1.0), (10.0, 3.0)] + weighted_incomplete = [(2.0, 1.0), (8.0, 2.0)] + weighted_errored = [(3.0, 1.0), (7.0, 2.0)] + + summary = StatusDistributionSummary.rate_distribution_from_timings( + successful=weighted_successful, + incomplete=weighted_incomplete, + errored=weighted_errored, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert summary.successful.count == 6 # Sum of weights + assert summary.incomplete.count == 3 + assert summary.errored.count == 3 + assert summary.total.count == 12 + + @pytest.mark.sanity + def test_concurrency_distribution_with_weighted_intervals(self): + """Test concurrency_distribution_from_timings with weighted intervals.""" + weighted_successful = [(0.0, 10.0, 2.0), (5.0, 15.0, 1.0)] + weighted_incomplete = [(8.0, 18.0, 3.0)] + weighted_errored = [(12.0, 20.0, 1.0)] + + summary = StatusDistributionSummary.concurrency_distribution_from_timings( + successful=weighted_successful, + incomplete=weighted_incomplete, + errored=weighted_errored, + ) + + assert isinstance(summary, StatusDistributionSummary) + assert summary.successful.count == 2 + assert summary.incomplete.count == 1 + assert summary.errored.count == 1 + assert summary.total.count == 4 diff --git a/tests/unit/utils/test_statistics.py b/tests/unit/utils/test_statistics.py deleted file mode 100644 index d0f04d99..00000000 --- a/tests/unit/utils/test_statistics.py +++ /dev/null @@ -1,785 +0,0 @@ -import math -import time -from typing import Literal - -import numpy as np -import pytest - -from guidellm.utils.statistics import ( - DistributionSummary, - Percentiles, - RunningStats, - StatusDistributionSummary, - TimeRunningStats, -) - - -def create_default_percentiles() -> Percentiles: - return Percentiles( - p001=0.1, - p01=1.0, - p05=5.0, - p10=10.0, - p25=25.0, - p50=50.0, - p75=75.0, - p90=90.0, - p95=95.0, - p99=99.0, - p999=99.9, - ) - - -def create_default_distribution_summary() -> DistributionSummary: - return DistributionSummary( - mean=50.0, - median=50.0, - mode=50.0, - variance=835, - std_dev=math.sqrt(835), - min=0.0, - max=100.0, - count=1001, - total_sum=50050.0, - percentiles=create_default_percentiles(), - ) - - -@pytest.mark.smoke -def test_percentiles_initialization(): - percentiles = create_default_percentiles() - assert percentiles.p001 == 0.1 - assert percentiles.p01 == 1.0 - assert percentiles.p05 == 5.0 - assert percentiles.p10 == 10.0 - assert percentiles.p25 == 25.0 - assert percentiles.p50 == 50.0 - assert percentiles.p75 == 75.0 - assert percentiles.p90 == 90.0 - assert percentiles.p95 == 95.0 - assert percentiles.p99 == 99.0 - assert percentiles.p999 == 99.9 - - -@pytest.mark.smoke -def test_percentiles_invalid_initialization(): - test_kwargs = { - "p001": 0.1, - "p01": 1.0, - "p05": 5.0, - "p10": 10.0, - "p25": 25.0, - "p50": 50.0, - "p75": 75.0, - "p90": 90.0, - "p95": 95.0, - "p99": 99.0, - "p999": 99.9, - } - test_missing_keys = list(test_kwargs.keys()) - - for missing_key in test_missing_keys: - kwargs = {key: val for key, val in test_kwargs.items() if key != missing_key} - with pytest.raises(ValueError): - Percentiles(**kwargs) - - -@pytest.mark.smoke -def test_percentiles_marshalling(): - percentiles = create_default_percentiles() - serialized = percentiles.model_dump() - deserialized = Percentiles.model_validate(serialized) - - for key, value in vars(percentiles).items(): - assert getattr(deserialized, key) == value - - -@pytest.mark.smoke -def test_distribution_summary_initilaization(): - distribution_summary = create_default_distribution_summary() - assert distribution_summary.mean == 50.0 - assert distribution_summary.median == 50.0 - assert distribution_summary.mode == 50.0 - assert distribution_summary.variance == 835 - assert distribution_summary.std_dev == math.sqrt(835) - assert distribution_summary.min == 0.0 - assert distribution_summary.max == 100.0 - assert distribution_summary.count == 1001 - assert distribution_summary.total_sum == 50050.0 - assert distribution_summary.percentiles.p001 == 0.1 - assert distribution_summary.percentiles.p01 == 1.0 - assert distribution_summary.percentiles.p05 == 5.0 - assert distribution_summary.percentiles.p10 == 10.0 - assert distribution_summary.percentiles.p25 == 25.0 - assert distribution_summary.percentiles.p50 == 50.0 - assert distribution_summary.percentiles.p75 == 75.0 - assert distribution_summary.percentiles.p90 == 90.0 - assert distribution_summary.percentiles.p95 == 95.0 - assert distribution_summary.percentiles.p99 == 99.0 - assert distribution_summary.percentiles.p999 == 99.9 - - -@pytest.mark.smoke -def test_distribution_summary_invalid_initialization(): - test_kwargs = { - "mean": 50.0, - "median": 50.0, - "mode": 50.0, - "variance": 835, - "std_dev": math.sqrt(835), - "min": 0.0, - "max": 100.0, - "count": 1001, - "total_sum": 50050.0, - "percentiles": create_default_percentiles(), - } - test_missing_keys = list(test_kwargs.keys()) - for missing_key in test_missing_keys: - kwargs = {key: val for key, val in test_kwargs.items() if key != missing_key} - with pytest.raises(ValueError): - DistributionSummary(**kwargs) # type: ignore[arg-type] - - -@pytest.mark.smoke -def test_distribution_summary_marshalling(): - distribution_summary = create_default_distribution_summary() - serialized = distribution_summary.model_dump() - deserialized = DistributionSummary.model_validate(serialized) - - for key, value in vars(distribution_summary).items(): - assert getattr(deserialized, key) == value - - -@pytest.mark.smoke -def test_distribution_summary_from_distribution_function(): - values = [val / 10.0 for val in range(1001)] - distribution = [(val, 1.0) for val in values] - distribution_summary = DistributionSummary.from_distribution_function(distribution) - assert distribution_summary.mean == pytest.approx(np.mean(values)) - assert distribution_summary.median == pytest.approx(np.median(values)) - assert distribution_summary.mode == 0.0 - assert distribution_summary.variance == pytest.approx(np.var(values, ddof=0)) - assert distribution_summary.std_dev == pytest.approx(np.std(values, ddof=0)) - assert distribution_summary.min == min(values) - assert distribution_summary.max == max(values) - assert distribution_summary.count == len(values) - assert distribution_summary.total_sum == sum(values) - assert distribution_summary.percentiles.p001 == pytest.approx( - np.percentile(values, 0.1) - ) - assert distribution_summary.percentiles.p01 == pytest.approx( - np.percentile(values, 1.0) - ) - assert distribution_summary.percentiles.p05 == pytest.approx( - np.percentile(values, 5.0) - ) - assert distribution_summary.percentiles.p10 == pytest.approx( - np.percentile(values, 10.0) - ) - assert distribution_summary.percentiles.p25 == pytest.approx( - np.percentile(values, 25.0) - ) - assert distribution_summary.percentiles.p50 == pytest.approx( - np.percentile(values, 50.0) - ) - assert distribution_summary.percentiles.p75 == pytest.approx( - np.percentile(values, 75.0) - ) - assert distribution_summary.percentiles.p90 == pytest.approx( - np.percentile(values, 90.0) - ) - assert distribution_summary.percentiles.p95 == pytest.approx( - np.percentile(values, 95.0) - ) - assert distribution_summary.percentiles.p99 == pytest.approx( - np.percentile(values, 99.0) - ) - assert distribution_summary.percentiles.p999 == pytest.approx( - np.percentile(values, 99.9) - ) - assert distribution_summary.cumulative_distribution_function is None - - distribution_summary_cdf = DistributionSummary.from_distribution_function( - distribution, include_cdf=True - ) - assert distribution_summary_cdf.cumulative_distribution_function is not None - assert len(distribution_summary_cdf.cumulative_distribution_function) == len(values) - - -def test_distribution_summary_from_values(): - values = [val / 10 for val in range(1001)] - distribution_summary = DistributionSummary.from_values(values) - assert distribution_summary.mean == pytest.approx(np.mean(values)) - assert distribution_summary.median == pytest.approx(np.median(values)) - assert distribution_summary.mode == 0.0 - assert distribution_summary.variance == pytest.approx(np.var(values, ddof=0)) - assert distribution_summary.std_dev == pytest.approx(np.std(values, ddof=0)) - assert distribution_summary.min == min(values) - assert distribution_summary.max == max(values) - assert distribution_summary.count == len(values) - assert distribution_summary.total_sum == sum(values) - assert distribution_summary.percentiles.p001 == pytest.approx( - np.percentile(values, 0.1) - ) - assert distribution_summary.percentiles.p01 == pytest.approx( - np.percentile(values, 1.0) - ) - assert distribution_summary.percentiles.p05 == pytest.approx( - np.percentile(values, 5.0) - ) - assert distribution_summary.percentiles.p10 == pytest.approx( - np.percentile(values, 10.0) - ) - assert distribution_summary.percentiles.p25 == pytest.approx( - np.percentile(values, 25.0) - ) - assert distribution_summary.percentiles.p50 == pytest.approx( - np.percentile(values, 50.0) - ) - assert distribution_summary.percentiles.p75 == pytest.approx( - np.percentile(values, 75.0) - ) - assert distribution_summary.percentiles.p90 == pytest.approx( - np.percentile(values, 90.0) - ) - assert distribution_summary.percentiles.p95 == pytest.approx( - np.percentile(values, 95.0) - ) - assert distribution_summary.percentiles.p99 == pytest.approx( - np.percentile(values, 99.0) - ) - assert distribution_summary.percentiles.p999 == pytest.approx( - np.percentile(values, 99.9) - ) - assert distribution_summary.cumulative_distribution_function is None - - distribution_summary_weights = DistributionSummary.from_values( - values, weights=[2] * len(values) - ) - assert distribution_summary_weights.mean == pytest.approx(np.mean(values)) - assert distribution_summary_weights.median == pytest.approx(np.median(values)) - assert distribution_summary_weights.mode == 0.0 - assert distribution_summary_weights.variance == pytest.approx( - np.var(values, ddof=0) - ) - assert distribution_summary_weights.std_dev == pytest.approx(np.std(values, ddof=0)) - assert distribution_summary_weights.min == min(values) - assert distribution_summary_weights.max == max(values) - assert distribution_summary_weights.count == len(values) - assert distribution_summary_weights.total_sum == sum(values) - assert distribution_summary_weights.cumulative_distribution_function is None - - distribution_summary_cdf = DistributionSummary.from_values(values, include_cdf=True) - assert distribution_summary_cdf.cumulative_distribution_function is not None - assert len(distribution_summary_cdf.cumulative_distribution_function) == len(values) - - -def test_distribution_summary_from_request_times_concurrency(): - # create consistent timestamped values matching a rate of 10 per second - requests = [(val / 10, val / 10 + 1) for val in range(10001)] - distribution_summary = DistributionSummary.from_request_times( - requests, distribution_type="concurrency" - ) - assert distribution_summary.mean == pytest.approx(10.0, abs=0.01) - assert distribution_summary.median == pytest.approx(10.0) - assert distribution_summary.mode == 10.0 - assert distribution_summary.variance == pytest.approx(0, abs=0.1) - assert distribution_summary.std_dev == pytest.approx(0, abs=0.3) - assert distribution_summary.min == pytest.approx(1) - assert distribution_summary.max == pytest.approx(10.0) - assert distribution_summary.count == 10 - assert distribution_summary.total_sum == pytest.approx(55.0) - assert distribution_summary.percentiles.p001 == pytest.approx(10, abs=5) - assert distribution_summary.percentiles.p01 == pytest.approx(10) - assert distribution_summary.percentiles.p05 == pytest.approx(10) - assert distribution_summary.percentiles.p10 == pytest.approx(10) - assert distribution_summary.percentiles.p25 == pytest.approx(10) - assert distribution_summary.percentiles.p50 == pytest.approx(10) - assert distribution_summary.percentiles.p75 == pytest.approx(10) - assert distribution_summary.percentiles.p90 == pytest.approx(10) - assert distribution_summary.percentiles.p95 == pytest.approx(10) - assert distribution_summary.percentiles.p99 == pytest.approx(10) - assert distribution_summary.percentiles.p999 == pytest.approx(10) - assert distribution_summary.cumulative_distribution_function is None - - distribution_summary_cdf = DistributionSummary.from_request_times( - requests, distribution_type="concurrency", include_cdf=True - ) - assert distribution_summary_cdf.cumulative_distribution_function is not None - assert len(distribution_summary_cdf.cumulative_distribution_function) == 10 - - -def test_distribution_summary_from_request_times_rate(): - # create consistent timestamped values matching a rate of 10 per second - requests = [(val / 10, val / 10 + 1) for val in range(10001)] - distribution_summary = DistributionSummary.from_request_times( - requests, distribution_type="rate" - ) - assert distribution_summary.mean == pytest.approx(10.0, abs=0.01) - assert distribution_summary.median == pytest.approx(10.0) - assert distribution_summary.mode == pytest.approx(10.0) - assert distribution_summary.variance == pytest.approx(0, abs=0.1) - assert distribution_summary.std_dev == pytest.approx(0, abs=0.3) - assert distribution_summary.min == pytest.approx(1.0) - assert distribution_summary.max == pytest.approx(10.0) - assert distribution_summary.count == 12 - assert distribution_summary.total_sum == pytest.approx(111.0) - assert distribution_summary.percentiles.p001 == pytest.approx(10.0, abs=0.5) - assert distribution_summary.percentiles.p01 == pytest.approx(10.0) - assert distribution_summary.percentiles.p05 == pytest.approx(10.0) - assert distribution_summary.percentiles.p10 == pytest.approx(10.0) - assert distribution_summary.percentiles.p25 == pytest.approx(10.0) - assert distribution_summary.percentiles.p50 == pytest.approx(10.0) - assert distribution_summary.percentiles.p75 == pytest.approx(10.0) - assert distribution_summary.percentiles.p90 == pytest.approx(10.0) - assert distribution_summary.percentiles.p95 == pytest.approx(10.0) - assert distribution_summary.percentiles.p99 == pytest.approx(10.0) - assert distribution_summary.percentiles.p999 == pytest.approx(10.0) - assert distribution_summary.cumulative_distribution_function is None - - distribution_summary_cdf = DistributionSummary.from_request_times( - requests, distribution_type="rate", include_cdf=True - ) - assert distribution_summary_cdf.cumulative_distribution_function is not None - assert len(distribution_summary_cdf.cumulative_distribution_function) == 12 - - -def test_distribution_summary_from_iterable_request_times(): - # create consistent timestamped values matching a rate of 10 per second - requests = [(val / 10, val / 10 + 1) for val in range(10001)] - # create 9 iterations for each request with first iter at start + 0.1 - # and spaced at 0.1 seconds apart - first_iter_times = [val / 10 + 0.1 for val in range(10001)] - iter_counts = [9 for _ in range(10001)] - first_iter_counts = [1 for _ in range(10001)] - - distribution_summary = DistributionSummary.from_iterable_request_times( - requests, first_iter_times, iter_counts, first_iter_counts - ) - assert distribution_summary.mean == pytest.approx(90.0, abs=0.1) - assert distribution_summary.median == pytest.approx(80.0) - assert distribution_summary.mode == pytest.approx(80.0) - assert distribution_summary.variance == pytest.approx(704.463, abs=0.001) - assert distribution_summary.std_dev == pytest.approx(26.541, abs=0.001) - assert distribution_summary.min == pytest.approx(0.0) - assert distribution_summary.max == pytest.approx(160.0) - assert distribution_summary.count == 44 - assert distribution_summary.total_sum == pytest.approx(3538.85, abs=0.01) - assert distribution_summary.percentiles.p001 == pytest.approx(80.0) - assert distribution_summary.percentiles.p01 == pytest.approx(80.0) - assert distribution_summary.percentiles.p05 == pytest.approx(80.0) - assert distribution_summary.percentiles.p10 == pytest.approx(80.0) - assert distribution_summary.percentiles.p25 == pytest.approx(80.0) - assert distribution_summary.percentiles.p50 == pytest.approx(80.0) - assert distribution_summary.percentiles.p75 == pytest.approx(80.0) - assert distribution_summary.percentiles.p90 == pytest.approx(160.0) - assert distribution_summary.percentiles.p95 == pytest.approx(160.0) - assert distribution_summary.percentiles.p99 == pytest.approx(160.0) - assert distribution_summary.percentiles.p999 == pytest.approx(160.0) - assert distribution_summary.cumulative_distribution_function is None - - distribution_summary_cdf = DistributionSummary.from_iterable_request_times( - requests, first_iter_times, iter_counts, first_iter_counts, include_cdf=True - ) - assert distribution_summary_cdf.cumulative_distribution_function is not None - assert len(distribution_summary_cdf.cumulative_distribution_function) == 44 - - -def test_status_distribution_summary_initialization(): - status_distribution_summary = StatusDistributionSummary( - total=create_default_distribution_summary(), - successful=create_default_distribution_summary(), - incomplete=create_default_distribution_summary(), - errored=create_default_distribution_summary(), - ) - assert status_distribution_summary.total.mean == 50.0 - assert status_distribution_summary.successful.mean == 50.0 - assert status_distribution_summary.incomplete.mean == 50.0 - assert status_distribution_summary.errored.mean == 50.0 - - -def test_status_distribution_summary_marshalling(): - status_distribution_summary = StatusDistributionSummary( - total=create_default_distribution_summary(), - successful=create_default_distribution_summary(), - incomplete=create_default_distribution_summary(), - errored=create_default_distribution_summary(), - ) - serialized = status_distribution_summary.model_dump() - deserialized = StatusDistributionSummary.model_validate(serialized) - - for key, value in vars(status_distribution_summary).items(): - for child_key, child_value in vars(value).items(): - assert getattr(getattr(deserialized, key), child_key) == child_value - - -def test_status_distribution_summary_from_values(): - value_types: list[Literal["successful", "incomplete", "error"]] = [ - "successful", - "incomplete", - "error", - ] * 1000 - values = [float(val % 3) for val in range(3000)] - status_distribution_summary = StatusDistributionSummary.from_values( - value_types, values - ) - assert status_distribution_summary.total.count == len(values) - assert status_distribution_summary.total.mean == pytest.approx(np.mean(values)) - assert status_distribution_summary.total.cumulative_distribution_function is None - assert status_distribution_summary.successful.mean == pytest.approx( - np.mean( - [val for ind, val in enumerate(values) if value_types[ind] == "successful"] - ) - ) - assert status_distribution_summary.successful.count == len( - [val for ind, val in enumerate(values) if value_types[ind] == "successful"] - ) - assert ( - status_distribution_summary.successful.cumulative_distribution_function is None - ) - assert status_distribution_summary.incomplete.mean == pytest.approx( - np.mean( - [val for ind, val in enumerate(values) if value_types[ind] == "incomplete"] - ) - ) - assert status_distribution_summary.incomplete.count == len( - [val for ind, val in enumerate(values) if value_types[ind] == "incomplete"] - ) - assert ( - status_distribution_summary.incomplete.cumulative_distribution_function is None - ) - assert status_distribution_summary.errored.mean == pytest.approx( - np.mean([val for ind, val in enumerate(values) if value_types[ind] == "error"]) - ) - assert status_distribution_summary.errored.count == len( - [val for ind, val in enumerate(values) if value_types[ind] == "error"] - ) - assert status_distribution_summary.errored.cumulative_distribution_function is None - - status_distribution_summary_cdf = StatusDistributionSummary.from_values( - value_types, values, include_cdf=True - ) - assert ( - status_distribution_summary_cdf.total.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.successful.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.incomplete.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.errored.cumulative_distribution_function - is not None - ) - - -def test_status_distribution_summary_from_request_times(): - request_types: list[Literal["successful", "incomplete", "error"]] = [ - "successful", - "incomplete", - "error", - ] * 1000 - requests = [((val % 3) / 10, (val % 3) / 10 + 1) for val in range(3000)] - status_distribution_summary = StatusDistributionSummary.from_request_times( - request_types, requests, distribution_type="concurrency" - ) - assert status_distribution_summary.total.mean == pytest.approx(2500.0, abs=0.01) - assert status_distribution_summary.total.cumulative_distribution_function is None - assert status_distribution_summary.successful.mean == pytest.approx( - 1000.0, abs=0.01 - ) - assert ( - status_distribution_summary.successful.cumulative_distribution_function is None - ) - assert status_distribution_summary.incomplete.mean == pytest.approx( - 1000.0, abs=0.01 - ) - assert ( - status_distribution_summary.incomplete.cumulative_distribution_function is None - ) - assert status_distribution_summary.errored.mean == pytest.approx(1000.0, abs=0.01) - assert status_distribution_summary.errored.cumulative_distribution_function is None - - status_distribution_summary_cdf = StatusDistributionSummary.from_request_times( - request_types, requests, distribution_type="concurrency", include_cdf=True - ) - assert ( - status_distribution_summary_cdf.total.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.successful.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.incomplete.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.errored.cumulative_distribution_function - is not None - ) - - -def test_status_distribution_summary_from_iterable_request_times(): - request_types: list[Literal["successful", "incomplete", "error"]] = [ - "successful", - "incomplete", - "error", - ] * 1000 - requests = [(val % 3 / 10, val % 3 / 10 + 1) for val in range(3000)] - first_iter_times = [val % 3 / 10 + 0.1 for val in range(3000)] - iter_counts = [9 for _ in range(3000)] - first_iter_counts = [1 for _ in range(3000)] - status_distribution_summary = StatusDistributionSummary.from_iterable_request_times( - request_types, - requests, - first_iter_times, - iter_counts, - first_iter_counts, - ) - assert status_distribution_summary.total.mean == pytest.approx(21666.66, abs=0.01) - assert status_distribution_summary.total.cumulative_distribution_function is None - assert status_distribution_summary.successful.mean == pytest.approx( - 8000.0, abs=0.01 - ) - assert ( - status_distribution_summary.successful.cumulative_distribution_function is None - ) - assert status_distribution_summary.incomplete.mean == pytest.approx( - 8000.0, abs=0.01 - ) - assert ( - status_distribution_summary.incomplete.cumulative_distribution_function is None - ) - assert status_distribution_summary.errored.mean == pytest.approx(8000.0, abs=0.01) - assert status_distribution_summary.errored.cumulative_distribution_function is None - - status_distribution_summary_cdf = ( - StatusDistributionSummary.from_iterable_request_times( - request_types, - requests, - first_iter_times, - iter_counts, - first_iter_counts, - include_cdf=True, - ) - ) - assert ( - status_distribution_summary_cdf.total.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.successful.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.incomplete.cumulative_distribution_function - is not None - ) - assert ( - status_distribution_summary_cdf.errored.cumulative_distribution_function - is not None - ) - - -def test_running_stats_initialization(): - running_stats = RunningStats() - assert running_stats.start_time == pytest.approx(time.time(), abs=0.01) - assert running_stats.count == 0 - assert running_stats.total == 0 - assert running_stats.last == 0 - assert running_stats.mean == 0 - assert running_stats.rate == 0 - - -def test_running_stats_marshalling(): - running_stats = RunningStats() - serialized = running_stats.model_dump() - deserialized = RunningStats.model_validate(serialized) - - for key, value in vars(running_stats).items(): - assert getattr(deserialized, key) == value - - -def test_running_stats_update(): - running_stats = RunningStats() - running_stats.update(1) - assert running_stats.count == 1 - assert running_stats.total == 1 - assert running_stats.last == 1 - assert running_stats.mean == 1 - time.sleep(1.0) - assert running_stats.rate == pytest.approx( - 1.0 / (time.time() - running_stats.start_time), abs=0.1 - ) - - running_stats.update(2) - assert running_stats.count == 2 - assert running_stats.total == 3 - assert running_stats.last == 2 - assert running_stats.mean == 1.5 - time.sleep(1) - assert running_stats.rate == pytest.approx( - 3 / (time.time() - running_stats.start_time), abs=0.1 - ) - - -def test_running_stats_add(): - running_stats = RunningStats() - mean = running_stats + 1 - assert mean == 1 - assert mean == running_stats.mean - assert running_stats.count == 1 - assert running_stats.total == 1 - assert running_stats.last == 1 - - -def test_running_stats_iadd(): - running_stats = RunningStats() - running_stats += 1 - assert running_stats.count == 1 - assert running_stats.total == 1 - assert running_stats.last == 1 - assert running_stats.mean == 1 - - -def test_time_running_stats_initialization(): - time_running_stats = TimeRunningStats() - assert time_running_stats.start_time == pytest.approx(time.time(), abs=0.01) - assert time_running_stats.count == 0 - assert time_running_stats.total == 0 - assert time_running_stats.last == 0 - assert time_running_stats.mean == 0 - assert time_running_stats.rate == 0 - assert time_running_stats.total_ms == 0 - assert time_running_stats.last_ms == 0 - assert time_running_stats.mean_ms == 0 - assert time_running_stats.rate_ms == 0 - - -def test_time_running_stats_marshalling(): - time_running_stats = TimeRunningStats() - serialized = time_running_stats.model_dump() - deserialized = TimeRunningStats.model_validate(serialized) - - for key, value in vars(time_running_stats).items(): - assert getattr(deserialized, key) == value - - -def test_time_running_stats_update(): - time_running_stats = TimeRunningStats() - time_running_stats.update(1) - assert time_running_stats.count == 1 - assert time_running_stats.total == 1 - assert time_running_stats.last == 1 - assert time_running_stats.mean == 1 - assert time_running_stats.total_ms == 1000 - assert time_running_stats.last_ms == 1000 - assert time_running_stats.mean_ms == 1000 - time.sleep(1.0) - assert time_running_stats.rate == pytest.approx( - 1.0 / (time.time() - time_running_stats.start_time), abs=0.1 - ) - assert time_running_stats.rate_ms == pytest.approx( - 1000 / (time.time() - time_running_stats.start_time), abs=0.1 - ) - - time_running_stats.update(2) - assert time_running_stats.count == 2 - assert time_running_stats.total == 3 - assert time_running_stats.last == 2 - assert time_running_stats.mean == 1.5 - assert time_running_stats.total_ms == 3000 - assert time_running_stats.last_ms == 2000 - assert time_running_stats.mean_ms == 1500 - time.sleep(1) - assert time_running_stats.rate == pytest.approx( - 3 / (time.time() - time_running_stats.start_time), abs=0.1 - ) - assert time_running_stats.rate_ms == pytest.approx( - 3000 / (time.time() - time_running_stats.start_time), abs=0.1 - ) - - -@pytest.mark.regression -def test_distribution_summary_concurrency_double_counting_regression(): - """Specific regression test for the double-counting bug in concurrency calculation. - - Before the fix, when events were merged due to epsilon, the deltas were summed - but then the active count wasn't properly accumulated, causing incorrect results. - - ### WRITTEN BY AI ### - """ - epsilon = 1e-6 - - # Create a scenario where multiple requests start at exactly the same time - # This should result in events being merged, testing the accumulation logic - same_start_time = 1.0 - requests = [ - (same_start_time, 3.0), - (same_start_time, 4.0), - (same_start_time, 5.0), - (same_start_time + epsilon / 3, 6.0), # Very close start (within epsilon) - ] - - distribution_summary = DistributionSummary.from_request_times( - requests, distribution_type="concurrency", epsilon=epsilon - ) - - # All requests start at the same time (or within epsilon), so they should - # all be considered concurrent from the start - # Expected timeline: - # - t=1.0-3.0: 4 concurrent requests - # - t=3.0-4.0: 3 concurrent requests - # - t=4.0-5.0: 2 concurrent requests - # - t=5.0-6.0: 1 concurrent request - - assert distribution_summary.max == 4.0 # All 4 requests concurrent at start - assert distribution_summary.min == 1.0 # 1 request still running at the end - - -@pytest.mark.sanity -def test_distribution_summary_concurrency_epsilon_edge_case(): - """Test the exact epsilon boundary condition. - - ### WRITTEN BY AI ### - """ - epsilon = 1e-6 - - # Test requests that are exactly epsilon apart - should be merged - requests_exactly_epsilon = [ - (1.0, 2.0), - (1.0 + epsilon, 2.5), # Exactly epsilon apart - (2.0, 2.5), # Another close request - ] - - dist_epsilon = DistributionSummary.from_request_times( - requests_exactly_epsilon, distribution_type="concurrency", epsilon=epsilon - ) - - # Should be treated as concurrent (merged events) - assert dist_epsilon.max == 2.0 - assert dist_epsilon.min == 2.0 - - # Test requests that are just over epsilon apart - should NOT be merged - requests_over_epsilon = [ - (1.0, 2.0), - (1.0 + epsilon * 1.1, 2.5), # Just over epsilon apart - (2.0, 2.5), # Another close request - ] - - dist_over_epsilon = DistributionSummary.from_request_times( - requests_over_epsilon, distribution_type="concurrency", epsilon=epsilon - ) - - # These should be treated separately, so max concurrency depends on overlap - # At t=1.0 to 1.0+epsilon*1.1: 1 concurrent - # At t=1.0+epsilon*1.1 to 2.0: 2 concurrent - # At t=2.0 to 2.5: 1 concurrent - assert dist_over_epsilon.max == 2.0 - assert dist_over_epsilon.min == 1.0