From c7779434ef03ed8bc03d435926941fd6693c9110 Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Fri, 11 Sep 2026 18:20:52 +0530 Subject: [PATCH 1/6] chore: bump version to 1.0.0, add gui package + docs dependency group + console script --- pyproject.toml | 14 +- uv.lock | 598 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 608 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5aecfb7..ad87225 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "algorithm-discovery-engine" -version = "0.3.0" +version = "1.0.0" description = "Multi-language algorithms & data structures solving engine (Java, C++, Rust, Python) with cross-language benchmarks and a local AI synthesizer that rediscovers algorithms from examples." readme = "README.md" requires-python = ">=3.10" @@ -27,24 +27,29 @@ keywords = [ "sequence-analysis", ] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", + "Intended Audience :: Education", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Java", "Programming Language :: C++", "Programming Language :: Rust", "Topic :: Education", + "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Scientific/Engineering :: Mathematics", ] +[project.scripts] +ade-gui = "gui.app:main" + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/algo_discovery", "src/ads", "src/synth"] +packages = ["src/algo_discovery", "src/ads", "src/synth", "src/gui"] [tool.uv] package = true @@ -55,6 +60,9 @@ dev = [ "pytest>=8.0", "ruff>=0.5", ] +docs = [ + "mkdocs-material>=9.5", +] [tool.ruff] line-length = 100 diff --git a/uv.lock b/uv.lock index c4197ab..3911cff 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "algorithm-discovery-engine" -version = "0.3.0" +version = "1.0.0" source = { editable = "." } [package.dev-dependencies] @@ -17,6 +17,9 @@ dev = [ { name = "pytest" }, { name = "ruff" }, ] +docs = [ + { name = "mkdocs-material" }, +] [package.metadata] @@ -26,6 +29,7 @@ dev = [ { name = "pytest", specifier = ">=8.0" }, { name = "ruff", specifier = ">=0.5" }, ] +docs = [{ name = "mkdocs-material", specifier = ">=9.5" }] [[package]] name = "ast-serialize" @@ -91,6 +95,209 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/0b/32f3c8162cb5b33f24bea94503dbdd6b55aee72d367d23a55f1338f3e1b7/ast_serialize-0.11.0-cp39-abi3-win_arm64.whl", hash = "sha256:eb22d9300e7a064fa8c45e2c1e568a4e36c4c91487ea0da5e7f589905365c866", size = 1134422, upload-time = "2026-09-08T14:58:38.267Z" }, ] +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backrefs" +version = "8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/56/4744bcd0c82184e80c52b0ac4076c261a8ffa1f1b343ff2f6e89ce0e1cef/backrefs-8.0.tar.gz", hash = "sha256:b556cd7d36c3a3a2f256b89590b176b8eddfb73bcfaee3a3ddd84ea66d21ce50", size = 7013081, upload-time = "2026-07-26T19:54:24.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/fd/9bf53b6a6f6f519ffaac765df2f2a25e5c2fc6d32cfd2b2747099e72c911/backrefs-8.0-py310-none-any.whl", hash = "sha256:4a627b817fd2dce43b79ab48da63613340509381cd8ce0897078a0bce79a2ab8", size = 380377, upload-time = "2026-07-26T19:54:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/e1/29/4bd7ae72a2634da00379c2b3bcc5439e7c94620235c6afea8af15229a973/backrefs-8.0-py311-none-any.whl", hash = "sha256:f0c35cf0102ba6b6070c12a492be3c1c1d3f5839529784b9a9565d6d04569a01", size = 392169, upload-time = "2026-07-26T19:54:18.782Z" }, + { url = "https://files.pythonhosted.org/packages/29/13/232505664e8e2a0c7a2eb0c505cfade9d715538f89a5d62bc4c272968f62/backrefs-8.0-py312-none-any.whl", hash = "sha256:87f0fae8c5f207fe9f4b2887efc71d42f4900ac78faa1af08d675ef303692dc5", size = 398084, upload-time = "2026-07-26T19:54:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/8a/69/47a3dc20abc4fa5486655fde681bd55e63211b46c886d8c02223d6468431/backrefs-8.0-py313-none-any.whl", hash = "sha256:601ce68ca12385dbda06ce264406b4c4210cf5b79fd0fd627592365c92f29a88", size = 400040, upload-time = "2026-07-26T19:54:21.194Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/e5f9b68a5b0e939a2fb933a66c20180d0c9241bf8927f7a47fa48c1675e9/backrefs-8.0-py314-none-any.whl", hash = "sha256:9ec96efa080938be92323e8e730e57718c9c88eb15ad70bbef4e1766df591408", size = 411903, upload-time = "2026-07-26T19:54:23.221Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/aa/554e2614f38fc34c58ff1d0911ae8535ad2516440d5482d76fe59f1088b0/charset_normalizer-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa", size = 369072, upload-time = "2026-08-15T08:16:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/03/6d/439231dfc3ccfa6f8c06477b7da2219cbd41a2de3d49084df8ec7b5100f2/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda", size = 251142, upload-time = "2026-08-15T08:16:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/7d819bd23a00ef45039146fa2cce1daa2f0771e758c5653ee1f6edac91ed/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45", size = 240714, upload-time = "2026-08-15T08:16:26.392Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2c/45847198c16f4b38090cc7423b2b6a9008e438704d8ab413211832498d31/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab", size = 279637, upload-time = "2026-08-15T08:16:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/2b/d8be3523ddf9f0b0f3e56d1359034aa10653a4d11564c697f802b4775766/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a", size = 276543, upload-time = "2026-08-15T08:16:29.399Z" }, + { url = "https://files.pythonhosted.org/packages/32/cd/4f564b8f132de25db594efc706897069f016790cea63a5669c9df2675f64/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3", size = 261644, upload-time = "2026-08-15T08:16:30.722Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e3/38b975422534a608f98c360e79c2f07c763d66dd4272300d45fb1fee54b0/charset_normalizer-3.5.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb", size = 259609, upload-time = "2026-08-15T08:16:32.248Z" }, + { url = "https://files.pythonhosted.org/packages/87/bd/fbc24d825c66f1c74f6ccdea3742c3d8354a4888e86d1315a197fee69061/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f", size = 252457, upload-time = "2026-08-15T08:16:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2d/918d0e98a0e679469ed05bb2d90c2088b4d315bb612969d8499f76fb5210/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea", size = 242240, upload-time = "2026-08-15T08:16:35.396Z" }, + { url = "https://files.pythonhosted.org/packages/20/c8/c36f6e0b2dfec351bd38cbc05362697e58bcd073d7dbd95154290c9714ce/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f", size = 280308, upload-time = "2026-08-15T08:16:36.825Z" }, + { url = "https://files.pythonhosted.org/packages/ca/7b/311b3e02e8c4092400c449c850a760d8c45d900983c83a70cc07208c551d/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182", size = 258679, upload-time = "2026-08-15T08:16:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b9/90/082cc45599c392f28c036a497f49e0634041a785fc3849c80ccf396d096f/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa", size = 277221, upload-time = "2026-08-15T08:16:39.62Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/b9aecf38d805cbcf84fa94f14c5d972a16561e20296a11dc799a5dcf3763/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818", size = 263799, upload-time = "2026-08-15T08:16:40.885Z" }, + { url = "https://files.pythonhosted.org/packages/b7/23/b38a20598d5a825f85d9d7636860e56ff0db1479f86497a6e485aa9326f7/charset_normalizer-3.5.1-cp310-cp310-win32.whl", hash = "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20", size = 182037, upload-time = "2026-08-15T08:16:42.198Z" }, + { url = "https://files.pythonhosted.org/packages/d2/21/83fffb77864408b8bf0fe1ca603926401d6f8775a8e150b39aacc9958f8a/charset_normalizer-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d", size = 206030, upload-time = "2026-08-15T08:16:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/b93135b5034b1157fb29554b0d06d4844ce62282f0e0a14036f93d7ee2e7/charset_normalizer-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5", size = 185092, upload-time = "2026-08-15T08:16:45.177Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, + { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, + { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, + { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, + { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -112,6 +319,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "idna" +version = "3.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -121,6 +349,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "librt" version = "0.15.0" @@ -251,6 +491,178 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/a6/800800bfed7b1fb10fc3f3d557785c3854e80d3f7a9800d784b176a1fc2d/librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc", size = 110700, upload-time = "2026-08-07T10:49:15.499Z" }, ] +[[package]] +name = "markdown" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/6f/da4c6aea59b3001f2e8c0ec7497475aadaf3b021c10cab5b2858f0f32b26/markdown-3.10.3.tar.gz", hash = "sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f", size = 372596, upload-time = "2026-07-30T19:05:29.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/69/4a5af2bc115a9a33fefe51709749de8262be3f9ba063d1753a837cdbc49c/markdown-3.10.3-py3-none-any.whl", hash = "sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea", size = 110757, upload-time = "2026-07-30T19:05:27.883Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { 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", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { 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", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { 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", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { 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", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { 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", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { 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", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { 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", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { 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", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { 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", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { 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", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { 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", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { 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", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { 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", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { 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", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/cd/c05d3a530ba7934f144fb45f7203cd236adc25c7bdcc34673d202f4b0278/mkdocs_material-9.7.7.tar.gz", hash = "sha256:c0649c065b1b0512d60aad8c10f947f8e455284475239b364b610f2deb4d0855", size = 4097923, upload-time = "2026-07-17T16:21:33.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/21/17c1bc9e6f47c972ad66fb2ac2568f99f90f1207eeb6fc3b34d094dba7b5/mkdocs_material-9.7.7-py3-none-any.whl", hash = "sha256:8ea9bb1737a5b524a5f9dcf2e1b4ebda8274ae3008aa7845720a97083bef708f", size = 9305438, upload-time = "2026-07-17T16:21:30.017Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + [[package]] name = "mypy" version = "2.3.1" @@ -336,6 +748,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + [[package]] name = "pathspec" version = "1.1.1" @@ -345,6 +766,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] +[[package]] +name = "platformdirs" +version = "4.11.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/18/f3bb8ef0d3b930692343da8aa4d3cbcd6749477c053959395ac81965a6e9/platformdirs-4.11.8.tar.gz", hash = "sha256:f23abafea7dd4276d1f29104b83598d7dcc567cafd07c9c951e66665645437fc", size = 37182, upload-time = "2026-09-08T22:20:42.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/e1/5b7b8bbb55084d1425bcb9bc823ff519e1b2be05f6ebb0089e2eacc38413/platformdirs-4.11.8-py3-none-any.whl", hash = "sha256:52f2f181bbfde907966932cc8312d967d02976422d66d537ea16092b8e291081", size = 24027, upload-time = "2026-09-08T22:20:41.537Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -363,6 +793,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] +[[package]] +name = "pymdown-extensions" +version = "11.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/17/2db4b414de89659144488e0d9c6c0bf0c8395841dc12d81d0532cc6ef310/pymdown_extensions-11.0.2.tar.gz", hash = "sha256:9506fcbe66fa355a775b768084334238dd6805020ac4b92bea0c0dda6f8f223d", size = 855419, upload-time = "2026-08-22T19:28:47.236Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/43/9f45ec4d14e596efc32c925a78104934790438b0c0628b70d741016734ad/pymdown_extensions-11.0.2-py3-none-any.whl", hash = "sha256:259910762019732caa1dfd76f3faa62c59f191d46573e80bcb1d13c0f675bbe5", size = 269929, upload-time = "2026-08-22T19:28:45.389Z" }, +] + [[package]] name = "pytest" version = "9.1.1" @@ -381,6 +824,109 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { 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", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { 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", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { 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", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { 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", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { 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", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { 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", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { 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", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { 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", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { 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", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { 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", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { 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", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { 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", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { 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", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { 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", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { 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", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { 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", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { 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", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { 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", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + [[package]] name = "ruff" version = "0.16.6" @@ -406,6 +952,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "tomli" version = "2.4.1" @@ -468,3 +1023,44 @@ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3 wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] From a2d564c75acfb33c11b386947a9494cbcc613ac4 Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Fri, 11 Sep 2026 18:21:19 +0530 Subject: [PATCH 2/6] feat: add Tkinter desktop GUI with headless-tested core logic - src/gui/core.py: headless pattern discovery, synth runner, catalog summary - src/gui/app.py: dark-themed Tkinter app (Discover / Pattern discovery / Engine tabs) - src/gui/__main__.py: python -m gui launcher with --selftest mode - tests/test_gui.py: headless core tests (catalog, patterns, synth smoke) --- src/gui/__init__.py | 3 + src/gui/__main__.py | 26 +++ src/gui/app.py | 407 ++++++++++++++++++++++++++++++++++++++++++++ src/gui/core.py | 149 ++++++++++++++++ tests/test_gui.py | 74 ++++++++ 5 files changed, 659 insertions(+) create mode 100644 src/gui/__init__.py create mode 100644 src/gui/__main__.py create mode 100644 src/gui/app.py create mode 100644 src/gui/core.py create mode 100644 tests/test_gui.py diff --git a/src/gui/__init__.py b/src/gui/__init__.py new file mode 100644 index 0000000..499bf48 --- /dev/null +++ b/src/gui/__init__.py @@ -0,0 +1,3 @@ +"""Desktop GUI shell for algorithm-discovery-engine (stdlib Tkinter).""" + +from gui import core # noqa: F401 \ No newline at end of file diff --git a/src/gui/__main__.py b/src/gui/__main__.py new file mode 100644 index 0000000..ab6361a --- /dev/null +++ b/src/gui/__main__.py @@ -0,0 +1,26 @@ +"""``python -m gui`` launcher. Runs without a display only when ``--selftest``.""" + +from __future__ import annotations + +import sys + + +def main() -> int: + if "--selftest" in sys.argv: + from gui import core + + print(f"gui selftest ok (catalog: {core.engine_catalog_counts()})") + return 0 + try: + import tkinter # noqa: F401 + except ImportError: + print("tkinter is not available in this Python build.", file=sys.stderr) + return 2 + from gui.app import main as app_main + + app_main() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/src/gui/app.py b/src/gui/app.py new file mode 100644 index 0000000..e1ed3c5 --- /dev/null +++ b/src/gui/app.py @@ -0,0 +1,407 @@ +"""Tkinter desktop GUI for algorithm-discovery-engine. + +Launch with ``python -m gui`` (or ``python engine/runner.py gui``). The UI is +pure stdlib — no third-party runtime dependencies. +""" + +from __future__ import annotations + +import contextlib +import queue +import subprocess +import sys +import threading +import tkinter as tk +import webbrowser +from collections.abc import Callable +from tkinter import ttk +from typing import Any, Literal + +from gui import core + +BG = "#0d1117" +PANEL = "#161b22" +BORDER = "#30363d" +TEXT = "#e6edf3" +MUTED = "#8b949e" +ACCENT = "#1f6feb" +GREEN = "#3fb950" +RED = "#f85149" + +Report = dict[str, Any] +Message = tuple[Callable[[Any], None], Any] + + +class App: + """Main application window.""" + + def __init__(self, root: tk.Tk) -> None: + self.root = root + self.root.title("algorithm-discovery-engine") + self.root.geometry("980x640") + self.root.minsize(800, 540) + self.root.configure(bg=BG) + self._style() + self._messages: queue.Queue[Message] = queue.Queue() + self._busy = False + self._tasks: list[ttk.Button] = [] + self.root.after(100, self._poll) + self._build() + + # ------------------------------------------------------------------ style + def _style(self) -> None: + style = ttk.Style(self.root) + with contextlib.suppress(tk.TclError): + style.theme_use("clam") + style.configure( + "TNotebook", background=BG, borderwidth=0, tabmargins=(4, 4, 4, 0) + ) + style.configure( + "TNotebook.Tab", + background=PANEL, foreground=MUTED, padding=(14, 8), borderwidth=0, + ) + style.map( + "TNotebook.Tab", + background=[("selected", ACCENT)], + foreground=[("selected", "#ffffff")], + ) + style.configure("TFrame", background=BG) + style.configure( + "Treeview", + background=PANEL, fieldbackground=PANEL, foreground=TEXT, + borderwidth=0, rowheight=24, + ) + style.configure( + "Treeview.Heading", + background=BORDER, foreground=TEXT, relief="flat", padding=(6, 5), + ) + style.map( + "Treeview", + background=[("selected", ACCENT)], + foreground=[("selected", "#ffffff")], + ) + style.configure("TLabel", background=BG, foreground=TEXT) + style.configure("Muted.TLabel", background=BG, foreground=MUTED) + style.configure( + "TButton", background=PANEL, foreground=TEXT, borderwidth=1, + focusthickness=0, padding=(10, 6), + ) + style.configure( + "Accent.TButton", background=ACCENT, foreground="#ffffff", + borderwidth=1, padding=(10, 6), + ) + style.configure( + "TEntry", background=PANEL, foreground=TEXT, fieldbackground=PANEL, + insertcolor=TEXT, bordercolor=BORDER, + ) + + # ------------------------------------------------------------ background + def _button( + self, + parent: ttk.Frame, + text: str, + command: Callable[[], None], + accent: bool = False, + ) -> ttk.Button: + button = ttk.Button( + parent, text=text, command=command, takefocus=False, + style="Accent.TButton" if accent else "TButton", + ) + self._tasks.append(button) + return button + + def _set_busy(self, busy: bool) -> None: + self._busy = busy + for button in self._tasks: + button.configure(state=tk.DISABLED if busy else tk.NORMAL) + + def _submit( + self, + task: Callable[[], Any], + on_done: Callable[[Any], None], + *labels: ttk.Label, + ) -> None: + def run() -> None: + try: + result = task() + except Exception as exc: # surfaced to the UI + result = exc + self._messages.put((on_done, result)) + + self._set_busy(True) + for label in labels: + label.configure(text="working…", foreground=ACCENT) + threading.Thread(target=run, daemon=True).start() + + def _poll(self) -> None: + try: + while True: + callback, payload = self._messages.get_nowait() + callback(payload) + except queue.Empty: + pass + self.root.after(100, self._poll) + + # ------------------------------------------------------------------ build + def _build(self) -> None: + root_frame = ttk.Frame(self.root, padding=(12, 10, 12, 8)) + root_frame.pack(fill=tk.BOTH, expand=True) + + self.status = ttk.Label(root_frame, text="ready", style="Muted.TLabel") + self.status.pack(side=tk.BOTTOM, anchor=tk.W, pady=(8, 0)) + + notebook = ttk.Notebook(root_frame) + notebook.pack(fill=tk.BOTH, expand=True) + + discover = ttk.Frame(notebook, padding=10) + patterns = ttk.Frame(notebook, padding=10) + engine = ttk.Frame(notebook, padding=10) + notebook.add(discover, text=" Discover ") + notebook.add(patterns, text=" Pattern discovery ") + notebook.add(engine, text=" Engine ") + + self._build_discover(discover) + self._build_patterns(patterns) + self._build_engine(engine) + self.refresh_report() + + def _build_discover(self, parent: ttk.Frame) -> None: + top = ttk.Frame(parent) + top.pack(fill=tk.X, pady=(0, 8)) + ttk.Label(top, text="Local algorithm synthesizer", font=("", 16, "bold")).pack(side=tk.LEFT) + self.summary_label = ttk.Label(top, text="", style="Muted.TLabel") + self.summary_label.pack(side=tk.RIGHT) + + self._tree = ttk.Treeview( + parent, show="headings", + columns=("target", "kind", "status", "novelty", "ms", "strategy"), + ) + tree_cols: list[tuple[str, str, int]] = [ + ("target", "Target", 190), + ("kind", "Kind", 120), + ("status", "Status", 110), + ("novelty", "Novelty", 130), + ("ms", "Search (ms)", 90), + ("strategy", "Strategy", 120), + ] + for col, title, width in tree_cols: + self._tree.heading(col, text=title) + anchor: Literal["w", "center"] = ( + "w" if col in ("target", "kind", "strategy") else "center" + ) + self._tree.column(col, width=width, anchor=anchor) + self._tree.pack(fill=tk.BOTH, expand=True) + + controls = ttk.Frame(parent) + controls.pack(fill=tk.X, pady=(8, 0)) + self._button(controls, "Run smoke pass", self.run_smoke, accent=True).pack(side=tk.LEFT) + self._button(controls, "Run full pass", self.run_full).pack(side=tk.LEFT, padx=6) + self._button(controls, "Refresh", self.refresh_report).pack(side=tk.LEFT) + self._button(controls, "Open report (markdown)", self.open_report).pack( + side=tk.LEFT, padx=6 + ) + self._button(controls, "Open solutions folder", self.open_solutions).pack(side=tk.LEFT) + + def _build_patterns(self, parent: ttk.Frame) -> None: + row = ttk.Frame(parent) + row.pack(fill=tk.X, pady=(0, 8)) + ttk.Label(row, text="Integer sequence:").pack(side=tk.LEFT) + self.sequence_var = tk.StringVar(value="1 4 9 16 25") + entry = ttk.Entry(row, textvariable=self.sequence_var) + entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=8) + entry.bind("", lambda _event: self.run_patterns()) + self._button(row, "Discover", self.run_patterns, accent=True).pack(side=tk.LEFT) + self.pattern_status = ttk.Label(row, text="", style="Muted.TLabel") + self.pattern_status.pack(side=tk.LEFT, padx=8) + + self._ptree = ttk.Treeview( + parent, show="headings", columns=("name", "confidence", "prediction", "detail"), + ) + pat_cols: list[tuple[str, str, int, Literal["w", "center"]]] = [ + ("name", "Hypothesis", 200, "w"), + ("confidence", "Confidence", 130, "center"), + ("prediction", "Next term", 120, "center"), + ("detail", "Detail", 400, "w"), + ] + for col, title, width, anchor in pat_cols: + self._ptree.heading(col, text=title) + self._ptree.column(col, width=width, anchor=anchor) + self._ptree.pack(fill=tk.BOTH, expand=True) + + def _build_engine(self, parent: ttk.Frame) -> None: + self.engine_summary = ttk.Label( + parent, text="", font=("", 12), style="Muted.TLabel", justify=tk.LEFT, + ) + self.engine_summary.pack(anchor=tk.W, pady=(0, 10)) + + controls = ttk.Frame(parent) + controls.pack(fill=tk.X) + self._button(controls, "Check vectors", self.run_check, accent=True).pack(side=tk.LEFT) + self._button(controls, "Run pytest", self.run_pytest).pack(side=tk.LEFT, padx=6) + self._button(controls, "Run build", self.run_build).pack(side=tk.LEFT) + self._button(controls, "Benchmark", self.run_bench).pack(side=tk.LEFT, padx=6) + self._button(controls, "Open docs", self.open_docs).pack(side=tk.LEFT) + self._button(controls, "Open GitHub", self.open_github).pack(side=tk.LEFT, padx=6) + + self.output = tk.Text( + parent, bg=PANEL, fg=TEXT, insertbackground=TEXT, relief=tk.FLAT, + wrap=tk.NONE, font=("DejaVu Sans Mono", 10), state=tk.DISABLED, + highlightthickness=1, highlightbackground=BORDER, + ) + self.output.pack(fill=tk.BOTH, expand=True, pady=(10, 0)) + + # ----------------------------------------------------------- discover tab + def _render_report(self, summary: dict[str, Any]) -> None: + for item in self._tree.get_children(): + self._tree.delete(item) + for entry in summary.get("targets", []): + status = entry.get("status", "?") + self._tree.insert( + "", tk.END, + values=( + entry.get("id", ""), + entry.get("kind", ""), + status, + entry.get("novelty", ""), + entry.get("search_ms", ""), + entry.get("strategy", "") or entry.get("time_class", ""), + ), + tags=(status,), + ) + self._tree.tag_configure("verified", foreground=GREEN) + self._tree.tag_configure("candidate-rejected", foreground=RED) + self._tree.tag_configure("no-candidate-found", foreground=MUTED) + if summary.get("exists"): + self.summary_label.configure( + text=f"verified {summary['verified']} · rejected {summary['rejected']} · " + f"missing {summary['missing']}" + ) + else: + self.summary_label.configure(text="no report yet", foreground=MUTED) + + def refresh_report(self) -> None: + self._render_report(core.synth_summary(load_report())) + + def run_smoke(self) -> None: + self._submit( + lambda: core.run_synth(smoke=True), + self._synth_done, + self.summary_label, + ) + + def run_full(self) -> None: + self._submit( + lambda: core.run_synth(smoke=False), + self._synth_done, + self.summary_label, + ) + + def _synth_done(self, payload: Any) -> None: + self._set_busy(False) + if isinstance(payload, Exception): + self.summary_label.configure(text=str(payload), foreground=RED) + self._render_report(core.synth_summary()) + else: + self._render_report(payload) + self.summary_label.configure(text="done", foreground=GREEN) + self.refresh_report() + + def open_report(self) -> None: + if core.REPORT_MD.exists(): + webbrowser.open(core.REPORT_MD.resolve().as_uri()) + + def open_solutions(self) -> None: + core.SOLUTIONS_DIR.mkdir(parents=True, exist_ok=True) + webbrowser.open(core.SOLUTIONS_DIR.resolve().as_uri()) + + # -------------------------------------------------------- patterns tab + def run_patterns(self) -> None: + self._submit( + lambda: core.run_patterns(self.sequence_var.get()), + self._patterns_done, + self.pattern_status, + ) + + def _patterns_done(self, payload: Any) -> None: + self._set_busy(False) + if isinstance(payload, Exception): + self.pattern_status.configure(text=str(payload), foreground=RED) + return + for item in self._ptree.get_children(): + self._ptree.delete(item) + for row in payload: + self._ptree.insert( + "", tk.END, + values=(row["name"], f"{row['confidence']:.2f}", row["prediction"], row["detail"]), + ) + self.pattern_status.configure(text=f"{len(payload)} hypotheses", foreground=GREEN) + + # ----------------------------------------------------------- engine tab + def _run_cmd(self, argv: list[str]) -> dict[str, Any]: + result = subprocess.run( + argv, cwd=core.ROOT, capture_output=True, text=True, timeout=900, check=False + ) + output = "".join((result.stdout or "")[-6000:]) + "\n" + "".join( + (result.stderr or "")[-6000:] + ) + return {"ok": result.returncode == 0, "output": output.strip() or "(no output)"} + + def run_check(self) -> None: + self._submit( + lambda: self._run_cmd([sys.executable, "engine/gen_tests.py", "--check"]), + self._engine_done, + ) + + def run_pytest(self) -> None: + self._submit( + lambda: self._run_cmd([sys.executable, "-m", "pytest", "-q"]), + self._engine_done, + ) + + def run_build(self) -> None: + self._submit( + lambda: self._run_cmd([sys.executable, "engine/runner.py", "build"]), + self._engine_done, + ) + + def run_bench(self) -> None: + self._submit( + lambda: self._run_cmd([sys.executable, "engine/runner.py", "bench"]), + self._engine_done, + ) + + def _engine_done(self, payload: Any) -> None: + self._set_busy(False) + self.output.configure(state=tk.NORMAL) + self.output.delete("1.0", tk.END) + if isinstance(payload, Exception): + self.output.insert(tk.END, str(payload)) + self.output.configure(state=tk.DISABLED) + return + body = payload["output"] + self.output.insert(tk.END, body) + self.output.configure(state=tk.DISABLED) + self.status.configure( + text="OK" if payload["ok"] else "FAILED", + foreground=GREEN if payload["ok"] else RED, + ) + + def open_docs(self) -> None: + webbrowser.open("https://dsk-dev-ai.github.io/algorithm-discovery-engine/") + + def open_github(self) -> None: + webbrowser.open("https://github.com/dsk-dev-ai/algorithm-discovery-engine") + + +def load_report() -> Report | None: + return core.load_report() + + +def main() -> None: + root = tk.Tk() + App(root) + root.mainloop() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/gui/core.py b/src/gui/core.py new file mode 100644 index 0000000..5f6306b --- /dev/null +++ b/src/gui/core.py @@ -0,0 +1,149 @@ +"""Headless logic powering the desktop GUI. + +Everything here avoids ``tkinter`` so it can run (and be tested) without a +display. The Tk layer in :mod:`gui.app` only renders what these helpers return. +""" + +from __future__ import annotations + +import json +import re +from pathlib import Path +from typing import Any, cast + +from algo_discovery import DiscoveryEngine +from synth import discovery as synth_discovery + +ROOT: Path = Path(__file__).resolve().parent.parent.parent +CATALOG_PATH: Path = ROOT / "catalog" / "problems.json" +TARGETS_PATH: Path = ROOT / "catalog" / "discovery_targets.json" +DISCOVERIES_DIR: Path = ROOT / "catalog" / "discoveries" +REPORT_JSON: Path = DISCOVERIES_DIR / "report.json" +REPORT_MD: Path = DISCOVERIES_DIR / "report.md" +SOLUTIONS_DIR: Path = DISCOVERIES_DIR / "solutions" + +_INT_RE = re.compile(r"-?\d+") + + +def _load_json(path: Path) -> dict[str, Any]: + return cast(dict[str, Any], json.loads(path.read_text(encoding="utf-8"))) + + +def version() -> str: + """Read the package version from the project metadata.""" + try: + from importlib import metadata + + return metadata.version("algorithm-discovery-engine") + except Exception: # pragma: no cover - source tree fallback + root_toml = ROOT / "pyproject.toml" + if root_toml.exists(): + for line in root_toml.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if line.startswith("version"): + return line.split("=")[1].strip().strip('"') + return "0.0.0" + + +def catalog_summary() -> dict[str, Any]: + """Summary of the shared multi-language catalog.""" + data = _load_json(CATALOG_PATH) + return { + "version": data.get("version", "?"), + "algorithms": list(data.get("algorithms", [])), + "data_structures": list(data.get("data_structures", [])), + "solved_problems": len(data.get("algorithms", [])), + } + + +def parse_sequence(text: str) -> list[int]: + """Parse a spaced/comma separated integer sequence for pattern discovery.""" + terms = [int(m) for m in _INT_RE.findall(text)] + if not terms: + raise ValueError("Enter at least one integer (e.g. 1, 4, 9, 16).") + return terms + + +def run_patterns(text: str, top: int = 8) -> list[dict[str, Any]]: + """Discover ranked hypotheses for a user-supplied integer sequence.""" + terms = parse_sequence(text) + result = DiscoveryEngine().discover(terms) + ranked = result.ranked[:top] + return [ + { + "name": score.name, + "confidence": score.confidence, + "prediction": score.prediction, + "detail": score.detail, + } + for score in ranked + ] + + +def run_synth( + smoke: bool = True, fuzz_count: int | None = None, scan_budget: int | None = None +) -> dict[str, Any]: + """Run the local algorithm synthesizer and return a summarized report. + + Raises ``RuntimeError`` if any target is rejected or missing, mirroring the + CLI's exit-code contract. + """ + report = synth_discovery.discover( + smoke=smoke, scan_budget=scan_budget, fuzz_count=fuzz_count + ) + summary = synth_summary(report) + if summary["rejected"] or summary["missing"]: + raise RuntimeError( + f"Discovery incomplete: rejected={summary['rejected']} missing={summary['missing']}" + ) + return summary + + +def synth_summary(report: dict[str, Any] | None = None) -> dict[str, Any]: + """Summarize a report dict (or the latest on disk).""" + if report is None: + report = load_report() + if report is None: + return { + "verified": 0, + "rejected": 0, + "missing": 0, + "targets": [], + "exists": False, + } + entries = report.get("targets", []) + verified = [e for e in entries if e.get("status") == "verified"] + return { + "verified": len(verified), + "rejected": sum(1 for e in entries if e.get("status") == "candidate-rejected"), + "missing": sum( + 1 for e in entries if e.get("status") == "no-candidate-found" + ), + "targets": entries, + "exists": True, + } + + +def load_report() -> dict[str, Any] | None: + """Load the latest discovery report from disk (``None`` if absent).""" + if not REPORT_JSON.exists(): + return None + return _load_json(REPORT_JSON) + + +def discovery_targets() -> list[dict[str, Any]]: + """The configured discovery targets for display.""" + data = _load_json(TARGETS_PATH) + return [ + {"id": item["id"], "kind": item.get("kind", "?"), "signature": item.get("signature", "")} + for item in data["targets"] + ] + + +def engine_catalog_counts() -> dict[str, int]: + """Simple numeric summary for the engine tab.""" + data = _load_json(CATALOG_PATH) + return { + "algorithms": len(data.get("algorithms", [])), + "data_structures": len(data.get("data_structures", [])), + } \ No newline at end of file diff --git a/tests/test_gui.py b/tests/test_gui.py new file mode 100644 index 0000000..4e934f3 --- /dev/null +++ b/tests/test_gui.py @@ -0,0 +1,74 @@ +"""Tests for the headless GUI core (no display required).""" + +from __future__ import annotations + +import pytest + +from gui import core + + +class TestCatalogSummary: + def test_counts(self) -> None: + summary = core.catalog_summary() + assert summary["algorithms"] + assert summary["data_structures"] + assert summary["solved_problems"] == len(summary["algorithms"]) + + def test_version_readable(self) -> None: + assert core.version() + + +class TestParseSequence: + def test_spaced(self) -> None: + assert core.parse_sequence("1 4 9 16 25") == [1, 4, 9, 16, 25] + + def test_comma_and_negatives(self) -> None: + assert core.parse_sequence("2, -4, 8, -16") == [2, -4, 8, -16] + + def test_empty_raises(self) -> None: + with pytest.raises(ValueError): + core.parse_sequence("abc") + + +class TestRunPatterns: + def test_quadratic(self) -> None: + rows = core.run_patterns("1 4 9 16 25", top=1) + assert rows + assert rows[0]["name"] in ("quadratic", "polynomial", "perfect-squares") + assert rows[0]["prediction"] == 36 + assert rows[0]["confidence"] > 0 + + def test_ordering_by_confidence(self) -> None: + rows = core.run_patterns("1 1 2 3 5 8 13 21", top=20) + confidences = [r["confidence"] for r in rows] + assert confidences == sorted(confidences, reverse=True) + + +class TestSynth: + def test_smoke_run_and_report(self) -> None: + summary = core.run_synth(smoke=True, fuzz_count=10) + assert summary["verified"] >= 1 + assert summary["rejected"] == 0 + assert summary["missing"] == 0 + assert core.REPORT_JSON.exists() + assert core.REPORT_MD.exists() + + def test_summary_reflects_disk(self) -> None: + on_disk = core.synth_summary() + assert on_disk["exists"] + assert set(on_disk) >= {"verified", "rejected", "missing", "targets"} + + def test_targets_listed(self) -> None: + targets = core.discovery_targets() + assert targets + assert all("id" in t and "kind" in t for t in targets) + + def test_report_schema(self) -> None: + report = core.load_report() + assert report is not None + entries = report["targets"] + assert entries + assert all( + {"status", "id", "source"} <= set(e) + for e in entries + ) \ No newline at end of file From 4b0ae2cbab8ed9b7b2649d9313acfbf2ca12d81d Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Fri, 11 Sep 2026 18:21:26 +0530 Subject: [PATCH 3/6] docs: add MkDocs Material documentation site Pages: index, engine, synthesis, patterns, gui, contributing MkDocs config with light/dark palette, code copy, admonitions Replaces the old static index.html landing page --- docs/assets/social-preview.png | Bin 0 -> 82304 bytes docs/contributing.md | 81 ++++++++++++++ docs/engine.md | 71 +++++++++++++ docs/gui.md | 61 +++++++++++ docs/index.html | 189 --------------------------------- docs/index.md | 84 +++++++++++++++ docs/patterns.md | 52 +++++++++ docs/synthesis.md | 55 ++++++++++ mkdocs.yml | 59 ++++++++++ 9 files changed, 463 insertions(+), 189 deletions(-) create mode 100644 docs/assets/social-preview.png create mode 100644 docs/contributing.md create mode 100644 docs/engine.md create mode 100644 docs/gui.md delete mode 100644 docs/index.html create mode 100644 docs/index.md create mode 100644 docs/patterns.md create mode 100644 docs/synthesis.md create mode 100644 mkdocs.yml diff --git a/docs/assets/social-preview.png b/docs/assets/social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..d8e0fc501f8bc12991f316531cdd8be433c21e51 GIT binary patch literal 82304 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%0Dms!LT@IrL^|Khpo= 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 I$SnAO0MTNKn*aa+ literal 0 HcmV?d00001 diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..e8fe38e --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,81 @@ +# Contributing + +Thanks for your interest in contributing to the Algorithm Discovery Engine. + +## Setup + +Requires Python ≥ 3.10 and [uv](https://docs.astral.sh/uv/), plus a JDK +(≥ 17), a C++17 compiler, and the Rust toolchain for the non-Python tiers. + +```bash +git clone https://github.com/dsk-dev-ai/algorithm-discovery-engine.git +cd algorithm-discovery-engine +uv sync --group dev --group docs +``` + +## Quality gates + +Run the full check suite before submitting: + +```bash +python engine/runner.py check # generated vectors in sync w/ the catalog +python engine/runner.py build # all four language tiers compile +python engine/runner.py test # catalog tests pass in all four languages +python engine/runner.py discover # synthesizer smoke pass (CI-friendly) +uv run ruff check src tests +uv run mypy -p algo_discovery -p ads -p synth -p gui +uv run pytest -q +uv run python -m gui --selftest # headless GUI core smoke test +uv run mkdocs build --strict # documentation builds cleanly +``` + +### Per-language quick checks + +```bash +cd languages/java && javac -d out $(find src -name '*.java') && java -cp out ads.TestRunner +cd languages/cpp && g++ -std=c++17 -O2 -I include tests/test_runner.cpp -o build/runner && ./build/runner +cd languages/rust && cargo test --quiet +``` + +## Adding a catalog problem + +1. Add the problem to `catalog/problems.json` with empty `tests`. +2. Implement it in every tier: `src/ads/`, `languages/java/`, `languages/cpp/`, + `languages/rust/` (following the existing per-problem conventions). +3. Run `uv run pytest -q` to auto-generate and fill the test vectors, then run + `python engine/runner.py check` to confirm they are committed in sync. +4. Wire it into `engine/runner.py` `BENCH_ALGOS` and each tier's benchmark if + you want it benchmarked. + +## Adding a discovery target + +1. Add a target entry to `catalog/discovery_targets.json` (id, kind, signature, + `arg_types`) with curated I/O examples. +2. Provide an oracle + fuzz generator in `src/synth/corpus.py` (see + `oracle_jump_game` / `fuzz_reach` for a template). +3. Verify with `uv run python -m synth discover --smoke` — the fully-verified + target should join the report as `verified`. + +## Documentation + +The site uses [MkDocs Material](https://squidfunk.github.io/mkdocs-material/): + +```bash +uv run mkdocs serve # local preview at http://127.0.0.1:8000 +uv run mkdocs build --strict # CI gate — must stay warnings-free +``` + +Documentation lives in `docs/`; pages map to the nav in `mkdocs.yml`. The CI +`docs` job builds with `--strict`, so dead links and broken fences block merges. + +## CI + +`.github/workflows/ci.yml` runs: ruff + mypy (all packages, including `gui`), +pytest on Python 3.10/3.12/3.13, Java/C++/Rust tests, `gen_tests --check`, a +discovery-smoke job, and a strict MkDocs build. `.github/workflows/pages.yml` +deploys the docs to GitHub Pages on every push to `main`. + +## Releasing + +Version bumps bump `pyproject.toml`, add a GitHub tag matching the version, and +a release with the changelog collated from the merged pull requests. \ No newline at end of file diff --git a/docs/engine.md b/docs/engine.md new file mode 100644 index 0000000..41479be --- /dev/null +++ b/docs/engine.md @@ -0,0 +1,71 @@ +# Multi-language engine + +## Catalog + +`catalog/problems.json` is the single source of truth. Every algorithm and +structure carries a shared test-vector set that is checked in **all four +languages**. + +| Algorithms (10) | Data structures (6) | +| -------------------------------------------------------- | ------------------------------ | +| `two_sum`, `binary_search`, `merge_sort`, `quick_sort` | `stack`, `queue`, `linked_list` | +| `max_subarray`, `lcs`, `knapsack_01`, `edit_distance` | `bst`, `trie`, `min_heap` | +| `graph_bfs`, `graph_dfs` | | + +`engine/gen_tests.py` expands these into identical per-language test vectors, +which are committed so every tier runs the exact same dataset. + +## Language tiers + +| Tier | Location | Approach | +| ------ | ------------------------ | ---------------------------------------------- | +| Java | `languages/java/` | OO reference implementations | +| C++ | `languages/cpp/` | modern C++17, RAII, iterators | +| Rust | `languages/rust/` | ownership-safe, zero dependencies | +| Python | `src/ads/` | regular + advanced (`*_advanced`, mypy strict) | + +## Running the engine + +```sh +python engine/runner.py check # assert committed vectors match the catalog +python engine/runner.py build # compile every tier (no execution) +python engine/runner.py test # run the catalog suite in all 4 languages +python engine/runner.py bench # benchmark all tiers, print comparison table +``` + +## Sample benchmark + +Microseconds, lower is better: + +``` +algorithm Python Java C++ Rust +merge_sort 928,850 85,358 66,972 39,647 +quick_sort 702,918 41,562 22,942 17,610 +max_subarray 444,168 17,881 9,702 78 +two_sum 32,847 30,393 582,269 659,066 +lcs 901,711 53,888 21,300 24,979 +knapsack_01 1,595 25,732 17,617 23,390 +``` + +!!! note + Languages may pick different strategies for the same problem — Java's + `two_sum` uses a hash map while C++/Rust use a quadratic scan — so the table + showcases trade-offs rather than an exact contest. + +## Per-language checks + +```sh +# Python +uv run pytest -q +uv run ruff check src tests +uv run mypy -p algo_discovery -p ads -p synth -p gui + +# Java +cd languages/java && javac -d out $(find src -name '*.java') && java -cp out ads.TestRunner + +# C++ +cd languages/cpp && g++ -std=c++17 -O2 -I include tests/test_runner.cpp -o build/runner && ./build/runner + +# Rust +cd languages/rust && cargo test --quiet +``` \ No newline at end of file diff --git a/docs/gui.md b/docs/gui.md new file mode 100644 index 0000000..5a96ba0 --- /dev/null +++ b/docs/gui.md @@ -0,0 +1,61 @@ +# Desktop GUI + +A small **Tkinter** desktop app (pure standard library — no third-party +dependencies) wraps the three engines behind buttons. + +## Launch + +```sh +python -m gui # from the repo root +uv run python -m gui +python engine/runner.py gui +``` + +`pip install algorithm-discovery-engine` won't include the GUI — the app is +meant to run from a checkout — but nothing beyond Python ≥ 3.10 with `tkinter` +is required. + +## Tabs + +### Discover + +Drives the [algorithm synthesizer](synthesis.md) from the UI. + +- **Run smoke pass** — CI-friendly reduced-budget pass (~2 min). +- **Run full pass** — the full 600k-candidate budget. +- **Refresh** — reload the latest `catalog/discoveries/report.json`. +- **Open report (markdown)** / **Open solutions folder** — jump straight to the + generated report and `solutions/*.py`. + +The table shows per-target id, kind, status, novelty, search time, and strategy. +Status colors mirror the CLI contract: green = verified, red = rejected. + +### Pattern discovery + +Type any integer sequence (spaces or commas, negatives welcome) and hit +**Discover**. The ranked hypotheses render as a table with confidence, predicted +next term, and the explanation from the [pattern discovery](patterns.md) +framework. + +### Engine + +A scratch terminal for the multi-language engine: + +- **Check vectors** — assert committed vectors match the catalog. +- **Run pytest** — the Python quality suite. +- **Run build** — compile Java, C++17, and Rust tiers. +- **Benchmark** — full cross-language benchmark table. +- **Open docs / Open GitHub** — external links. + +Long-running actions run in a background thread with the buttons disabled; the +status bar reports readiness and failures. The GUI is fully functional on a +headless box only for the *core* logic (see `gui/core.py`); rendering needs a +display. + +## Architecture + +``` +src/gui/core.py pure logic (no tkinter) — tested headlessly in CI +src/gui/app.py Tkinter rendering + background worker thread +src/gui/__main__.py launcher (python -m gui) +``` \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index d8a6bd5..0000000 --- a/docs/index.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - -algorithm-discovery-engine - - - -

    - - \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..a578ca1 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,84 @@ +# algorithm-discovery-engine + +**One catalog, four languages, zero dependencies — and a synthesizer that +rediscovers the algorithms for you.** + +![social preview](assets/social-preview.png) + +[![CI](https://github.com/dsk-dev-ai/algorithm-discovery-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/dsk-dev-ai/algorithm-discovery-engine/actions/workflows/ci.yml) +[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Sponsor](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F-Sponsor-red?style=flat-square&logo=githubsponsors&logoColor=white)](https://github.com/sponsors/dsk-dev-ai) + +The same algorithms and data structures, solved, tested, and benchmarked in +**Java, C++17, Rust, and Python**, from a single `catalog/problems.json` source +of truth with byte-identical generated test vectors across every language. + +On top sits a **local algorithm synthesizer** that rediscovers verified +algorithms — Kadane, best-time-to-buy-and-sell, jump game — from input/output +examples alone (no APIs, no model calls), plus an **automated mathematical +pattern discovery** framework for integer sequences. + +Every tier ships as pure standard-library code, and the repo is **CI-green by +default**: Python 3.10–3.13, Java 21, GCC C++17, stable Rust, and a +discovery-smoke job. + +!!! tip "New in v1.0.0" + A [desktop GUI](gui.md) built purely on the standard library (`python -m + gui`) — discover patterns, run the synthesizer, and drive the engine + without touching the terminal. + +## Highlights + +- **10 algorithms + 6 data structures**, implemented and tested in all four languages. +- **Cross-language benchmarks** — one harness, one comparison table. +- **Verified algorithm synthesis** — 7/7 targets rediscovered/confirmed by the + local synthesizer, fuzz-checked against independent oracles. +- **Pattern discovery** — 10 built-in hypotheses with confidence, detail, and + next-term predictions. +- **Zero runtime dependencies** — the solving engine, synthesizer, and GUI use + only the standard library. + +## Quick start + +```sh +git clone https://github.com/dsk-dev-ai/algorithm-discovery-engine +cd algorithm-discovery-engine + +python engine/runner.py test # run the catalog suite in all 4 languages +python engine/runner.py bench # benchmark all tiers side by side +python engine/runner.py discover # synthesize + verify algorithms +python -m gui # open the desktop app +``` + +No install needed: Python ≥ 3.10 for the Python engine; a JDK, C++17 compiler, +and the Rust toolchain unlock the other three tiers. + +## Documentation + +| Page | What it covers | +| --- | --- | +| [Multi-language engine](engine.md) | catalog, language tiers, running, benchmarks | +| [Algorithm synthesis](synthesis.md) | how the synthewer discovers and verifies algorithms | +| [Pattern discovery](patterns.md) | discovering hypotheses for integer sequences | +| [Desktop GUI](gui.md) | launching the app and its three tabs | +| [Contributing](contributing.md) | setup, quality gates, CI, adding problems/targets | + +## Project layout + +``` +catalog/problems.json single source of truth (tests + examples) +catalog/discovery_targets.json discovery targets (curated examples + oracles) +catalog/discoveries/ synthesizer reports + discovered solutions +engine/gen_tests.py generates identical test vectors per language +engine/runner.py build / test / benchmark / synthesize dispatcher +src/ads/ Python solving engine (regular + advanced) +src/algo_discovery/ pattern-discovery framework (original) +src/synth/ local algorithm synthesizer +src/gui/ Tkinter desktop app (stdlib only) +docs/ this documentation site (MkDocs) +languages/java/src/ads/ Java tier (+ TestRunner, Benchmark) +languages/cpp/include/ads/ C++17 headers (+ tests, bench) +languages/rust/src/ Rust tier (+ examples/benchmark.rs) +``` + +MIT licensed — see [LICENSE](https://github.com/dsk-dev-ai/algorithm-discovery-engine/blob/main/LICENSE). \ No newline at end of file diff --git a/docs/patterns.md b/docs/patterns.md new file mode 100644 index 0000000..7084501 --- /dev/null +++ b/docs/patterns.md @@ -0,0 +1,52 @@ +# Pattern discovery + +The `algo_discovery` package ranks candidate hypotheses for an integer sequence +and scores them with confidence, an explanation, and (when applicable) a +predicted next term. + +## CLI + +```sh +uv run python -m algo_discovery 1 4 9 16 25 +``` + +## Python API + +```python +from algo_discovery import DiscoveryEngine + +engine = DiscoveryEngine() + +# Squares +result = engine.discover((1, 4, 9, 16, 25)) +print(result.best.name) # "quadratic" +print(result.best.prediction) # 36 + +# Subset of hypotheses only +from algo_discovery.engine import hypotheses_by_name +engine = DiscoveryEngine(hypotheses=hypotheses_by_name(["arithmetic", "fibonacci-like"])) +``` + +## Built-in hypotheses + +| Hypothesis | Detects | +| --------------------- | -------------------------------------------------- | +| `constant` | identical terms | +| `arithmetic` | constant differences | +| `geometric` | constant ratios | +| `quadratic` | constant second differences | +| `cubic` | constant third differences | +| `powers-of-two` | 2^n growth ladder | +| `fibonacci-like` | recurrence a(n) = a(n-1) + a(n-2) | +| `affine-recurrence` | general linear recurrences | +| `prime` | primes / prime-like patterns | +| `alternating-sign` | sign oscillation | + +Each yields a confidence (0–1), an explanation string, and a predicted next term +when it fits. + +## Feature extraction + +Every hypothesis sees the same feature vector derived from the sequence: +differences, ratios, sign patterns, monotonicity, palindromicity, growth rate, +gcd, and a normalized numeric encoding. \ No newline at end of file diff --git a/docs/synthesis.md b/docs/synthesis.md new file mode 100644 index 0000000..72c043d --- /dev/null +++ b/docs/synthesis.md @@ -0,0 +1,55 @@ +# Algorithm synthesis + +`src/synth/` searches for candidate algorithms directly from input/output +examples and verifies them out-of-sample before reporting anything. The scan is +fully local — no external APIs, no model calls. + +## How it works + +- **Grammar search** (`scan` kind) enumerates single-pass *scanner* programs: a + handful of running-state variables updated per element, drawn from an + operator/pool grammar. A candidate must match the curated examples **and** + survive fuzz verification (60+ inputs) against an independent reference + oracle. +- **Strategy templates** (`vote`, `seen`, `fib`, `circular-kadane`) supply + parametric skeletons — Boyer-Moore voting, hash-set membership, Fibonacci + pumping, circular Kadane — that are fuzz-verified just like grammar results. +- **Novelty classification**: every verified candidate is tagged + `rediscovered` (already in `catalog/problems.json`) or `new-to-catalog` (a + candidate worth porting to the four language tiers). + +## Running it + +```sh +uv run python -m synth discover --smoke # ~2 min, CI-friendly +uv run python -m synth discover # full pass (~600k candidates/target cap) +``` + +Or from the dispatcher: `python engine/runner.py discover`. + +## Reading the report + +Results land in `catalog/discoveries/`: + +- `report.json` — machine-readable results per target +- `report.md` — human-readable table + verified sources +- `solutions/.py` — runnable discovered algorithms + +The CLI exits non-zero if any target is rejected or missing, so the discovery +pass plugs straight into CI. Current smoke run: **7 targets · 7 verified · +0 rejected**. + +## Example: a discovered jump-game scanner + +```python +def discovered_jump_game(arg): + s0 = 0 + s1 = 0 + for i, x in enumerate(arg): + s1 = max(s1, i - s0) # how far the current reach overshoots index i + s0 = max(s0, i + x) # extend the reach + return s1 == 0 +``` + +This scanner was synthesized from examples alone and verified against a +reference oracle plus fuzz inputs before being reported. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..7161e58 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,59 @@ +site_name: algorithm-discovery-engine +site_description: > + Multi-language algorithms & data structures solving engine (Java, C++, Rust, + Python) with cross-language benchmarks, automated pattern discovery, and a + local synthesizer that rediscovers algorithms from examples. +site_url: https://dsk-dev-ai.github.io/algorithm-discovery-engine/ +repo_url: https://github.com/dsk-dev-ai/algorithm-discovery-engine +edit_uri: edit/main/docs/ + +theme: + name: material + palette: + # Palette toggle for automatic light/dark mode. + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: blue + toggle: + icon: material/weather-night + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: blue + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - navigation.tabs + - navigation.sections + - navigation.top + - content.code.copy + +markdown_extensions: + - admonition + - tables + - toc: + permalink: true + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.superfences + +nav: + - Home: index.md + - Multi-language engine: engine.md + - Algorithm synthesis: synthesis.md + - Pattern discovery: patterns.md + - Desktop GUI: gui.md + - Contributing: contributing.md + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/dsk-dev-ai/algorithm-discovery-engine + - icon: fontawesome/solid/heart + link: https://github.com/sponsors/dsk-dev-ai + +copyright: MIT License © dsk-dev-ai — built and maintained by + NextGenAI Labs. \ No newline at end of file From 1b28421f95fbc92d1d06e6928e5a5d87ea322d31 Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Fri, 11 Sep 2026 18:21:34 +0530 Subject: [PATCH 4/6] ci: add docs build + gui smoke jobs, update Pages for MkDocs - ci: add 'Docs (strict build)' job (mkdocs build --strict) - ci: add 'GUI core smoke' job (python -m gui --selftest + pytest gui tests) - ci: add -p gui to mypy type-check - pages: switch from static upload to mkdocs build + deploy --- .github/workflows/ci.yml | 30 +++++++++++++++++++++++++++++- .github/workflows/pages.yml | 9 ++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 163d766..09a5658 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Lint run: uv run ruff check src tests - name: Type check - run: uv run mypy -p algo_discovery -p ads -p synth + run: uv run mypy -p algo_discovery -p ads -p synth -p gui python-test: name: Pytest (3.10, 3.12, 3.13) @@ -98,3 +98,31 @@ jobs: run: uv sync --group dev - name: Run synth discover --smoke run: uv run python engine/runner.py discover + + gui: + name: GUI core smoke + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: "3.12" + - name: Sync deps + run: uv sync --group dev + - name: Headless GUI selftest + run: uv run python -m gui --selftest + - name: GUI core tests + run: uv run pytest -q tests/test_gui.py + + docs: + name: Docs (strict build) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: "3.12" + - name: Sync docs deps + run: uv sync --group docs + - name: Build docs + run: uv run mkdocs build --strict --site-dir site diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index d414ef1..db1d464 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -18,8 +18,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: "3.12" + - name: Sync docs deps + run: uv sync --group docs + - name: Build docs site + run: uv run mkdocs build --strict --site-dir site - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v3 with: - path: docs + path: site - uses: actions/deploy-pages@v4 \ No newline at end of file From a10d47f5383d8ba3dd0677b946dab0cec0400a0b Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Fri, 11 Sep 2026 18:21:43 +0530 Subject: [PATCH 5/6] feat: add gui command to engine/runner.py runner.py gui runs --selftest then launches the Tkinter app --- engine/runner.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/engine/runner.py b/engine/runner.py index 57e3d16..70e1600 100644 --- a/engine/runner.py +++ b/engine/runner.py @@ -9,6 +9,7 @@ build Compile every language tier (no execution). check Verify committed test vectors are in sync with the catalog. discover Run the algorithm synthesizer (smoke pass, ~2 min). + gui Launch the Tkinter desktop app. """ from __future__ import annotations @@ -199,6 +200,12 @@ def cmd_discover_smoke() -> None: ) +def cmd_gui() -> None: + check([sys.executable, "-m", "gui", "--selftest"], ROOT, "gui selftest") + print("Launching GUI — close the window to exit.") + subprocess.run([sys.executable, "-m", "gui"], cwd=ROOT, check=False) + + def main() -> None: if len(sys.argv) < 2: print(__doc__) @@ -210,6 +217,7 @@ def main() -> None: "build": cmd_build, "check": cmd_check, "discover": cmd_discover_smoke, + "gui": cmd_gui, } handlers[command]() From 0985ff88cd715d7dba8932ed13c3fceb6abdb2a3 Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Fri, 11 Sep 2026 18:25:06 +0530 Subject: [PATCH 6/6] docs: update README + CONTRIBUTING for v1.0.0 (GUI, docs site, quality gates) --- CONTRIBUTING.md | 6 ++++-- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65a96f7..62aba0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Requires Python >= 3.10 and [uv](https://docs.astral.sh/uv/), plus a JDK ```bash git clone https://github.com/dsk-dev-ai/algorithm-discovery-engine.git cd algorithm-discovery-engine -uv sync --group dev +uv sync --group dev --group docs ``` ## Quality gates @@ -23,7 +23,9 @@ python engine/runner.py build # all four language tiers compile python engine/runner.py test # catalog tests pass in all four languages python engine/runner.py discover # synthesizer smoke pass (CI-friendly) uv run ruff check src tests -uv run mypy -p algo_discovery -p ads -p synth +uv run mypy -p algo_discovery -p ads -p synth -p gui +uv run python -m gui --selftest +uv run mkdocs build --strict ``` ### Per-language quick checks diff --git a/README.md b/README.md index c4e60d2..6b742c0 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ [![Forks](https://img.shields.io/github/forks/dsk-dev-ai/algorithm-discovery-engine?style=flat-square&logo=github&logoColor=white)](https://github.com/dsk-dev-ai/algorithm-discovery-engine/forks) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Last commit](https://img.shields.io/github/last-commit/dsk-dev-ai/algorithm-discovery-engine?style=flat-square&logo=git&logoColor=white)]() +[![Docs](https://img.shields.io/badge/docs-live-blue?style=flat-square&logo=materialformkdocs&logoColor=white)](https://dsk-dev-ai.github.io/algorithm-discovery-engine/) +[![GUI](https://img.shields.io/badge/available-gui_tkinter-blueviolet?style=flat-square)](#desktop-gui) [![Sponsor](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F-Sponsor-red?style=flat-square&logo=githubsponsors&logoColor=white)](https://github.com/sponsors/dsk-dev-ai) **Python** · **Java** · **C++** · **Rust** @@ -38,10 +40,12 @@ game) from input/output examples alone — no APIs, no model calls. next-term predictions. - **Zero dependencies per language** — no framework, no package, no JIT magic; pure standard library in all four tiers. +- **Desktop GUI** — a Tkinter app (`python -m gui`) built on the standard library only; + core logic is headless-tested in CI. - **Generated-then-committed test vectors** — identical tests enforced by CI in every language; a single `--check` keeps them in sync with the catalog. - **CI-green by default** — grid of Python 3.10–3.13, Java 21, GCC C++17, stable Rust, - plus a discovery-smoke job. + discovery-smoke, GUI-core smoke, and a strict docs build. ## Quick start @@ -52,6 +56,7 @@ cd algorithm-discovery-engine python engine/runner.py test # build + run the catalog suite in all 4 languages python engine/runner.py bench # benchmark all tiers, side by side python engine/runner.py discover # synthesize + verify algorithms from examples +python -m gui # open the desktop app ``` No install required — Python ≥ 3.10 and (for the non-Python tiers) a JDK, a C++17 @@ -154,13 +159,46 @@ explanation, and next-term prediction. uv run python -m algo_discovery 1 4 9 16 ``` +## Desktop GUI + +A small **Tkinter** desktop app (no third-party runtime dependencies) wraps the +three engines behind a clean dark-themed interface: + +```sh +python -m gui # launch from the repo root +python engine/runner.py gui # same thing via the dispatcher +ade-gui # if installed with pip/uv +``` + +| Tab | What it does | +| ----------------- | ---------------------------------------------------------------------- | +| **Discover** | Run the synthesizer (smoke/full), view the per-target verified table, open the report. | +| **Pattern discovery** | Enter an integer sequence, discover ranked hypotheses with next-term predictions. | +| **Engine** | Check vectors, run pytest, build all tiers, benchmark, open docs/GitHub links. | + +Core logic lives in `gui/core.py` and is tested headlessly in CI (`-p gui` mypy ++ `pytest -q tests/test_gui.py`). + +## Documentation + +Full docs: **https://dsk-dev-ai.github.io/algorithm-discovery-engine/** + +Build locally: + +```sh +uv sync --group docs +uv run mkdocs serve # live preview at http://127.0.0.1:8000 +``` + ## Development ```sh -uv sync --group dev -uv run pytest -q # 118 tests (catalog + synthesizer) +uv sync --group dev --group docs +uv run pytest -q # 118+ tests (catalog + synthesizer + GUI core) uv run ruff check src tests -uv run mypy -p algo_discovery -p ads -p synth +uv run mypy -p algo_discovery -p ads -p synth -p gui +uv run python -m gui --selftest # headless GUI smoke +uv run mkdocs build --strict # documentation builds cleanly python engine/runner.py check # keep generated vectors in sync before committing ``` @@ -178,6 +216,8 @@ engine/runner.py build / test / benchmark / synthesize dispatche src/ads/ Python solving engine (regular + advanced) src/algo_discovery/ pattern-discovery framework (original) src/synth/ local algorithm synthesizer +src/gui/ Tkinter desktop app (stdlib only) +docs/ documentation site (MkDocs Material) languages/java/src/ads/ Java tier (+ TestRunner, Benchmark) languages/cpp/include/ads/ C++17 headers (+ tests, bench) languages/rust/src/ Rust tier (+ examples/benchmark.rs)
    - - - -
    -

    one catalog, four languages,
    zero dependencies

    -

    The same algorithms and data structures — solved, tested, and benchmarked in Java, C++, Rust, and Python — plus a local synthesizer that rediscovers the algorithms for you.

    - - algorithm-discovery-engine social preview -
    - -
    -
    -
    4
    languages, one catalog
    -
    10
    algorithms
    -
    6
    data structures
    -
    118
    tests, CI-green
    -
    7/7
    discoveries verified
    -
    -
    - -
    -

    What's inside

    -

    One catalog/problems.json as the source of truth, byte-identical test vectors in every tier.

    -
    -
    -

    Multi-language engine

    -

    Algorithms and structures implemented in Java 21, C++17, Rust, and Python (regular + optimized advanced tiers), zero dependencies per language.

    -
    JavaC++17RustPython
    -
    -
    -

    Cross-language benchmarks

    -

    A single harness times all four tiers. Compare strategies — Java’s hash-map two-sum vs. the quadratic scans in C++/Rust.

    -
    runner.py bench
    -
    -
    -

    Algorithm synthesizer

    -

    Grammar-based search + templates rediscover Kadane, buy-and-sell, and jump-game algorithms from examples alone — fuzz-verified out-of-sample.

    -
    7 targets · 7 verified · 0 rejected
    -
    -
    -

    Pattern discovery

    -

    The original framework ranks hypotheses for any integer sequence: arithmetic, geometric, quadratic, Fibonacci-like, with next-term predictions.

    -
    10 built-in hypotheses
    -
    -
    -
    - -
    -

    Quick start

    -
    # no install required (Python ≥ 3.10)
    -git clone https://github.com/dsk-dev-ai/algorithm-discovery-engine
    -cd algorithm-discovery-engine
    -
    -python engine/runner.py test        # run the catalog suite in all 4 languages
    -python engine/runner.py bench       # benchmark all tiers side by side
    -python engine/runner.py discover    # synthesize + verify algorithms from examples
    -
    - -
    -

    Catalog

    -

    Shared test vectors asserted in all four languages.

    - - - - - -
    Algorithms (10)Data structures (6)
    two_sum · binary_search · merge_sort · quick_sortstack · queue · linked_list · bst
    max_subarray · lcs · knapsack_01 · edit_distancetrie · min_heap
    graph_bfs · graph_dfs
    -
    - - - -