diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml index 0b62c5c..f98b8ae 100644 --- a/.github/workflows/ci-cd.yaml +++ b/.github/workflows/ci-cd.yaml @@ -5,8 +5,6 @@ on: [push, pull_request] permissions: contents: read - - jobs: test: runs-on: ubuntu-latest @@ -35,3 +33,21 @@ jobs: run: configlock init project_info.yaml - name: check locked file run: configlock lock project_info.yaml + + - name: linting + run: uvx ruff check . + - name: formatting + run: uvx ruff format --check . + - name: type_consitency + run: uv run pyright . + - name: complexity check + run: uvx radon cc -s -a . + - name: tests + run: uv run pytest -v --durations=0 --cov --cov-report=xml + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: build + run: uv build diff --git a/docs/index.html b/docs/index.html index 12da1d1..7cdcea7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,17 +7,18 @@

ConfigLock Web Portal

- - - - + + + +

Result:

No data yet...
- + + \ No newline at end of file diff --git a/docs/script.js b/docs/script.js index 4832efb..a639bd0 100644 --- a/docs/script.js +++ b/docs/script.js @@ -1,18 +1,36 @@ import YAML from 'https://esm.sh/yaml'; +function formInputs(){ + const user = document.getElementById('userName').value; + const repo = document.getElementById('repoName').value; + const branch = document.getElementById('branchName').value; + const path = document.getElementById('pathToFile').value; -async function fetchFile() { - const user = document.getElementById('user').value; - const repo = document.getElementById('repo').value; - const branch = document.getElementById('branch').value; - const path = document.getElementById('path').value; const output = document.getElementById('output'); const urlBox = document.getElementById('url'); + return [user, repo, branch, path, output, urlBox]; + +} + +function retriveGithubCodeblocks(user, repo, branch, path){ + + const lockFilePath = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${path}`; + const validatorFilePath = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/src/configlock/validator.py`; + + return [lockFilePath, validatorFilePath]; + +} + + +async function fetchFile() { + + const [user, repo, branch, path, output, urlBox] = formInputs(); + + const [lockFilePath, validatorFilePath] = retriveGithubCodeblocks(user,repo,branch,path); + output.innerText = 'Fetching...'; - const lockFile = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${path}`; - const validatorFile = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/src/configlock/validator.py`; - urlBox.innerText = lockFile; + urlBox.innerText = lockFilePath; let timeoutId; @@ -25,9 +43,9 @@ async function fetchFile() { let pyodide = await loadPyodide({ indexURL: "https://cdn.jsdelivr.net/pyodide/v0.26.1/full/"}); - const lockFileContents = await fetch(lockFile); + const lockFileContents = await fetch(lockFilePath); - const validatorCodeContents = await fetch(validatorFile); + const validatorCodeContents = await fetch(validatorFilePath); const pythonCode = await validatorCodeContents.text(); pyodide.runPython(pythonCode); diff --git a/pyproject.toml b/pyproject.toml index 3633d01..51e63d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,8 +24,11 @@ build-backend = "uv_build" [dependency-groups] dev = [ + "pyright>=1.1.410", "pytest>=9.0.3", + "radon>=6.0.1", "reloadserver>=1.0.0", + "ruff>=0.15.15", ] [tool.pytest.ini_options] diff --git a/src/configlock/helper.py b/src/configlock/helper.py index 2b7155d..29f4b35 100644 --- a/src/configlock/helper.py +++ b/src/configlock/helper.py @@ -24,7 +24,7 @@ def check_file_identicality(file_path:str, config_file_path: str | None = CONFIG filecmp.clear_cache() res = filecmp.cmp(file_path, config_file_path, shallow=False) return res - except Exception as exc: + except Exception: filecmp.clear_cache() res = filecmp.cmp(file_path, config_file_path, shallow=False) return res @@ -47,7 +47,7 @@ def read_yaml(file_path: str) -> dict: except FileNotFoundError: raise else: - typer.echo(f"Sucessfully read file") + typer.echo("Sucessfully read file") return data @@ -58,7 +58,7 @@ def read_json(file_path: str) -> dict: except FileNotFoundError: raise else: - typer.echo(f"Sucessfully read file") + typer.echo("Sucessfully read file") return data def write_json(data: dict, file_path: str | None = CONFIG_LOG_FILE_PATH) -> None: @@ -71,7 +71,7 @@ def write_json(data: dict, file_path: str | None = CONFIG_LOG_FILE_PATH) -> None except Exception: raise else: - typer.echo(f"Sucessfully wrote file") + typer.echo("Sucessfully wrote file") def check_file_and_read_file(file_path: str) -> dict: diff --git a/src/configlock/validator.py b/src/configlock/validator.py index efd112b..b4b339f 100644 --- a/src/configlock/validator.py +++ b/src/configlock/validator.py @@ -4,6 +4,13 @@ keys_to_ignore = {"version"} +''' +Note: +This is a strict class, meaning that webassembly will use this class. +Beware of the contents, and try to be efficient, keep the class minimal. +''' + + @dataclass class ValidationContext: new_path: str diff --git a/tests/unit_test/test_validator_class.py b/tests/unit_test/test_validator_class.py new file mode 100644 index 0000000..bb0bb3c --- /dev/null +++ b/tests/unit_test/test_validator_class.py @@ -0,0 +1,74 @@ + +import inspect + +from configlock import validator + + +def helper_sig(name_class) -> inspect.Signature: + + sig = inspect.getfullargspec(name_class).annotations + + return sig + + +def test_sig_context(): + + val_context_sig = helper_sig(name_class=validator.ValidationContext) + + sig_list = ["new_path", "current_path", "order_matters"] + + assert ("return", None) in val_context_sig.items() + assert(all(key in val_context_sig for key in sig_list)) + assert ("new_path", str) in val_context_sig.items() + assert ("current_path", str) in val_context_sig.items() + + +def test_sig_walk_n_order(): + + + walk_n_order_sig = helper_sig(name_class=validator.walk_yaml_with_no_order) + + + sig_list = ["current_data", "new_data", "context", "depth"] + + assert(all(key in walk_n_order_sig for key in sig_list)) + assert ("context", validator.ValidationContext) in walk_n_order_sig.items() + + + +def test_sig_walk_w_order(): + + walk_w_order = helper_sig(name_class=validator.walk_yaml_in_order) + + + sig_list = ["current_data", "new_data", "context", "depth"] + + assert(all(key in walk_w_order for key in sig_list)) + assert ("context", validator.ValidationContext) in walk_w_order.items() + + + + + + accept_n_key = helper_sig(name_class=validator.accept_new_keys) + + + accept_n_val = helper_sig(name_class=validator.accept_new_value) + + + + + + + + + + +if __name__ == "__main__": + #print(inspect.getfullargspec(validator.ValidationContext)) + sig = inspect.getfullargspec(validator.ValidationContext).annotations + print(sig) + print("new_path" in sig) + print( ("return", None) in sig.items()) + print(helper_sig(name_class=validator.walk_yaml_with_no_order)) + print(inspect.signature(validator.walk_yaml_in_order(0,0,0))) diff --git a/uv.lock b/uv.lock index 64e5c73..c373386 100644 --- a/uv.lock +++ b/uv.lock @@ -111,8 +111,11 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pyright" }, { name = "pytest" }, + { name = "radon" }, { name = "reloadserver" }, + { name = "ruff" }, ] [package.metadata] @@ -125,8 +128,11 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "pyright", specifier = ">=1.1.410" }, { name = "pytest", specifier = ">=9.0.3" }, + { name = "radon", specifier = ">=6.0.1" }, { name = "reloadserver", specifier = ">=1.0.0" }, + { name = "ruff", specifier = ">=0.15.15" }, ] [[package]] @@ -158,6 +164,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 = "mando" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/24/cd70d5ae6d35962be752feccb7dca80b5e0c2d450e995b16abd6275f3296/mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500", size = 37868, upload-time = "2022-02-24T08:12:27.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a", size = 28149, upload-time = "2022-02-24T08:12:25.24Z" }, +] + [[package]] name = "markdown-it-py" version = "4.2.0" @@ -179,6 +197,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "packaging" version = "26.2" @@ -206,6 +233,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pyright" +version = "1.1.410" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/33/288b5868fa00846dacf249633719d747893e54aebd196b9968ac1878a5d3/pyright-1.1.410-py3-none-any.whl", hash = "sha256:5e961bed37cacf96b3f7cd7b1da39b350a9239aa2e69138d0e88f728cfaf296c", size = 6082448, upload-time = "2026-06-01T17:35:46.387Z" }, +] + [[package]] name = "pytest" version = "9.0.3" @@ -267,6 +307,19 @@ wheels = [ { 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 = "radon" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "mando" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/6d/98e61600febf6bd929cf04154537c39dc577ce414bafbfc24a286c4fa76d/radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5", size = 1874992, upload-time = "2023-03-26T06:24:38.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859", size = 52784, upload-time = "2023-03-26T06:24:33.949Z" }, +] + [[package]] name = "reloadserver" version = "1.0.0" @@ -307,6 +360,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] +[[package]] +name = "ruff" +version = "0.15.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, + { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -316,6 +394,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[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 = "typer" version = "0.25.1" @@ -331,6 +418,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, ] +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + [[package]] name = "urllib3" version = "2.7.0"