From 8d14d62cff6b7f6867ba0a576204bf9d57d1febe Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Wed, 17 Jun 2026 07:27:42 -0700 Subject: [PATCH 01/12] Updating docker workflow versions and switching all BERDataLakehouse mentions to KBaseDataLakehouse --- .github/workflows/docker_build.yaml | 8 +- .github/workflows/tests.yml | 4 +- .github/workflows/trivy.yaml | 4 +- README.md | 13 +- notebooks/pangenome_refseq.py | 5 +- pyproject.toml | 5 +- scripts/parse_index.py | 15 +- scripts/s3_local.py | 7 +- uv.lock | 921 ++++++++++++++-------------- 9 files changed, 504 insertions(+), 478 deletions(-) diff --git a/.github/workflows/docker_build.yaml b/.github/workflows/docker_build.yaml index 5949ec5b..880508a9 100644 --- a/.github/workflows/docker_build.yaml +++ b/.github/workflows/docker_build.yaml @@ -41,16 +41,16 @@ jobs: uses: actions/checkout@v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Extract Docker metadata - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 id: meta with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build test Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 id: build-test-image with: context: . @@ -66,7 +66,7 @@ jobs: docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TEST_TAG }} test - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 id: build-push-image if: ${{ github.event_name == 'release' || github.event_name == 'push' }} with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0cf56526..67d8460e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,11 +49,11 @@ jobs: shell: bash continue-on-error: false run: | - docker pull ghcr.io/berdatalakehouse/spark_notebook:main + docker pull ghcr.io/kbasedatalakehouse/spark_notebook:main echo "Running tests inside container..." # slightly nasty hack chmod 777 . - docker run --rm -e NB_USER=runner -v .:/tmp/cdm ghcr.io/berdatalakehouse/spark_notebook:main /bin/bash /tmp/cdm/scripts/run_tests.sh + docker run --rm -e NB_USER=runner -v .:/tmp/cdm ghcr.io/kbasedatalakehouse/spark_notebook:main /bin/bash /tmp/cdm/scripts/run_tests.sh - name: Send to Codecov id: send_to_codecov diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index 94b60a64..d61dacbb 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Build an image from Dockerfile run: | @@ -52,6 +52,6 @@ jobs: timeout: 15m - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: "trivy-results.sarif" diff --git a/README.md b/README.md index 59813af5..b8f196b5 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Repo for CDM input data loading and wrangling - [Environment and python management](#environment-and-python-management) - [Installation](#installation) - [Lakehouse and Use with Jupyter notebooks](#lakehouse-and-use-with-jupyter-notebooks) + - [Jupyter Kernel Environment Variables](#jupyter-kernel-environment-variables) - [Running import pipelines](#running-import-pipelines) - [Development](#development) - [Spark and other non-python dependencies](#spark-and-other-non-python-dependencies) @@ -103,24 +104,24 @@ Current endpoints include: ### Spark and other non-python dependencies -Some parts of this codebase rely on having a Spark instance available. Spark dependencies are pulled in by the `berdl-notebook-utils` package from [BERDataLakehouse/spark_notebook](https://github.com/BERDataLakehouse/spark_notebook), and the [Docker container](https://github.com/BERDataLakehouse/spark_notebook/pkgs/container/spark_notebook) generated by the same repo should be used for development and testing to mimic the container where code will be run. +Some parts of this codebase rely on having a Spark instance available. Spark dependencies are pulled in by the `berdl-notebook-utils` package from [KBaseDataLakehouse/spark_notebook](https://github.com/KBaseDataLakehouse/spark_notebook), and the [Docker container](https://github.com/KBaseDataLakehouse/spark_notebook/pkgs/container/spark_notebook) generated by the same repo should be used for development and testing to mimic the container where code will be run. Pull the docker image: ```sh -> docker pull ghcr.io/berdatalakehouse/spark_notebook:main +> docker pull ghcr.io/kbasedatalakehouse/spark_notebook:main ``` Mount the current directory at `/tmp/cdm` and run the tests: ```sh -> docker run --rm -e NB_USER=runner -v .:/tmp/cdm ghcr.io/berdatalakehouse/spark_notebook:main /bin/bash /tmp/cdm/scripts/run_tests.sh +> docker run --rm -e NB_USER=runner -v .:/tmp/cdm ghcr.io/kbasedatalakehouse/spark_notebook:main /bin/bash /tmp/cdm/scripts/run_tests.sh ``` Run the container interactively as the user `runner`; current directory is mounted at `/tmp/cdm`: ```sh -> docker run --rm -e NB_USER=runner -it -v .:/tmp/cdm ghcr.io/berdatalakehouse/spark_notebook:main +> docker run --rm -e NB_USER=runner -it -v .:/tmp/cdm ghcr.io/kbasedatalakehouse/spark_notebook:main ``` This will launch a bash shell; the contents of the `cdm-data-loaders` directory are mounted at `/tmp/cdm`. @@ -129,13 +130,13 @@ This will launch a bash shell; the contents of the `cdm-data-loaders` directory Run the container and sleep: ```sh -> docker run --rm -e NB_USER=runner -it -v .:/tmp/cdm ghcr.io/berdatalakehouse/spark_notebook:main sleep 100000000 +> docker run --rm -e NB_USER=runner -it -v .:/tmp/cdm ghcr.io/kbasedatalakehouse/spark_notebook:main sleep 100000000 ``` The `sleep` command will run the container for long enough that you can then connect to it via Docker Desktop or the [VSCode Containers extension](https://code.visualstudio.com/docs/containers/overview). -See the [BERDataLakehouse/spark_notebook](https://github.com/BERDataLakehouse/spark_notebook) repo for more information on the container and for a full docker-compose set up to mimic the BER Data Lakehouse container infrastructure. +See the [KBaseDataLakehouse/spark_notebook](https://github.com/KBaseDataLakehouse/spark_notebook) repo for more information on the container and for a full docker-compose set up to mimic the BER Data Lakehouse container infrastructure. ### Tests diff --git a/notebooks/pangenome_refseq.py b/notebooks/pangenome_refseq.py index e537a3cf..a86c0fa5 100644 --- a/notebooks/pangenome_refseq.py +++ b/notebooks/pangenome_refseq.py @@ -11,8 +11,6 @@ - missing_refseq_ids """ -from __future__ import annotations - import logging import tempfile import urllib.request @@ -20,9 +18,8 @@ from typing import TYPE_CHECKING import click -from pyspark.sql.functions import regexp_replace - from berdl_notebook_utils.setup_spark_session import get_spark_session +from pyspark.sql.functions import regexp_replace if TYPE_CHECKING: from pyspark.sql import SparkSession diff --git a/pyproject.toml b/pyproject.toml index fa246a6f..8832a284 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -170,10 +170,11 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "*.ipynb" = ["T201"] # ignore printing in notebooks +"**/__init__.py" = ["D104"] # no docstrings for __init__ files +"scripts/**/*.py" = ["T201"] # ignore printing in scripts "tests/**/*.py" = ["S101", "T201", "FBT001", "FBT002", "ARG002"] # use of assert, booleans, unused mock args "tests/integration/**/*.py" = ["S101", "T201", "FBT001", "FBT002", "ARG002", "ANN401"] "tests/utils/test_s3.py" = ["ANN401"] -"**/__init__.py" = ["D104"] [tool.ruff.lint.mccabe] # Flag errors (`C901`) whenever the complexity level exceeds 15. @@ -217,5 +218,5 @@ AWS_SECRET_ACCESS_KEY = "test_access_secret" [tool.uv.sources] cdm-schema = { git = "https://github.com/kbase/cdm-schema.git" } -berdl-notebook-utils = { git = "https://github.com/BERDataLakehouse/spark_notebook.git", subdirectory = "notebook_utils" } +berdl-notebook-utils = { git = "https://github.com/KBaseDataLakehouse/spark_notebook.git", subdirectory = "notebook_utils" } cdm-task-service-client = { git = "https://github.com/kbase/cdm-task-service-client.git" } diff --git a/scripts/parse_index.py b/scripts/parse_index.py index 1ca592ef..220eed61 100644 --- a/scripts/parse_index.py +++ b/scripts/parse_index.py @@ -3,6 +3,9 @@ import json import sys from pathlib import Path +from typing import Final + +N_EXPECTED_ARGS: Final[int] = 3 def main() -> None: @@ -19,8 +22,8 @@ def main() -> None: Prints the values associated with the specified key, one per line. """ # Check if the correct number of arguments is provided - if len(sys.argv) < 3: - print("Usage: parse_json.py ") # noqa: T201 + if len(sys.argv) < N_EXPECTED_ARGS: + print("Usage: parse_json.py ") sys.exit(1) input_file = sys.argv[1] # Path to the JSON file @@ -30,13 +33,13 @@ def main() -> None: # Attempt to open and read the JSON file with Path(input_file).open() as file: data = json.load(file) - except Exception as e: - print(f"Error reading JSON file: {e}") # noqa: T201 + except Exception as e: # noqa: BLE001 + print(f"Error reading JSON file: {e}") sys.exit(1) # throw error if top level data structure is incorrect if not isinstance(data, dict): - print("Error: expected JSON file to be a dictionary") # noqa: T201 + print("Error: expected JSON file to be a dictionary") sys.exit(1) # iterate over dictionary values and extract the values from dicts containing the target key @@ -44,7 +47,7 @@ def main() -> None: # Print each extracted path on a new line for path in extracted_paths: - print(path) # noqa: T201 + print(path) if __name__ == "__main__": diff --git a/scripts/s3_local.py b/scripts/s3_local.py index 8302b91a..4ad57e5f 100755 --- a/scripts/s3_local.py +++ b/scripts/s3_local.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# ruff: noqa: T201, EM101, EM102, TRY003, D103 +# ruff: noqa: D103 """Thin S3 CLI for local CEPH testing (no aws-cli install required). Usage (all commands assume ``uv run`` from the repo root): @@ -19,7 +19,7 @@ import os import sys -import cdm_data_loaders.utils.s3 as s3 +from cdm_data_loaders.utils import s3 def _client() -> None: @@ -41,7 +41,8 @@ def _client() -> None: def main() -> None: if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS: # noqa: PLR2004 cmds = ", ".join(COMMANDS) - raise SystemExit(f"Usage: s3_local.py <{cmds}> [args ...]\n\n{__doc__}") + err_msg = f"Usage: s3_local.py <{cmds}> [args ...]\n\n{__doc__}" + raise SystemExit(err_msg) _client() COMMANDS[sys.argv[1]](sys.argv[2:]) diff --git a/uv.lock b/uv.lock index c052cf16..878d1638 100644 --- a/uv.lock +++ b/uv.lock @@ -41,7 +41,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.14.0" +version = "3.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -52,72 +52,72 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/61/d11f7d9a3144bffe825247d6367cd93053666da50b94707c9129c78868d5/aiohttp-3.14.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:25400d710641a8040bf022a8a99f579e581ffa1c5bd42c33255d7d6f3957c127", size = 502399, upload-time = "2026-06-01T19:38:25.955Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9b/a7e317625d36356844f8bb022cabd305b541f968856cc3c2e0b58e53ee6e/aiohttp-3.14.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:c5492b9929826e07cc3fcb9739ae87aab05dff6b5e67a9b73fd1700c6d008981", size = 510068, upload-time = "2026-06-01T19:38:27.828Z" }, - { url = "https://files.pythonhosted.org/packages/11/41/cc2d2cfbfbdc3126ba258f3cd27d1ac8a33492ae3c35a4583ee21f0ba7f1/aiohttp-3.14.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3366751d68d237c621264233a32f3078bbc21b7904ab90a77e03d21390c742c6", size = 481670, upload-time = "2026-06-01T19:38:29.836Z" }, - { url = "https://files.pythonhosted.org/packages/3c/07/381f4023c3b08cb616e520f566d8c58957abad54e56441d41fe67cfb0195/aiohttp-3.14.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:57ea07d28695a7a40304d42251892a8df765e5588c10ee32afeddcd5df33c0a2", size = 487591, upload-time = "2026-06-01T19:38:31.704Z" }, - { url = "https://files.pythonhosted.org/packages/fb/4d/4506fdb7a022bdf70011a3bbb4ca00c5c570026ef6a3c5bd7bc70c39089c/aiohttp-3.14.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:076cb014191ae2e65d949e1ad01f1dcfe33e32789b5172510f3e79c79fc04d50", size = 496503, upload-time = "2026-06-01T19:38:33.6Z" }, - { url = "https://files.pythonhosted.org/packages/ef/7d/c814111e04894a45d9e2defc94443879a6f118d9633d5fedfe6e2e8af5f0/aiohttp-3.14.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2f3fc37054564dee64a855b5b092d87ec35dcddfaabf7dacb1c8a2b1f83dc0a9", size = 745870, upload-time = "2026-06-01T19:38:36.013Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ee/80eee0efddfe187e7cd05027086b7ce1c0e492e82a4eda58f5c5543a44a0/aiohttp-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8fcaef74d2ab0f607d7ff85a0d15e21bb5a258c4a58df1908396eb50d7f4ed3c", size = 505588, upload-time = "2026-06-01T19:38:38.282Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f8/0f28f04eef75d52fc9c715dde7ce9c0abb810fd20cfeb0fea7afd2ab1e98/aiohttp-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4c01b0bfc6209590960e68eac083cd22d5d87c21f974dd6208cafa5d3542bc8", size = 504492, upload-time = "2026-06-01T19:38:40.611Z" }, - { url = "https://files.pythonhosted.org/packages/ff/db/44c755232085545065c94378dfce38641b1aee647f4939fcd32f5b32e719/aiohttp-3.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f12eb7896e81caf403a2b18c9406426f1207361e7239c057ab29c076d4257e83", size = 1752111, upload-time = "2026-06-01T19:38:42.682Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6a/42e030a46743841414402a3b00cd3d78419055e86c66fb5822c14b5abfc6/aiohttp-3.14.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6c79a044cacf360ec46738d863d2f41c9300d2a06ef4a7402ea0df306a350e61", size = 1729674, upload-time = "2026-06-01T19:38:44.79Z" }, - { url = "https://files.pythonhosted.org/packages/34/26/3199beb415202e3108e7b83ecebe10914d806d33fb9860c3e4aa60a19be3/aiohttp-3.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85e0675f47be4eff0636bf88c02140ea89168ae0df3ff1f3f464e9de9610d277", size = 1798808, upload-time = "2026-06-01T19:38:47.01Z" }, - { url = "https://files.pythonhosted.org/packages/bd/94/b9b6fcf0ee17c21d0d19fb8c22bf83ad18f82e702a9c3bd901a868f5e446/aiohttp-3.14.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b33e751cab03fdc960095b1e326cb5a03f5ee577d6ded59f3d1c100f8668882", size = 1891921, upload-time = "2026-06-01T19:38:49.233Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a3/3800dbd095cb2bb165a7ea5d94d790914677e27f45638c7d80e3f34c8945/aiohttp-3.14.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9224c6dd7f5c749aba4f61315a894601448b28d94d12f4dea0903e26d2096", size = 1777241, upload-time = "2026-06-01T19:38:52.04Z" }, - { url = "https://files.pythonhosted.org/packages/21/2a/45be91ad1b860508557448d4cc2e165a2ee68dd865657b73bf66cc5a00fb/aiohttp-3.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6281aecdf2732940f4fe06bd6adec5ae4d59b78b080b8e3a6b81467301010988", size = 1579554, upload-time = "2026-06-01T19:38:54.508Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3d/dc94df99ed1511fdf28314f722643ed334112643cab00223577085e788c4/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:23e8314e7aed8576fbe33314d218bd81447a3adbc91dc36f1163bf583cd3084c", size = 1714864, upload-time = "2026-06-01T19:38:56.788Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e4/1f1c8acbb3acd5c8f795473b92c9c3d44eb60a5692c6104256c8a1c83a0c/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3b54fbff46127aeafdd764cecd0d99fa2f24a0e37ea5c18a7c3a4ac450df1db3", size = 1749803, upload-time = "2026-06-01T19:38:59.367Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c8/c45ea6e7ed84cebba939b9c334498a045ba19d79c61b0110df5f21580de3/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b27d89af91a555f58e08e4902dbcbc48862fd40095720ca705990476bd93b7ac", size = 1765023, upload-time = "2026-06-01T19:39:01.651Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a1/a932941784432962fe390e1066823aaef64b4e5ac9fa595df57b5fe472a9/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:25d2326a4967bf705a9f9913a13005e93b6020ad8a9f6bd6bd78850d5171332e", size = 1571671, upload-time = "2026-06-01T19:39:04.044Z" }, - { url = "https://files.pythonhosted.org/packages/b0/01/e1280feac522597a4d46eb67a0cdfa053cfae263033030b761ab146f29fb/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a1d209375c503472b3c0a340cdf3c55fcd82e84b46dda7caeaced59faba373ec", size = 1789904, upload-time = "2026-06-01T19:39:06.294Z" }, - { url = "https://files.pythonhosted.org/packages/fa/10/ab28818262f4d26bdb47ed5f1fc7999b69e2fc6e0370b02d0f49011f45ea/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:666c7c5036df57b693026398b69b41874a1931ac5b3485fd910e57bfac253869", size = 1754516, upload-time = "2026-06-01T19:39:08.788Z" }, - { url = "https://files.pythonhosted.org/packages/af/cc/c122eabd7a1b7e0c9bbdd6be60e4715905b858399145d9df872bb94f1427/aiohttp-3.14.0-cp313-cp313-win32.whl", hash = "sha256:23f094a1ef64823fd35854ddf5c7a80a078162f37f9d2f7c6142b51a6affa456", size = 448656, upload-time = "2026-06-01T19:39:11.171Z" }, - { url = "https://files.pythonhosted.org/packages/41/a5/bab07d79848a00eedd8ed979ccb302aaea3ac6eb9fa16bd0ed87135869b4/aiohttp-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e03abdaa17d553f17e1d1d06bb266b3970106c78051d06795723e748d8e49d11", size = 475803, upload-time = "2026-06-01T19:39:13.439Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/f03ade8566c153666a3871afccbedf6d99911da006325e1fc6cf72a2de99/aiohttp-3.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:acdb400538cf4769543548bb5d1eb23d39bed4f96554a6078cb728c7cb2c268b", size = 443889, upload-time = "2026-06-01T19:39:15.945Z" }, - { url = "https://files.pythonhosted.org/packages/28/03/5f36ab196a88ba5e9648ae5643e6531e67a3a8c0e96f9c6510ff41540fec/aiohttp-3.14.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:363ef9e91014e7891679bfb2ac0a7c6ea93435dbbfd10ecf41b9f06fcf506c5f", size = 503330, upload-time = "2026-06-01T19:39:18.195Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ce/8b49ec2f30f68e02f314f4832186cd45e583360a5a386058be36855d23b6/aiohttp-3.14.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:884a4edbdad77be9d0ef36142c8b504351b170df0bf62b51e784fadabf311c42", size = 509822, upload-time = "2026-06-01T19:39:20.396Z" }, - { url = "https://files.pythonhosted.org/packages/1a/fe/6edbf5d39bf29322b6816365b17ed8ede4dace164a3aea1abcd30110eb78/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:70ea956f6cc4a37620966b56c2e205d88ca3e6d85ec063277e414b1035cddad3", size = 483329, upload-time = "2026-06-01T19:39:22.607Z" }, - { url = "https://files.pythonhosted.org/packages/1b/5a/fae531bdbc6456fb6241f46b7b81e4d8a0dd3fc09118a0055dc7141ac1ec/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:ea3b9806c89f61da22fddf1f12dd524fb368e5e28f1261fbdafe5c3cd8ce893b", size = 489502, upload-time = "2026-06-01T19:39:24.881Z" }, - { url = "https://files.pythonhosted.org/packages/36/f4/48a7b0414db7fed77a03d5dde34508c026afd83510ab6bca08c313855776/aiohttp-3.14.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a071be341c2bd9b0188e62d173509f024e0a35b1c342c53c50f8daaeda8c3bd8", size = 497357, upload-time = "2026-06-01T19:39:27.197Z" }, - { url = "https://files.pythonhosted.org/packages/75/75/e85a13a370acc007fca5feb1fd1b88ac2d8426e6dadd625479b7cadd55a3/aiohttp-3.14.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:198cfe61bf253b19da1fb3e0fa122249dc4f14c12709493fed8054aa0411cc76", size = 750898, upload-time = "2026-06-01T19:39:29.563Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e4/3d637f800c724eff0e2bed64df72557444482366fd0a35b0cec0e6968f6c/aiohttp-3.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc203d6ce6b9106d54e2a93f41dfdfebfbca2d99962ba503bfd3e5921a6549e", size = 506986, upload-time = "2026-06-01T19:39:31.872Z" }, - { url = "https://files.pythonhosted.org/packages/1d/df/35161f3598bf7501d2b2a805b41ab4f45a2e34150c421bcb4ef8c0d281a7/aiohttp-3.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e19d17ab02bf16832a2c8c0d55a486792c5b1645665652ee9531aebcc30cb72", size = 508033, upload-time = "2026-06-01T19:39:34.137Z" }, - { url = "https://files.pythonhosted.org/packages/e5/39/b36e5d3d31e850fb4691dd3e941684ac490a2559249f6fa634b6b0fdf020/aiohttp-3.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d925fba0c14d5b498a8028b0107beebdfd16c5d48d702ff54f879cb017aaaca3", size = 1746213, upload-time = "2026-06-01T19:39:36.654Z" }, - { url = "https://files.pythonhosted.org/packages/b1/28/24e1409e605a9aa5d84abe0e2acb365354b70ae56d40948101cabe3341ab/aiohttp-3.14.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d33e61021222ce7f9792bcac870d6f58d8adfceda33ab857b01264f4560f2c5f", size = 1705862, upload-time = "2026-06-01T19:39:38.968Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d0/e5eb3ff1daeaf644c7e36a957517672494122628e067c38b263fa04eda77/aiohttp-3.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:44eca38755d0105bb32f47d085f5dd449846a449e1245fc105889e3279dcf8e3", size = 1798909, upload-time = "2026-06-01T19:39:41.334Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ba/8943f906f0570342886ababb9a722a44e360f786a028c5e0b0e29e3f735b/aiohttp-3.14.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f13087e06f68fea4941c21a0c541c00553aa16e4f8fd7bbe2b198df761e964d6", size = 1868892, upload-time = "2026-06-01T19:39:43.807Z" }, - { url = "https://files.pythonhosted.org/packages/3a/05/27df32c844b2156e1675a8d8ec22d963e3c8ba469ed7ceb1863320c7b521/aiohttp-3.14.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff82be7f1ef73634cb77890a770743239bc3d487b848669be1c599889336dc0a", size = 1751659, upload-time = "2026-06-01T19:39:46.398Z" }, - { url = "https://files.pythonhosted.org/packages/7f/62/da182e5910ab912b2e88aa919b61a16046a37a95714a5795b02eb57b2d18/aiohttp-3.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a150c0875ac8fd87f1c398650841308a30d65facf7416b12dbdb9cfdcbe5a48c", size = 1578775, upload-time = "2026-06-01T19:39:48.902Z" }, - { url = "https://files.pythonhosted.org/packages/66/e3/53c67097e8a5ce98625e91e3fa7f43c9c6940de680345d03b3509a72a078/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:edc01ea4e1ec5a1649a28866262bf24195889ff7b27bdd947029a6086741de9b", size = 1710090, upload-time = "2026-06-01T19:39:51.392Z" }, - { url = "https://files.pythonhosted.org/packages/dd/55/0e2732ca598c7a4dfe8a775662376d0ca2977cb1030e48386d4da5d9a456/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:540632bf882ff8fc88f2e1697be0761578e89e0d79fb4a8a6d65dc5da7e729d4", size = 1715016, upload-time = "2026-06-01T19:39:53.807Z" }, - { url = "https://files.pythonhosted.org/packages/5a/96/f0b73730798c9ca525afc30b39f1f81bbe24e245d9654c54d3b39d63212d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:860a86bc2c80237f5dff52edcf427e10a8d8352271fd84845429a3e60199e02c", size = 1763810, upload-time = "2026-06-01T19:39:56.31Z" }, - { url = "https://files.pythonhosted.org/packages/71/cc/11acb6c4518f448323405a7312b6f255d0f974a34373ad1db7633c4aadc8/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5cbd50e6a50d6b99283a826b18cbdebf65b0797689a7535cb0e9dd37be0f63c3", size = 1573064, upload-time = "2026-06-01T19:39:58.718Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/28c31dde0a7dc98c0ee7d0da2ddcec3f7688c4fc131e5989e278d0c03c0a/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20144819e99db593e22bbd2f3f2691a5e149f879142d6b8670254708853ff4fb", size = 1775765, upload-time = "2026-06-01T19:40:01.195Z" }, - { url = "https://files.pythonhosted.org/packages/b8/69/155c4ef3aec96417d47024800472b33b16c5d8a665371dcd044c2afdf25d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:26b6d79aa54cb4ed50cc7d41ed14e99e0f1fc8e7c2d42f2e05b37aea897b2b52", size = 1733716, upload-time = "2026-06-01T19:40:03.631Z" }, - { url = "https://files.pythonhosted.org/packages/5f/44/6126116fd8a316b712bb615660b855c78466bb67ba1bb1742427eafcf7ac/aiohttp-3.14.0-cp314-cp314-win32.whl", hash = "sha256:106ed074a856f3e21d186b8579e2c8afb6da598e267cdaab01059e13db2fc44d", size = 453684, upload-time = "2026-06-01T19:40:06.277Z" }, - { url = "https://files.pythonhosted.org/packages/a2/d7/eff4c58a88c5cac5e38b55f44fb8a6d3929c3cbd77356e383e094d3220bd/aiohttp-3.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f770846edae8f00ecc57af825bce811f787f87a7dcf0e90d191790efe5b31f7", size = 481758, upload-time = "2026-06-01T19:40:08.653Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ed/17b5bd9fbcb46e688f02e572f517754a9a75831e7b54702f027761dc4fa5/aiohttp-3.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:acf1581c4f21ed4b80a2dded504d87b055a071a84d5737ea966435f768275ac6", size = 450557, upload-time = "2026-06-01T19:40:11.03Z" }, - { url = "https://files.pythonhosted.org/packages/12/34/6180103ce9aabc8ebff3f7bb55a1228ffe60f61042823031d9692cb7b101/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6aa1a40f9cbb3da9f80714c5966b8946c21e6a2530d809b9498b33161e3c8733", size = 787878, upload-time = "2026-06-01T19:40:13.401Z" }, - { url = "https://files.pythonhosted.org/packages/92/e9/08954a40e8b7baa3d8beadd2b074b186e9b1e9c8ddabc288678a6265de50/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b62af5a8cc96a194eaa01a9ed7b34a3ffa58d3d8daaa1a0d7a749353ad12d228", size = 524400, upload-time = "2026-06-01T19:40:15.972Z" }, - { url = "https://files.pythonhosted.org/packages/08/6a/b5965a634ac4d5ba99a463314cf4ab214ca073fcdc38a15e0294273701fc/aiohttp-3.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6eb63b1417efaf7d1002a6ad034a40d44376afcc16508a57f8e74b49ad26a095", size = 527904, upload-time = "2026-06-01T19:40:18.28Z" }, - { url = "https://files.pythonhosted.org/packages/06/b4/932bcdd850c354d9bcca30f360e475d7852e30413fbbd44b182782ed5432/aiohttp-3.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c20b9ad156a79eb97be5cf9e069eec01d2f0dc8472ffbd75299a8b2d4c2cbbde", size = 1912162, upload-time = "2026-06-01T19:40:20.825Z" }, - { url = "https://files.pythonhosted.org/packages/c6/85/ce79bab0310d2e3fd2d7bc7e44412abeff7c8338f8a21dd0f2f1714989e5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:40ae7b0642c25632c7eabc4a04754012691864d2a1b93becf7cddb76027b838a", size = 1778813, upload-time = "2026-06-01T19:40:23.726Z" }, - { url = "https://files.pythonhosted.org/packages/05/54/ba62ac2d1bc87e010aad23751e383b8794e45d931df67677313a2da78823/aiohttp-3.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95f5217e76a046b9f228a101717ef8d42b1eb3d9d196d15202db5bf41df88936", size = 1899969, upload-time = "2026-06-01T19:40:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/dc/82/7cc7907725d83a19f31551334061e1ab8e108b1d7ac52632a2a844a4acb5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a4a9f17e85b80878c176695c1998c790e83731d8271881e5d356488652a1f9e", size = 1991771, upload-time = "2026-06-01T19:40:29.061Z" }, - { url = "https://files.pythonhosted.org/packages/d0/1c/a57de71a4508c93a830b77c28af3d08cd97f606dedfc6b94275347744508/aiohttp-3.14.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:145262119b07d7f95abc1839add35ba2bfc84551d4b4660ca11542c0b215455b", size = 1868606, upload-time = "2026-06-01T19:40:31.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ae/3839726cd49150a53ed340cc24ce5ba09d4c2117020ef9d45542bec5eb2f/aiohttp-3.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:49a33ded29b0b2fa7a367a02cf0fb89af602bb87542a16177ec8ce1c9c51d12a", size = 1665437, upload-time = "2026-06-01T19:40:35.01Z" }, - { url = "https://files.pythonhosted.org/packages/35/1e/c237923232c7da7f0392ea25d89fc5e60c0e93f685f4ebca8e7bcdd5271c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cc736a9c9fc2bc4dd71fd404815741b6573df27c3f985948ec4076989ac57de", size = 1834090, upload-time = "2026-06-01T19:40:37.733Z" }, - { url = "https://files.pythonhosted.org/packages/98/02/a5a7a2524f92d3911761b405a7c067c751891942144adc13e2ad79611e39/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4141a3e5342ee3053a9cab54d25b64ed28289c1041e4c54b3d99839314d90ce", size = 1816907, upload-time = "2026-06-01T19:40:40.46Z" }, - { url = "https://files.pythonhosted.org/packages/fa/76/a8b9f0d09234d516af9f2d7dd715557f33b5da3b0b56ead41d1170e86e3c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e30871b2d58996cb81aac52d2b1d15ac05257131ef0f90f18c2115a380fbfe7c", size = 1840382, upload-time = "2026-06-01T19:40:43.48Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8e/140e715a0a4bbc211979ea30ec8396ad2ed5bf90ab87d8058fc4668b1923/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:667b881d083ccae3900ea5a241e17e5007ca78844c53ed389bb63d48f729d9c7", size = 1659497, upload-time = "2026-06-01T19:40:46.265Z" }, - { url = "https://files.pythonhosted.org/packages/10/c7/7ba5de8af9650b9767b063c675427b8685f43fa7ce563673a7bc3af60f08/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b584dfe615d151e9b8f0a8ecb3aee6147f2927ec5b95ba25fe621f5377510928", size = 1870829, upload-time = "2026-06-01T19:40:49.583Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bc/2aaab2f85cadb26ea59c091fa2b8e370d625154b5c14b478f1b489d07551/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6199707cc40e0e9cd39c36fbc97bec416c704e1d0ddce03412bb3b3e6a90ccd0", size = 1832281, upload-time = "2026-06-01T19:40:52.303Z" }, - { url = "https://files.pythonhosted.org/packages/39/98/31b9ad9fbc01f0075ee7221002df5fd2d10b647f451ca5f30edc802d9dd6/aiohttp-3.14.0-cp314-cp314t-win32.whl", hash = "sha256:a8d93334d4961c9d566b1f046c81dee475b7c21eb730728d38237bfa70d1c8e6", size = 490597, upload-time = "2026-06-01T19:40:54.937Z" }, - { url = "https://files.pythonhosted.org/packages/59/1f/299b21441c8de42ff70fddc7cfe65e92f810abcf740739a09b56f7835364/aiohttp-3.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d2ffe9b614f50f069068b3b52e73414e4107fc10b7efc939a76acff9251fdd2", size = 525789, upload-time = "2026-06-01T19:40:57.306Z" }, - { url = "https://files.pythonhosted.org/packages/70/11/7f83fcba9ee05d4c54d61b3f8104da0d43a59adac44dd28effc0c9a10422/aiohttp-3.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7a3fc4358e65826c515350f199c210de747cf669998211b1ee6c2e46de364b24", size = 467399, upload-time = "2026-06-01T19:40:59.993Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, + { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, + { url = "https://files.pythonhosted.org/packages/fe/22/a73ccbf9dbd6e26dda0b24d5fd5db7da92ee3383a79f47677ffb834c5c5b/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd", size = 485841, upload-time = "2026-06-07T21:07:19.555Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/57ed8eaf596321c2ad747bd480fb1700dbd7177c60dfc9e4c187f629662e/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe", size = 492088, upload-time = "2026-06-07T21:07:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, + { url = "https://files.pythonhosted.org/packages/75/7f/8cdaa24fc7983865e0915153b96a9ac5bcdd3548d64c5a27d17cecccad2d/aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876", size = 751998, upload-time = "2026-06-07T21:07:25.046Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/01/a2d5f96cd4e74424864d30bc0a7e44d0a12dacdcfa91b5b2d1bd3dca6bf3/aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6", size = 508657, upload-time = "2026-06-07T21:07:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ed/3c0fb5c500fdd8e7ebc10d1889c04384fffa1a9163eac1356088ca9da1b1/aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96", size = 1757907, upload-time = "2026-06-07T21:07:31.03Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ab/d4c924d9bd5be3050c226612413ce68cb54c70d2c31b661bfc8d9a5b6a70/aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f", size = 1737565, upload-time = "2026-06-07T21:07:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/37326821ff779084020cdc33224d20b19f42f4183a500ff92022a739eda7/aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296", size = 1799018, upload-time = "2026-06-07T21:07:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4f/6e947ba73e4ce09070761c05ed3a8ceb7c21f5e46798671d8b2aac0e4626/aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa", size = 1894416, upload-time = "2026-06-07T21:07:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6e/dbf1d0625dc711fb2851f4f3c3055c39ed58bae92082d8c627dbe6013736/aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451", size = 1783881, upload-time = "2026-06-07T21:07:39.063Z" }, + { url = "https://files.pythonhosted.org/packages/44/c2/5e25098a67268ed369483ae7d1a58bd0a13d03aab860d2a0e4a6eb25b046/aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c", size = 1587572, upload-time = "2026-06-07T21:07:41.058Z" }, + { url = "https://files.pythonhosted.org/packages/2a/bd/cf9cee17e140f942a3de73e658a543aa8fbf35a5fc67a9d2538d52d77f0b/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca", size = 1722137, upload-time = "2026-06-07T21:07:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/89/6d/5684f8c59045c96f81a18cefbc1fbbd79d25b88f1c622f2a5c5c08fcb632/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09", size = 1755953, upload-time = "2026-06-07T21:07:45.933Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/35caf3170f8359760740a7d9aa0fff2e344bef98e1d1186f5a0f6dec17e6/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397", size = 1766479, upload-time = "2026-06-07T21:07:48.047Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a1/b0c61e7a137f0d81de49a82023a6df73c3c16d6fefb0f8e4a93d21639002/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080", size = 1580077, upload-time = "2026-06-07T21:07:50.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/41/194ea4623693009fcefebef7aef63c141754f153e9cd0d39d3b9e36c175c/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345", size = 1791688, upload-time = "2026-06-07T21:07:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/ba/45/4de841f005cfe1fd63e2a2fe011262c515e2a62aa6994b15947e7d717ac9/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588", size = 1761094, upload-time = "2026-06-07T21:07:54.113Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, + { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, + { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, + { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, + { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, + { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, + { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, + { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, + { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, + { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, + { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, + { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, ] [[package]] @@ -161,7 +161,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.105.2" +version = "0.109.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -173,21 +173,21 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/46/47581b8c689c743ceabf6a0f9ff48472160900ce802d26c0fb50423997b3/anthropic-0.105.2.tar.gz", hash = "sha256:0e26b90841c2dced7cc6e98d21d5517d0be33f1876b8e779f478202e28bcaa07", size = 853789, upload-time = "2026-05-29T00:21:14.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/b7/9a8e2f79011e89dd6eeb599c27332aed765dac9d6fbee3a55e68e4e3ec25/anthropic-0.109.2.tar.gz", hash = "sha256:d37db299597c7bc124b49b767ff135f1e6456b64af2b2fad4b63b2a1df333cf0", size = 927559, upload-time = "2026-06-15T17:30:25.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/75/be0c357e33a5a56c8f9db5b4212f886138d2bf59c0952d858f6b75d710ef/anthropic-0.105.2-py3-none-any.whl", hash = "sha256:e53ed5f6bf36fb1ecb9b25d8634cfd30e02fab9fb3374a0c2d5c585874757230", size = 837507, upload-time = "2026-05-29T00:21:15.528Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f2/bee5de8a2699fc8a3cce34d61c7a2626a2c310ddde7ea5611327eb0ddbe9/anthropic-0.109.2-py3-none-any.whl", hash = "sha256:e0fb4ca5df0ed983248c9c6c3242adc81d9cfddb8725902da53698554117abac", size = 923800, upload-time = "2026-06-15T17:30:23.124Z" }, ] [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, ] [[package]] @@ -244,49 +244,51 @@ wheels = [ [[package]] name = "arro3-core" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/e7/d84370ea85be641a8c57f4f8296e8465d30e46938cc9480d384a3ee0084c/arro3_core-0.8.0.tar.gz", hash = "sha256:b75d8281b87a87d3b66836bab89951ae06421970e5f880717723a93e38743f40", size = 93557, upload-time = "2026-02-23T15:12:20.622Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/61/a6a33a24bc4eccfbf168d7765d96488193789b48d8a916d8d42aae3a8e75/arro3_core-0.8.0-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:051b1c46b424c207b7ee2f5ae50f8f88cb79d167c3e4000adf59a0e3e3994331", size = 2901125, upload-time = "2026-02-23T15:10:00.796Z" }, - { url = "https://files.pythonhosted.org/packages/d4/60/cfe8b327ea30d8183e9b9eaca9668a8e6ce7c6e187701dc83a0820ddc0fb/arro3_core-0.8.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:c6b0e0b8914e634096fb377046bfcd21420b50141394e8cc1b12d43a98df1a43", size = 2632882, upload-time = "2026-02-23T15:10:04.335Z" }, - { url = "https://files.pythonhosted.org/packages/c0/99/71d9e31022d68c8cf104ed9c744291657c6a5fe94348869edfdaf1e8dab2/arro3_core-0.8.0-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4c20b6a55016ecd3f37f7dadf4d13d5a03dd51b7385e8f4130931447d110700", size = 3108341, upload-time = "2026-02-23T14:48:30.745Z" }, - { url = "https://files.pythonhosted.org/packages/39/1f/c067cc12b306b8a0dbec1e24a9c9e32dc5b5f3f9179466873d5c5666f124/arro3_core-0.8.0-cp311-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90dbbde6294d7349b2713e308cd3ef284de75003e8b5ad927f1716e7062525ce", size = 3216570, upload-time = "2026-02-23T14:49:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9b/f253dd3281e2d980c81e1526f9386b24c6a55e9bd152dd259032f94aceee/arro3_core-0.8.0-cp311-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee6693d496ab733fce43b2e83f9f7b5147db6906b3fbeba3b2d4108ffae5fbec", size = 3422198, upload-time = "2026-02-23T14:50:50.472Z" }, - { url = "https://files.pythonhosted.org/packages/2e/66/70786ee1cfdd03d36d456c4ef02a35506b7ae256c70a74bd7abf135daba0/arro3_core-0.8.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d882481e2f739fe449ca9bf724f4b33185fc48ba87dd82a26a64e6a23f5ed2f8", size = 2996395, upload-time = "2026-02-23T14:51:03.946Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b6/adf08e655df3ea07c460f3e441736face4de29277fdd753d5ba1fd89a43e/arro3_core-0.8.0-cp311-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d56d08a3e08864512d343a4d75e468beba743abc3a9d139e14bf3e81d0d8d79b", size = 2777566, upload-time = "2026-02-23T14:47:46.817Z" }, - { url = "https://files.pythonhosted.org/packages/07/9b/3d0b811a143372398b4c31eb58a9011774f20d184a1ba3d6dff99023205d/arro3_core-0.8.0-cp311-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02c47e2d87f063e04c12c074f4cc66efd65fc9c6b14db7f80934827ec46c589d", size = 3203472, upload-time = "2026-02-23T14:51:16.938Z" }, - { url = "https://files.pythonhosted.org/packages/77/88/987517aa8902f93e6395bafa1ade91fadae3aef49474199de5e1f75e42c7/arro3_core-0.8.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:72fa13702df4698884900e60824fecda434f61ffecb5ff0d914bf9f0afa79fe9", size = 2950379, upload-time = "2026-02-23T15:10:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/6a/3a/e059061b6ace4090b8ec4f9170811a3fdcca3181ff126c6714c382b144ed/arro3_core-0.8.0-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:8ab0bc6ad9b449b8a939e13ce94f6cacfea1d21953d437a8aa2ff8b4622512e0", size = 3386585, upload-time = "2026-02-23T15:10:18.51Z" }, - { url = "https://files.pythonhosted.org/packages/f8/80/7161d0d0326597775784db854e58b88d748127df7e072a099ec36c1fb355/arro3_core-0.8.0-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:975a3e3dea90789608d40c54b4176b9b72c9664a4cd2c842914ac62c489b1f06", size = 3313967, upload-time = "2026-02-23T15:10:20.993Z" }, - { url = "https://files.pythonhosted.org/packages/3b/62/13fbb9fdfae011513f944e45804e528a041c0e35efab9363ccdd716cde65/arro3_core-0.8.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7c3658fda04e0816333c8dda702c777d305b581876cd4176b15877726231b350", size = 3215978, upload-time = "2026-02-23T15:10:24.593Z" }, - { url = "https://files.pythonhosted.org/packages/bf/81/c0983e56969d8039116ffcf1bb3eafc17f8f34b2b63229970562bba6b52c/arro3_core-0.8.0-cp311-abi3-win_amd64.whl", hash = "sha256:a988c6cb74f97df4d276d5496f8667b6d5d95311d453ef32b28fb933b5ae96c4", size = 3176374, upload-time = "2026-02-23T15:10:27.902Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b6/08f088efd3737bcdaed98057b51c9d20d622e62e5b7dd626c6d60e67bd93/arro3_core-0.8.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:3cfa6b5c3981711a602c357afae1f16a6daa380cac8365100365560852e51d4a", size = 2890907, upload-time = "2026-02-23T15:10:32.408Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a4/2f1e20b879587a0419699a50e60aed9d2802423f8e5df844f31fa81f64d6/arro3_core-0.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4438167e4c357bafe66e8716adf5a55d73d79cf31bd4f7db465491605ee4afbc", size = 2625446, upload-time = "2026-02-23T15:10:36.324Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e7/92dbdf38de67435f04b5e2d013460e5a12ccac8edabd6a47a159c2f8acf7/arro3_core-0.8.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5ddc9a49b04ff179e1f6281164ee88008e73a0a72a931449c24ad0f8897be220", size = 3108513, upload-time = "2026-02-23T14:48:32.841Z" }, - { url = "https://files.pythonhosted.org/packages/16/a8/b8e7c8b64f0df4fd9c0f0e2faa2753658664d2dec9109d4e2ae2d470fb14/arro3_core-0.8.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85dfb4df87cd7e9adc17798e4468d5ea4f3e5dbd7845abebe1c85bba2a092ba3", size = 3211045, upload-time = "2026-02-23T14:49:14.962Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e8/657194c4cfc8516984ec560cd326c1b6ab8e83becc6bdb761508019704b1/arro3_core-0.8.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d4abad932811cadc1ae3e4976c4bb797e025c2451ae551edc60cf34a807edcf", size = 3424840, upload-time = "2026-02-23T14:50:52.742Z" }, - { url = "https://files.pythonhosted.org/packages/26/d6/0ceb8490347f3317cee4a902d3999a1d729cf9a074310d89a046fd93fb18/arro3_core-0.8.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c8a80c8ece04cb45328eba5667dacdef553dbe379443920f98b25d8ce3db761", size = 2994109, upload-time = "2026-02-23T14:51:05.837Z" }, - { url = "https://files.pythonhosted.org/packages/a5/82/1ef508fd796d341898a55f9c86f48ffa5d74a658159faad096d03929b419/arro3_core-0.8.0-cp313-cp313t-manylinux_2_24_aarch64.whl", hash = "sha256:12fc8c7133102c77661051a5e55c331a84dc58a3a8fe58fd18c38fcb61fa80d8", size = 2775585, upload-time = "2026-02-23T14:47:49.084Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ac/7e23539e5ba39a6534eb374a3a0e0178d25e8278cdf3d531bca89bd2bd82/arro3_core-0.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:278f2d35b4144ef7c77a037fd68dccacd651eda462cf2e739a85043109749cd3", size = 3204688, upload-time = "2026-02-23T14:51:18.986Z" }, - { url = "https://files.pythonhosted.org/packages/f0/cc/e2788c16f383a82d75a273bfe6a741e647d5ba4615c884c462e0e8a7d53e/arro3_core-0.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b7173b44e8809eb772a8bdb51dd866edb32682aac0c80055ea8e3c79077ad8c5", size = 2950218, upload-time = "2026-02-23T15:10:48.828Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7d/ba5ad9dcd69f8465011eef8558b7536eeb90384fa6f054874e2252d5a707/arro3_core-0.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:bc69ca8cbd02a2a0d63d8278182380ba79d62c798ada8768fd700e8e5168b4c1", size = 3386355, upload-time = "2026-02-23T15:10:51.527Z" }, - { url = "https://files.pythonhosted.org/packages/58/59/5369b3575af4093633f894206d94f3102a19b6e7f07c17f1c8035c78542e/arro3_core-0.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bc02ce82e8681d87c1d9fa27c0bc8322c982d93ba12a317dce33756cee79f285", size = 3312564, upload-time = "2026-02-23T15:10:54.502Z" }, - { url = "https://files.pythonhosted.org/packages/08/d3/d3da1020627d6d9408979e4dd7f466a66cc08e41a1f2b778d8cdaf7725df/arro3_core-0.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e66450987724a1f71bdfa1f721486af09bd07cb86227f575805e6f94f764b4f", size = 3213371, upload-time = "2026-02-23T15:10:56.666Z" }, - { url = "https://files.pythonhosted.org/packages/c9/47/dddb6852b57403a306a477d64befb2c0d0536baba8700581d785f0fef6e7/arro3_core-0.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:03fc7a1348a9d42f48061d45825e823985ee10c80aa509bafc0e84b10e7ecbb4", size = 3164236, upload-time = "2026-02-23T15:11:00.222Z" }, - { url = "https://files.pythonhosted.org/packages/68/3f/c15e183e63504c86e81d28c3672a9c3d01f48b7f9691a78c0e47cab831d3/arro3_core-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:be7dd0088bbab7b528d8d754b0fa05506e26da62f4a5d2f741fe94d7548e724e", size = 2890665, upload-time = "2026-02-23T15:11:04.753Z" }, - { url = "https://files.pythonhosted.org/packages/a1/45/b808cd7b1ba7afe6de4223414ca8191c030266d437ee69cce269b76e8a23/arro3_core-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:396496e96e4b86ac73aef32263c607c2161b878f334cf6ef954aaa74c8f1267f", size = 2625876, upload-time = "2026-02-23T15:11:08.236Z" }, - { url = "https://files.pythonhosted.org/packages/a1/63/cbb9f41624b6301dac4540e6fd5b6d18e6fe16c47bda0534330e6b22999e/arro3_core-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:828032a416906af1d84702301885098ab0bc2aa9f956f677b676161aeabeb06d", size = 3108175, upload-time = "2026-02-23T14:48:34.654Z" }, - { url = "https://files.pythonhosted.org/packages/75/f3/b9cf731acb9a910091518da1234d51904a1d0b615f16a13fc883331c627d/arro3_core-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d56b263bbc747691d08b3902a5f0d77adfb180d0544f9c52d622b2b79cd21f", size = 3211409, upload-time = "2026-02-23T14:49:17.204Z" }, - { url = "https://files.pythonhosted.org/packages/24/f8/30992bf19380285a9bc1a0c52aae26802679911c3787e804952505e7c4e5/arro3_core-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f08c07be0ff8d893d756ba20381b4fcbdf50af3c2bcec677529664920c07cf5", size = 3425205, upload-time = "2026-02-23T14:50:55.802Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/44de5c60e3058947d8733cae3c916e33f96b875b05ac795188def5542680/arro3_core-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34b280c70fe6bd6ca4c236f552d09b51ac551dc1c24793c9142ce89087346371", size = 2994668, upload-time = "2026-02-23T14:51:07.771Z" }, - { url = "https://files.pythonhosted.org/packages/1e/79/447e62f939183216361c6bfc8e3445e21835c2ae1a31e4ab817eb5d7cdc4/arro3_core-0.8.0-cp314-cp314t-manylinux_2_24_aarch64.whl", hash = "sha256:37202b826dd9695fc775064806bc07897c04caacef9403ea9d6706635f95ebdd", size = 2775761, upload-time = "2026-02-23T14:47:50.944Z" }, - { url = "https://files.pythonhosted.org/packages/58/d7/aa6572d46908e2986968887cec55d6c771ceea6a0ab14c7d219365a4ee09/arro3_core-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b70530b95d36e1409023f7bde3e9aeb75e3048852beb44263d98685c9f0d8f37", size = 3204821, upload-time = "2026-02-23T14:51:21.002Z" }, - { url = "https://files.pythonhosted.org/packages/41/f2/3c14108c13872b4143ffec3cddde56921caab04e45bf3a473769e8ff5b59/arro3_core-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:faf03d46e0a1817bf3959c21f2ca4d2bd2d61277b5319439df3044082e10effa", size = 2950512, upload-time = "2026-02-23T15:11:20.941Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/b4e1b9f90543eb560683f05520abced6ca9b236f12b147490da538d6028f/arro3_core-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:7a120ee05477c7e28565ce0b7572413a093745bb314195c4206c0ef578abea1b", size = 3386434, upload-time = "2026-02-23T15:11:23.584Z" }, - { url = "https://files.pythonhosted.org/packages/f1/55/4c7fc0e9f4e816c49ba3b520d87478b4900db3ae3e5186d0d333300918cc/arro3_core-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a19842cfa196f07c7fd7398d08eec5bdeed331b522dcbbf9d53830180f8d6d66", size = 3312814, upload-time = "2026-02-23T15:11:26.247Z" }, - { url = "https://files.pythonhosted.org/packages/e7/fc/a4209e468b87bec36ee41afe9a01848f6ac2855055fcefad57da04c8896a/arro3_core-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6ceab802cc609498e47dc214967d282af8c3104c7a83aff008739192cf821e8", size = 3213623, upload-time = "2026-02-23T15:11:29.263Z" }, - { url = "https://files.pythonhosted.org/packages/c6/84/61882d6491f38d9362d9382a914a47fd3992c57ee76b35646ea01d65b0bb/arro3_core-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:355e22a8845cbc6379e705f71a08c9cdaab6a7facc63a863e43ee5dc56ed7976", size = 3163287, upload-time = "2026-02-23T15:11:31.69Z" }, +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/c8/fc5bacb6fc264dc61e46d4832f690015b7f6c693ff5dea8a1e53b63cb772/arro3_core-0.8.1.tar.gz", hash = "sha256:1df54a8e2c14a877f291d90de65f00bafe9cc6d5958417ff749f178f059dcd39", size = 93684, upload-time = "2026-06-11T18:01:37.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/75/29517738623cccba1d60d0aa65b896bdd046ab4a82c5cd5a9fca115f6d7a/arro3_core-0.8.1-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:64d3ea60da4279e9c6a9b2e60abf7f4fefb6643544ecb2dfde899f72f1f91bad", size = 3085640, upload-time = "2026-06-11T17:59:46.597Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b5/bff1842ed8dbb2134b004f78c52d977b52a6df1d9389b1284aad02bf4d93/arro3_core-0.8.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:463db0b7f698abc19f4274d6df697560bc9c19463cfdbd01094bb0fa6ddd1206", size = 2800705, upload-time = "2026-06-11T17:59:48.242Z" }, + { url = "https://files.pythonhosted.org/packages/32/20/ef60404d5008f84bca50510f65d7acfe26812e61638ee19a416bf3f11530/arro3_core-0.8.1-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:881afcb9c83334ac26498b429265223c676e028cd5d88fb0c909578f8e0330de", size = 3272103, upload-time = "2026-06-11T17:59:49.641Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8e/6687efc8e0414cfde6281b1f4ea3b5b44aeb318584e39113d645d1f913a2/arro3_core-0.8.1-cp311-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1dc03e1e5965528bfc7e26a6ee857e3b6fc5f201ff530a03dc731b9f3fb14fd", size = 3405750, upload-time = "2026-06-11T17:59:51.037Z" }, + { url = "https://files.pythonhosted.org/packages/2e/38/2567f26cd041387a2c1f381f1757b5246184de22dd8bd9dbbcb91a1b3586/arro3_core-0.8.1-cp311-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:410cdca92392be39a5580059ebf9a6f89a0410af6dc10a8baa76e9d511fa6624", size = 3467290, upload-time = "2026-06-11T17:59:52.672Z" }, + { url = "https://files.pythonhosted.org/packages/85/38/2c5af3a3e8c806188f1b3a651cbaa6c22d1f094c41e82900d40219c2b337/arro3_core-0.8.1-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4082c6bbff7f164619d99dffcbc2313ed69024653a9e58d9f94cc65d9226f6c", size = 3195582, upload-time = "2026-06-11T17:59:54.536Z" }, + { url = "https://files.pythonhosted.org/packages/e4/8d/d7d8686901a273743c53aae98f898f00caaedea807e28854200f2a7365e4/arro3_core-0.8.1-cp311-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:40da2ee0701f217cd482e61228023ee9d8993984daccaf6ded089035b5fdc132", size = 2950909, upload-time = "2026-06-11T17:59:56.05Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8d/f44198a859b13f519b331906d08079c7c281f8267d718082a3ca858c93ee/arro3_core-0.8.1-cp311-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d79b12a83403b1a100731587d33f09a818a4ff93472f84fcdf3a38d3934dfc5e", size = 3403497, upload-time = "2026-06-11T17:59:57.651Z" }, + { url = "https://files.pythonhosted.org/packages/88/6d/431d2ef9942a30599db4a86cf13b7f1de92d340717438074a25659d382f4/arro3_core-0.8.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ff2da3c888ec504291deafd6965f2364f14f1fb63977f202f2f7680f10909f6", size = 3130901, upload-time = "2026-06-11T17:59:59.262Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9e/458e8596c675fb88075137ab21d4c4a2f9e585101a97d77e910839862168/arro3_core-0.8.1-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0bbcfb60ddf9b571387b4c0aaf78171d2ee7e0d335c08514bc247685f01bc086", size = 3550071, upload-time = "2026-06-11T18:00:00.808Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/8fa1f4a49e707ea364022620ca5033ba69b6396215e0bae1fb9fa9efcfe0/arro3_core-0.8.1-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:44f6256cae47d369fe9076e9fa61f5c2899447b76ca5bda641668ad07cf26bf6", size = 3508957, upload-time = "2026-06-11T18:00:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/7f/02/0126ff3b2ff48187315a9782280c0f6412c066559e22bf206ceec2791299/arro3_core-0.8.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b2517ea3fb7cb15a41e2d0e3496d91afe4703eca86a88072f917bef044c2b8ee", size = 3408923, upload-time = "2026-06-11T18:00:03.97Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fd/a19cb50480a769b1cc3f347efa1a808e13b853bb3b1d94b289677115fe92/arro3_core-0.8.1-cp311-abi3-win_amd64.whl", hash = "sha256:6a96df94a4538ab9acf01873eecf35161ad0c54ab79134247e69a42cd66515dc", size = 3368045, upload-time = "2026-06-11T18:00:05.578Z" }, + { url = "https://files.pythonhosted.org/packages/5c/86/1ac6eed229482b1ef6ac6f3211c6760012aaaa026b0a385a18e42ec0ff74/arro3_core-0.8.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:71165a75a7d22c16226085ed2255afe84741369cd35eb01cc153c4687e85b49f", size = 1724964, upload-time = "2026-06-11T18:00:07.653Z" }, + { url = "https://files.pythonhosted.org/packages/4a/32/c2e4a65b3b4f7e00552d280e9c2eaaca59c19466b0f95e4e08d6a020ab2d/arro3_core-0.8.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:dc81e079d182dfc2ba6bace815608dbab0158809b9268c4eb59816685f7871fb", size = 3071070, upload-time = "2026-06-11T18:00:09.12Z" }, + { url = "https://files.pythonhosted.org/packages/f0/32/56016de27757bcc0d66999ae2fec624eaa27bd097ee69ac7a5a0d52d407b/arro3_core-0.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7695e9fca1e2c0064571bea65d9b7909a957ed9be4c81718d19356e578818649", size = 2794497, upload-time = "2026-06-11T18:00:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6e/a7aac0e87d6d63672d4ebb7e8466b276fde2a6413579008fadd1e8e919c5/arro3_core-0.8.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad0f507dfafb1e8a8c15e8a902bccdd9a9c2cf2102fc39ad35c22e23ca76a65e", size = 3272090, upload-time = "2026-06-11T18:00:12.561Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fd/8b96e57eea8ebf5fa4e2556b06c84a29ea25ded924f56d300807ff377a94/arro3_core-0.8.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fc91a59cb27c7660134b9ed0c067d53c0f5bb1722f16f2cf7d4c446ce1086c4", size = 3399753, upload-time = "2026-06-11T18:00:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/80/8b/76f1385ecd0448fcc60902a4474f3f00bd2197fe6d7fa4434bb6a0b3dee9/arro3_core-0.8.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71bc86880256cca5f22802ca444a92b3e9edbb5c9e5b74df1cbfcfe2ca788097", size = 3468912, upload-time = "2026-06-11T18:00:15.502Z" }, + { url = "https://files.pythonhosted.org/packages/0f/15/e1c3aefee23d926be9f75f125d6d94f189f28826aa01713498f1de60b080/arro3_core-0.8.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94528515f59146b5b4421e468bc269f682754ff082434cccf0ecbfb0d74d0b38", size = 3193338, upload-time = "2026-06-11T18:00:17.064Z" }, + { url = "https://files.pythonhosted.org/packages/de/3d/bfa3963bd09941f69d810a387bbea61deb8a091101c1765216266ec61394/arro3_core-0.8.1-cp313-cp313t-manylinux_2_24_aarch64.whl", hash = "sha256:e348fa5349e6655df4b1ea281ab71e0794c749ba842da54248eb942201f93fb8", size = 2950166, upload-time = "2026-06-11T18:00:18.607Z" }, + { url = "https://files.pythonhosted.org/packages/21/07/67bdc66582294d5914b8f7462907b1e44377ccb77a0f7e7b7760c9f81fd1/arro3_core-0.8.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d97170d56690655699977f4535120f170c5047813341c2b0264abc3af00625dc", size = 3401097, upload-time = "2026-06-11T18:00:20.135Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a3/ab0d46e8fe2337f0ce1789949c5078e63d532fd628af4f254702e1359989/arro3_core-0.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6f71fdd46e1a86bc1db0733361130e51447d2d3c7d5fca13937381e2ba085dca", size = 3128454, upload-time = "2026-06-11T18:00:21.598Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6e/e90cea2555aaa429c16173cbc121b41fb6a656e70ae9f21edbe3f9e73eb0/arro3_core-0.8.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3e2c2e4c6b36a0f493f07222eadf7c628155f4a306c650a14c4d6d343870043c", size = 3550294, upload-time = "2026-06-11T18:00:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/15/38/f3db72e411fbaea83844d19b1965abbdf3dd387083f99a715d3b3752b461/arro3_core-0.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90d31db38bc7cbbe6061d23ae11b3e830b55ce60ba6f0550c70915bab4cafe41", size = 3507806, upload-time = "2026-06-11T18:00:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/47/73/5d011dad78ac58aaa61e9cc65d349b879afec6781c5aa78f80c839fe359f/arro3_core-0.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d28d87625d6c743fee1d2e20252531db6cff6c5218b89055a9f7492af99c14c", size = 3408910, upload-time = "2026-06-11T18:00:27.843Z" }, + { url = "https://files.pythonhosted.org/packages/6a/43/be86ea01c2d53d32376602cb2efa9a9b7a25d98e14ad4b5bf0d7da8b0564/arro3_core-0.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b394218ad71ce266088e0e65bfad0542556adf0eb93197b66afaa2009358662f", size = 3356174, upload-time = "2026-06-11T18:00:29.395Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0f/2f7b5b458fd38ecf9277fd59919e4c90047795d1abdfd47f0081c48c28fa/arro3_core-0.8.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a7c227982c0fd762271d550a6242e54ff7d2aa7aff73c918e127042cf9601ec0", size = 1710676, upload-time = "2026-06-11T18:00:30.947Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/b64c9325173b7d87030955007af55348b866a1c02545382261d8966d8bb1/arro3_core-0.8.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5861781e6363db0707e1a4ba5166025a1113d921ccff870a0a097f27ab2dece1", size = 3072327, upload-time = "2026-06-11T18:00:32.313Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bf/2c58a549b2409439fcac4ce2abe31ef060d05114a1e5412ce3088e2a2cde/arro3_core-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8c1d9ff8b2848bf48bbcad962271b2adeda922457cf23591464c12af9b0ddbf6", size = 2794872, upload-time = "2026-06-11T18:00:33.92Z" }, + { url = "https://files.pythonhosted.org/packages/1f/21/dee8d1c9309820783fe30ad29149508638743cdc61d2851f0132b370ac49/arro3_core-0.8.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:246057dc9d283a283cb05b228f710e1ada3b4a8c483e519b0ec8b0d8499204db", size = 3272432, upload-time = "2026-06-11T18:00:35.563Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b2/dbc6a3d5ece2cb11c3041821c8322df500b8c8030534d9f791b66b29c090/arro3_core-0.8.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d55178ad56c637f278286370532e58256eff48f71d3c36a4e5f8652eb7300b3", size = 3400822, upload-time = "2026-06-11T18:00:37.129Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/0ca0e96eebc7f96af4ad8b78c545a9477e8db271aa090043bb8f797e7f58/arro3_core-0.8.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a917195d7553a188bbb9fa815d430ff70ee53357fcc6fdb395d0e4436a66243", size = 3469220, upload-time = "2026-06-11T18:00:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/8e/70/3e9c63e9e4499d373304ceb1315afc8dd326395dd0692ccdebcfec703dab/arro3_core-0.8.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2435dc4057d604650a34d195e6977ef40b26af0b9dea8f1b842a924a270de2fc", size = 3193813, upload-time = "2026-06-11T18:00:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a8/48842a836d7fc2bdac16ecaf4e60c9fd98f46d1e9c80f45b85b071ccaffb/arro3_core-0.8.1-cp314-cp314t-manylinux_2_24_aarch64.whl", hash = "sha256:c8b90ad6fbdd3507c20bdbc2b23c88929a3f7c49a8a43697f316e084b5664289", size = 2950818, upload-time = "2026-06-11T18:00:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/bb/46/635ca59c20b972f0575b9fd1b7debebd94bcba1d6fd68271bfff65d3ae91/arro3_core-0.8.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8918dd0fcf5c3650332678ddbfb7c13ce1f0534f9669ab6839c38069885318cf", size = 3401202, upload-time = "2026-06-11T18:00:44.654Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/020d032cf4937cca0652f434613d327f8f11e10b4a3fbd4ccf48846c3158/arro3_core-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cc8c7c9b81cad2b4eaaeb29da25854bddb5bb373a278f1f1046b277fa7f2ee6", size = 3129352, upload-time = "2026-06-11T18:00:46.322Z" }, + { url = "https://files.pythonhosted.org/packages/b2/55/ce1c840af64e8c5d4f8b684a4c00c5f6d260659c3d2f732c6074eb5597fe/arro3_core-0.8.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2aa0bda5251e93c67318aa8315e563b02fe39d5577b72654c335d69fce802d03", size = 3550475, upload-time = "2026-06-11T18:00:48.021Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/fa8f1a53cf10fe33029619d14f06525e08ea35c1bcdce42157b230098e1b/arro3_core-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cd462e7ef7735c341c27f754756b244d90a9485818c5ea090fa46009bd8ae252", size = 3507877, upload-time = "2026-06-11T18:00:49.798Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e5/bf8349b2e5613e6c0caf2f4f0758d61dc273649c2bdcfc96978ab9bc1686/arro3_core-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:480c33d62d66adf92fd731e77728f81b7bd3fcde4e2eb313f5616b5097b1875f", size = 3409360, upload-time = "2026-06-11T18:00:51.393Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0d/e13efbbc448bc817f8343bf12606d22f83011682c821ccd32dda304816be/arro3_core-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:51bea8dc0bc0230b6af8d1b258cf2a7e4f69770ed99062592ce1b43b17732727", size = 3355557, upload-time = "2026-06-11T18:00:52.922Z" }, ] [[package]] @@ -341,10 +343,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/85/a12515514de8969b9e7fa40bb782501a336a8a985cc3093309120a80b627/awscrt-0.32.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a6782c19a00f354c7b232b675f09cde94d1ca37bcf29009b8779b3f6395b27b4", size = 4366435, upload-time = "2026-04-24T22:59:25.53Z" }, ] +[[package]] +name = "backports-zstd" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/b5/5a873da082bd08acd6a497f7aae224e94a7c27fa8f24488089cc50a16c84/backports_zstd-1.6.0.tar.gz", hash = "sha256:80a7859ffe70bf239d7a2ce15293bdeb5b4280ff7dc326ffab312b0e254dbb24", size = 1000009, upload-time = "2026-06-14T10:50:58.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/06/c430537d59c55d49bcd15ecf4b1aa965453219caad810a4f2b484816f4be/backports_zstd-1.6.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:2ace939e4d620e119423606f2d3d7115f8707733bf57f279ad9a9383f875986f", size = 400327, upload-time = "2026-06-14T10:50:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/36/48/2f8323bb0e3ebba88b54877a2979afeb83983fb2ca572f09ad61aae2d3a0/backports_zstd-1.6.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:4c68a9ed2df0cca51d774c521e68a34d2e3d9ebfc687ef8096adfd4f345b551d", size = 454276, upload-time = "2026-06-14T10:50:13.667Z" }, + { url = "https://files.pythonhosted.org/packages/7c/39/87a665244a65f5b87a06b848c29a8cce07e91d59c5988ee2a32c0293a21c/backports_zstd-1.6.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:30576f49b82328ec8af16c11100efe52ca88526f71bbe100ef6b4e707dc13bf2", size = 357457, upload-time = "2026-06-14T10:50:14.906Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/854d4a47bb8b7a48bfb2ed381c7b03a70efb4fc49f0e4a1509b38a2e1727/backports_zstd-1.6.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b4bddfcfb6679215d6f4dc5f79a1f9301af339480d70527a14b57a1f2e6b6cbf", size = 366139, upload-time = "2026-06-14T10:50:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/8f/de/c3af43eb8df6f2581e157e18a3e0121eadb826055b2fde3f91ec188689cb/backports_zstd-1.6.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:65048ed08c5124f05ff9f355ab9703014bb2dbe7f8d9948ce193685b1775f442", size = 446683, upload-time = "2026-06-14T10:50:17.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/87cf3d883d386c10ac52f5322604fb9afdd204229f4c47d4a820a839b8ff/backports_zstd-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5918fc6b31437208721276964323933cd86077b8d5b469c59c1b3fd2c8220a05", size = 436869, upload-time = "2026-06-14T10:50:19.113Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/9479e6f0f18824ad38e8d7dd85161ab0842a198be669421232925bb30960/backports_zstd-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b6c8b02ab0ccb2431bb7bc238be91d158b308915e7b07937388e540466fe7e7", size = 363090, upload-time = "2026-06-14T10:50:20.302Z" }, + { url = "https://files.pythonhosted.org/packages/d9/74/a5e98fe108e17c91d9bc590a19e77f5d47d579e34d3f5bc098a949d6c27c/backports_zstd-1.6.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:711e6b98f8924e8b4a61ff97ab6321f33de024e1ed6a32f5123763aeda8459be", size = 507070, upload-time = "2026-06-14T10:50:21.536Z" }, + { url = "https://files.pythonhosted.org/packages/69/f5/392bb7dce7363b77bc5403060f418fad438b9cfdd3edd10d65cee7d8fd11/backports_zstd-1.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ba9ac10fc393e5123a08802e0e895a107cb4a66b9973d2844dbd8a343111e59", size = 477200, upload-time = "2026-06-14T10:50:22.91Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4d/dfb665806ba4f74bc48071d32006843b53568c4a17ff627a3061de5eaa09/backports_zstd-1.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f723219335387d7546412d8141e0303590600949b4184a1391a0c6a3c756058", size = 582724, upload-time = "2026-06-14T10:50:24.28Z" }, + { url = "https://files.pythonhosted.org/packages/57/b2/beeca7393a8310debd82ee2f0ce5c1801e8d7cb673f7f226f4a0866ca238/backports_zstd-1.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64b94d7a836568926a3309ff510c7f8261b881b341fd4992cabf4f0998878f8a", size = 643493, upload-time = "2026-06-14T10:50:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/38/26/ce90e9eed6f25aaa4a4fa305a2aaf2d2ad81fd69de8eb248ddd91c80d1e0/backports_zstd-1.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e39258a09b1c7ca70b5e94a5c5ccfe4700b4250b8077cfeab31d0f79565d4c9b", size = 492190, upload-time = "2026-06-14T10:50:27.205Z" }, + { url = "https://files.pythonhosted.org/packages/17/9b/37b9b146df1f5452419a96071a7017cbac212ec9b137d7a88ca46dc2aa9e/backports_zstd-1.6.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:15b1aae0f64cd742df4bba1d989d0a09a6ec619202543fdba684640454541fd3", size = 567432, upload-time = "2026-06-14T10:50:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/06/66/81b30991be83237529f36335ac3682bce26409064b906ac6122874575196/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25b5ddc789480072551af571a746e9500356b2aff0499861cf2ca07ea7431e68", size = 483021, upload-time = "2026-06-14T10:50:29.654Z" }, + { url = "https://files.pythonhosted.org/packages/49/2a/792c65dcc1e45eb0c1bdc012ee94b84867186bfe27a860d0813bd216f03b/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a13cfa3410a75e4cb87abdb669aaf79da861cb79299159054ff8f77b9671bc40", size = 510596, upload-time = "2026-06-14T10:50:31.657Z" }, + { url = "https://files.pythonhosted.org/packages/1d/22/01b92a600505620e4cb5f20429e181f30458b7207ca8b52ca5ca6068c35f/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2ddab55a5f54dec8acfad68ef70f1c704fd21919990ddc238afbd6f496e61c6a", size = 587143, upload-time = "2026-06-14T10:50:32.868Z" }, + { url = "https://files.pythonhosted.org/packages/d8/60/4672f5110b9eb01388cc6225a739e3a5fcd749a63a9c4c1450a04fa27113/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fa305a84087e10d7a85e8a8a3dcba8cdbda4868f2180173b264b7b488fd37c55", size = 565238, upload-time = "2026-06-14T10:50:34.173Z" }, + { url = "https://files.pythonhosted.org/packages/5c/3b/19928d60ea7d25820bf12ef88de74534ca85b56ff7cf13c1b0e74e3a3d7c/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:df27b57d214a3124fbe4e933ef5a903d4567f154260d9aece8c797a987f2a205", size = 633970, upload-time = "2026-06-14T10:50:35.506Z" }, + { url = "https://files.pythonhosted.org/packages/df/97/c4cecb3e0ff53563ef9819f0395d919ceaae9c5147392ac23bac7afdb20f/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:28fecd73459d74910ae1987ab84b7bef690d3dd860948430dd5555108b006daf", size = 496539, upload-time = "2026-06-14T10:50:37.015Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f4/46b2f29d2938a80e56e61a19f11ab093f531a9f8cd0ec8eeaac1246bcd99/backports_zstd-1.6.0-cp313-cp313-win32.whl", hash = "sha256:3e689af303df287142770abe3a48bbefd24dab4a09da5807d0e1fa8c75bab026", size = 291451, upload-time = "2026-06-14T10:50:38.518Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ad/b529f92166da61f496621345f95d2dc583c8ca5ac553c084a4ef6c12cd71/backports_zstd-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:b067b1ef9c8e41fb0882c828aa37829938b5c0dab067eca72b23fc24c563b9da", size = 329023, upload-time = "2026-06-14T10:50:39.742Z" }, + { url = "https://files.pythonhosted.org/packages/30/d8/6be904d20345fbebec583ca83676e01f30c76118b283eb666d8ec8291ca1/backports_zstd-1.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:a838296f5b84c920172fb579cac894d255c1fc25457c7234613ddcfa385e49b7", size = 291636, upload-time = "2026-06-14T10:50:41.004Z" }, +] + [[package]] name = "berdl-notebook-utils" version = "0.0.1" -source = { git = "https://github.com/BERDataLakehouse/spark_notebook.git?subdirectory=notebook_utils#b89af8ba4d136bd982f5b4060eafd7b2c704c724" } +source = { git = "https://github.com/KBaseDataLakehouse/spark_notebook.git?subdirectory=notebook_utils#d6584abac55408423865d2f78daab870060d71f7" } dependencies = [ { name = "attrs" }, { name = "cacheout" }, @@ -374,7 +406,7 @@ dependencies = [ [[package]] name = "bioregistry" -version = "0.13.58" +version = "0.13.62" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -389,9 +421,9 @@ dependencies = [ { name = "tqdm" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/5e/22e9ffc92d81d2325b2134723e971671ea09e1467b07e76d557c45e6c6e6/bioregistry-0.13.58.tar.gz", hash = "sha256:06e6f1188988502cf117d4476107e03f0b1460cc31fdaf7f470493ccf5f82361", size = 6050994, upload-time = "2026-05-30T04:43:14.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/a3/aaf3fbeae7df7967a8203f919dc85f276a91e128035c42d2ae0d3969f9a0/bioregistry-0.13.62.tar.gz", hash = "sha256:d5cb226eb287be202e6ce33b7c2800cc7ed9f497dc4675e6945f40abfe0e2033", size = 6073578, upload-time = "2026-06-13T05:14:17.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/9b/b9ce93afd4abb2c8a55c1f4204f4043617992f26248ef89ef4d8fe811648/bioregistry-0.13.58-py3-none-any.whl", hash = "sha256:2272f23aa2bc31b384a52613b890de7310f89d3b0a3bec83490395babc6655aa", size = 6141702, upload-time = "2026-05-30T04:43:11.605Z" }, + { url = "https://files.pythonhosted.org/packages/61/f3/779624aec98ac5ef06e03335b050ee8008267f08043e466f9f2b65d1177c/bioregistry-0.13.62-py3-none-any.whl", hash = "sha256:1c95f5ebe426e88c8c5f4453c98804679fcc0cb3a6995d3410c8dca32909f2c2", size = 6166032, upload-time = "2026-06-13T05:14:15.034Z" }, ] [[package]] @@ -457,6 +489,7 @@ source = { editable = "." } dependencies = [ { name = "bioregistry" }, { name = "boto3", extra = ["crt"] }, + { name = "cdm-task-service-client" }, { name = "click" }, { name = "defusedxml" }, { name = "delta-spark" }, @@ -495,6 +528,7 @@ xml = [ requires-dist = [ { name = "bioregistry", specifier = ">=0.13.58" }, { name = "boto3", extras = ["crt"], specifier = ">=1.42.55" }, + { name = "cdm-task-service-client", git = "https://github.com/kbase/cdm-task-service-client.git" }, { name = "click", specifier = ">=8.4.1" }, { name = "defusedxml", specifier = ">=0.7.1" }, { name = "delta-spark", specifier = ">=4.2.0" }, @@ -510,7 +544,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "berdl-notebook-utils", git = "https://github.com/BERDataLakehouse/spark_notebook.git?subdirectory=notebook_utils" }, + { name = "berdl-notebook-utils", git = "https://github.com/KBaseDataLakehouse/spark_notebook.git?subdirectory=notebook_utils" }, { name = "cdm-task-service-client", git = "https://github.com/kbase/cdm-task-service-client.git" }, { name = "moto", extras = ["s3"], specifier = ">=5.2.1" }, { name = "pytest", specifier = ">=9.0.2" }, @@ -548,11 +582,11 @@ dependencies = [ [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] [[package]] @@ -783,69 +817,66 @@ wheels = [ [[package]] name = "cryptography" -version = "48.0.0" +version = "49.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, - { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, - { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, - { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, - { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, - { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, - { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, - { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, - { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, - { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, - { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, - { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, - { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, - { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, - { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, - { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, - { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, - { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, - { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, - { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, - { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, - { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, - { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, - { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, ] [[package]] name = "curies" -version = "0.13.11" +version = "0.13.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "pystow" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/95/eb5da50c3d9ba371b37a3fe2c8a496d30c2e0934ceb7b7ea5786f568351c/curies-0.13.11.tar.gz", hash = "sha256:b9cb5fab9d2cade296811343aa2af722298350faca8273e805036c8d0080f0cf", size = 72192, upload-time = "2026-05-07T15:49:24.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/66/20c0babb17c401dd9ddb57413eb9a11192528f11f856378587ba97d2e7b5/curies-0.13.13.tar.gz", hash = "sha256:aac8b3175680315566395ac754f6fa0878112d0fa6e8997cb65d9af91c66b778", size = 72376, upload-time = "2026-06-12T10:27:52.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/82/30edbe41d5d00641fecde5470a029a6dd378357bb4ae67d8b8717f1a9293/curies-0.13.11-py3-none-any.whl", hash = "sha256:dae85622a6730a83b2f0854530e7a35bcd2b931a648f181ceeddbe19da25f300", size = 81882, upload-time = "2026-05-07T15:49:26.938Z" }, + { url = "https://files.pythonhosted.org/packages/72/97/db70517204cd8aec8cb0773036333407405f6552bf928234d68531b0acd2/curies-0.13.13-py3-none-any.whl", hash = "sha256:43051140cd1f089a832f139633b14d473c09fa48a2f36e1b9b94c1270812267e", size = 82043, upload-time = "2026-06-12T10:27:51.334Z" }, ] [[package]] @@ -946,7 +977,7 @@ wheels = [ [[package]] name = "dlt" -version = "1.27.2" +version = "1.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -975,9 +1006,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/40/5aab6d1ff64226df39a3e241e31452907e32793e18f87ec99c956ce0ddd0/dlt-1.27.2.tar.gz", hash = "sha256:e9cf70883df0d04a1d40e1dea95d34dee29de978a9500d1d434eae881b5d0279", size = 1070289, upload-time = "2026-05-29T16:33:47.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/e9/45803a93a15e901ecb29c9ecbb63e346166b6c485df123f0fa9ff482b042/dlt-1.28.0.tar.gz", hash = "sha256:8fad65ca4c2a7c74b0b78d87ebed3f974cf9ed635e6f662bf4e8dec4be14f53f", size = 1085533, upload-time = "2026-06-15T16:14:52.893Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/69/d30f79f9fc1f08268c71c4a17f150429f4d00833f18f51059250c02ee03b/dlt-1.27.2-py3-none-any.whl", hash = "sha256:1c54d394864ca2cf1b042a28dafe393f24afbd60653edffc20dc6b77066cab2a", size = 1347718, upload-time = "2026-05-29T16:33:43.835Z" }, + { url = "https://files.pythonhosted.org/packages/22/73/522146bd24ca8e610b70b7ca939a2d4cbf6a9c6a124cd12063a7e290fd05/dlt-1.28.0-py3-none-any.whl", hash = "sha256:47673d39557d6a80fb02552e8ff00176a23019e7082a28e3469ac76b8c924430", size = 1363253, upload-time = "2026-06-15T16:14:55.413Z" }, ] [package.optional-dependencies] @@ -1022,33 +1053,33 @@ wheels = [ [[package]] name = "duckdb" -version = "1.5.3" +version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/00/d579dcb2a536b6ea3a2563cdad6844f77d81a9b2d4b22a858097f2468acf/duckdb-1.5.3.tar.gz", hash = "sha256:df39428eb130faa35ae96fd35245bdeae6ecf43936250b116b5fead568eb9f16", size = 18026640, upload-time = "2026-05-20T11:55:31.901Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/29/9bad86ed7aa812d8c822a27c15c355b6d5423b991feeec86ed18027b6daa/duckdb-1.5.4.tar.gz", hash = "sha256:f9e32f1cdd106793d79d190186bed9e75289d51e68bd9174e47c04bffedeab6f", size = 18046634, upload-time = "2026-06-17T10:48:52.499Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/9c/a528eb09d8be51954c485864bd06753e616939a080cbc3dd4417e8c94a57/duckdb-1.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e75a6122c12579a99848517f6f00a4e342aebda3590c30fe9b5cc5f39d5e6afc", size = 32626254, upload-time = "2026-05-20T11:54:53.65Z" }, - { url = "https://files.pythonhosted.org/packages/ec/3c/1534c0a6db347c05eb7d0f6ecfb7aefbe74cbff398e4892a8fd1903a20e8/duckdb-1.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd3963c1cb9d9567777f4a898a9dbe388a2fe9724681801b1e7d6d93eecf1b76", size = 17300917, upload-time = "2026-05-20T11:54:56.628Z" }, - { url = "https://files.pythonhosted.org/packages/23/fa/beafb91e6e152d2161c4a9cbc472334c87607eb61ad7104b5a7fa8d8d7b1/duckdb-1.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3d5db8c0b55e072cf437948ebb5d7e23d7b9d03d905fa5f9145583e65aa447f7", size = 15449411, upload-time = "2026-05-20T11:54:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/50/0a/49b6fe04e2fcd63729eb607dadd44818dde77342a4f5ce086c6c92f1dd4d/duckdb-1.5.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ce80aed7a538422129a57eaca9141e3afb51f8bf562b1908b1576c9725b5b22", size = 19333120, upload-time = "2026-05-20T11:55:01.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/4c/0907c3f76adb9dd90e67610b31e0304a35814e65c4c41a354a262c09b885/duckdb-1.5.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787df63824f07bf18022dbc3b8ca4b2bfab0ebe616464f55c6e8cd0f59ea762e", size = 21453266, upload-time = "2026-05-20T11:55:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/d2f23a7803ddbbd9413f7572ecf66a15120ed5ced7ce5c73e698c1406b76/duckdb-1.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:bb5bb5dcdd09d62ee60f0ddbbef918e71cce304ffe28428b1131949d39ffaabf", size = 13118640, upload-time = "2026-05-20T11:55:07.389Z" }, - { url = "https://files.pythonhosted.org/packages/27/d5/7ba2316415bcdab6edd765bbbe35c2ca8a3800f2fe695cd70e3cdb997f09/duckdb-1.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2fa17ecdd5d3db122836cb71bb93601c2106a3be883c17dffddc02fbf3fa7888", size = 13926409, upload-time = "2026-05-20T11:55:10.166Z" }, - { url = "https://files.pythonhosted.org/packages/a5/c2/d4b6f8a5e4d3bc25773be6da76a99d9661ebbf3552c007c460d2dd59dbf8/duckdb-1.5.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4bfa9a4dadf71e83e2c4eaca2f9421c82a54defecc1b0b4c0be95e2389dec4fe", size = 32636685, upload-time = "2026-05-20T11:55:13.158Z" }, - { url = "https://files.pythonhosted.org/packages/42/58/e835c8298979d29db7a62cb5acc29e9b57aeaca7cdde2fcd3ac980f5cb18/duckdb-1.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aea7baf67ad7e1829ac76f67d7dcbd7fb1f57c3eb179d55ac30952df4709ae30", size = 17308134, upload-time = "2026-05-20T11:55:16.194Z" }, - { url = "https://files.pythonhosted.org/packages/c9/46/617b51363f5613418c8b224b3cce16b58e6dde80904566bec232579c1d4e/duckdb-1.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b0b4f088a65d77e1217ce5d7eff889e63fedc44281200d899ff47c84d8ff836", size = 15449891, upload-time = "2026-05-20T11:55:18.687Z" }, - { url = "https://files.pythonhosted.org/packages/b3/72/354146656e8d9ba3853d3a5ee80a481b8c5f70edfc3d5ae80a8c4479c967/duckdb-1.5.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe8d0c1f6a120aa03fa6e0d03897c71a1842e6cf7afd31d181348391f7108fe1", size = 19338499, upload-time = "2026-05-20T11:55:21.34Z" }, - { url = "https://files.pythonhosted.org/packages/56/8f/65fc623b51448f2bfba1a9ec6ab3debb4664c0876c0113a5e782600b53ac/duckdb-1.5.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0405eae18ec6e8210a471c97dbfe87a7e4d605274b7fe572a1f276e92158f13", size = 21455828, upload-time = "2026-05-20T11:55:23.847Z" }, - { url = "https://files.pythonhosted.org/packages/2b/db/d0274cbe9f5fe219f77c0bdf900ac77103569e83c102a4225ce04cbc607d/duckdb-1.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:33ae08b3e818d7613d8936744b67718c2062c2f530376895bfd89efb51b81538", size = 13640011, upload-time = "2026-05-20T11:55:26.276Z" }, - { url = "https://files.pythonhosted.org/packages/07/5d/8f1899b8bef291caf953992fcd6c24df9f29387a35645e58c2504a5ca473/duckdb-1.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:746433e49bbc667b4df283153415fbe37e9083e0eff6c3cd6e54de7536869cd4", size = 14411554, upload-time = "2026-05-20T11:55:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/3fcb34e523a9bad1f0557a6c7691a71ba66c43a05e5be9ee96a9a841ed65/duckdb-1.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42a612e67d64450b446eb69695290d460713eef46e0f64467ab9dfe96264ee05", size = 32708366, upload-time = "2026-06-17T10:48:18.084Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/bff5054c2c1d65decab36aa6296621e51a2a575a9f250db7ab9b83a325d6/duckdb-1.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3fb6f07d54ecf4d0d3c5179a2361fdddfafa14de4fc42696de4632479b703421", size = 17345735, upload-time = "2026-06-17T10:48:20.67Z" }, + { url = "https://files.pythonhosted.org/packages/93/12/d1b2b344e9699246aada6f9de5156e708fb476e2780e5bff9b5d95fe11d9/duckdb-1.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f32ad7e0286c1c29ab6c73b29118c86101f8eee46aae54f54d0b50916f542f6", size = 15488568, upload-time = "2026-06-17T10:48:23.038Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d1/ac56c6096e3e95da60b2c5dd5a0f0eb5540a80622e2e4f8faab893ec4e96/duckdb-1.5.4-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:698ec90bd5d5538bd5f6d212a4b61af443d240703cf45f134738535026556ea5", size = 19368184, upload-time = "2026-06-17T10:48:25.601Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/2ae4c3e157a19d9b4ac1f09a5dea6f93012334cc2db09f1e0c71eb99693d/duckdb-1.5.4-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136cea7f886b78caf4035485b4b1e766e8b309e999f9e83a966f81ebb8122844", size = 21486523, upload-time = "2026-06-17T10:48:27.817Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/c3d8d21e0d0db8faa81eeeb3a55b9932f5a0a16466cb968dc713a653d701/duckdb-1.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:bd6777e8ddd74fb603a6d09766bfcff28638189f8aaa61fc0dffd9e9a4baa8e5", size = 13147807, upload-time = "2026-06-17T10:48:30.017Z" }, + { url = "https://files.pythonhosted.org/packages/44/48/ddf8d3740e3d28582944f70d84e720b5dc28c10ec22b668a0e0bd965f2f2/duckdb-1.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:73f4878a3012283024a64a1909e440aac12091ef336f671fc142f7e87449ce0c", size = 13965189, upload-time = "2026-06-17T10:48:32.251Z" }, + { url = "https://files.pythonhosted.org/packages/62/01/67ac4cbc8e552a1e14c029b5c443d828e68f94d5d913c574f577e1db277e/duckdb-1.5.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4647968629d0677bbcc2416c7aeda8685eb84e4ca15a6dbd4f82a66cfc91a532", size = 32714364, upload-time = "2026-06-17T10:48:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/eb44d983fa56b175f971eea251bde284a36d26cbb93fcb68035061f54078/duckdb-1.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e8fcef301cf68d3951ea1eb8ac4d76cea0a6f6a08f4c78fe4026fc96d217bebc", size = 17349820, upload-time = "2026-06-17T10:48:37.126Z" }, + { url = "https://files.pythonhosted.org/packages/10/b2/b9dc7624b105d414585b8530451c1162c0b4750c0be9be2e497bb47a8a9b/duckdb-1.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f6f39cd0dc6948dee17fd130aec55114f97a8ef6e1db519b9774087962bc5c8c", size = 15498160, upload-time = "2026-06-17T10:48:40.032Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/61356444f6a8c62dec3c3d129abfc53f428de1d484093d1bb381db441231/duckdb-1.5.4-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:262f068158beb5943f2c618f4e54b46db8306b959f90dce956f90a89f613673d", size = 19374183, upload-time = "2026-06-17T10:48:42.698Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f4/d5d633dd7c5138d8f7c434e6ac2553c831b7fb658494efa8d0bc73df8623/duckdb-1.5.4-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d2307a76d199077b0055b354e90e857479461a0d875437535dd4833172c8b6d", size = 21487202, upload-time = "2026-06-17T10:48:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/c0/26/5be13bbd5c3421dccfc1ad4ca9da4b97c5a3ddd73f66542092f3167ec52c/duckdb-1.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:6dcbb81a1276bc48deb4d562bce4f8895e4fc6348750a096e30052345c6d6552", size = 13666989, upload-time = "2026-06-17T10:48:47.764Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/4d52f3f9f9703a226b26b80bdae3f6905aeefe5221bf1815fc93ff02ca25/duckdb-1.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:0f8722346024e5d9f02b58bf7b0491a629f97fdc8a04a10e432940f471ee387a", size = 14449863, upload-time = "2026-06-17T10:48:50.18Z" }, ] [[package]] name = "elementpath" -version = "5.1.1" +version = "5.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/95/eeb61a2a917bf506d1402748e71c62399d8dcdd8cdccd64c81962832c260/elementpath-5.1.1.tar.gz", hash = "sha256:c4d1bd6aed987258354d0ea004d965eb0a6818213326bd4fd9bde5dacdb20277", size = 375378, upload-time = "2026-01-20T21:42:27.175Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/66/2bb88f2d9bed40654a6fe40b83009784d34aafb4bd5eb87719fcfd33dde2/elementpath-5.1.2.tar.gz", hash = "sha256:43d67a38700153eb45349c6e14a4d4b3a06e390c31df06539f730a96baf9a7fc", size = 375909, upload-time = "2026-06-13T07:12:15.695Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/5a/4ddfd9aeecdc75a78b5d85d882abc2b822115caae2c00a4d0eb23cc101fc/elementpath-5.1.1-py3-none-any.whl", hash = "sha256:57637359065e60654422d8474c1749b09bb21348012b7197f868027e3c09c9b9", size = 259962, upload-time = "2026-01-20T21:42:24.127Z" }, + { url = "https://files.pythonhosted.org/packages/97/44/e595bc3dbd9293016dd6d0d211a0580616aa255bf2ef4e5ee0ffca2496f0/elementpath-5.1.2-py3-none-any.whl", hash = "sha256:dd35750b3f0d2b4df553d5ef2101011cde6a0e8080c51110765b5853e02aac8e", size = 260278, upload-time = "2026-06-13T07:12:13.196Z" }, ] [[package]] @@ -1192,11 +1223,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.4.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, ] [[package]] @@ -1234,11 +1265,11 @@ wheels = [ [[package]] name = "giturlparse" -version = "0.14.0" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/7f25a604a406be7d7d0f849bfcbc1603df084e9e58fe6170980c231138e4/giturlparse-0.14.0.tar.gz", hash = "sha256:0a13208cb3f60e067ee3d09d28e01f9c936065986004fa2d5cd6db7758e9f6e6", size = 15637, upload-time = "2025-10-22T09:21:11.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8a/b70b84cc78f9059d627f09560c81a11e8f046570d220a24e169489cad6c9/giturlparse-0.15.0.tar.gz", hash = "sha256:9af3f1fd5c4a0cac94ddb283593635005646393ee0debbe330d1bdff8866bf2c", size = 16138, upload-time = "2026-06-16T07:28:56.021Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/f9/9ff5a301459f804a885f237453ba81564bc6ee54740e9f2676c2642043f6/giturlparse-0.14.0-py2.py3-none-any.whl", hash = "sha256:04fd9c262ca9a4db86043d2ef32b2b90bfcbcdefc4f6a260fd9402127880931d", size = 16299, upload-time = "2025-10-22T09:21:10.818Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/147a2771ab655b9353781fb2f95c94eaf1d8576dccc991c1a61d0d355067/giturlparse-0.15.0-py2.py3-none-any.whl", hash = "sha256:76d2e6983b037356ab99b30683e533ac3db96409b68e2163a20fc3aff6446f10", size = 16683, upload-time = "2026-06-16T07:28:55.184Z" }, ] [[package]] @@ -1302,47 +1333,47 @@ wheels = [ [[package]] name = "grpcio" -version = "1.81.0" +version = "1.81.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/f3/23f47b24f8d8c2028eba501db3acfbb2f592cbb5995eaa6e363a627b74d7/grpcio-1.81.0.tar.gz", hash = "sha256:a5acd7efd3b1fe9b4eb0bcaaa1507eed68a0ad0678b654c3f7b464df9ba9dca5", size = 13032272, upload-time = "2026-06-01T05:56:22.827Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/29/779ee53c931d0fd55c1d459fde43e485172caa3ac87cbd43d003a13a0185/grpcio-1.81.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:62bbe463c9f0f2ff24e31bd25f8dd8b4bae78900e315915a3195a0ef1471a855", size = 6054973, upload-time = "2026-06-01T05:55:25.043Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b6/7211807926b5a17f8d9a5d47c739a163d6812fefe3e4714e81cf92945ed7/grpcio-1.81.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43c121e135ae44d1559b430db2b2dfad7421cbbe40e1deba506c7dc62b439719", size = 12048662, upload-time = "2026-06-01T05:55:28.453Z" }, - { url = "https://files.pythonhosted.org/packages/64/89/b1b93ef6b34bd20bbaf707fa99133bc9cc302139d5ec6f77a165c7169796/grpcio-1.81.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f345de40ef2e65f63645d53d251824e6070e07804827c5b00ec2e44555f9f901", size = 6599116, upload-time = "2026-06-01T05:55:31.185Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bc/c89f9b9d1c22895715356a1e009554dae66319e97826bb4d30bcda7d29e8/grpcio-1.81.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8c0855a350886f713b9e458e2a10d208009dcaa849f574e39cd6067db1fe1279", size = 7307591, upload-time = "2026-06-01T05:55:33.463Z" }, - { url = "https://files.pythonhosted.org/packages/65/4a/1df2a4cb4a1386e066ab7e4175e34bb884b35ccb60d3621c09c84af6aabb/grpcio-1.81.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a524cd530900bd24511fcb7f2ed144da4ea37711c4b094475d0bceca7a93a170", size = 6811797, upload-time = "2026-06-01T05:55:36.731Z" }, - { url = "https://files.pythonhosted.org/packages/8d/dc/fa189d20601a1be25b08850cfb733879bbb1047b62a8feec3a60e3e1a87b/grpcio-1.81.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e7746ba3e6efc9e2b748eff59470a2b8684d5a9ec607c6580bcaa5be175820bc", size = 7415131, upload-time = "2026-06-01T05:55:39.451Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a3/5625c48cb48d23c6631b3e5294f88e4c751f22a52591ae78859fab96dca1/grpcio-1.81.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aaaa4f7f2057d795952e4eacf3f342be8b5b156992f6ac85023c8b98794ebd47", size = 8408398, upload-time = "2026-06-01T05:55:42.219Z" }, - { url = "https://files.pythonhosted.org/packages/75/34/0f8202c6809a46c2b4d69125ef3667c40b1c211f8e19930e5fa1f1197039/grpcio-1.81.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fba53cb96004b2b7fb758b46b2288cb49d0b658316a4e73f3ef67230616ee65", size = 7844481, upload-time = "2026-06-01T05:55:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/c0/95/c3366b5b5edf4c4adc90f2e29ca16e57965a8e56dc8d2ee89565ba1905bb/grpcio-1.81.0-cp313-cp313-win32.whl", hash = "sha256:c197e2ef75a442528072b29e9755da299110e8610e8bcbb59a6b4cf55384f005", size = 4182777, upload-time = "2026-06-01T05:55:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/a9/a7/932f2f748511a32e641a2aba0d30dded3ed6e8bc330e0924e4d5d86853e6/grpcio-1.81.0-cp313-cp313-win_amd64.whl", hash = "sha256:194eddfacc84d80f50512e9fd4ee851d5f2499f18f299c95aa8fb4748f0537e0", size = 4928085, upload-time = "2026-06-01T05:55:50.158Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1d/28b231333857deb840bc3d182ae087510170ea6d68f21393aeb0fe499530/grpcio-1.81.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:a9351055f52660b58f3d4890ea66188b5134399f82b11aa0c55bd4b99eff5390", size = 6055712, upload-time = "2026-06-01T05:55:52.889Z" }, - { url = "https://files.pythonhosted.org/packages/e8/b8/999c14f9dff0fc47549d2e827cba1343ddc18e1d1bf0d06d2cf628eecbd9/grpcio-1.81.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:300f3337b6425fd16ead9a4f9b2ac25801acb64aa5bc0b99eb69901645b2b1d2", size = 12057189, upload-time = "2026-06-01T05:55:55.952Z" }, - { url = "https://files.pythonhosted.org/packages/1e/3d/1fbde079572562af65351151d840525a13879eb7b481d35b55cd64c6127a/grpcio-1.81.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:97bbd623f7ded558fd4f7cb5a4f600c4d4de65c5dd364c83a5b14b2a10a2d3b5", size = 6608136, upload-time = "2026-06-01T05:55:59.069Z" }, - { url = "https://files.pythonhosted.org/packages/32/89/1f17cb6882abfd8e5a303a25d5d1665abef5a8c499a96198c65a651d1b85/grpcio-1.81.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ff83d889e3ebf6341c8c7864ad8031591ad5ca61599072fc511644d1eb962d2b", size = 7307045, upload-time = "2026-06-01T05:56:02.376Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/f98e91b2e755652e637ea2144318b0229b290062199f761b445fe1fa6015/grpcio-1.81.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c4fe218c5a35e1d87a5a26544237f1fa41dfd9cbd3c856b0810a30061f8b0aaf", size = 6812794, upload-time = "2026-06-01T05:56:05.777Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0c/77892d715ac41e7ec0ace2a50080ffb64e189188056f607a66fe0014d1ee/grpcio-1.81.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b8b025b6af43ee0ad4a70307025d77bcab5adde7c4597786010d802c203e9fc5", size = 7422767, upload-time = "2026-06-01T05:56:08.524Z" }, - { url = "https://files.pythonhosted.org/packages/3f/b8/aa04590c6564714d94954515f15a236e59d4b9b3ad01e615f1b706d7792d/grpcio-1.81.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3d4e0ce5a40a998cf608c8ba60ecfe18fdf364a9aa193ae4ac3faeecd0e86757", size = 8408551, upload-time = "2026-06-01T05:56:11.283Z" }, - { url = "https://files.pythonhosted.org/packages/43/3d/4f4a3450a1973568910c6909cb74abbf2126f68aefae5976962f9f7ad50d/grpcio-1.81.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa948712c8e5fa40ec250870bda14bc7578e1bb832a8912d9d2a0f720518edbe", size = 7846468, upload-time = "2026-06-01T05:56:14.536Z" }, - { url = "https://files.pythonhosted.org/packages/88/f4/5827fd248221ad3b44161c23ce9b5f4ee405b04fc6da5fd402a9aa87a84a/grpcio-1.81.0-cp314-cp314-win32.whl", hash = "sha256:fbbe81314a9d92156abce8b62c09364eb8bafc0ca2a19919a45ec64b5c6cb664", size = 4264427, upload-time = "2026-06-01T05:56:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/127dc2b246096ad50ef7c8d9b7b31d757787aeb796368bcdd4454e4204c4/grpcio-1.81.0-cp314-cp314-win_amd64.whl", hash = "sha256:b93cee313cae4e113fbb3a0ce1ea5633db6f63cfde2b2dc1d817429026b2a50b", size = 5070848, upload-time = "2026-06-01T05:56:19.735Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, ] [[package]] name = "grpcio-status" -version = "1.81.0" +version = "1.81.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/b6/cdc177114997d15c887fb09ccfd16705c8ceb8b4ca2487902b54a7bfd1af/grpcio_status-1.81.0.tar.gz", hash = "sha256:b6fe9788cfdd1f0f63c0528a1e0bfdb41e8ff0583e920d2d8e8888598c01bb69", size = 13900, upload-time = "2026-06-01T06:00:32.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/26/0aa9168c87882381fd810d140c279a2490ed6aee655f0515d6f56c5ca404/grpcio_status-1.81.1.tar.gz", hash = "sha256:9389a03e746017b10f0630c064289201458f3ce01f5d7ef4b0bebc1ef6cf82ad", size = 13923, upload-time = "2026-06-11T12:58:48.636Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/b7/5aa346bf1cdecd4ed64b86c10a4d5a089ce3da89145f8328caf0b22b240d/grpcio_status-1.81.0-py3-none-any.whl", hash = "sha256:10eb4c2309db902dc26c1873e80a821bf794be772c10dfd83030f7f59f165fab", size = 14634, upload-time = "2026-06-01T06:00:13.345Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5e/5abfec5f7e89d3b7993d57cfb025ca5f968a2c18656d7fcda2b6919440b9/grpcio_status-1.81.1-py3-none-any.whl", hash = "sha256:08072fa9995f4a95c647fc6f4f85e2411573d00087bcabdf30f260114338f232", size = 14638, upload-time = "2026-06-11T12:58:31.982Z" }, ] [[package]] @@ -1454,7 +1485,7 @@ wheels = [ [[package]] name = "ipykernel" -version = "7.2.0" +version = "7.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, @@ -1464,21 +1495,21 @@ dependencies = [ { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, + { name = "nest-asyncio2" }, { name = "packaging" }, { name = "psutil" }, { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/c4/e4a38f579de4225a561305666f7541cdabb30075def2aa1ac17bd73c1fb5/ipykernel-7.3.0.tar.gz", hash = "sha256:9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09", size = 184899, upload-time = "2026-06-10T08:41:25.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/77b271f5dc58bfbc0b577c877b2365d1ffea2afe66a80c13f2312820348c/ipykernel-7.3.0-py3-none-any.whl", hash = "sha256:897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057", size = 120583, upload-time = "2026-06-10T08:41:23.648Z" }, ] [[package]] name = "ipython" -version = "9.14.0" +version = "9.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1493,9 +1524,9 @@ dependencies = [ { name = "stack-data" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/c2/c0064cf15d026501a1ef70e42efd9c3f818663089399aacc5e37a82901c1/ipython-9.14.0.tar.gz", hash = "sha256:6f27ff0f1d9ea050e0551f71568bc4b34d8aba579e8f111c5b4175f44ac6b4aa", size = 4432601, upload-time = "2026-05-29T15:13:24.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/23/3a27530575643c8bb7bfc757a28e2e7ef80092afbf59a2bc5716320b6602/ipython-9.14.1.tar.gz", hash = "sha256:f913bf74df06d458e46ced84ca506c23797590d594b236fe60b14df213291e7b", size = 4433457, upload-time = "2026-06-05T08:12:34.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a3/9e59340f02c1dc8f8c0a05b09244712b8609eb5439f9996e887e2b82f452/ipython-9.14.0-py3-none-any.whl", hash = "sha256:8fd984a3372c14b12790b084ba6b5cff5678c0cb063244a0034f06a51f20d6c2", size = 627457, upload-time = "2026-05-29T15:13:22.942Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/58818a63eaf8982b67632b1bc20585c811611b15a8da19d6012323dc76a5/ipython-9.14.1-py3-none-any.whl", hash = "sha256:5d4a9ecaa3b10e6e5f269dd0948bdb58ca9cb851899cd23e07c320d3eb11613c", size = 627770, upload-time = "2026-06-05T08:12:33.045Z" }, ] [[package]] @@ -1537,11 +1568,11 @@ wheels = [ [[package]] name = "itables" -version = "2.8.0" +version = "2.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/ef/d4ad45b67f741a70293628aa49adc78ed5665976556dbd0f4d04850636a7/itables-2.8.0.tar.gz", hash = "sha256:b8325fbdbd061004f21ea62425944e5e71a64c2a64849964e0a4b000cde70362", size = 1465957, upload-time = "2026-05-17T09:44:25.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/0d/e4a935862ee77e06062c6b797357c7aaf9d4ba9a32d6eb129018d0d19be4/itables-2.8.1.tar.gz", hash = "sha256:562c7d716d667f3faf87ffe1044a19747a3b231ee6aa7725eb6f908caa18c429", size = 1526821, upload-time = "2026-06-10T22:28:07.66Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/bb/3c497ab4a60d921a25340e5e3f646c2e55145454a9312b515f093c16d534/itables-2.8.0-py3-none-any.whl", hash = "sha256:9965c2edc457490d2709d28bd5eabf8f4ac43b44b0e08693fc9bb466a7738926", size = 1489733, upload-time = "2026-05-17T09:44:22.849Z" }, + { url = "https://files.pythonhosted.org/packages/ad/22/eb6ae7468ba673fcb891ff3142e13ffa18f6a43183e6dd8f224b2b4321d3/itables-2.8.1-py3-none-any.whl", hash = "sha256:262e3908771af90634546fe4a5ed63e0d442a6957efbcdcd2ae5cad4845b76e3", size = 1551238, upload-time = "2026-06-10T22:28:05.09Z" }, ] [[package]] @@ -1722,7 +1753,7 @@ wheels = [ [[package]] name = "jupyter-client" -version = "8.8.0" +version = "8.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, @@ -1730,10 +1761,11 @@ dependencies = [ { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/5512503b088997c2250b8bf18258fba9d9ce5ead641183700960d3c9d342/jupyter_client-8.9.1.tar.gz", hash = "sha256:a58f730dd9e728ba16ba1d62ebccf7ffe1ebbdbce4e95cfae941b7321ae1f4fa", size = 359256, upload-time = "2026-06-09T13:15:01.033Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6f/56d39bf385c5c27988aebaf0c18a2a17e960575740100973511018bd904e/jupyter_client-8.9.1-py3-none-any.whl", hash = "sha256:0b7a295bc46e8751e9adae84781f726c851c1d911bd793edc4a3bde942e3da81", size = 109828, upload-time = "2026-06-09T13:14:58.835Z" }, ] [[package]] @@ -1760,35 +1792,35 @@ wheels = [ [[package]] name = "langchain" -version = "1.3.4" +version = "1.3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/3f/034eb6cbef90bfccc89b7f8ed0c1d853dc9cb0bea17c7a269534c647ba3a/langchain-1.3.4.tar.gz", hash = "sha256:d6e0654c22848925534f5c0a706f9be481bb09a619ec60a738fbd1e5502e457a", size = 606617, upload-time = "2026-06-02T20:04:49.411Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/7c/651d0dc4913a7a892156c03dd343b99cfe19ee729e6911ab1f4fe7567b8b/langchain-1.3.9.tar.gz", hash = "sha256:9b14ef0db9ef314299ded858b22ca2a40b8f1b05c8c9cb6b82d53a53075fef00", size = 631514, upload-time = "2026-06-12T16:53:27.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/29/9ffe99c7dc4891a0215ec59c423bea320f943c08a231bc5bae392a438a83/langchain-1.3.4-py3-none-any.whl", hash = "sha256:e51b05ab23d056bc6bf2d97d8c694fb92d6d5765126fef74565d007c27581672", size = 125286, upload-time = "2026-06-02T20:04:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b7/55/3481619d21b9bdfbfda8680fba5cfc6cfe926789b8eaaad95353078cfa20/langchain-1.3.9-py3-none-any.whl", hash = "sha256:4af49ad1095799e4408b489fb79d4b8b49292453618b202d8a697fca59bb6871", size = 132873, upload-time = "2026-06-12T16:53:25.489Z" }, ] [[package]] name = "langchain-anthropic" -version = "1.4.4" +version = "1.4.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/f6/113cf54064b5847367b5a535d80ca2bee34edaea9fddb56ac4466de5e595/langchain_anthropic-1.4.4.tar.gz", hash = "sha256:9ea39ed345f8b13f13bb37549130eedcec2d032eb08b4942642e758c5ba3ece5", size = 687917, upload-time = "2026-05-28T20:20:20.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/f5/cd397b94aeed5fa0e8ab9595b9fb578ac99f424d42220defe6626e6a1a7b/langchain_anthropic-1.4.6.tar.gz", hash = "sha256:78942d4458d883b7d362438a095ed501ed84f44d402622404482481fc973b9da", size = 706540, upload-time = "2026-06-12T16:54:15.352Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/e6/d13340529db7fc52ffb1436b18e988df29ebfdd207a2944975136cc82093/langchain_anthropic-1.4.4-py3-none-any.whl", hash = "sha256:10a5d2915e8d4fd8a9f169774e760b089c7f14de044e13259db5f1c2d3d59d9b", size = 51263, upload-time = "2026-05-28T20:20:18.757Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/927dbbc5a1f5fea1a69adc2883f034cbd1430004e36f4eacd302d500393a/langchain_anthropic-1.4.6-py3-none-any.whl", hash = "sha256:dbd412a956b6b8b0716d9d8460ef71f834a6731cdbfc59e6160482a4a9fb5200", size = 51797, upload-time = "2026-06-12T16:54:14.159Z" }, ] [[package]] name = "langchain-classic" -version = "1.0.7" +version = "1.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1799,9 +1831,9 @@ dependencies = [ { name = "requests" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/78/84b5065816f348c39fefa4316f209f0135e8410216340a953bec17d9e4e4/langchain_classic-1.0.7.tar.gz", hash = "sha256:debbec8065e69b95108d2652e8d5c44f4516e19aa8d716c02ed2211c3aee099d", size = 10554118, upload-time = "2026-05-07T15:46:56.8Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/65/6b5e8a7ff2f2968652c88a67dcecb925b9d8f0a0ce9458c76cd5a0dbd138/langchain_classic-1.0.8.tar.gz", hash = "sha256:ada0cc341a8a5b80fb24d73bdfaaeb849056ee2d8a41cc468355163fd3667484", size = 10557071, upload-time = "2026-06-10T21:27:54.866Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/78/2d9980d028ff0523eea503a77c200e2ff252a3a75eb6e7842bcf5f9c979b/langchain_classic-1.0.7-py3-none-any.whl", hash = "sha256:d9d9be38f7aa534ed0259c2410432e34a1f80b1d491e686749bb55af56479be3", size = 1041386, upload-time = "2026-05-07T15:46:54.845Z" }, + { url = "https://files.pythonhosted.org/packages/99/9a/b8f5cb7490fdbf233088031fc69c9c747439d4097f67f196c1eb4869916d/langchain_classic-1.0.8-py3-none-any.whl", hash = "sha256:1a11ea7fbe630c4f2af2f3873d27718ceac9488cf32d0821030be7cf039a6213", size = 1041536, upload-time = "2026-06-10T21:27:52.767Z" }, ] [[package]] @@ -1828,7 +1860,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.4.0" +version = "1.4.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1841,9 +1873,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/59/de/679a53472c25860837e32c0442c962fa86e95317a36460e2c9d5c91b17c2/langchain_core-1.4.0.tar.gz", hash = "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", size = 920260, upload-time = "2026-05-11T18:42:35.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/2b/fffaff399d20a56d40b9562fa19701e91abd72d8c9d9bc8c2673077b56b6/langchain_core-1.4.7.tar.gz", hash = "sha256:7a825d77de0a3f39adbd9d09612a75e85527e14a52c1601089bcc062972d9f2b", size = 952522, upload-time = "2026-06-12T19:23:57.588Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1a/86c38c27b81913a1c6c12448cab55defb5a1097c7dc9a4cea83f55477a2d/langchain_core-1.4.0-py3-none-any.whl", hash = "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c", size = 548120, upload-time = "2026-05-11T18:42:33.992Z" }, + { url = "https://files.pythonhosted.org/packages/de/3e/dcdffa60078ae7b3a00ebb4cbbf1a204a14c3609983c604886523a7d4418/langchain_core-1.4.7-py3-none-any.whl", hash = "sha256:bcadd51951140ecdcba98311dbd931ba5de02a5ba8a2288dad5069c1eea2a13d", size = 554941, upload-time = "2026-06-12T19:23:55.826Z" }, ] [[package]] @@ -1876,28 +1908,28 @@ wheels = [ [[package]] name = "langchain-openai" -version = "1.2.2" +version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/1b/c506c7f41156d3a6b4582b4c487f480001b8741deecc6e2d4931fdf4cf2c/langchain_openai-1.2.2.tar.gz", hash = "sha256:8698ffcee9a086e91ab6d207f0026181a03effcbf86bf9aee1808ee35af69dcc", size = 1147539, upload-time = "2026-05-21T22:08:31.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/4c/cf3c5a03f1d2e2e4367c1527231162a99d0f1c94113e1203c00469c860e4/langchain_openai-1.3.2.tar.gz", hash = "sha256:240917ae88d754b389a6f2ae06fa262c50c094eb4f576c27d560dff6b86c2f62", size = 3236213, upload-time = "2026-06-13T05:42:12.5Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/8e/7406c99afacafc8c2ce0fa4152f9f8b9598c93ceb291959821abd053b982/langchain_openai-1.2.2-py3-none-any.whl", hash = "sha256:7da39a3c70cbafa93853456199e39a264dc70651be79b12ac49b4f6a448bce2d", size = 99631, upload-time = "2026-05-21T22:08:29.527Z" }, + { url = "https://files.pythonhosted.org/packages/03/21/cbf6c3786de881b214c8c6c9f61fe44c9c47608428676a5cd5c5b2b0cda5/langchain_openai-1.3.2-py3-none-any.whl", hash = "sha256:3d247f43bba9f85d32a374b1bdf3932a0d1e3c60913ebeadf68630de52add67e", size = 119775, upload-time = "2026-06-13T05:42:11.088Z" }, ] [[package]] name = "langchain-protocol" -version = "0.0.16" +version = "0.0.17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/e7/8300ba22d968653051fd06e3117d783872dddf3dcebdd6b1d386836eb43c/langchain_protocol-0.0.16.tar.gz", hash = "sha256:806c7cdd951b1c4f692fa40fce60821ff0f221d4360e27673ddf2c2b99c2b7ff", size = 5969, upload-time = "2026-05-28T23:05:11.121Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/b3/4e2429876c7a35585618caa2b9f9089f7162a6b50562b614ad82ac11c17e/langchain_protocol-0.0.17.tar.gz", hash = "sha256:e7cbe58c205df4b4fd87dc6d5bb23f10e13b236d0e2e1b0b9d05bc2b648f3eea", size = 6026, upload-time = "2026-06-12T18:39:51.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/9c/06dfcc88d02a6364e8d864c421ddd3736305cb0a6c853f75c302c80fe17c/langchain_protocol-0.0.16-py3-none-any.whl", hash = "sha256:3658c142c5d0fb3a023a4be442ce4c15c6d626aab6135eb79a76dc64ad19c3c3", size = 7037, upload-time = "2026-05-28T23:05:10.163Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/a1bfe72c6ec856e99773bbd96c8086421e554b3693d0142b9ea009c6ac92/langchain_protocol-0.0.17-py3-none-any.whl", hash = "sha256:982a08fe152586ed10d4ff3d538c2e0b5766e5f307cdea325e10be3f2c17cae6", size = 7096, upload-time = "2026-06-12T18:39:50.973Z" }, ] [[package]] @@ -1914,7 +1946,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.2.4" +version = "1.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1924,9 +1956,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/43/dac5a2621c1e57f8eb7f0703f6f6fe34a5caf62f8f0fb4d2bb395bb454ea/langgraph-1.2.4.tar.gz", hash = "sha256:5df076973a2d23efb13eceb279d1e5b46feebcbbeded0a86a2ef669abd9e4399", size = 720374, upload-time = "2026-06-02T17:07:37.347Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/9d/7c9ebd17b95569122e2d2e641f535cf086c870d66bb8e59be33cdba856b3/langgraph-1.2.5.tar.gz", hash = "sha256:09a3bdec6fdb3228623fc78b6f69a1400d383f66348d0b04d0efb692022cc6ef", size = 712532, upload-time = "2026-06-12T20:30:58.498Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/9e/31ca236104966d7bb14ea9e93cfd73350aea8c41008ddf057b65794ed10d/langgraph-1.2.4-py3-none-any.whl", hash = "sha256:ffe3e1e31dce28907640f82525858470f293506d2b272d07ea3b3ce97974b067", size = 245402, upload-time = "2026-06-02T17:07:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/a2/03/187281cf61845c5a9c397ae6cd9cd73bb54b39435e5575a7b83c853e5b76/langgraph-1.2.5-py3-none-any.whl", hash = "sha256:9286bb5def82fc865959c14378fe473518dc097d586225f622f029637a2a4bb9", size = 246150, upload-time = "2026-06-12T20:30:57.018Z" }, ] [[package]] @@ -1973,7 +2005,7 @@ wheels = [ [[package]] name = "langsmith" -version = "0.8.8" +version = "0.8.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -1987,9 +2019,9 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/93/28df12b3b3c776077983b92f1299c623592b5999695af2a755fb90ff048b/langsmith-0.8.8.tar.gz", hash = "sha256:9d00e54f54d833c1914003527ff03ad0364741034330da72f0adbeaba852b6cf", size = 4468035, upload-time = "2026-05-31T22:14:57.698Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/19/1ed2af9c6d5d7a148e6b3e809b0af8ce8848e1f66a0726c8223d30e5292b/langsmith-0.8.16.tar.gz", hash = "sha256:8c943f0c9185fe2a9637b5b442828b7efd823b1de28d50d14c136c79660f909b", size = 4513275, upload-time = "2026-06-15T17:41:24.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/71/94a8f2b573278a0b0b7dfd37663c0ddd36867f9e2bba69addd183de0cd56/langsmith-0.8.8-py3-none-any.whl", hash = "sha256:9d60d724c0d187c036e184b3ffdf9fa5c6822aa0bb88144a5fb898e79be645af", size = 402712, upload-time = "2026-05-31T22:14:55.908Z" }, + { url = "https://files.pythonhosted.org/packages/c3/13/8186a9867c67f3fef9958a1d60b45f46c1a9b5d28f67d8fd136f28ceab3f/langsmith-0.8.16-py3-none-any.whl", hash = "sha256:081e57c0175d142192683288740a796eb0eb32d9e703b4bf9133678ceefe3286", size = 500303, upload-time = "2026-06-15T17:41:22.33Z" }, ] [[package]] @@ -2173,7 +2205,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.27.2" +version = "1.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2191,9 +2223,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/3c/347cf965d313f5d41764e7d46bea6ffe7d9ef13b983cc429b0340962a082/mcp-1.27.2.tar.gz", hash = "sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef", size = 621116, upload-time = "2026-05-29T17:16:04.039Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/ee/94c6c50ffc5b5cf4737052275d11b57367f32d1a8516e31dcd60591b3916/mcp-1.28.0.tar.gz", hash = "sha256:559d3f9943674cafbe5744c5d3794f3237e8b47f9bbc58e20c0fad680d8487c2", size = 636040, upload-time = "2026-06-16T21:37:17.996Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/11/252c6f971dc4f16af1d98a1c469d8ba523aab00d1bb76b4d3bc1ff32eacc/mcp-1.27.2-py3-none-any.whl", hash = "sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5", size = 220498, upload-time = "2026-05-29T17:16:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/4c1dc1fbb688641a712d34650c3d58bbbdcb314ddb75bc5817bbf33515a4/mcp-1.28.0-py3-none-any.whl", hash = "sha256:9c1e7cf3a9125557e418ecd4fed8e9adddce81b0dfdae4d6601d700f5beb71a4", size = 221959, upload-time = "2026-06-16T21:37:16.579Z" }, ] [[package]] @@ -2312,7 +2344,7 @@ wheels = [ [[package]] name = "moto" -version = "5.2.1" +version = "5.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -2323,9 +2355,9 @@ dependencies = [ { name = "werkzeug" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/e9/c38202162db2e76623176be9f1dbc9aa41228ffa91ee8da2d3986082c3e3/moto-5.2.1.tar.gz", hash = "sha256:ccb2f3e1dfa82e50e054bda98b0be708d244d2668364dcc1d45e8d3de6091bde", size = 8634437, upload-time = "2026-05-10T19:11:57.286Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/63/d944f387582cc53f53febbff2b3fa36a6d2ed7c1feef8990bf646cfa9cba/moto-5.2.2.tar.gz", hash = "sha256:aac8023a429e125e91c91f8f4730a67b54f518cda587352f7e67252fe3168f75", size = 8678761, upload-time = "2026-06-06T18:57:54.931Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/79/8085b7c1ecd48d0535c3c8444a1d8df2926e457dce8e55fabc332a382c9c/moto-5.2.1-py3-none-any.whl", hash = "sha256:19d2fbd6e613aa5b4e364c52cd5d3cea371643a0f4210689a703227bd2924c5c", size = 6671379, upload-time = "2026-05-10T19:11:53.543Z" }, + { url = "https://files.pythonhosted.org/packages/c1/45/13cff46f4f617a6e97e1d497d75abd913e250bb4c823a4985668c6e593e4/moto-5.2.2-py3-none-any.whl", hash = "sha256:3817f1e39721ca833579b921e53e3b68547ace6a34d848c9486fbb5905808de9", size = 6698689, upload-time = "2026-06-06T18:57:51.435Z" }, ] [package.optional-dependencies] @@ -2416,12 +2448,12 @@ wheels = [ ] [[package]] -name = "nest-asyncio" -version = "1.6.0" +name = "nest-asyncio2" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/73/731debf26e27e0a0323d7bda270dc2f634b398e38f040a09da1f4351d0aa/nest_asyncio2-1.7.2.tar.gz", hash = "sha256:1921d70b92cc4612c374928d081552efb59b83d91b2b789d935c665fa01729a8", size = 14743, upload-time = "2026-02-13T00:34:04.386Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/3179b85b0e1c3659f0369940200cd6d0fa900e6cefcc7ea0bc6dd0e29ffb/nest_asyncio2-1.7.2-py3-none-any.whl", hash = "sha256:f5dfa702f3f81f6a03857e9a19e2ba578c0946a4ad417b4c50a24d7ba641fe01", size = 7843, upload-time = "2026-02-13T00:34:02.691Z" }, ] [[package]] @@ -2489,7 +2521,7 @@ wheels = [ [[package]] name = "openai" -version = "2.40.0" +version = "2.42.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2501,9 +2533,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/9f/136562ec6c3b1a50fe06eb0bb34ed21f0d7426ec0140e5cc43ac785b69a5/openai-2.40.0.tar.gz", hash = "sha256:9a756f91f274a24ad6026cbcb2042fd356c8d4a10e8f347b08d34465e585f7a2", size = 781177, upload-time = "2026-06-01T21:48:23.878Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/e2/c8c7eb634fd2438c2f3fc5c385dd34d0f9d04d6623bcfdf3adcefcf6d7ca/openai-2.42.0.tar.gz", hash = "sha256:5263ee2868608571499df225471770238c8f3ff1ac296874bfc096bbabb6da3a", size = 785143, upload-time = "2026-06-16T21:52:35.798Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/46/180e14be801a75bc13f234cb1b594b232adeb9c84e60a9ab1832e8333591/openai-2.40.0-py3-none-any.whl", hash = "sha256:2b205637ff214477f9ce9ab035e9f494db0e3fa8f1e599008953735fbf6ff1ff", size = 1350935, upload-time = "2026-06-01T21:48:21.462Z" }, + { url = "https://files.pythonhosted.org/packages/01/61/8ea2cbff2531ee0210706aff60174cd10a5a14c1ca17a8c29b1eb34d0b1b/openai-2.42.0-py3-none-any.whl", hash = "sha256:8cd46b1877cfd4aea7ade9474940cc4f78dbd41cce3893de11c12510d0b70caf", size = 1354709, upload-time = "2026-06-16T21:52:33.712Z" }, ] [[package]] @@ -2701,7 +2733,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "(platform_python_implementation != 'PyPy' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "ptyprocess" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -2826,17 +2858,17 @@ wheels = [ [[package]] name = "protobuf" -version = "7.35.0" +version = "7.35.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" }, - { url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" }, - { url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" }, - { url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" }, + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, ] [[package]] @@ -3208,20 +3240,21 @@ connect = [ [[package]] name = "pystow" -version = "0.8.15" +version = "0.8.18" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "backports-zstd", marker = "python_full_version < '3.14'" }, { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/7a/8f48a8a07d7bfd5c2bdca9c4925516025dc2abe02d3c200d97b4ce99cc2a/pystow-0.8.15.tar.gz", hash = "sha256:1513ab112cfe557bc84d5b30b111b2caca1c7b643e3ab266564977de2d121b10", size = 53839, upload-time = "2026-05-31T21:48:22.48Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/28/3c09097f4d91fab75eef706c92156cb9bb210789d804584fea8bf630cafe/pystow-0.8.18.tar.gz", hash = "sha256:bb2565005e22ce749e9c659c054920df57541e6ec808ad44bac28eed2f5b142c", size = 54520, upload-time = "2026-06-17T09:01:16.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/e8/6b5bb1d699b69926d2358c56acb29dbd3280e86283fd36938b261edea997/pystow-0.8.15-py3-none-any.whl", hash = "sha256:f15c4c515ea602c044df7ed23562086a7cc57f2c9143e19e3fa8aecfa5cf15aa", size = 61110, upload-time = "2026-05-31T21:48:20.956Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8f/2a1759d75a9bd2a640486a7e84fe0f3e638a2d6953b66037a1bc3c892e95/pystow-0.8.18-py3-none-any.whl", hash = "sha256:f9d792a5b279614fbc3701531ac4e2e258687c3ad5d3e1921beac2b249a4e75c", size = 61808, upload-time = "2026-06-17T09:01:14.871Z" }, ] [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -3230,9 +3263,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, ] [[package]] @@ -3310,11 +3343,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.30" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/82/c8cd43a6e0719bf5a3b034f6726dd701f75829c08944c83d4b95d02ed0e8/python_multipart-0.0.30.tar.gz", hash = "sha256:0edfe0475c1f46ddd3ff7785a626f6118af32bdcf359bb21260367313bb32118", size = 46316, upload-time = "2026-05-31T19:24:55.198Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/fd/0318007beb234790993d3ec5afd051d1dbceb733e81e3afe2b981ece3f37/python_multipart-0.0.30-py3-none-any.whl", hash = "sha256:830964def8c90607ac5daa00514e3987815865713ade8d20febc9177ac0c3c5b", size = 29730, upload-time = "2026-05-31T19:24:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] [[package]] @@ -3340,15 +3373,18 @@ wheels = [ [[package]] name = "pywin32" -version = "311" +version = "312" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, ] [[package]] @@ -3699,41 +3735,41 @@ wheels = [ [[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" }, +version = "0.15.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, + { url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" }, + { url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, + { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, + { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" }, ] [[package]] name = "s3fs" -version = "2026.4.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiobotocore" }, { name = "aiohttp" }, { name = "fsspec" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/d8/76f3dc1558bdf4494b117a9f7a9cc0a5d9d34edadc9e5d7ceabc5a6a7c37/s3fs-2026.4.0.tar.gz", hash = "sha256:5bdce0abb00b0435ee150807a45fea727451dbc22de4cbc116464f8504ab9d37", size = 85986, upload-time = "2026-04-29T20:52:51.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/00/6677343dc919d6c072bb04d80210afdd22c16838a8d16b3315c122dc728f/s3fs-2026.6.0.tar.gz", hash = "sha256:b28de7082d0a4f72392884bdc497e34a4a1582f675d214c7da0acf6e950a0083", size = 87358, upload-time = "2026-06-16T02:05:48.719Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a4/9d1ea10ebc9e028a289a72fec84da170689549a8102c8aacfcad26bc5035/s3fs-2026.4.0-py3-none-any.whl", hash = "sha256:de0d2a1f33cdf03831fd2382d278c6e4e31fe57c3bf2f703c61f8aec6b703e2a", size = 32392, upload-time = "2026-04-29T20:52:50.295Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0b/f68a968b49876eae0f2a515387093cebb2eb9451380a96741cc20efac0d0/s3fs-2026.6.0-py3-none-any.whl", hash = "sha256:60576e31bb31193c1f643f32b4c6439548720ea6918ac702e21cd757c80b5db8", size = 32573, upload-time = "2026-06-16T02:05:47.608Z" }, ] [[package]] @@ -3867,45 +3903,45 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.50" +version = "2.0.51" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, - { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, - { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, - { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, - { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, - { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, - { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, - { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, - { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, - { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, - { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" }, + { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" }, + { url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" }, + { url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" }, + { url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, ] [[package]] name = "sqlglot" -version = "30.8.0" +version = "30.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/64/89299aefc6ebdf4fc899f5dc14c7fcb7eb9da9290a2b4d615ae7ab884b17/sqlglot-30.8.0.tar.gz", hash = "sha256:1c5f93fb742dd9aaa75eee6bb33a637794a858b9a86375fac23a2dc0f7bc127e", size = 5869750, upload-time = "2026-05-13T09:04:38.923Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/de/1542b04dcac0f85706fb8ce1ce7d2abcc230cdf10ea9e3aa7345393e5a90/sqlglot-30.11.0.tar.gz", hash = "sha256:1a23c6e2adb41da61fda46b1848d2fa26341d447fc0f0cd5ca21160362100991", size = 5893125, upload-time = "2026-06-11T17:11:37.845Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/4e/80705091aaf9c95e125d243f0aa871bc9f3670b4c9d963e6bad3b3dce8ff/sqlglot-30.8.0-py3-none-any.whl", hash = "sha256:af903378c331d5b72277a1b41118f07bc3e50cf4478e2d47eed12c96ee6a22a4", size = 687831, upload-time = "2026-05-13T09:04:36.336Z" }, + { url = "https://files.pythonhosted.org/packages/e3/86/53edf106e8cd3c883ccd0c6b470bf00ddf877a86e667665343b2d597329d/sqlglot-30.11.0-py3-none-any.whl", hash = "sha256:cffdee57d1f2f5472dc9f13087e618cf795841172b7d5ef78b63a051a52d2710", size = 698721, upload-time = "2026-06-11T17:11:35.737Z" }, ] [[package]] @@ -3923,7 +3959,7 @@ wheels = [ [[package]] name = "sssom-pydantic" -version = "0.5.10" +version = "0.5.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "curies" }, @@ -3932,9 +3968,9 @@ dependencies = [ { name = "pyyaml" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/4b/a4902af1cf9e55a4b05310594b025d163bfe0564d6479534beb1244038ef/sssom_pydantic-0.5.10.tar.gz", hash = "sha256:f011e5550eecfa006354dd62bdd732f477123ff470c19cac8b80dc4d46f7f095", size = 62512, upload-time = "2026-05-27T09:42:46.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/23/02d4e16a12ac622231992e1fb57586ffd5e474afbcc5f84e4677ad08ed86/sssom_pydantic-0.5.11.tar.gz", hash = "sha256:69e57b2c786640bbcd8a0b662e67ff13adb659b059ed2e4f649490e112e398b7", size = 69738, upload-time = "2026-06-09T10:53:25.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/94/1cd079f84f29b619af179f2e3200d2d6e6f49ac9af451a6e0fad890e5b85/sssom_pydantic-0.5.10-py3-none-any.whl", hash = "sha256:b22f0473d6c5021aabda5e776a30f173b15bd7fccec67946fee2b9f52e7fadb4", size = 75873, upload-time = "2026-05-27T09:42:45.494Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/fd19236d12994440102a74ad9888ec9142bee508494d655470ebb64db5d6/sssom_pydantic-0.5.11-py3-none-any.whl", hash = "sha256:02851b369063ef248c6fc0e6f56f23caf24197673ef538126c5f4d2dd8fc09ba", size = 83827, upload-time = "2026-06-09T10:53:24.386Z" }, ] [[package]] @@ -3953,14 +3989,14 @@ wheels = [ [[package]] name = "starlette" -version = "1.2.1" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/44/ec35f1b6e83094b997da438a02c8c9b0ade2b1e84cfc48bd4656780760a6/starlette-1.2.1.tar.gz", hash = "sha256:9b9b5ebb992e67d6093741e63c2f59e4f6fff986f81163c087867bd7b924b3f6", size = 2701854, upload-time = "2026-05-31T01:07:51.847Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/54/196d0c1db10af76baa4f64894448505d60d3cdf70ef92cbb35f46a4e4c71/starlette-1.2.1-py3-none-any.whl", hash = "sha256:4de0082d08c8f6764a85a54cf1120d6939507a19905c7768acad2a9f875d2b89", size = 73350, upload-time = "2026-05-31T01:07:50.09Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, ] [[package]] @@ -4068,40 +4104,40 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.6" +version = "6.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/57/6d7303a77ae439d9189108f76c0c4fd89ee5e2cc8387bffb55232565c4ed/tornado-6.5.6.tar.gz", hash = "sha256:9a365179fe8ff6b8766f602c0f67c185d778193e9bdd828b19f0b6ed7764177d", size = 518139, upload-time = "2026-05-27T15:35:54.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/0d/b4f481e18c5a51864e6d12b9a05ecf72919696680b747c958c3fc1f4fbae/tornado-6.5.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:65fcfaafb079435c2c19dc9e07c0f1cf0fa9051759ed0a7d0a3ba7ea7f64919c", size = 447737, upload-time = "2026-05-27T15:35:38.122Z" }, - { url = "https://files.pythonhosted.org/packages/9e/9c/5430c39fcab1144d35860f457b15e9c08b4bc7ac86764354204e983d6183/tornado-6.5.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:38bc01b4acacded2de63ae78023548e41ebe6fbed3ec05a796d7ae3ad893887e", size = 445899, upload-time = "2026-05-27T15:35:40.519Z" }, - { url = "https://files.pythonhosted.org/packages/8b/79/fa7e14a2f939c807a8d30619b4eb604eab219601b78792516ebe22d40cf9/tornado-6.5.6-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b942e6a137fda31ff54bf8e6e2c8d1c37f1f50583f3ed53fb840b53b9601d104", size = 448964, upload-time = "2026-05-27T15:35:42.106Z" }, - { url = "https://files.pythonhosted.org/packages/a7/71/bd67d5f5199f937dafe03a49a37989f60f600ff6fef34c79412a829d97bd/tornado-6.5.6-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8666946e70171b8c3f1fc9b7876fac492e84822c4c7f3746f4e8f8bc9ac92a79", size = 449935, upload-time = "2026-05-27T15:35:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a4/c24388c9cf5b3c3a513b56a158af9f23092c9a2810d789e294310797df21/tornado-6.5.6-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c34cfab7ad6d104f052f55de06d39bbafc5885cfeb4da688803308dbcfa90b7", size = 449767, upload-time = "2026-05-27T15:35:45.793Z" }, - { url = "https://files.pythonhosted.org/packages/a5/eb/6a07ad550c3f7b37244bd0becdf293ec3d3e961783d8b720a97df50de1b2/tornado-6.5.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:385f35e4e22fb52551dfcda4cdc8c30c61c2c001aef5ddad99cdfe116952efd3", size = 449174, upload-time = "2026-05-27T15:35:47.485Z" }, - { url = "https://files.pythonhosted.org/packages/bb/84/3469e098dccdb6763130e06aacd786bb4363fca7b590a55c101ddf34ed30/tornado-6.5.6-cp39-abi3-win32.whl", hash = "sha256:db475f1b67b2809b10bb16264829087724ca8d24fe4ed47f7b8675cae453ef86", size = 450230, upload-time = "2026-05-27T15:35:49.322Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3c/273a04e0b9dd9016f1685cca0c1c8795a71ac88a34a8c889a0b443483226/tornado-6.5.6-cp39-abi3-win_amd64.whl", hash = "sha256:6739bf1e8eb09230f1280ddbd3236f0309db70f2c551a8dbc40f62babdf82f79", size = 450667, upload-time = "2026-05-27T15:35:51.194Z" }, - { url = "https://files.pythonhosted.org/packages/02/98/0cffe22a224f60c5fb1e3aa0b76f9da2e1ca78b0e9545e3d077c68ce60a7/tornado-6.5.6-cp39-abi3-win_arm64.whl", hash = "sha256:2543597b24a695d72338a9a77818362d72387c03ae173f1f169eadc5c91466ac", size = 449690, upload-time = "2026-05-27T15:35:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" }, + { url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" }, + { url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" }, ] [[package]] name = "tqdm" -version = "4.67.3" +version = "4.68.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, ] [[package]] name = "traitlets" -version = "5.15.0" +version = "5.15.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/22/40f55b26baeab80c2d7b3f1db0682f8954e4617fee7d90ce634022ef05c6/traitlets-5.15.0.tar.gz", hash = "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971", size = 163197, upload-time = "2026-05-06T08:05:58.016Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/98/a9937a969d018a23badfea0b381f66783649d48e0ea6c41923265c3cbeb3/traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40", size = 85877, upload-time = "2026-05-06T08:05:55.853Z" }, + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, ] [[package]] @@ -4124,7 +4160,7 @@ wheels = [ [[package]] name = "typer" -version = "0.26.6" +version = "0.26.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -4132,9 +4168,9 @@ dependencies = [ { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/8a/8dc5733b8939e7f1a71173091a6e27a1658345edbff548a0bf3f5bb26173/typer-0.26.6.tar.gz", hash = "sha256:cdbc160fe7e795b835fb6016419494a521a67bfb86b9476a1ccd0e7727d3ae5b", size = 201595, upload-time = "2026-06-02T13:47:50.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/c7/519138260db5e2fe03a509bf9e8ef6af9a514d3565c8fa74fc4fededbae1/typer-0.26.6-py3-none-any.whl", hash = "sha256:49f96d9ee5730cef607bbe155042f40b41fa4c0d0dec04990d580837493805be", size = 122464, upload-time = "2026-06-02T13:47:51.768Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" }, ] [[package]] @@ -4169,14 +4205,14 @@ wheels = [ [[package]] name = "tzlocal" -version = "5.3.1" +version = "5.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/55/15e2340963d2bfedcc6042da3911438fd336f8ae96b65bdbe3a29766da0c/tzlocal-5.4.3.tar.gz", hash = "sha256:3a8c9bc18cf47e1dcde252ea0e6a72a6cde320a400b6ac6db1f1f8cccd553c00", size = 30873, upload-time = "2026-06-17T04:17:41.764Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, + { url = "https://files.pythonhosted.org/packages/42/28/fc144409c71569e928585f8f3c629d80d1ca3ef40175e9222f01588f98c9/tzlocal-5.4.3-py3-none-any.whl", hash = "sha256:24ce97bb58e2a973f7640ec2553ab4e6f6d5a0d0d1aa9dc43bca21d89e1feb82", size = 18039, upload-time = "2026-06-17T04:17:40.027Z" }, ] [[package]] @@ -4199,79 +4235,66 @@ wheels = [ [[package]] name = "uuid-utils" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/a1/822ceef22d1c139cffebe4b1b660cfaa10253d5c770aa2598dc8e9497593/uuid_utils-0.16.0.tar.gz", hash = "sha256:d6902d4375dfba4c9902c736bb82d3c040417b67f7d0fa48910ddfdb1ac95de7", size = 42596, upload-time = "2026-05-19T07:44:23.28Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/9b/74c1f47a9b4f138a254e51528e5ffaeba6bf99ecead9f0c4b6fccccfbfcb/uuid_utils-0.16.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d34cf9681e8892fad2a63e393068e544505408748cd8bf0c3517d753a01528d4", size = 563166, upload-time = "2026-05-19T07:44:10.494Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1c/009e37b70f1f0ff17e7103a36bafde33d503d9ea7fe739761aa3e3c9fde6/uuid_utils-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0681d1bdb7956e0c6d581e7601dabcfb2b08c25d2a65189f4e9b102c94f5ff46", size = 289529, upload-time = "2026-05-19T07:43:54.466Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5e/e0323d54321166639eb2be5e8a464f5cb0fc04d72d91f3e78944bb6a1da8/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed45fb8732d216426227096b55accbb87cba57febc86a044d90780b090eb99d0", size = 326328, upload-time = "2026-05-19T07:45:31.901Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a3/046f6cb958467c3bf4a163a8a53b178b64a62e21ed8ad5b2c1dacb3a2cfc/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b617a334bb01ef2ff8c22900f5a14125eb9063f602131494cc9dc59519beaa5b", size = 332322, upload-time = "2026-05-19T07:43:41.284Z" }, - { url = "https://files.pythonhosted.org/packages/67/80/01914e3949744db7acd0006885e5542fbebb6e39114857d007d29b3265c2/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a750d8aeb8ae880aa9a2529606bde0e994bcc7448730c953107f357a28e6102e", size = 445787, upload-time = "2026-05-19T07:45:36.102Z" }, - { url = "https://files.pythonhosted.org/packages/14/ef/f6908f41279f205d70c8a0d5dcb25dd6802741d7f88e3f0123453c3584d3/uuid_utils-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a250e111903c4368745fce5ac2aa607bd477c62d3307e45347338fdb64b38e0", size = 324678, upload-time = "2026-05-19T07:45:12.77Z" }, - { url = "https://files.pythonhosted.org/packages/11/4a/bf841ba90f829c7779d82155e0f4b88ef6726ccc25507d064d50ac2cd329/uuid_utils-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:95b7f480010ea98a29ee809857a98aa923008c68129af1b39244adccff7377fb", size = 349704, upload-time = "2026-05-19T07:44:47.172Z" }, - { url = "https://files.pythonhosted.org/packages/e6/31/3b5c60172b8c57bf4ca485484b8e4edef550ca324f9287f1183be97422e2/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:420aa3ca403cedb73490b6ea3aeefeea7e0455f5ce60bbf856390ee872ae3306", size = 502456, upload-time = "2026-05-19T07:45:00.821Z" }, - { url = "https://files.pythonhosted.org/packages/88/bf/3da8d497af80fd51d8bf85551c77ede67f07825924ec5987bf9b6031014a/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b8a9a7b1065a12d40f2cc25b7d705ab34954cc57095034367bca39ebcf4a876b", size = 607727, upload-time = "2026-05-19T07:44:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/bd/4e/7c8cf03ec15cd6f40e4cbab81b2b4a625461327f68c7971e54723280ec3e/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f235ac5827d74ac630cc87f29278cdaa5d2f273613a6e05bbd96df7aa4170776", size = 566204, upload-time = "2026-05-19T07:44:51.225Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5f/af955feae69cce7fd2121ca3f790ff4b85ad2e17b2149546f50753e1a047/uuid_utils-0.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c8083284488b84ad178e74add64cfd1e74e8be5e30821e5acbc5019281c658b0", size = 529986, upload-time = "2026-05-19T07:45:57.85Z" }, - { url = "https://files.pythonhosted.org/packages/10/cf/3fec757e51bef10eb41ae8075f5442c60e85ff456b42d16a3063f5dc6c80/uuid_utils-0.16.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:27a071a899ba46a551d6524dbbc5a98b88be176d0f55ddf72cf71c005326ac10", size = 98683, upload-time = "2026-05-19T07:44:16.369Z" }, - { url = "https://files.pythonhosted.org/packages/40/a7/cd1adbea7ef882a70db064c00cd93b12e11027b4cdd7ffd79e95c35fc3e3/uuid_utils-0.16.0-cp313-cp313-win32.whl", hash = "sha256:924a8de04460e4cf65998ad0b6568084f7c51740ebd3254d07a0bcde35a84af6", size = 168822, upload-time = "2026-05-19T07:44:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/74/99/617ceb9e3a95b23837012740979baf71afad723b70daf34862da3f7c17a1/uuid_utils-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:5279bc7ab3c6683f1c67314695bee14d869015acbbc677bdb0015190fe753d16", size = 174967, upload-time = "2026-05-19T07:44:56.022Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d8/148ae707bfc36d482e39db679c86b81bdce264d4feb9df5d40a03b7687e3/uuid_utils-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:61a9c4c26ad12ac66fa4bfd0fdb8494724fe7a5b98a9fcd43e78e2b388663dbb", size = 173142, upload-time = "2026-05-19T07:43:50.171Z" }, - { url = "https://files.pythonhosted.org/packages/21/05/ca6d60705e71fdeaa3431dad94e279a8213c5573cb2925e1aabf3dc0330a/uuid_utils-0.16.0-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73486b6aa3f755a6c97000f5ea67e7ac78d6df89bf22980789a1e943e24b74f0", size = 564408, upload-time = "2026-05-19T07:44:38.351Z" }, - { url = "https://files.pythonhosted.org/packages/eb/8c/b9a0462c38535c1662acb1025768e2d626bee5ce9e1790bad6b5381162ea/uuid_utils-0.16.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f1614572fd9345cdc3dde3f40c237345719fabca1aa87d2d87b321d523cfa34d", size = 289923, upload-time = "2026-05-19T07:45:19.611Z" }, - { url = "https://files.pythonhosted.org/packages/f2/33/a53afeef1a56051551a0f5a801e4bce411dd73c6a8c99bad16902651256d/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9346ce6eb1fbd8b03a6b331d66016afcb4edcdff6eac708e21391600529a016a", size = 325762, upload-time = "2026-05-19T07:45:18.261Z" }, - { url = "https://files.pythonhosted.org/packages/72/ca/4462a4f36365d7ee72d41e05e6bcfe127e861b073ab37c25b2c8a518317c/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a0fc6eb3fd821466fbab69cf356c6ec2b7327266bbbc740a2eb57c77c4bef965", size = 332359, upload-time = "2026-05-19T07:45:34.886Z" }, - { url = "https://files.pythonhosted.org/packages/c5/67/9d3373fa7c5a746fdecc64e30caf915c29eb632203508d87676f9243ed03/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13a797e5e8f0dadc18351a5aa013815ddac25dce6864072a539d510910c95f71", size = 445483, upload-time = "2026-05-19T07:44:49.598Z" }, - { url = "https://files.pythonhosted.org/packages/57/08/ce01aa6d897fc7f875844fe58cad0a542c8ebf089d9242b654b56260ecb8/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57c3583b1f1c00a94f59726a5e2b988fa209221143919a1af5c2fc24e318fc98", size = 326281, upload-time = "2026-05-19T07:44:59.677Z" }, - { url = "https://files.pythonhosted.org/packages/76/ef/2c719b2c26bb5b5e5061a1435c11ad2bd33ac3cd6d4cd0c7c3ac1d3396ed/uuid_utils-0.16.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:caac9c8b1d50e8fbddc76e93bfefbef472978eb45adbfdb6289d578816992953", size = 350809, upload-time = "2026-05-19T07:45:28.076Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9b/c1ed447328b32229cca38ac4c62d309eab006e5e9c4020e2056a175bc607/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:91db59bad97ed2b9d2c6ed25082fe9762b2c422e694fe06786b28cf4e776ac4c", size = 502088, upload-time = "2026-05-19T07:44:09.208Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e0/8442f4efe7bde72f0b4ae5f675d0c7fbe209ad0b54718b8ddf43c46c6fae/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:41985e342a30e76366a8becc60bbdb07d72cd1b86ec657b1f31654e9fb1baada", size = 607631, upload-time = "2026-05-19T07:44:19.384Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1e/9a9fa261edf4c972f28ae83421377e3ab8dbd0bd7db58fd316e782d09a3b/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1b0dcedf9266bf34a54d5cbe78648eaa627e02352f2a6923ed647530aea2f661", size = 567618, upload-time = "2026-05-19T07:43:58.478Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/1bcfdb9d539bd42736dd6076470a42fbb5db23f79712c0a06aa0a3752f7b/uuid_utils-0.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26fe23ab60f05de4ad70aaa5b6a4c2a7bbd43055e3dd6f6b31efba0532ac9c71", size = 530971, upload-time = "2026-05-19T07:45:06.348Z" }, - { url = "https://files.pythonhosted.org/packages/24/0c/18945f417d6bb4d0dd2b7652fe36c58c4e83bcf593b9b326b83aa40b853a/uuid_utils-0.16.0-cp313-cp313t-win32.whl", hash = "sha256:7f8cf49c05d58523a0f977cb7f11afc05791a0fa164d7303b8365a34750638e7", size = 169369, upload-time = "2026-05-19T07:44:32.581Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cc/c0eb0c3fab2ed80d706369b750029143b53126809b77b36bcbb77da66bab/uuid_utils-0.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e99f9a8b2420b228faba23a637e96efaf5c6a678b2e225870f24431c82707f50", size = 175384, upload-time = "2026-05-19T07:45:56.623Z" }, - { url = "https://files.pythonhosted.org/packages/b7/77/50ac87b6e18b1c686f700aa38c9471a990683c6a955f71ac1a6677ed8145/uuid_utils-0.16.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6853b627983aa1b4fd95aa52d9e87136eb94a7b3b7de0fbb1db8a498d457eeec", size = 564108, upload-time = "2026-05-19T07:43:55.609Z" }, - { url = "https://files.pythonhosted.org/packages/83/16/65046676de246bb5334d9f58aa96d2feb9fc347fda3556aaff7da1c2fc7a/uuid_utils-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:f44b65ae0c329843817d9c90e36a7a3c677b413bf407c99e67db874dac49dad3", size = 289967, upload-time = "2026-05-19T07:45:38.886Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/54fa988606a15dfd2028e925d8eb9c3ee6edbf1eb7692a67b37282880b56/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de8a365795a76f347f5622621c2bee543cffa0c70949f3ee093bdefc9d926dcc", size = 325835, upload-time = "2026-05-19T07:44:42.02Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1b/50622f967ceacea1f89fd065d9bfd395b51acb02cfb0a4ddc8fa9ff0c983/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:426a8c9af90242d879706ccf29da56f0b0712e7739fb0bbe16baacabc75596e2", size = 332607, upload-time = "2026-05-19T07:43:42.42Z" }, - { url = "https://files.pythonhosted.org/packages/12/f5/4059706be6617e2787e375ea52994ce3c3fa3920b7d4a9c8ebf7895681a5/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833bc4b3c3fc24be541f67b01b4a75b6b9942a9b7137395b4eb35435948bd6da", size = 444287, upload-time = "2026-05-19T07:43:37.106Z" }, - { url = "https://files.pythonhosted.org/packages/65/d5/f44b2710563da687a368f0ce4dcbd462dfb6708bcd46439d831991d595c7/uuid_utils-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb5252d7c00d586077f10e169d6e6d0b0d0f806d8a085073f0d19b4737aef4e", size = 324949, upload-time = "2026-05-19T07:45:33.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a7/a69e859e37d26c5603f0bc0ae481860f691224f140e5a832f325b804770d/uuid_utils-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b3377ce388fd7bf8d231ec9d1d4f58c8e87888ddea93581f60ed6f878a4f722", size = 349651, upload-time = "2026-05-19T07:43:59.998Z" }, - { url = "https://files.pythonhosted.org/packages/db/73/4139cd3ca7b81ea283c1c8769373e9b2008241c0744a8ffb25f0a1b31325/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:12b6310beb38adc173ec5dc89e98812fd7e3d98f87f3ef01d2ea6ecb5d87994f", size = 502326, upload-time = "2026-05-19T07:45:40.292Z" }, - { url = "https://files.pythonhosted.org/packages/cb/8c/858101583fbad1b3fa04da88b1f7170836aa0f00b4cb712063325c44466d/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a49b5a75497643479c919e2e537a4a36224ac3aaa0fada61b75d87024021ac3e", size = 607689, upload-time = "2026-05-19T07:44:48.355Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bd/8f3d54a4763dd91ebd0f3d7b0c2ec434e4e0b1fc667b03a44d611a465ec6/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:63bfdf00be51b6b3b79275d6767d034ea5c7a0caa067a35d72861284100cb60a", size = 566214, upload-time = "2026-05-19T07:44:53.519Z" }, - { url = "https://files.pythonhosted.org/packages/54/76/4c9a8d9baaa243c7902d84dbba4d51b1ab51c379c66d3fd6368ff6933ecf/uuid_utils-0.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7525bc59ac4579c32317d2493dd42cf134b9bb50cd0bc6a41dd9f77e4740dde6", size = 529989, upload-time = "2026-05-19T07:44:43.141Z" }, - { url = "https://files.pythonhosted.org/packages/6d/13/d32cea997f880cedde415730ce0e872ebfd7a040155ae0bbda70eccd208e/uuid_utils-0.16.0-cp314-cp314-win32.whl", hash = "sha256:fbcac6e6710aa2e4bfbb81762758e01470dc56d5048ba4253acc77c9833568ff", size = 169146, upload-time = "2026-05-19T07:45:46.655Z" }, - { url = "https://files.pythonhosted.org/packages/1c/19/9fc55172d8fe59e1f27a14d598b427fa508a7ebb35fa7b7b99c24fa0ef13/uuid_utils-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:d23fcaf37368a1647319187ef6f8b741bf079f033065899bc2d00a44b0a1214a", size = 175364, upload-time = "2026-05-19T07:45:55.335Z" }, - { url = "https://files.pythonhosted.org/packages/89/5d/fcd9226b715c5aa0638fcdd6deaf0de6c6c3c451c692cd76bfca810c6512/uuid_utils-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:ea3265f8e2b452a4870f3298cb1d183dc4e36a3682cbb264dbe46af31267e706", size = 173268, upload-time = "2026-05-19T07:44:31.19Z" }, - { url = "https://files.pythonhosted.org/packages/c1/64/97ec9af95e58b8187f2934008ffab26e1604d149e34fe01c388b0543a24f/uuid_utils-0.16.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:99f8420c3ed59f89a086782ac197e257f4b1debb4545dffa90cf5db23f96c892", size = 564464, upload-time = "2026-05-19T07:44:40.856Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6d/e4082f407484ac28923c0bf8e861e71d277118d8b7542d0a350340e45350/uuid_utils-0.16.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:259bab73c241743d684dcc3507feb76f484d720545e4e4805582aeff8e19700b", size = 290087, upload-time = "2026-05-19T07:44:01.084Z" }, - { url = "https://files.pythonhosted.org/packages/8c/43/c5c5f273c0ff889f20f10344784f9197dd00eb81ccc294330d4b949fea7e/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:897e8ef0dc5e4ac0b17cf9cae84bb41e560d806280ec5b93db7475b504022105", size = 325532, upload-time = "2026-05-19T07:43:47.508Z" }, - { url = "https://files.pythonhosted.org/packages/13/7f/669aa899ab5378374d28a28231e6978f739921a1af394c7ebd6cc86e2639/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5af79cde16a7600dfccb7d431aec0afd3088ff170b6a09887bf3f7ab3cc7c81", size = 332209, upload-time = "2026-05-19T07:43:51.528Z" }, - { url = "https://files.pythonhosted.org/packages/2b/57/a2a32406d79a222794ef98a19254fd9a81a029a0f32d7740fba9873bff1f/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bece1a6f677ca36047442c465d8166643eed9818b9e43e0bf42d3cf73e92dcff", size = 445507, upload-time = "2026-05-19T07:44:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/85459a35bfa7d73e79acbc4eab1cf6aa6e4d9d022c3260ed9dea539c7f0b/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3444498e7b099499c8a607d7771377020fa55f7274e46f54106af19f752d7", size = 326154, upload-time = "2026-05-19T07:45:23.587Z" }, - { url = "https://files.pythonhosted.org/packages/84/9e/e965efdbb503ed14d6e57aec1a22b98326ed24cc2fb48e750c4d192267a0/uuid_utils-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:542098f6cb6874aebeff98715f3ab7646fbe0f2ffb24509ca372828c68c4ed0e", size = 350905, upload-time = "2026-05-19T07:44:36.957Z" }, - { url = "https://files.pythonhosted.org/packages/23/ae/4321867888a783d03b7c053c0b68ca45d03974d86fcebf44d4ec268db397/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7207b25fe534bcf4d57e0110f90670e61c1c38b6f4598ba855af69ab428fc118", size = 502098, upload-time = "2026-05-19T07:44:17.696Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9a/914a47bf42479bff0ce3e1fa1cbe3585354708edc928e27687cf91de9c26/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:16dc5c6e439f75b0456114e955983e2156c1f38887733e54d54205d3005223e4", size = 607032, upload-time = "2026-05-19T07:44:22.151Z" }, - { url = "https://files.pythonhosted.org/packages/85/4c/2abacd6badba61a047eaa39c8347656229d12843bd9bbe4906daa6dc752c/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6d3ee32c57898d8415242b08d5dd086bc4f7bcbbb3fc102ef257f3d793eb294", size = 567664, upload-time = "2026-05-19T07:45:21.043Z" }, - { url = "https://files.pythonhosted.org/packages/53/1f/9d1a09521276424da19dc0d74456aed3311170fec181b28fa6acba45d963/uuid_utils-0.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7555f120a2282d1901c9a632c2398a614101af4fe3f7c8114aa0f1d8c1978855", size = 530996, upload-time = "2026-05-19T07:45:44.229Z" }, - { url = "https://files.pythonhosted.org/packages/b4/22/14dbedb6b61f492d5524077fd10bbfb137583b0f0aafa6cd870ccb43f39a/uuid_utils-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:756575d082ea4cb7d2f923d5b640c0efe7c82573aab49220c4e09b62d13737ff", size = 169358, upload-time = "2026-05-19T07:45:05.146Z" }, - { url = "https://files.pythonhosted.org/packages/25/f4/a636806c98401a1108f2456e9cc3fa39a618145bfb1d0860c57203159cfe/uuid_utils-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:aa50261a83991dbb570a00573741455bd8f3249444f7329e5bdcd494799d1504", size = 174813, upload-time = "2026-05-19T07:45:59.579Z" }, - { url = "https://files.pythonhosted.org/packages/75/12/3823742459d87a100deb24bb6b41692aa961b267abd130fa7739cdf7d409/uuid_utils-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:22a17e93a371d850ffce8fcdbacc2239f890efe73aa3262b6170c1febc08afe1", size = 171733, upload-time = "2026-05-19T07:45:29.283Z" }, +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/8a/4ef2cd407871a21f948ad447a2754294a4fbe0fdb6cc96f4575490ae8df0/uuid_utils-0.16.1.tar.gz", hash = "sha256:60add5671aaf99cb2fe03359d9f04da27443eadd03ce523e3c64635acd3123fb", size = 42864, upload-time = "2026-06-16T07:28:05.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/0e/5be838a04c609ff6c62664eee5e0b590fcc38f70bd0c8f10438b83e92988/uuid_utils-0.16.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1808d4453c0d0469092c7d84c90e695229e26275287f10d1717d1be8f6c08acf", size = 562651, upload-time = "2026-06-16T07:26:51.26Z" }, + { url = "https://files.pythonhosted.org/packages/fc/19/bd2964d8562b4d2e7554f0e42a08901da924b459b1b7ab4b3f99319ebb6c/uuid_utils-0.16.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6c1af7d68f53b4c91dc8c291a347a74bf2b0a25c9a28a66a24197c2c593ce024", size = 289002, upload-time = "2026-06-16T07:26:52.82Z" }, + { url = "https://files.pythonhosted.org/packages/15/9a/23faa0390415789a3a0e3cbaf73f643ec668d5f8fe96c10bb4f326e3280f/uuid_utils-0.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e82e709fb3f260a2680fa8a4918b600de153357fb5c9f69463a8c28b5a737e00", size = 325747, upload-time = "2026-06-16T07:26:54.318Z" }, + { url = "https://files.pythonhosted.org/packages/93/ed/7694ba36f2f424f5e311752ce0861e71e6959bc62c2dd3a1e8587c2b6984/uuid_utils-0.16.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2dd909678ee21a77d21c81b394c99f9a40ec11ec8b60a7074f1b01a765e70b97", size = 332541, upload-time = "2026-06-16T07:26:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b5/5cbb166d77554f4d0aaa2c728014e4011f58509ecf4d0bf62272abc8b650/uuid_utils-0.16.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c6cc68b3e08164b09bdcec302fd597584739dc45e701b2a0473fcf2406ae5ba", size = 446733, upload-time = "2026-06-16T07:26:57.094Z" }, + { url = "https://files.pythonhosted.org/packages/da/7f/d9f9881fc980acc2db6c7b51afb29e674ba9375d96eedb1b3ad9f14680c5/uuid_utils-0.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be8a39895ddc1f4b1814c7f08178755e77e53197f609232f19ef0798bcb68bf", size = 325006, upload-time = "2026-06-16T07:26:58.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/b0/7e30e090bbb1c66bab4b2b7ab70ee843bfd0d20638d4e814d060ee6c822a/uuid_utils-0.16.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5069e78a05010bd7e8ee1add95ca030d1365014f68de5fbbcb6dc1a313ec19a4", size = 349415, upload-time = "2026-06-16T07:27:00.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/b0/3c41dc45c2695080f8d92f6cea9faf1935bfb2d0ceddbe77036aa5f5decb/uuid_utils-0.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6c95a339679edca5908b0769c404f103ab4c39b20eb424059b1219709d75d56b", size = 501896, upload-time = "2026-06-16T07:27:01.627Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/77f97e71718b4fe8b167da2316205cc07c47b44547683bb6609adf0ee82e/uuid_utils-0.16.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1f547598a5afc1456768a6b7eb2952e9db936d730fdf2b3e60740d4417e1eecd", size = 608180, upload-time = "2026-06-16T07:27:03.321Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e3/86cde5a431df2c7a0f58e4399d277d591c5bc841e107de7f989ae380808d/uuid_utils-0.16.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:07a9c972f7e22e3a937671415cc006999ebe6e243051bf8f22e553e2a089bcc5", size = 566688, upload-time = "2026-06-16T07:27:04.981Z" }, + { url = "https://files.pythonhosted.org/packages/aa/45/0339e76cacff0d69a384813e41a38d5bc6dbf69a20ce9b453d803d527cba/uuid_utils-0.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:327ec607b6386c99c50486bd0f2a7d0c5996e6085764a923425327f3fa8c8787", size = 530675, upload-time = "2026-06-16T07:27:06.448Z" }, + { url = "https://files.pythonhosted.org/packages/16/d4/a66fc1d1d20df8d164f6821a132457c91dab26596486db04f0cbc191100b/uuid_utils-0.16.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:c8188ffd368ab005833e51b304b0f5f2967fa97bb10b01a4c91ab2ea01d4f33c", size = 98702, upload-time = "2026-06-16T07:27:07.849Z" }, + { url = "https://files.pythonhosted.org/packages/05/af/b09772d65dc09d4123766f3048992c644820fc647af90635b913c7f746b3/uuid_utils-0.16.1-cp313-cp313-win32.whl", hash = "sha256:717696c17f1d15f577bcf90464163d39f7b36d8925b3cbbd044032855c59050b", size = 167333, upload-time = "2026-06-16T07:27:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/15/02/589d2b5a2fbc75e8a6cda982b42455bae8e49ea8d849dce5e24c044a764b/uuid_utils-0.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4702353d3ca02d36db089af40ab963a0b7231c496f5012433e8deda1027a4d6b", size = 174327, upload-time = "2026-06-16T07:27:10.28Z" }, + { url = "https://files.pythonhosted.org/packages/bf/7f/b000e716d77ab1a275bfaf16cc58e97ac6bfc69a7df41d11797800ed6e3f/uuid_utils-0.16.1-cp313-cp313-win_arm64.whl", hash = "sha256:5915ed4face0439d566360db0040aa91236be8f1d717dffd92e312da52f14434", size = 173166, upload-time = "2026-06-16T07:27:11.509Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/2cfe257f5e3d988234bb476cb5bffaf4b58ed7955b9547bef1fab9c6ee07/uuid_utils-0.16.1-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f3fa8c13516ecb3f8fdc6d454f2aa82412ea155071d003ef592522f7248eff2e", size = 568057, upload-time = "2026-06-16T07:27:12.903Z" }, + { url = "https://files.pythonhosted.org/packages/04/11/bb14b73b42e733945b015ef6374676e6c50ebe26f5a506440087a8357f4e/uuid_utils-0.16.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:647f53c992ae2799924c870065f2615a0e1e3dd1394273b7b64de1d965ffe251", size = 291621, upload-time = "2026-06-16T07:27:14.355Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ad/3a84dac53f45d6f5e9eb7bd5732ebd30cc32194e972ed19cab4842cd7737/uuid_utils-0.16.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4e72deb0c4fc48ffceec4b5776460734ccfdd26cdc4f466e6b525db528cf3c9", size = 327397, upload-time = "2026-06-16T07:27:15.674Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c0/bdd42425146679e3d62236b29c4a59ae5c774a2c57c7ee4cc32f9dfa0725/uuid_utils-0.16.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554379de42217d21fa4cb141877df317d015bc75e6f80cc86d87054dd8c63aa2", size = 333715, upload-time = "2026-06-16T07:27:17.338Z" }, + { url = "https://files.pythonhosted.org/packages/4e/1f/24feb9df2298982ae58eb750e3a2836170149292a7d80d30c7c4e2c9863c/uuid_utils-0.16.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec209a454806b34e2d6fd41706d62a77c2f671192388369f65947b50f8dce14b", size = 449442, upload-time = "2026-06-16T07:27:18.838Z" }, + { url = "https://files.pythonhosted.org/packages/90/f2/f3ee2c3b08eceb2e1718d2d5f31f4968fb807444f6e28ec5b2d257b009e5/uuid_utils-0.16.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1f5958466456ba330273320baa9a1e78a0773f3c4af22bfdcbf3ea49b77277", size = 326917, upload-time = "2026-06-16T07:27:20.329Z" }, + { url = "https://files.pythonhosted.org/packages/29/58/71334bae33eec0730abbf5c5bd021757b9766d26724744c635c73915d2d9/uuid_utils-0.16.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bc891f047c29e4e5947942fa386eab642fed082bccb932e4b1f29fb98c760f5", size = 352474, upload-time = "2026-06-16T07:27:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/90/3c/de5e5d26866cf6da46399a74f07c60f8a79a9366a1391e084f1edc5caf2d/uuid_utils-0.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f74c53f01aa5a0d5f69d44f9c18b4ae676b9456ff914340c7c37032547c36553", size = 504007, upload-time = "2026-06-16T07:27:23.022Z" }, + { url = "https://files.pythonhosted.org/packages/55/93/fe2afc29b32af5a6bc1f362785f5be161cf98a823b5a4e3b46a7a1c8f859/uuid_utils-0.16.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1168f364da715143e58c1cf3f0533fc62be18cb08d89ff934faa0b3911b07f4b", size = 609178, upload-time = "2026-06-16T07:27:24.456Z" }, + { url = "https://files.pythonhosted.org/packages/cf/64/902551df2ff562212e80d11055789749e3bc015f63418291047fe255d978/uuid_utils-0.16.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e13d6f28174ff47cc2c956d1b1ced15ecb41b485dc567f0868c242fef767d6ab", size = 568997, upload-time = "2026-06-16T07:27:26.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/d7/c4501cb4fb09b7cf31cef630def234bbd07b89e69828e53777b0e2322c74/uuid_utils-0.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3474a0aa88a59451bfe1053e59c342ce9c57efaca864d26106ffe40f1d18ad2e", size = 532417, upload-time = "2026-06-16T07:27:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a8/24e10e7a2d8da2b74fff7dae4fc3f05ce57908008a8cbc4362843b4e75e2/uuid_utils-0.16.1-cp314-cp314-win32.whl", hash = "sha256:3823a2aa06b912ff119d2ed1da19f549ffb2b0ac3187560d1afd5d5e0f905e21", size = 169368, upload-time = "2026-06-16T07:27:28.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c7/e2934ccb5a81e7ca01aa76a747aadafaf011f209b5c2aacb9226ac1b6b17/uuid_utils-0.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:8c29a69b1710716ef356e3e4adf557f122381cf0622bd8034900f33f3621ad10", size = 175886, upload-time = "2026-06-16T07:27:30.483Z" }, + { url = "https://files.pythonhosted.org/packages/97/8b/e92640d2dd21da3a5f719af234ecf3595a9eb4594f8e7561fd625c91f7ac/uuid_utils-0.16.1-cp314-cp314-win_arm64.whl", hash = "sha256:793ce3c40cb6690de63ba5859ba1a7d324dd317368a6d941a9a069cc3121f80e", size = 174527, upload-time = "2026-06-16T07:27:31.908Z" }, + { url = "https://files.pythonhosted.org/packages/82/6c/8599d326903aa36f6df377af5704f4c8dd09f91b63dbc5af629ad970516f/uuid_utils-0.16.1-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a60cec7b6b0a8fa6048f386b50fa3741ec31e4a4c64682d9d4a6cc809381d655", size = 570237, upload-time = "2026-06-16T07:27:33.465Z" }, + { url = "https://files.pythonhosted.org/packages/f2/18/69b97093bfb26281d8ac539186af343ec9ab7a43bf2e45871d5e5c3ff9f5/uuid_utils-0.16.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:856507775e45660a00e603622108ad07be666ea2f0c333c51d8d857c490d25e7", size = 292799, upload-time = "2026-06-16T07:27:34.944Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b0/718e9df18b6214b64d063b652ff5801001783942a8527ccac28dabd81ecf/uuid_utils-0.16.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d41154865431e6e9b3bf28c8ecead4af3653f24029f64561dc0968537ebd890c", size = 327278, upload-time = "2026-06-16T07:27:36.258Z" }, + { url = "https://files.pythonhosted.org/packages/4e/90/72feb2f35deeebbbb87342943e1041290133f46594c37351e9fda4104486/uuid_utils-0.16.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ca3ab4579e7335b1e7cf0be5a889cd2f9561150377e88264663235ed94f0c44", size = 333903, upload-time = "2026-06-16T07:27:37.66Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/64b5fa6f42da85d8f4cf2d838c152ff9eb2b5e14d24e3784fb3836fd7ae5/uuid_utils-0.16.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e981840285825ece6a861c600b3e0da93575a67aac20fcf77d185e615a6ef6", size = 449100, upload-time = "2026-06-16T07:27:38.989Z" }, + { url = "https://files.pythonhosted.org/packages/88/45/347f0f28a0f2a6c7b078c50624838b5060fce62ad0c512750cba18bd3745/uuid_utils-0.16.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79b42065d01244c04c92ba252e6cf877edf845cf5a55ed7d5a14e1ad3f3efee3", size = 327961, upload-time = "2026-06-16T07:27:40.426Z" }, + { url = "https://files.pythonhosted.org/packages/79/32/ef685e83e4560d4be94878691853142e3c100a571c5fcd6e9af029ae8bc7/uuid_utils-0.16.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d6c492464bccfab7872bbd6a7257841f70259bf9eb16f98e1b78648ed40ea2dc", size = 353327, upload-time = "2026-06-16T07:27:41.825Z" }, + { url = "https://files.pythonhosted.org/packages/00/b8/3ba75dc9bc5c0dae3bb590ff8cff661077c2fd79b81163c56dc0ed0090eb/uuid_utils-0.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c0bedc59f321b299ff6626ef76c163e4731614066103708c0158054b7fee184", size = 503853, upload-time = "2026-06-16T07:27:43.149Z" }, + { url = "https://files.pythonhosted.org/packages/05/08/a70888d1eb780bb20b7236482bbcd89b734bb261a5ae9301b4c1579b29b6/uuid_utils-0.16.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a99383f696d5f8b3621a8d5f8501e8760539276f05ae341b26b842b414f25c28", size = 609392, upload-time = "2026-06-16T07:27:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/36/6b/9f9265911697a27fa0436ca9eca41a65907b0f74ca56240fad9d3d235248/uuid_utils-0.16.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:684efe2e5980a516f369d393b87d30ddfcc1d30ab9d50d47881d86b719f07e30", size = 569730, upload-time = "2026-06-16T07:27:46.339Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/b4c45682b4fcf69304c2069491b11819637d4bc56d3af8fb061042b0689c/uuid_utils-0.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5480507adc72cda50a4d923c68c7f82f39e7833a92379298743efdb084646bda", size = 533883, upload-time = "2026-06-16T07:27:47.805Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/08199e799f2d2f29c6d7266d6484aa4ca92830e2839e24013dba652c9c5f/uuid_utils-0.16.1-cp314-cp314t-win32.whl", hash = "sha256:f330f1ab05ad26a6bdd4717136c2a4e417b853688ceb40b1a537130cf542cf0c", size = 169760, upload-time = "2026-06-16T07:27:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/7f/08/4c0deed2973b4069a888407f1eeec1f0e7a9792ebb033f995f239129caa9/uuid_utils-0.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f29c7afeff321567b47cd5ce26560bef2528f317f398f127ac5d1c3c8b864f7", size = 176469, upload-time = "2026-06-16T07:27:50.444Z" }, + { url = "https://files.pythonhosted.org/packages/ae/86/3b2c2369de084276f31af6e4c26d41a88d48b842305a8fd999fb7a1a6b78/uuid_utils-0.16.1-cp314-cp314t-win_arm64.whl", hash = "sha256:702559aad80d8be2ff31a425004654153b2d7ed9448dceea32cf7e83ecc5e403", size = 174144, upload-time = "2026-06-16T07:27:51.645Z" }, ] [[package]] name = "uvicorn" -version = "0.48.0" +version = "0.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", marker = "sys_platform != 'emscripten'" }, { name = "h11", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, ] [[package]] @@ -4298,11 +4321,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.7.0" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, ] [[package]] From d406e77be65ab491cda1b06bd7c2ec52938f83cb Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Wed, 17 Jun 2026 07:29:37 -0700 Subject: [PATCH 02/12] Updating lockfile --- uv.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/uv.lock b/uv.lock index 878d1638..7dc078e2 100644 --- a/uv.lock +++ b/uv.lock @@ -489,7 +489,6 @@ source = { editable = "." } dependencies = [ { name = "bioregistry" }, { name = "boto3", extra = ["crt"] }, - { name = "cdm-task-service-client" }, { name = "click" }, { name = "defusedxml" }, { name = "delta-spark" }, @@ -528,7 +527,6 @@ xml = [ requires-dist = [ { name = "bioregistry", specifier = ">=0.13.58" }, { name = "boto3", extras = ["crt"], specifier = ">=1.42.55" }, - { name = "cdm-task-service-client", git = "https://github.com/kbase/cdm-task-service-client.git" }, { name = "click", specifier = ">=8.4.1" }, { name = "defusedxml", specifier = ">=0.7.1" }, { name = "delta-spark", specifier = ">=4.2.0" }, From 09806ecbc27da9dfffa5e1c2cac72c9235b8e8da Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Tue, 16 Jun 2026 14:01:59 -0700 Subject: [PATCH 03/12] Switch to __name__ logger; rename/moved file batcher --- .gitignore | 2 + pyproject.toml | 8 +- src/cdm_data_loaders/audit/checkpoint.py | 12 +- src/cdm_data_loaders/audit/metrics.py | 8 +- src/cdm_data_loaders/audit/rejects.py | 7 +- src/cdm_data_loaders/audit/run.py | 14 +- src/cdm_data_loaders/ncbi_ftp/assembly.py | 4 +- src/cdm_data_loaders/ncbi_ftp/manifest.py | 4 +- src/cdm_data_loaders/ncbi_ftp/metadata.py | 4 +- src/cdm_data_loaders/ncbi_ftp/promote.py | 4 +- .../parsers/uniprot/metalink.py | 5 +- .../parsers/uniprot/relnotes.py | 5 +- .../parsers/uniprot/uniprot_kb.py | 4 +- .../parsers/uniprot/uniref.py | 5 +- .../pipelines/all_the_bacteria.py | 14 +- src/cdm_data_loaders/pipelines/core.py | 19 +- .../pipelines/cts_defaults.py | 19 +- .../pipelines/ncbi_ftp_download.py | 9 +- .../pipelines/ncbi_rest_api.py | 33 +- src/cdm_data_loaders/pipelines/uniprot_kb.py | 9 +- src/cdm_data_loaders/pipelines/uniref.py | 9 +- src/cdm_data_loaders/readers/dsv.py | 4 +- src/cdm_data_loaders/utils/batcher.py | 92 ++++++ .../utils/download/async_client.py | 5 +- src/cdm_data_loaders/utils/download/core.py | 6 +- .../utils/download/sync_client.py | 5 +- src/cdm_data_loaders/utils/file_system.py | 71 ----- src/cdm_data_loaders/utils/ftp_client.py | 5 +- src/cdm_data_loaders/utils/gz.py | 12 +- src/cdm_data_loaders/utils/s3.py | 19 +- src/cdm_data_loaders/utils/xml_utils.py | 6 +- .../validation/dataframe_validator.py | 4 +- tests/conftest.py | 18 +- tests/pipelines/conftest.py | 37 +-- tests/pipelines/test_core.py | 18 +- tests/pipelines/test_cts_defaults.py | 10 +- tests/pipelines/test_ncbi_rest_api.py | 2 +- .../{test_file_system.py => test_batcher.py} | 286 +++++++++++++----- tests/utils/test_gz.py | 8 +- 39 files changed, 485 insertions(+), 321 deletions(-) create mode 100644 src/cdm_data_loaders/utils/batcher.py delete mode 100644 src/cdm_data_loaders/utils/file_system.py rename tests/utils/{test_file_system.py => test_batcher.py} (63%) diff --git a/.gitignore b/.gitignore index 4af9be0e..3c709c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +data/ + .DS_Store # Byte-compiled / optimized / DLL files diff --git a/pyproject.toml b/pyproject.toml index 8832a284..3d92bcac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -172,7 +172,13 @@ ignore = [ "*.ipynb" = ["T201"] # ignore printing in notebooks "**/__init__.py" = ["D104"] # no docstrings for __init__ files "scripts/**/*.py" = ["T201"] # ignore printing in scripts -"tests/**/*.py" = ["S101", "T201", "FBT001", "FBT002", "ARG002"] # use of assert, booleans, unused mock args +# S101: use of assert +# T201: print statements +# FBT001, FBT002: booleans as args +# ANN401: type annotations +# ARG002: unused mock args +# PLR0913: too many arguments +"tests/**/*.py" = ["S101", "T201", "FBT001", "FBT002", "ARG002", "PLR0913"] "tests/integration/**/*.py" = ["S101", "T201", "FBT001", "FBT002", "ARG002", "ANN401"] "tests/utils/test_s3.py" = ["ANN401"] diff --git a/src/cdm_data_loaders/audit/checkpoint.py b/src/cdm_data_loaders/audit/checkpoint.py index 0aa152da..e9cc86af 100644 --- a/src/cdm_data_loaders/audit/checkpoint.py +++ b/src/cdm_data_loaders/audit/checkpoint.py @@ -1,11 +1,12 @@ """Checkpoint audit table functions: adding and updating information on data import pipeline execution.""" +from logging import Logger, getLogger + from delta.tables import DeltaTable from pyspark.sql import SparkSession from pyspark.sql import functions as sf from cdm_data_loaders.audit.schema import ( - AUDIT_SCHEMA, CHECKPOINT, LAST_ENTRY_ID, PIPELINE, @@ -18,7 +19,8 @@ current_run_expr, ) from cdm_data_loaders.core.pipeline_run import PipelineRun -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger + +logger: Logger = getLogger(__name__) # Checkpoint table-related functions @@ -58,7 +60,7 @@ def upsert_checkpoint( .whenNotMatchedInsertAll() .execute() ) - get_cdm_logger().info("%s %s: checkpoint created/updated", run.pipeline, run.run_id) + logger.info("%s %s: checkpoint created/updated", run.pipeline, run.run_id) def update_checkpoint_status(spark: SparkSession, run: PipelineRun, status: str) -> None: @@ -80,7 +82,7 @@ def update_checkpoint_status(spark: SparkSession, run: PipelineRun, status: str) # N.b. this may not work correctly if another process updates the table in the interim metrics = delta.history(1).select("operationMetrics").collect()[0][0] if int(metrics.get("numUpdatedRows", 0)) == 0: - get_cdm_logger().warning( + logger.warning( "%s %s: cannot update '%s' to status %s because no record exists.", run.pipeline, run.run_id, @@ -88,7 +90,7 @@ def update_checkpoint_status(spark: SparkSession, run: PipelineRun, status: str) status, ) else: - get_cdm_logger().info("%s %s: checkpoint successfully updated to status %s", run.pipeline, run.run_id, status) + logger.info("%s %s: checkpoint successfully updated to status %s", run.pipeline, run.run_id, status) def load_checkpoint(spark: SparkSession, run: PipelineRun) -> str | None: diff --git a/src/cdm_data_loaders/audit/metrics.py b/src/cdm_data_loaders/audit/metrics.py index db725451..792715d1 100644 --- a/src/cdm_data_loaders/audit/metrics.py +++ b/src/cdm_data_loaders/audit/metrics.py @@ -1,11 +1,12 @@ """Audit table for recording metrics.""" +from logging import Logger, getLogger + from delta.tables import DeltaTable from pyspark.sql import DataFrame, Row, SparkSession from pyspark.sql import functions as sf from cdm_data_loaders.audit.schema import ( - AUDIT_SCHEMA, METRICS, N_INVALID, N_READ, @@ -19,9 +20,8 @@ current_run_expr, ) from cdm_data_loaders.core.pipeline_run import PipelineRun -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def write_metrics( @@ -91,6 +91,6 @@ def write_metrics( .whenNotMatchedInsertAll() .execute() ) - get_cdm_logger().info("%s %s: ingest metrics written to '%s' table.", run.pipeline, run.run_id, METRICS) + logger.info("%s %s: ingest metrics written to '%s' table.", run.pipeline, run.run_id, METRICS) return metrics diff --git a/src/cdm_data_loaders/audit/rejects.py b/src/cdm_data_loaders/audit/rejects.py index d92c10a6..50ad19e3 100644 --- a/src/cdm_data_loaders/audit/rejects.py +++ b/src/cdm_data_loaders/audit/rejects.py @@ -1,5 +1,7 @@ """Audit table for recording data rejected as invalid during ingest.""" +from logging import Logger, getLogger + import pyspark.sql.functions as sf from pyspark.sql import DataFrame from pyspark.sql.types import StructField @@ -16,9 +18,8 @@ TIMESTAMP, ) from cdm_data_loaders.core.pipeline_run import PipelineRun -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def write_rejects( @@ -81,4 +82,4 @@ def write_rejects( # write to disk rejects_df.write.format("delta").mode("append").saveAsTable(f"{run.namespace}.{REJECTS}") - get_cdm_logger().info("%s %s: invalid rows written to '%s' audit table.", run.pipeline, run.run_id, REJECTS) + logger.info("%s %s: invalid rows written to '%s' audit table.", run.pipeline, run.run_id, REJECTS) diff --git a/src/cdm_data_loaders/audit/run.py b/src/cdm_data_loaders/audit/run.py index e128b258..ee6dff76 100644 --- a/src/cdm_data_loaders/audit/run.py +++ b/src/cdm_data_loaders/audit/run.py @@ -1,11 +1,12 @@ """Run audit table functions: additions and updates to the run table, which tracks overall run status for a pipeline.""" +from logging import Logger, getLogger + from delta.tables import DeltaTable from pyspark.sql import SparkSession from pyspark.sql import functions as sf from cdm_data_loaders.audit.schema import ( - AUDIT_SCHEMA, END_TIME, ERROR, PIPELINE, @@ -21,7 +22,8 @@ match_run, ) from cdm_data_loaders.core.pipeline_run import PipelineRun -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger + +logger: Logger = getLogger(__name__) def _table_not_updated(delta: DeltaTable) -> bool: @@ -73,14 +75,14 @@ def complete_run(spark: SparkSession, run: PipelineRun, records_processed: int) ) # check whether rows were updated by looking in the delta log if _table_not_updated(delta): - get_cdm_logger().warning( + logger.warning( "%s %s: cannot update '%s' to status %s because no record exists.", run.pipeline, run.run_id, RUN, STATUS_SUCCESS, ) - get_cdm_logger().info("%s %s: run completed", run.pipeline, run.run_id) + logger.info("%s %s: run completed", run.pipeline, run.run_id) def fail_run(spark: SparkSession, run: PipelineRun, error: Exception) -> None: @@ -104,11 +106,11 @@ def fail_run(spark: SparkSession, run: PipelineRun, error: Exception) -> None: ) # check whether rows were updated by looking in the delta log if _table_not_updated(delta): - get_cdm_logger().warning( + logger.warning( "%s %s: cannot update '%s' to status %s because no record exists.", run.pipeline, run.run_id, RUN, STATUS_ERROR, ) - get_cdm_logger().error("%s %s: run failed with %s", run.pipeline, run.run_id, error.__repr__()) + logger.error("%s %s: run failed with %s", run.pipeline, run.run_id, error.__repr__()) diff --git a/src/cdm_data_loaders/ncbi_ftp/assembly.py b/src/cdm_data_loaders/ncbi_ftp/assembly.py index 8368cc75..7bc9ca15 100644 --- a/src/cdm_data_loaders/ncbi_ftp/assembly.py +++ b/src/cdm_data_loaders/ncbi_ftp/assembly.py @@ -8,15 +8,15 @@ import contextlib import time from ftplib import FTP +from logging import Logger, getLogger from pathlib import Path, PurePosixPath from typing import Any from cdm_data_loaders.ncbi_ftp.constants import ACCESSION_PARTS_REGEX, ASSEMBLY_PATH_REGEX, FTP_HOST -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.checksums import verify_md5 from cdm_data_loaders.utils.ftp_client import connect_ftp, ftp_noop_keepalive, ftp_retrieve_text -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) FILE_FILTERS = [ "_gene_ontology.gaf.gz", diff --git a/src/cdm_data_loaders/ncbi_ftp/manifest.py b/src/cdm_data_loaders/ncbi_ftp/manifest.py index bb7acf79..152b2435 100644 --- a/src/cdm_data_loaders/ncbi_ftp/manifest.py +++ b/src/cdm_data_loaders/ncbi_ftp/manifest.py @@ -15,6 +15,7 @@ from dataclasses import dataclass, field from datetime import UTC, datetime from http import HTTPStatus +from logging import Logger, getLogger from pathlib import Path, PurePosixPath from typing import Any from urllib.parse import urlsplit @@ -28,11 +29,10 @@ parse_md5_checksums_file, ) from cdm_data_loaders.ncbi_ftp.constants import ACCESSION_PARTS_REGEX, ASSEMBLY_PATH_REGEX -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.ftp_client import FTP, connect_ftp, ftp_noop_keepalive, ftp_retrieve_text from cdm_data_loaders.utils.s3 import head_object, list_matching_objects -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) _DATABASE_ACC_PREFIX: dict[str, str] = { "refseq": "GCF_", diff --git a/src/cdm_data_loaders/ncbi_ftp/metadata.py b/src/cdm_data_loaders/ncbi_ftp/metadata.py index 511d6ea8..eeaff084 100644 --- a/src/cdm_data_loaders/ncbi_ftp/metadata.py +++ b/src/cdm_data_loaders/ncbi_ftp/metadata.py @@ -19,15 +19,15 @@ import json import tempfile from datetime import UTC, datetime +from logging import Logger, getLogger from pathlib import Path, PurePosixPath from typing import Any, TypedDict from frictionless import Package -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.s3 import copy_object, get_s3_client -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) _NCBI_CONTRIBUTOR = { "contributor_type": "Organization", diff --git a/src/cdm_data_loaders/ncbi_ftp/promote.py b/src/cdm_data_loaders/ncbi_ftp/promote.py index 38b63111..a107a01f 100644 --- a/src/cdm_data_loaders/ncbi_ftp/promote.py +++ b/src/cdm_data_loaders/ncbi_ftp/promote.py @@ -10,6 +10,7 @@ from collections import defaultdict from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import UTC, datetime +from logging import Logger, getLogger from pathlib import Path, PurePosixPath from typing import Any @@ -24,7 +25,6 @@ create_descriptor, upload_descriptor, ) -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.s3 import ( copy_object, delete_objects, @@ -34,7 +34,7 @@ upload_file, ) -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) DEFAULT_LAKEHOUSE_KEY_PREFIX: PurePosixPath = PurePosixPath("tenant-general-warehouse/kbase/datasets/ncbi") diff --git a/src/cdm_data_loaders/parsers/uniprot/metalink.py b/src/cdm_data_loaders/parsers/uniprot/metalink.py index 601d497a..8dec3c22 100644 --- a/src/cdm_data_loaders/parsers/uniprot/metalink.py +++ b/src/cdm_data_loaders/parsers/uniprot/metalink.py @@ -6,19 +6,18 @@ """ import datetime +from logging import Logger, getLogger from pathlib import Path from typing import Any from xml.etree.ElementTree import Element from defusedxml.ElementTree import parse -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger - NS = {"": "http://www.metalinker.org/"} NOW = datetime.datetime.now(tz=datetime.UTC) COLUMNS = ["id", "db", "xref"] -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def parse_metalink(metalink_xml_path: Path | str) -> Element | None: diff --git a/src/cdm_data_loaders/parsers/uniprot/relnotes.py b/src/cdm_data_loaders/parsers/uniprot/relnotes.py index 6a14f098..a4604e35 100644 --- a/src/cdm_data_loaders/parsers/uniprot/relnotes.py +++ b/src/cdm_data_loaders/parsers/uniprot/relnotes.py @@ -20,11 +20,10 @@ import datetime as dt import re +from logging import Logger, getLogger from pathlib import Path from typing import Any -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger - RELEASE_VERSION_DATE: re.Pattern[str] = re.compile( r"is pleased to announce UniProt Knowledgebase \(UniProtKB\) Release\s+(\w+) \((\d{1,2}-[a-zA-Z]+-\d{4})\)\." ) @@ -38,7 +37,7 @@ DATE_FORMAT = "%d-%b-%Y" -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def parse_relnotes(relnotes_path: Path) -> dict[str, Any]: diff --git a/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py b/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py index 399d84e2..fbf29c61 100644 --- a/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py +++ b/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py @@ -5,12 +5,12 @@ """ import datetime +from logging import Logger, getLogger from pathlib import Path from typing import Any from lxml.etree import Element, tounicode -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.helpers import _ensembl_type from cdm_data_loaders.utils.xml_utils import get_text @@ -43,7 +43,7 @@ "pubmed": "PMID", } -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) # TODO: FIXME!!! diff --git a/src/cdm_data_loaders/parsers/uniprot/uniref.py b/src/cdm_data_loaders/parsers/uniprot/uniref.py index 6de4c06e..f833d1c9 100644 --- a/src/cdm_data_loaders/parsers/uniprot/uniref.py +++ b/src/cdm_data_loaders/parsers/uniprot/uniref.py @@ -13,13 +13,12 @@ """ import datetime +from logging import Logger, getLogger from pathlib import Path from typing import Any from lxml.etree import Element, tounicode -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger - UNIREF_URL = "http://uniprot.org/uniref" ENTRY_XML_TAG = f"{{{UNIREF_URL}}}entry" NS = "ns" @@ -38,7 +37,7 @@ "NCBI taxonomy": "NCBITaxon", } -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def dump_xml_element(element: Element) -> str: diff --git a/src/cdm_data_loaders/pipelines/all_the_bacteria.py b/src/cdm_data_loaders/pipelines/all_the_bacteria.py index 8455ad5a..671dc1e1 100644 --- a/src/cdm_data_loaders/pipelines/all_the_bacteria.py +++ b/src/cdm_data_loaders/pipelines/all_the_bacteria.py @@ -6,7 +6,7 @@ all_atb_files.tsv: https://osf.io/xv7q9/files/r6gcp (or Rg6cp, casing varies) -# TODO: change the output to delta tables. +# TODO: change the output to parquet files. """ @@ -33,9 +33,6 @@ from cdm_data_loaders.utils.download.sync_client import FileDownloader from cdm_data_loaders.utils.s3 import stream_to_s3 -logger = logging.getLogger("dlt") - - DATASET_NAME = "all_the_bacteria" ALL_FILES_TSV_FILE_ID = "R6gcp" ALL_ATB_FILE_NAME = "all_atb_files.tsv" @@ -119,7 +116,7 @@ def load_patterns(pattern_file: Path) -> re.Pattern | None: if patterns: return re.compile("^(" + "|".join(patterns) + ")$") except Exception: - logger.exception("Could not load patterns from %s", str(pattern_file)) + logging.getLogger(__name__).exception("Could not load patterns from %s", str(pattern_file)) return None @@ -133,6 +130,7 @@ def download_atb_index_tsv(settings: AtbSettings) -> Path: :return: path to the downloaded file :rtype: Path """ + logger = logging.getLogger(__name__) # get the all_atb_files.tsv file info from the OSF API and retrieve the download link osf_client = RESTClient( base_url="https://api.osf.io/v2/", @@ -183,6 +181,7 @@ def get_file_download_links(settings: AtbSettings, atb_files_tsv: Path) -> Gener :yield: list of fields to download :rtype: Generator[list[dict[str, Any]], Any] """ + logger = logging.getLogger(__name__) pattern_to_match = settings.pattern_matches with atb_files_tsv.open() as index_file: reader = csv.DictReader(index_file, delimiter="\t") @@ -202,9 +201,7 @@ def get_file_download_links(settings: AtbSettings, atb_files_tsv: Path) -> Gener logger.error(err_msg) raise RuntimeError(err_msg) - files_to_download = [row for row in all_lines if pattern_to_match.match(row["project"])] - - yield files_to_download + yield [row for row in all_lines if pattern_to_match.match(row["project"])] def osf_file_downloader(settings: AtbSettings, atb_file_list: list[dict[str, Any]]) -> Generator[DataItemWithMeta, Any]: @@ -215,6 +212,7 @@ def osf_file_downloader(settings: AtbSettings, atb_file_list: list[dict[str, Any :param atb_file_list: info about files to transfer, as a list of dictionaries :type atb_file_list: list[dict[str, Any]] """ + logger = logging.getLogger(__name__) client = FileDownloader() successful_downloads = [] for f in atb_file_list: diff --git a/src/cdm_data_loaders/pipelines/core.py b/src/cdm_data_loaders/pipelines/core.py index 3d821f59..23e9638e 100644 --- a/src/cdm_data_loaders/pipelines/core.py +++ b/src/cdm_data_loaders/pipelines/core.py @@ -12,12 +12,10 @@ from pydantic import ValidationError from pydantic_settings import SettingsError -from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_BATCH_SIZE, BatchedFileInputSettings, CtsSettings -from cdm_data_loaders.utils.file_system import BatchCursor +from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_PIPELINE_BATCH_SIZE, BatchedFileInputSettings, CtsSettings +from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher from cdm_data_loaders.utils.xml_utils import stream_xml_file -logger = logging.getLogger("dlt") - def construct_env_var() -> None: """Use environment variables to construct a new environment variable.""" @@ -38,6 +36,7 @@ def sync_configs(settings: CtsSettings, dlt_config: Any) -> None: def dump_settings(settings: CtsSettings) -> None: """Dump the pipeline settings to the logger.""" settings_minus_dlt_config = settings.model_dump(exclude={"dlt_config"}) + logger: logging.Logger = logging.getLogger(__name__) logger.info("Pipeline settings:") logger.info(settings_minus_dlt_config) @@ -50,16 +49,15 @@ def run_cli(settings_cls: type[CtsSettings], pipeline_fn: Callable[[Any], None]) """ # piece together env vars construct_env_var() - # instantiate the config try: settings = settings_cls(dlt_config=dlt.config) sync_configs(settings, dlt.config) except (SettingsError, ValidationError, ValueError): - logger.exception("Error initialising config") + logging.getLogger(__name__).exception("Error initialising config") raise except Exception: - logger.exception("Unexpected error setting up config") + logging.getLogger(__name__).exception("Unexpected error setting up config") raise dump_settings(settings) @@ -102,6 +100,7 @@ def run_pipeline( pipeline = dlt.pipeline(destination=destination, **pipeline_kwargs) slack_hook: str | None = pipeline.runtime_config.slack_incoming_hook + logger: logging.Logger = logging.getLogger(__name__) if not slack_hook: logger.info("Slack webhook not configured; no Slack alerts will be sent.") @@ -138,13 +137,17 @@ def stream_xml_file_resource( :param log_interval: log a progress message every N entries :type log_interval: int """ + logger = logging.getLogger(__name__) timestamp = datetime.datetime.now(tz=datetime.UTC) batch_params: dict[str, Any] = {} if settings.start_at: batch_params["start_at"] = settings.start_at - batcher = BatchCursor(settings.input_dir, batch_size=DEFAULT_BATCH_SIZE, **batch_params) + batcher = NumericFileSequenceBatcher( + directory=settings.input_dir, batch_size=DEFAULT_PIPELINE_BATCH_SIZE, **batch_params + ) while files := batcher.get_batch(): + logger.debug("Files to be processed:%s", "".join("- " + str(f) + "\n" for f in files)) for file_path in files: logger.info("Reading from %s", str(file_path)) for n_entries, entry in enumerate(stream_xml_file(file_path, xml_tag)): diff --git a/src/cdm_data_loaders/pipelines/cts_defaults.py b/src/cdm_data_loaders/pipelines/cts_defaults.py index 2ea95128..d6d70315 100644 --- a/src/cdm_data_loaders/pipelines/cts_defaults.py +++ b/src/cdm_data_loaders/pipelines/cts_defaults.py @@ -1,22 +1,23 @@ """Common defaults for running pipelines on the KBase CTS.""" -from typing import Any, Self +from typing import Any, Final, Self import dlt.common.configuration.accessors from frozendict import frozendict from pydantic import AliasChoices, Field, computed_field, field_validator, model_validator from pydantic_settings import BaseSettings, CliSuppress, SettingsConfigDict -INPUT_MOUNT = "/input_dir" -OUTPUT_MOUNT = "/output_dir" - -VALID_DESTINATIONS = ["local_fs", "s3"] -DEFAULT_BATCH_SIZE = 50 -DEFAULT_START_AT = 0 +from cdm_data_loaders.utils.batcher import MIN_START_AT # TODO: frozendict can be moved to the stdlib implementation when py 3.15 is released. +INPUT_MOUNT: Final[str] = "/input_dir" +OUTPUT_MOUNT: Final[str] = "/output_dir" + +VALID_DESTINATIONS: list[str] = ["local_fs", "s3"] + + DEFAULT_CTS_SETTINGS = frozendict( { "dev_mode": False, @@ -31,10 +32,12 @@ DEFAULT_BATCH_FILE_SETTINGS = frozendict( { **DEFAULT_CTS_SETTINGS, - "start_at": DEFAULT_START_AT, + "start_at": MIN_START_AT, } ) +DEFAULT_PIPELINE_BATCH_SIZE: Final[int] = 50 + ARG_ALIASES = frozendict( { diff --git a/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py b/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py index a309aa70..ea4054c4 100644 --- a/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py +++ b/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py @@ -13,6 +13,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import UTC, datetime from ftplib import error_temp +from logging import Logger, getLogger from pathlib import Path, PurePosixPath from typing import Any @@ -29,13 +30,9 @@ ) from cdm_data_loaders.pipelines.core import run_cli from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_SETTINGS_CONFIG_DICT, INPUT_MOUNT, OUTPUT_MOUNT -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.ftp_client import ThreadLocalFTP from cdm_data_loaders.utils.s3 import get_s3_client, upload_file -logger = get_cdm_logger() - - DEFAULT_STAGING_KEY_PREFIX: PurePosixPath = PurePosixPath("staging") @@ -105,7 +102,7 @@ def _upload_assembly_dir( if upload_file(f, str(dest_prefix), show_progress=False): count += 1 else: - logger.warning("Failed to upload %s to %s", f, dest_prefix) + getLogger(__name__).warning("Failed to upload %s to %s", f, dest_prefix) f.unlink() shutil.rmtree(assembly_dir, ignore_errors=True) return count @@ -130,6 +127,7 @@ def download_batch( :param limit: optional limit for testing :return: report dict with overall stats """ + logger: Logger = getLogger(__name__) with manifest_path.open() as f: assembly_paths = [PurePosixPath(line.strip()) for line in f if line.strip() and not line.startswith("#")] @@ -268,6 +266,7 @@ def download_and_stage( # noqa: PLR0913, PLR0915 :param dry_run: when ``True``, download but skip all S3 uploads :return: download report extended with ``staged_objects``, ``staging_key_prefix``, ``dry_run`` """ + logger: Logger = getLogger(__name__) if manifest_s3_key is not None and manifest_local_path is not None: msg = "Provide exactly one of manifest_s3_key or manifest_local_path, not both" raise ValueError(msg) diff --git a/src/cdm_data_loaders/pipelines/ncbi_rest_api.py b/src/cdm_data_loaders/pipelines/ncbi_rest_api.py index 7d656988..3082064a 100644 --- a/src/cdm_data_loaders/pipelines/ncbi_rest_api.py +++ b/src/cdm_data_loaders/pipelines/ncbi_rest_api.py @@ -1,4 +1,9 @@ -"""Pipeline to import data from the NCBI API.""" +"""Pipeline to import data from the NCBI API. + +Like other importers in the cdm-data-loaders repo, the NCBI REST API pipeline +expects input files with the naming format `{text_string}_{digits}.{ext}`, e.g. +batch_001.txt, batch_002.txt, batch_003.txt. +""" import logging import os @@ -7,7 +12,7 @@ from functools import partial from itertools import islice from pathlib import Path -from typing import Any +from typing import Any, Final from urllib.parse import parse_qs, urlparse import dlt @@ -27,25 +32,23 @@ run_pipeline, ) from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_SETTINGS_CONFIG_DICT, CtsSettings -from cdm_data_loaders.utils.file_system import BatchCursor +from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher -DATASET_NAME = "ncbi_rest_api" +DATASET_NAME: Final[str] = "ncbi_rest_api" NCBI_API_KEY = os.environ.get("NCBI_API_KEY") or "DEMO_KEY" # Max number of items to request per page from the NCBI REST API (max allowed is 1000). -MAX_RESULTS_PER_PAGE = 1000 +MAX_RESULTS_PER_PAGE: Final[int] = 1000 # Max number of IDs to send in a multi-ID query # Max URL length seems to be 4611 (as of Jun 2026) -MAX_IDS_PER_QUERY = 250 - -DATASET = "dataset" -ANNOTATION = "annotation" -ERROR = "error" +MAX_IDS_PER_QUERY: Final[int] = 250 -QUERY_TYPE_REGEX = re.compile(r"^(" + DATASET + r"|" + ANNOTATION + r")$") +DATASET: Final[str] = "dataset" +ANNOTATION: Final[str] = "annotation" +ERROR: Final[str] = "error" -logger = logging.getLogger("dlt") +QUERY_TYPE_REGEX: Final[re.Pattern[str]] = re.compile(r"^(" + DATASET + r"|" + ANNOTATION + r")$") REST_CLIENT_HOOKS = {} @@ -252,7 +255,7 @@ def get_dataset_reports(assembly_id_list: list[str]) -> dict[str, None | dict[st if not assembly_id_list: return {} - logger.info("fetching dataset reports for:\n%s", ", ".join(sorted(assembly_id_list))) + logging.getLogger(__name__).info("fetching dataset reports for:\n%s", ", ".join(sorted(assembly_id_list))) assembly_dataset_reports = [] for page in ncbi_genome_client.paginate( @@ -273,7 +276,7 @@ def get_dataset_reports(assembly_id_list: list[str]) -> dict[str, None | dict[st def get_annotation_report(assembly_id: str) -> list[dict[str, Any]] | None: """Fetch the annotation report for an assembly from the NCBI datasets REST API.""" - logger.info("fetching annotation report for %s", assembly_id) + logging.getLogger(__name__).info("fetching annotation report for %s", assembly_id) page_data = [] for page in ncbi_genome_client.paginate( @@ -333,7 +336,7 @@ def assemble_assembly_reports( def assembly_list() -> Generator[list[str], Any, Any]: """List of assemblies to fetch.""" settings = get_settings() - batcher = BatchCursor(settings.input_dir, batch_size=1) + batcher = NumericFileSequenceBatcher(directory=settings.input_dir, batch_size=1) while files := batcher.get_batch(): for file_path in files: with file_path.open() as f: diff --git a/src/cdm_data_loaders/pipelines/uniprot_kb.py b/src/cdm_data_loaders/pipelines/uniprot_kb.py index a17e5be1..3f064878 100644 --- a/src/cdm_data_loaders/pipelines/uniprot_kb.py +++ b/src/cdm_data_loaders/pipelines/uniprot_kb.py @@ -1,7 +1,7 @@ """DLT pipeline to import UniProt data.""" from collections.abc import Generator -from typing import Any +from typing import Any, Final import dlt from dlt.extract.items import DataItemWithMeta @@ -15,8 +15,8 @@ ) from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_SETTINGS_CONFIG_DICT, BatchedFileInputSettings -APP_NAME = "uniprot_kb_importer" -UNIPROT_LOG_INTERVAL = 1000 +APP_NAME: Final[str] = "uniprot_kb_importer" +UNIPROT_LOG_INTERVAL: Final[int] = 1000 class UniProtSettings(BatchedFileInputSettings): @@ -28,7 +28,7 @@ class UniProtSettings(BatchedFileInputSettings): ) -@dlt.resource(name="parse_uniprot", parallelized=True) +@dlt.resource(name="parse_uniprot", file_format="parquet", parallelized=True) def parse_uniprot(settings: UniProtSettings) -> Generator[DataItemWithMeta, Any]: """Parse the information from UniProt files, batch by batch.""" yield from stream_xml_file_resource( @@ -48,7 +48,6 @@ def run_uniprot_pipeline(settings: UniProtSettings) -> None: "pipeline_name": "uniprot_kb", "dataset_name": "uniprot_kb", }, - pipeline_run_kwargs={"table_format": "delta"}, ) diff --git a/src/cdm_data_loaders/pipelines/uniref.py b/src/cdm_data_loaders/pipelines/uniref.py index f4703da5..b453e897 100644 --- a/src/cdm_data_loaders/pipelines/uniref.py +++ b/src/cdm_data_loaders/pipelines/uniref.py @@ -1,7 +1,7 @@ """DLT pipeline to import UniRef data.""" from collections.abc import Generator -from typing import Any +from typing import Any, Final import dlt from dlt.extract.items import DataItemWithMeta @@ -19,8 +19,8 @@ BatchedFileInputSettings, ) -APP_NAME = "uniref_importer" -UNIREF_LOG_INTERVAL = 10000 +APP_NAME: Final[str] = "uniref_importer" +UNIREF_LOG_INTERVAL: Final[int] = 10000 UNIREF_VARIANT_ALIASES = ["-u", "--uniref", "--uniref-variant", "--uniref_variant"] @@ -56,7 +56,7 @@ def validate_uniref_variant(cls, v: str) -> str: return v -@dlt.resource(name="parse_uniref", parallelized=True) +@dlt.resource(name="parse_uniref", file_format="parquet", parallelized=True) def parse_uniref(settings: UnirefSettings) -> Generator[DataItemWithMeta, Any]: """Parse the information from UniRef files, batch by batch. @@ -86,7 +86,6 @@ def run_uniref_pipeline(settings: UnirefSettings) -> None: "pipeline_name": f"uniref_{settings.uniref_variant}", "dataset_name": "uniprot_kb", }, - pipeline_run_kwargs={"table_format": "delta"}, ) diff --git a/src/cdm_data_loaders/readers/dsv.py b/src/cdm_data_loaders/readers/dsv.py index bd74280a..d89bb376 100644 --- a/src/cdm_data_loaders/readers/dsv.py +++ b/src/cdm_data_loaders/readers/dsv.py @@ -1,12 +1,12 @@ """Generic DSV file reader with validation of incoming data.""" +from logging import Logger, getLogger from typing import Any from pyspark.sql import DataFrame, SparkSession from pyspark.sql.types import StringType, StructField, StructType from cdm_data_loaders.core.constants import INVALID_DATA_FIELD_NAME -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger # mapping of delimiters to format names (for logging) # spark defaults to separating on commas if nothing is specified @@ -29,7 +29,7 @@ } -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def get_format_name(delimiter: str | None) -> str: diff --git a/src/cdm_data_loaders/utils/batcher.py b/src/cdm_data_loaders/utils/batcher.py new file mode 100644 index 00000000..00312114 --- /dev/null +++ b/src/cdm_data_loaders/utils/batcher.py @@ -0,0 +1,92 @@ +"""File system-related utilities.""" + +import re +from logging import Logger, getLogger +from pathlib import Path +from typing import Annotated, Final, Self + +from pydantic import BaseModel, Field, model_validator + +# Matches files like: name_00001.ext or name_00001.ext.gz +FILE_NAME_REGEX: re.Pattern[str] = re.compile(r"^\w+_(\d+)(\.\w+)+$") + +MIN_BATCH_SIZE: Final[int] = 1 +MIN_START_AT: Final[int] = 1 +MIN_END_AT: Final[int] = 0 + +logger: Logger = getLogger(__name__) + + +class NumericFileSequenceBatcher(BaseModel): + """A batcher that can be used to retrieve batches of files from a directory. + + Searches the directory for files with names ending in `_[0-9]+` + (underscore followed by one or more digits) with one or more extensions (e.g. `.gz`, `.txt`, `.tar.gz`). + + Assumes that file names only contain alphanumeric characters, underscore, and extension(s). + + :param directory: directory to retrieve files from; strings will be coerced to Paths + :type directory: Path, required + :param batch_size: number of files to return per invocation, defaults to 1 + :type batch_size: int, optional + :param start_at: file number to start at, defaults to 1. + Must be greater than or equal to 1. + :type start_at: int, optional + :param end_at: file number to end at, inclusive (i.e. if set to 15, file_0015.txt will be the last file) + If set to 0, the end_at parameter is ignored -- i.e. there is no maximum value. + Must be greater than or equal to 0 + Defaults to 0 + :type end_at: int, optional + :param file_regex: pattern that the file names should match. Capture group 1 is expected to be numeric. + :type file_regex: re.Pattern, optional + """ + + directory: Path + batch_size: Annotated[int, Field(ge=MIN_BATCH_SIZE, strict=True)] = MIN_BATCH_SIZE + start_at: Annotated[int, Field(ge=MIN_START_AT, strict=True)] = MIN_START_AT + end_at: Annotated[int, Field(ge=MIN_END_AT, strict=True)] = MIN_END_AT + file_regex: re.Pattern[str] = Field(default=FILE_NAME_REGEX) + + @model_validator(mode="after") + def check_start_at_end_at(self) -> Self: + """Ensure that the end_at value, if set and non-zero, is greater than the start_at value. + + :raises ValueError: if end_at is set and start_at exceeds end_at + :return: self + :rtype: Self + """ + if self.end_at != 0 and self.end_at < self.start_at: + err_msg = "end_at must be greater than start_at" + raise ValueError(err_msg) + return self + + def _get_sequence_number(self, path: Path) -> int: + match = self.file_regex.match(path.name) + return int(match.group(1)) # pyright: ignore[reportOptionalMemberAccess] + + def get_batch(self) -> list[Path]: + """Return the next `batch_size` files whose sequence number >= start_at. + + Re-scans the directory on every call to pick up newly added files and + updates `start_at` to the next file in the directory list. + """ + if self.end_at and self.start_at > self.end_at: + return [] + + matched = sorted(p for p in self.directory.iterdir() if p.is_file() and self.file_regex.match(p.name)) + if not matched: + logger.warning("No matching files found in %s", str(self.directory)) + return [] + + eligible = [ + p + for p in matched + if self._get_sequence_number(p) >= self.start_at + and (not self.end_at or self._get_sequence_number(p) <= self.end_at) + ] + + batch = eligible[: self.batch_size] + if batch: + self.start_at = self._get_sequence_number(batch[-1]) + 1 + + return batch diff --git a/src/cdm_data_loaders/utils/download/async_client.py b/src/cdm_data_loaders/utils/download/async_client.py index 09a9a4d8..c4af27e9 100644 --- a/src/cdm_data_loaders/utils/download/async_client.py +++ b/src/cdm_data_loaders/utils/download/async_client.py @@ -20,7 +20,7 @@ """ import asyncio -import logging +from logging import Logger, getLogger from pathlib import Path from typing import Any @@ -32,14 +32,13 @@ wait_exponential, ) -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.download.core import ( DownloadCore, DownloadError, NonRetryableDownloadError, ) -logger: logging.Logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def get_async_httpx_client() -> httpx.AsyncClient: diff --git a/src/cdm_data_loaders/utils/download/core.py b/src/cdm_data_loaders/utils/download/core.py index cf788203..ad72f9cc 100644 --- a/src/cdm_data_loaders/utils/download/core.py +++ b/src/cdm_data_loaders/utils/download/core.py @@ -1,16 +1,14 @@ """File downloader with tenacity for retry support.""" import hashlib -import logging from collections.abc import AsyncIterable, Iterable +from logging import Logger, getLogger from pathlib import Path from typing import Any import httpx -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger - -logger: logging.Logger = get_cdm_logger() +logger: Logger = getLogger(__name__) class DownloadError(Exception): diff --git a/src/cdm_data_loaders/utils/download/sync_client.py b/src/cdm_data_loaders/utils/download/sync_client.py index 0f3c1369..96a64629 100644 --- a/src/cdm_data_loaders/utils/download/sync_client.py +++ b/src/cdm_data_loaders/utils/download/sync_client.py @@ -19,7 +19,7 @@ """ -import logging +from logging import Logger, getLogger from pathlib import Path from typing import Any @@ -32,14 +32,13 @@ wait_exponential, ) -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.download.core import ( DownloadCore, DownloadError, NonRetryableDownloadError, ) -logger: logging.Logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def get_httpx_client() -> httpx.Client: diff --git a/src/cdm_data_loaders/utils/file_system.py b/src/cdm_data_loaders/utils/file_system.py deleted file mode 100644 index 0660b881..00000000 --- a/src/cdm_data_loaders/utils/file_system.py +++ /dev/null @@ -1,71 +0,0 @@ -"""File system-related utilities.""" - -import re -from pathlib import Path - -# Matches files like: name_00001.ext or name_00001.ext.gz -FILE_NAME_REGEX = re.compile(r"^\w+_(\d+)(\.\w+)+$") - - -class BatchCursor: - """A batcher that can be used to retrieve batches of files from a directory.""" - - def __init__(self, directory: str | Path, batch_size: int = 1, start_at: int = 1, end_at: int = 0) -> None: - """Initialise a new directory batch cursor. - - :param directory: directory to retrieve files from - :type directory: str | Path - :param batch_size: number of files to return per invocation, defaults to 1 - :type batch_size: int, optional - :param start_at: file number to start at, defaults to 1 - :type start_at: int, optional - :param end_at: file number to end at, inclusive (i.e. if set to 15, file_0015.txt will be the last file) - Defaults to 0, which implies no end_at - :type end_at: int, optional - """ - errs = [] - if not isinstance(batch_size, int) or batch_size < 1: - errs.append("batch_size must be an integer, 1 or greater") - if not isinstance(start_at, int) or start_at < 0: - errs.append("start_at must be an integer, 0 or greater") - if not isinstance(end_at, int) or end_at < 0: - errs.append("end_at must be an integer, 1 or greater") - elif end_at > 0 and end_at < start_at: - # end_at must be greater than start_at - errs.append("end_at must be greater than start_at") - if errs: - err_msg = f"Error{'' if len(errs) == 1 else 's'} initialising BatchCursor:{'\n- '.join(errs)}\n" - raise ValueError(err_msg) - - self.directory = Path(directory) - self.batch_size: int = batch_size - self.start_at: int = start_at - self.end_at: int | None = end_at if end_at > 0 else None - self.file_regex: re.Pattern[str] = FILE_NAME_REGEX - - def _get_sequence_number(self, path: Path) -> int: - match = self.file_regex.match(path.name) - return int(match.group(1)) # pyright: ignore[reportOptionalMemberAccess] - - def get_batch(self) -> list[Path]: - """Return the next `batch_size` files whose sequence number >= start_at. - - Re-scans the directory on every call to pick up newly added files and - updates `start_at` to the next file in the directory list. - """ - if self.end_at and self.start_at > self.end_at: - return [] - - matched = sorted(p for p in self.directory.iterdir() if p.is_file() and self.file_regex.match(p.name)) - eligible = [ - p - for p in matched - if self._get_sequence_number(p) >= self.start_at - and (self.end_at is None or self._get_sequence_number(p) <= self.end_at) - ] - - batch = eligible[: self.batch_size] - if batch: - self.start_at = self._get_sequence_number(batch[-1]) + 1 - - return batch diff --git a/src/cdm_data_loaders/utils/ftp_client.py b/src/cdm_data_loaders/utils/ftp_client.py index 6eab9cd4..b2dc0ccb 100644 --- a/src/cdm_data_loaders/utils/ftp_client.py +++ b/src/cdm_data_loaders/utils/ftp_client.py @@ -11,6 +11,7 @@ import threading import time from ftplib import FTP, error_temp +from logging import Logger, getLogger from pathlib import Path, PurePosixPath from tenacity import ( @@ -21,9 +22,7 @@ wait_fixed, ) -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger - -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) DEFAULT_TIMEOUT = 60 diff --git a/src/cdm_data_loaders/utils/gz.py b/src/cdm_data_loaders/utils/gz.py index 83564f38..ebc8482c 100644 --- a/src/cdm_data_loaders/utils/gz.py +++ b/src/cdm_data_loaders/utils/gz.py @@ -2,21 +2,23 @@ import gzip import shutil +from logging import Logger, getLogger from pathlib import Path import click -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger +logger: Logger = getLogger(__name__) -logger = get_cdm_logger() - -def decompress_file(file: Path) -> None: +def decompress_file(file: str | Path) -> None: """Decompress a gzip file. :param file: file to decompress - :type file: Path + :type file: str | Path """ + if not isinstance(file, Path): + file = Path(file) + if file.suffix != ".gz": logger.info("File %s does not end with .gz: skipping decompression", str(file)) return diff --git a/src/cdm_data_loaders/utils/s3.py b/src/cdm_data_loaders/utils/s3.py index 5dbcae9a..e6920a45 100644 --- a/src/cdm_data_loaders/utils/s3.py +++ b/src/cdm_data_loaders/utils/s3.py @@ -1,5 +1,7 @@ """Utilities for s3 interaction.""" +import json +from logging import Logger, getLogger from pathlib import Path from types import ModuleType from typing import Any @@ -7,13 +9,10 @@ import boto3 import botocore import botocore.client -import json import tqdm from botocore.config import Config from botocore.exceptions import ClientError -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger - CDM_LAKE_BUCKET = "cdm-lake" DEFAULT_EXTRA_ARGS = {"ChecksumAlgorithm": "CRC64NVME"} @@ -29,7 +28,7 @@ _s3_client: botocore.client.BaseClient | None = None -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) def get_s3_client(args: dict[str, str | None] | None = None) -> botocore.client.BaseClient: @@ -588,7 +587,8 @@ def delete_objects(bucket: str, keys: list[str]) -> list[dict[str, Any]]: def cmd_mb(args: list[str]) -> None: """Create a bucket: ``mb s3://bucket``.""" if not args: - raise SystemExit("Usage: s3_local.py mb s3://BUCKET") + err_msg = "Usage: s3_local.py mb s3://BUCKET" + raise SystemExit(err_msg) bucket, _ = split_s3_path(args[0], allow_bucket_only=True) s3 = get_s3_client() try: @@ -602,7 +602,8 @@ def cmd_mb(args: list[str]) -> None: def cmd_cp(args: list[str]) -> None: """Recursive upload: ``cp LOCAL_DIR s3://bucket/prefix/``.""" if len(args) < 2: # noqa: PLR2004 - raise SystemExit("Usage: s3_local.py cp LOCAL_DIR s3://BUCKET/PREFIX/") + err_msg = "Usage: s3_local.py cp LOCAL_DIR s3://BUCKET/PREFIX/" + raise SystemExit(err_msg) local_dir = Path(args[0]) bucket, prefix = split_s3_path(args[1]) prefix = prefix.rstrip("/") + "/" if prefix else "" @@ -622,7 +623,8 @@ def cmd_cp(args: list[str]) -> None: def cmd_ls(args: list[str]) -> None: """List objects: ``ls s3://bucket/prefix/ [--limit N]``.""" if not args: - raise SystemExit("Usage: s3_local.py ls s3://BUCKET/PREFIX/ [--limit N]") + err_msg = "Usage: s3_local.py ls s3://BUCKET/PREFIX/ [--limit N]" + raise SystemExit(err_msg) bucket, prefix = split_s3_path(args[0]) limit = 20 if "--limit" in args: @@ -642,7 +644,8 @@ def cmd_ls(args: list[str]) -> None: def cmd_head(args: list[str]) -> None: """Show metadata: ``head s3://bucket/key``.""" if not args: - raise SystemExit("Usage: s3_local.py head s3://BUCKET/KEY") + err_msg = "Usage: s3_local.py head s3://BUCKET/KEY" + raise SystemExit(err_msg) bucket, key = split_s3_path(args[0]) s3 = get_s3_client() meta = {} diff --git a/src/cdm_data_loaders/utils/xml_utils.py b/src/cdm_data_loaders/utils/xml_utils.py index 57d395d3..95f4d566 100644 --- a/src/cdm_data_loaders/utils/xml_utils.py +++ b/src/cdm_data_loaders/utils/xml_utils.py @@ -12,12 +12,13 @@ import gzip from collections.abc import Generator +from logging import Logger, getLogger from pathlib import Path from typing import Any from lxml.etree import Element, iterparse -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger +logger: Logger = getLogger(__name__) def get_text(elem: Element | None, default: str | None = None) -> str | None: @@ -136,7 +137,6 @@ def stream_xml_file(file_path: str | Path, element_with_ns: str) -> Generator[El :yield: elements from the file :rtype: Generator[Element, Any] """ - logger = get_cdm_logger() if isinstance(file_path, Path): file_path = str(file_path) logger.info("Streaming XML from %s", file_path) @@ -185,7 +185,7 @@ def parse_head_matter(fh, ns_dict) -> dict[str, str]: if ns[""] != ns_dict["ns"]: if ns[""] in ns_dict.values(): - get_cdm_logger().warning("xmlns set to '%s'", ns[""]) + logger.warning("xmlns set to '%s'", ns[""]) ns_dict["ns"] = ns[""] else: msg = f"Unexpected default namespace: got '{ns['']}', expected '{ns_dict['ns']}'" diff --git a/src/cdm_data_loaders/validation/dataframe_validator.py b/src/cdm_data_loaders/validation/dataframe_validator.py index 51b1e08a..5d4357b4 100644 --- a/src/cdm_data_loaders/validation/dataframe_validator.py +++ b/src/cdm_data_loaders/validation/dataframe_validator.py @@ -2,6 +2,7 @@ from collections.abc import Callable from dataclasses import dataclass +from logging import Logger, getLogger from typing import Any from pyspark.sql import DataFrame, SparkSession @@ -12,10 +13,9 @@ from cdm_data_loaders.audit.rejects import write_rejects from cdm_data_loaders.audit.schema import ROW_ERRORS from cdm_data_loaders.core.pipeline_run import PipelineRun -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.validation.validation_result import ValidationResult -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) @dataclass diff --git a/tests/conftest.py b/tests/conftest.py index fc3b3909..15ed18d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,11 @@ from collections.abc import Generator from copy import deepcopy from pathlib import Path -from typing import Any +from typing import Any, Final import pytest from berdl_notebook_utils.setup_spark_session import generate_spark_conf +from frozendict import frozendict from pyspark.conf import SparkConf from pyspark.sql import DataFrame, SparkSession from pyspark.sql.types import ( @@ -34,20 +35,17 @@ from cdm_data_loaders.readers.dsv import INVALID_DATA_FIELD from cdm_data_loaders.utils.cdm_logger import get_cdm_logger -SAVE_DIR = "spark.sql.warehouse.dir" +SAVE_DIR: Final[str] = "spark.sql.warehouse.dir" -TEST_NS = "test_ns" -PIPELINE_RUN = {RUN_ID: "1234-5678-90", PIPELINE: "KeystoneXL", SOURCE: "/path/to/file"} -ALT_PIPELINE_RUN = {RUN_ID: "9876-5432-10", PIPELINE: "KeystoneXXXL", SOURCE: "/path/to/dir"} +TEST_NS: Final[str] = "test_ns" +PIPELINE_RUN = frozendict({RUN_ID: "1234-5678-90", PIPELINE: "KeystoneXL", SOURCE: "/path/to/file"}) +ALT_PIPELINE_RUN = frozendict({RUN_ID: "9876-5432-10", PIPELINE: "KeystoneXXXL", SOURCE: "/path/to/dir"}) @pytest.fixture(autouse=True) def logging_setup(caplog: pytest.LogCaptureFixture) -> None: - """Fiddle with the loggers used in the tests for a better experience. - - N.b. this is overwritten by the conftest in the pipelines directory, which uses the dlt logger. - """ + """Fiddle with the loggers used in the tests for a better experience.""" vcr_logger = logging.getLogger("vcr") vcr_logger.setLevel("ERROR") # turn on log propagation for the dlt logger @@ -80,7 +78,7 @@ def spark(tmp_path: Path) -> Generator[SparkSession, Any]: spark = SparkSession.builder.config(conf=spark_conf).getOrCreate() save_dir = spark.conf.get(SAVE_DIR).removeprefix("file:") # pyright: ignore[reportOptionalMemberAccess] if save_dir != str(tmp_path): - get_cdm_logger().error(f"spark dir: {tmp_path}; save dir: {save_dir}") + logging.getLogger(__name__).error("spark dir: %s; save dir: %s", tmp_path, save_dir) yield spark spark.catalog.clearCache() spark.stop() diff --git a/tests/pipelines/conftest.py b/tests/pipelines/conftest.py index 4e891d1c..b1059f2c 100644 --- a/tests/pipelines/conftest.py +++ b/tests/pipelines/conftest.py @@ -1,9 +1,8 @@ """Shared fixtures for pipelines tests.""" -import logging from itertools import batched from pathlib import Path -from typing import Any +from typing import Any, Final from unittest.mock import MagicMock import dlt @@ -14,8 +13,8 @@ from cdm_data_loaders.pipelines import core from cdm_data_loaders.pipelines.all_the_bacteria import AtbSettings from cdm_data_loaders.pipelines.cts_defaults import ( + DEFAULT_BATCH_FILE_SETTINGS, DEFAULT_CTS_SETTINGS, - DEFAULT_START_AT, BatchedFileInputSettings, CtsSettings, ) @@ -25,10 +24,10 @@ CASSETTES_DIR = "tests/cassettes" -START_AT_VALUE = 50 -START_AT_STRING = "50" - -CONFIG_BUCKET = {"local_fs": "/output_dir", "s3": "s3://some/s3/bucket"} +START_AT_VALUE: Final[int] = 50 +START_AT_STRING: Final[str] = "50" +TEST_LOG_CONFIG_FILE: Final[str] = "log_conf.json" +CONFIG_BUCKET = frozendict({"local_fs": "/output_dir", "s3": "s3://some/s3/bucket"}) TEST_DLT_CONFIG = frozendict( { @@ -54,12 +53,14 @@ def _generate_dlt_config() -> dict[str, Any]: } -DESTINATION_TO_OUTPUT = { - "local_fs": TEST_DLT_CONFIG["destination.local_fs.bucket_url"], - "s3": TEST_DLT_CONFIG["destination.s3.bucket_url"], -} +DESTINATION_TO_OUTPUT = frozendict( + { + "local_fs": TEST_DLT_CONFIG["destination.local_fs.bucket_url"], + "s3": TEST_DLT_CONFIG["destination.s3.bucket_url"], + } +) -DESTINATION_OUTPUT = DESTINATION_TO_OUTPUT[DEFAULT_CTS_SETTINGS["use_destination"]] +DESTINATION_OUTPUT: Final[str] = DESTINATION_TO_OUTPUT[DEFAULT_CTS_SETTINGS["use_destination"]] DEFAULT_CTS_SETTINGS_RECONCILED = frozendict( { @@ -70,7 +71,7 @@ def _generate_dlt_config() -> dict[str, Any]: } ) -DEFAULT_BATCH_FILE_SETTINGS_RECONCILED = frozendict({**DEFAULT_CTS_SETTINGS_RECONCILED, "start_at": DEFAULT_START_AT}) +DEFAULT_BATCH_FILE_SETTINGS_RECONCILED = frozendict({**DEFAULT_BATCH_FILE_SETTINGS, **DEFAULT_CTS_SETTINGS_RECONCILED}) TEST_CTS_SETTINGS = frozendict( { @@ -123,7 +124,7 @@ def _generate_dlt_config() -> dict[str, Any]: def make_batcher(files: list[Path], batch_size: int = 5) -> MagicMock: - """Return a mock BatchCursor that yields ``files`` in batches then an empty list.""" + """Return a mock NumericFileSequenceBatcher that yields ``files`` in batches then an empty list.""" batches = [list(b) for b in batched(files, batch_size, strict=False)] mock_batcher = MagicMock() mock_batcher.get_batch.side_effect = [*batches, []] @@ -192,20 +193,20 @@ def mock_dlt(monkeypatch: pytest.MonkeyPatch, dlt_config: dict[str, Any]) -> Mag @pytest.fixture def patched_io(monkeypatch: pytest.MonkeyPatch) -> tuple[MagicMock, MagicMock]: - """Patch BatchCursor and stream_xml_file inside core; return (mock_batcher_cls, mock_stream).""" + """Patch NumericFileSequenceBatcher and stream_xml_file inside core; return (mock_batcher_cls, mock_stream).""" mock_batcher_cls = MagicMock() mock_stream = MagicMock(return_value=[]) - monkeypatch.setattr(core, "BatchCursor", mock_batcher_cls) + monkeypatch.setattr(core, "NumericFileSequenceBatcher", mock_batcher_cls) monkeypatch.setattr(core, "stream_xml_file", mock_stream) return mock_batcher_cls, mock_stream @pytest.fixture def patched_io_empty_batcher(monkeypatch: pytest.MonkeyPatch) -> tuple[MagicMock, MagicMock]: - """Like patched_io but BatchCursor immediately returns an empty batch.""" + """Like patched_io but NumericFileSequenceBatcher immediately returns an empty batch.""" mock_batcher_cls = MagicMock() mock_stream = MagicMock(return_value=[]) mock_batcher_cls.return_value = make_batcher([]) - monkeypatch.setattr(core, "BatchCursor", mock_batcher_cls) + monkeypatch.setattr(core, "NumericFileSequenceBatcher", mock_batcher_cls) monkeypatch.setattr(core, "stream_xml_file", mock_stream) return mock_batcher_cls, mock_stream diff --git a/tests/pipelines/test_core.py b/tests/pipelines/test_core.py index 0cc15b48..62b86778 100644 --- a/tests/pipelines/test_core.py +++ b/tests/pipelines/test_core.py @@ -22,7 +22,7 @@ sync_configs, ) from cdm_data_loaders.pipelines.cts_defaults import ( - DEFAULT_BATCH_SIZE, + DEFAULT_PIPELINE_BATCH_SIZE, VALID_DESTINATIONS, BatchedFileInputSettings, CtsSettings, @@ -273,7 +273,7 @@ def test_run_cli_reraises_settings_error( ({"destination": {}}, ValueError, "No valid destinations found in dlt configuration"), ], ) -def test_run_cli_reraises_validation_errors( # noqa: PLR0913 +def test_run_cli_reraises_validation_errors( settings_cls: type[CtsSettings], bad_dlt_config: None | dict[str, Any], error: type[Exception], @@ -542,7 +542,7 @@ def test_run_pipeline_dev_mode_absent_from_pipeline_kwargs_when_false( def test_stream_xml_resource_nonzero_start_at_passed_to_batcher( patched_io: tuple[MagicMock, MagicMock], start_at: int ) -> None: - """start_at > 0 is truthy and must be forwarded to BatchCursor.""" + """start_at > 0 is truthy and must be forwarded to NumericFileSequenceBatcher.""" mock_batcher_cls, _ = patched_io mock_batcher_cls.return_value = make_batcher([]) settings = make_batched_settings(input_dir="/i", start_at=start_at) @@ -601,7 +601,7 @@ def test_stream_xml_file_resource_empty_batch_yields_nothing( patched_io: tuple[MagicMock, MagicMock], caplog: pytest.LogCaptureFixture, ) -> None: - """No items yielded when BatchCursor returns an empty batch; BatchCursor receives correct args.""" + """No items yielded when NumericFileSequenceBatcher returns an empty batch; NumericFileSequenceBatcher receives correct args.""" mock_batcher = MagicMock() mock_batcher.get_batch.return_value = [] mock_batcher_cls, mock_stream = patched_io @@ -610,11 +610,13 @@ def test_stream_xml_file_resource_empty_batch_yields_nothing( results = list(stream_xml_file_resource(config, "xml_tag", MagicMock())) assert results == [] - expected_batcher_kwargs = {"batch_size": DEFAULT_BATCH_SIZE} - if config.start_at: - expected_batcher_kwargs["start_at"] = config.start_at + expected_batcher_kwargs = { + "directory": config.input_dir, + "batch_size": DEFAULT_PIPELINE_BATCH_SIZE, + "start_at": config.start_at, + } - assert mock_batcher_cls.call_args_list == [call(config.input_dir, **expected_batcher_kwargs)] + assert mock_batcher_cls.call_args_list == [call(**expected_batcher_kwargs)] mock_batcher.get_batch.assert_called_once() mock_stream.assert_not_called() assert caplog.records == [] diff --git a/tests/pipelines/test_cts_defaults.py b/tests/pipelines/test_cts_defaults.py index 26428a3d..ee3de206 100644 --- a/tests/pipelines/test_cts_defaults.py +++ b/tests/pipelines/test_cts_defaults.py @@ -8,11 +8,11 @@ from cdm_data_loaders.pipelines.cts_defaults import ( ARG_ALIASES, - DEFAULT_START_AT, VALID_DESTINATIONS, BatchedFileInputSettings, CtsSettings, ) +from cdm_data_loaders.utils.batcher import MIN_START_AT from tests.pipelines.conftest import ( DEFAULT_BATCH_FILE_SETTINGS_RECONCILED, DEFAULT_CTS_SETTINGS_RECONCILED, @@ -98,7 +98,7 @@ def test_settings_all_settings_specified( ) -> None: """Ensure the CTS settings are set up correctly.""" s = make_settings(settings_cls, dlt_config=dlt_config, **args) - check_settings(s, dict(expected)) + check_settings(s, expected) @pytest.mark.parametrize("settings_cls", SETTINGS_CLASSES) @@ -108,7 +108,7 @@ def test_cli_app_run_default_settings(settings_cls: type[CtsSettings], dlt_confi expected = ( DEFAULT_CTS_SETTINGS_RECONCILED if settings_cls == CtsSettings else DEFAULT_BATCH_FILE_SETTINGS_RECONCILED ) - check_settings(s, dict(expected)) + check_settings(s, expected) @pytest.mark.parametrize("settings_cls", SETTINGS_CLASSES) @@ -403,7 +403,7 @@ def test_settings_generate_pipeline_raw_data_dirs( "output": DESTINATION_TO_OUTPUT[use_destination] if output == "" else OUTPUT_PATHS[output][OUT], } if settings_cls == BatchedFileInputSettings: - expected["start_at"] = DEFAULT_START_AT + expected["start_at"] = MIN_START_AT if (OUTPUT_PATHS[expected["output"]][S3] and use_destination == "local_fs") or ( OUTPUT_PATHS[expected["output"]][S3] is False and use_destination == "s3" @@ -463,7 +463,7 @@ def test_cli_app_run_generate_pipeline_raw_data_dirs( "output": DESTINATION_TO_OUTPUT[use_destination] if output == "" else OUTPUT_PATHS[output][OUT], } if settings_cls == BatchedFileInputSettings: - expected["start_at"] = DEFAULT_START_AT + expected["start_at"] = MIN_START_AT if (OUTPUT_PATHS[expected["output"]][S3] and use_destination == "local_fs") or ( OUTPUT_PATHS[expected["output"]][S3] is False and use_destination == "s3" diff --git a/tests/pipelines/test_ncbi_rest_api.py b/tests/pipelines/test_ncbi_rest_api.py index 5a744fa4..bf9b0811 100644 --- a/tests/pipelines/test_ncbi_rest_api.py +++ b/tests/pipelines/test_ncbi_rest_api.py @@ -246,7 +246,7 @@ def test_settings_all_params_set(settings: frozendict, reconciled: frozendict) - "use_output_dir_for_pipeline_metadata", ARG_ALIASES["use_output_dir_for_pipeline_metadata"], ) -def test_cli_all_variants( # noqa: PLR0913 +def test_cli_all_variants( query_type: str, batch_size: str, dev_mode: str, diff --git a/tests/utils/test_file_system.py b/tests/utils/test_batcher.py similarity index 63% rename from tests/utils/test_file_system.py rename to tests/utils/test_batcher.py index ffd9ddd1..e47c8799 100644 --- a/tests/utils/test_file_system.py +++ b/tests/utils/test_batcher.py @@ -1,12 +1,20 @@ """Tests of file system-related utilities.""" +import logging from copy import deepcopy from pathlib import Path from typing import Any import pytest +from pydantic import ValidationError -from cdm_data_loaders.utils.file_system import FILE_NAME_REGEX, BatchCursor +from cdm_data_loaders.utils.batcher import ( + FILE_NAME_REGEX, + MIN_BATCH_SIZE, + MIN_END_AT, + MIN_START_AT, + NumericFileSequenceBatcher, +) # the maximum file number in the directory MAX_FILE_NUMBER = 15 @@ -46,11 +54,10 @@ EXPECTED[None] = EXPECTED[1] # batch_size greater than # of records EXPECTED[20] = EXPECTED[15] -# add in results for start_at == 0 or not specified +# add in results where start_at is not specified (i.e. uses the default value) for ix, vals in EXPECTED.items(): if ix is not None: EXPECTED[ix][None] = vals[1] - EXPECTED[ix][0] = vals[1] EXPECTED_END_AT = deepcopy(EXPECTED) @@ -87,58 +94,188 @@ def file_dir(tmp_path: Path) -> Path: return tmp_path -def test_batcher_defaults() -> None: +@pytest.fixture(scope="module") +def non_matching_file_names() -> list[str]: + """List of file names that do not match the pattern required by NumericFileSequenceBatcher.""" + return [ + # no numbers + "README.md", + # contains non-\w character + ".hidden_00001.txt", + # no extension + "data_00001", + # files in nested dirs -- will not be found + "nested/data_00010.txt", + "nested/dir1/data_00020.txt", + ] + + +@pytest.fixture +def mixed_dir(tmp_path: Path, non_matching_file_names: list[str]) -> Path: + """Directory containing valid files alongside files that should be ignored.""" + make_sequence(tmp_path, "data", "txt", range(1, 6)) + more_files = [ + "data_123.txt", + "data_000001.txt", + "data_000100.txt.gz", + "data_000200.txt.tar.gz", + "data_000400.csv.gz", + "file_000300.txt.tar.gz", + *non_matching_file_names, + ] + make_files(tmp_path, more_files) + return tmp_path + + +def test_init_batcher_defaults() -> None: + """Ensure defaults are set correctly.""" + bc = NumericFileSequenceBatcher(directory="path/to/dir") # type: ignore[reportArgumentType] + assert bc.batch_size == MIN_BATCH_SIZE + assert bc.start_at == MIN_START_AT + assert bc.end_at == MIN_END_AT + assert bc.file_regex == FILE_NAME_REGEX + assert bc.directory == Path("path/to/dir") + + +def test_init_batcher_values() -> None: """Ensure defaults are set correctly.""" - bc = BatchCursor(".") - assert bc.start_at == 1 - assert bc.batch_size == 1 - assert bc.end_at is None + start_at = 26 + batch_size = 10 + end_at = 26 + bc = NumericFileSequenceBatcher( + directory=Path("path/to/dir"), start_at=start_at, batch_size=batch_size, end_at=end_at + ) + assert bc.start_at == start_at + assert bc.batch_size == batch_size + assert bc.end_at == end_at assert bc.file_regex == FILE_NAME_REGEX + assert bc.directory == Path("path/to/dir") -def test_batcher_default_end_at() -> None: - """Ensure default end_at is set correctly.""" - bc = BatchCursor(".", end_at=0) - assert bc.end_at is None +def test_init_batcher_no_directory() -> None: + """Ensure the batcher throws an error if a directory is not specified.""" + with pytest.raises(ValidationError, match=r"directory\s+Field required") as err: + NumericFileSequenceBatcher() # type: ignore[reportCallIssue] + assert err.match(r"directory\s+Field required") -@pytest.mark.parametrize("batch_size", [None, 0, -1, 1.0, 1.2345678, -15, -1234567890, "something", "50", "None"]) -def test_invalid_batch_size(batch_size: float | str | None) -> None: - """Test invalid batch_size, start_at, end_at, and file_regex parameters.""" - with pytest.raises(ValueError, match="batch_size must be an integer, 1 or greater"): - BatchCursor(".", batch_size) # pyright: ignore[reportArgumentType] +VALID_INTEGER = r"\s+Input should be a valid integer" +GTE_ONE = r"\s+Input should be greater than or equal to 1" +GTE_ZERO = r"\s+Input should be greater than or equal to 0" -@pytest.mark.parametrize("start_at", [None, -1, 1.0, 1.2345678, -15, -1234567890, "something", "50", "None"]) -def test_invalid_start_at_params(start_at: float | None) -> None: - """Test invalid start_at parameters.""" - with pytest.raises(ValueError, match="start_at must be an integer, 0 or greater"): - BatchCursor(".", start_at=start_at) # pyright: ignore[reportArgumentType] +GTE_ONE_TEST_VALUES = [ + (None, VALID_INTEGER), + (0, GTE_ONE), + (-1, GTE_ONE), + (1.0, VALID_INTEGER), + (1.2345678, VALID_INTEGER), + (-15, GTE_ONE), + (-1234567890, GTE_ONE), + ("something", VALID_INTEGER), + ("50", VALID_INTEGER), + ("None", VALID_INTEGER), +] -@pytest.mark.parametrize("end_at", [None, -1, 1.0, 1.2345678, -15, -1234567890, "something", "50", "None"]) -def test_invalid_end_at_params(end_at: float | None) -> None: +@pytest.mark.parametrize(("batch_size", "err_msg"), GTE_ONE_TEST_VALUES) +def test_init_batcher_invalid_batch_size(batch_size: float | str | None, err_msg: str) -> None: + """Test invalid batch_size, start_at, end_at, and file_regex parameters.""" + with pytest.raises(ValidationError, match=f"batch_size{err_msg}"): + NumericFileSequenceBatcher(directory=".", batch_size=batch_size) # pyright: ignore[reportArgumentType] + + +@pytest.mark.parametrize(("start_at", "err_msg"), GTE_ONE_TEST_VALUES) +def test_init_batcher_invalid_start_at_params(start_at: float | None, err_msg: str) -> None: + """Test invalid start_at parameters.""" + with pytest.raises(ValidationError, match=f"start_at{err_msg}"): + NumericFileSequenceBatcher(directory=".", start_at=start_at) # pyright: ignore[reportArgumentType] + + +@pytest.mark.parametrize( + ("end_at", "err_msg"), + [ + (None, VALID_INTEGER), + (-1, GTE_ZERO), + (1.0, VALID_INTEGER), + (1.2345678, VALID_INTEGER), + (-15, GTE_ZERO), + (-1234567890, GTE_ZERO), + ("something", VALID_INTEGER), + ("50", VALID_INTEGER), + ("None", VALID_INTEGER), + ], +) +def test_init_batcher_invalid_end_at_params(end_at: str | float | None, err_msg: str) -> None: """Test invalid end_at parameters.""" - with pytest.raises(ValueError, match="end_at must be an integer, 1 or greater"): - BatchCursor(".", end_at=end_at) # pyright: ignore[reportArgumentType] + with pytest.raises(ValidationError, match=f"end_at{err_msg}"): + NumericFileSequenceBatcher(directory=".", end_at=end_at) # pyright: ignore[reportArgumentType] + + +@pytest.mark.parametrize( + ("batch_size", "batch_size_err_msg"), + [ + (None, VALID_INTEGER), + (0, GTE_ONE), + (-1, GTE_ONE), + (1.0, VALID_INTEGER), + ("50", VALID_INTEGER), + ], +) +@pytest.mark.parametrize( + ("start_at", "start_at_err_msg"), + [ + (None, VALID_INTEGER), + (0, GTE_ONE), + (1.0, VALID_INTEGER), + (-1234567890, GTE_ONE), + ("50", VALID_INTEGER), + ], +) +@pytest.mark.parametrize( + ("end_at", "end_at_err_msg"), + [ + (None, VALID_INTEGER), + (-1, GTE_ZERO), + (1.2345678, VALID_INTEGER), + (-1234567890, GTE_ZERO), + ("50", VALID_INTEGER), + ], +) +def test_init_batcher_multiple_errors( + batch_size: str | float | None, + start_at: str | float | None, + end_at: str | float | None, + batch_size_err_msg: str, + start_at_err_msg: str, + end_at_err_msg: str, +) -> None: + """Ensure that supplying the NumericFileSequenceBatcher with lots of dodgy values triggers an error.""" + with pytest.raises(ValidationError, match="3 validation errors for NumericFileSequenceBatcher") as exc: + NumericFileSequenceBatcher(directory=Path(), start_at=start_at, end_at=end_at, batch_size=batch_size) # type: ignore[reportArgumentType] + assert exc.match(f"start_at{start_at_err_msg}") + assert exc.match(f"end_at{end_at_err_msg}") + assert exc.match(f"batch_size{batch_size_err_msg}") -def test_invalid_start_vs_end_at_params() -> None: +@pytest.mark.parametrize(("start_at", "end_at"), [(5, 3), (123456789, 123456788)]) +def test_init_batcher_invalid_start_vs_end_at_params(start_at: int, end_at: int) -> None: """Ensure that an error is thrown if start_at and end_at are not compatible.""" - with pytest.raises(ValueError, match="end_at must be greater than start_at"): - BatchCursor(".", start_at=2, end_at=1) + with pytest.raises(ValidationError, match="end_at must be greater than start_at"): + NumericFileSequenceBatcher(directory="some/dir", start_at=start_at, end_at=end_at) # pyright: ignore[reportArgumentType] -def test_ok_start_end_at_params() -> None: +@pytest.mark.parametrize(("start_at", "end_at"), [(1, 0), (1, 1), (20, 20), (5, 0), (3, 7)]) +def test_init_batcher_valid_start_vs_end_at_params(start_at: int, end_at: int) -> None: """Ensure that 0 is a valid end_at parameter, regardless of start_at value.""" - bc = BatchCursor(".", start_at=5, end_at=0) - assert bc.end_at is None - assert bc.start_at == 5 # noqa: PLR2004 + bc = NumericFileSequenceBatcher(directory=".", start_at=start_at, end_at=end_at) # pyright: ignore[reportArgumentType] + assert bc.start_at == start_at + assert bc.end_at == end_at def test_end_at_greater_than_start_at_during_iteration() -> None: """Ensure that if end_at is smaller than start_at during iteration, an empty list is returned.""" - bc = BatchCursor(".", start_at=0, end_at=5) + bc = NumericFileSequenceBatcher(directory=".", start_at=MIN_START_AT, end_at=5) # pyright: ignore[reportArgumentType] assert bc.end_at == 5 # noqa: PLR2004 bc.start_at = 10 assert bc.get_batch() == [] @@ -149,7 +286,7 @@ def test_end_at_greater_than_start_at_during_iteration() -> None: # your basic batch @pytest.mark.parametrize("end_at", [None, 0, CUTOFF_VALUE]) -@pytest.mark.parametrize("start_at", [None, 0, 1, 6, 8, 11]) +@pytest.mark.parametrize("start_at", [None, 1, 6, 8, 11]) @pytest.mark.parametrize("batch_size", EXPECTED.keys()) def test_get_batch_parametrized( file_dir: Path, @@ -166,7 +303,7 @@ def test_get_batch_parametrized( if end_at is not None: cursor_params["end_at"] = end_at - cursor = BatchCursor(file_dir, **cursor_params) + cursor = NumericFileSequenceBatcher(directory=file_dir, **cursor_params) # generate the expected files expected_files: list[list[Path]] = [ @@ -202,14 +339,14 @@ def test_get_batch_parametrized( def test_get_batch_default_start_at_is_zero(file_dir: Path) -> None: """Ensure that the default start_at is 0.""" - cursor_default = BatchCursor(file_dir, batch_size=3) - cursor_explicit = BatchCursor(file_dir, batch_size=3, start_at=0) + cursor_default = NumericFileSequenceBatcher(directory=file_dir, batch_size=3) + cursor_explicit = NumericFileSequenceBatcher(directory=file_dir, batch_size=3, start_at=MIN_START_AT) assert cursor_default.get_batch() == cursor_explicit.get_batch() def test_get_batch_start_at_matches_sequence_number(file_dir: Path) -> None: """Ensure start_at value matches sequence number.""" - cursor = BatchCursor(file_dir, batch_size=5, start_at=15) + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=5, start_at=15) result = cursor.get_batch() assert len(result) == 1 assert result[0].name == "report_00015.csv" @@ -219,8 +356,8 @@ def test_get_batch_start_at_matches_sequence_number(file_dir: Path) -> None: def test_get_batch_start_at_advances_after_get_batch(file_dir: Path) -> None: """Ensure that the start_at value changes after each successful get_batch operation.""" batch_size = 5 - cursor = BatchCursor(file_dir, batch_size=batch_size, start_at=0) - assert cursor.start_at == 0 + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=batch_size, start_at=MIN_START_AT) + assert cursor.start_at == MIN_START_AT batch_1 = cursor.get_batch() assert cursor.start_at == batch_size + 1 # next file is report_00006.csv batch_2 = cursor.get_batch() @@ -238,7 +375,7 @@ def test_get_batch_start_at_advances_after_get_batch(file_dir: Path) -> None: def test_get_batch_cursor_does_not_advance_on_empty_result(file_dir: Path) -> None: """Ensure that the cursor does not advance if the batch is empty.""" start_at = 999 - cursor = BatchCursor(file_dir, batch_size=5, start_at=start_at) + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=5, start_at=start_at) cursor.get_batch() assert cursor.start_at == start_at @@ -246,7 +383,7 @@ def test_get_batch_cursor_does_not_advance_on_empty_result(file_dir: Path) -> No def test_get_batch_partial_batch_advances_correctly(file_dir: Path) -> None: """Ensure that the cursor only advances as far as the last file in the batch.""" # Only 3 files remain from 13 onward - cursor = BatchCursor(file_dir, batch_size=5, start_at=13) + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=5, start_at=13) result = cursor.get_batch() assert result == [file_dir / f"report_{n:05}.csv" for n in [13, 14, 15]] assert cursor.start_at == 16 # noqa: PLR2004 @@ -255,11 +392,11 @@ def test_get_batch_partial_batch_advances_correctly(file_dir: Path) -> None: def test_get_batch_cursor_can_be_reset(file_dir: Path) -> None: """Ensure that the cursor can be reset.""" batch_size = 5 - cursor = BatchCursor(file_dir, batch_size=batch_size) + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=batch_size) original_result = cursor.get_batch() assert cursor.start_at == batch_size + 1 # set cursor to 0 - cursor.start_at = 0 + cursor.start_at = 1 reset_result = cursor.get_batch() assert cursor.start_at == batch_size + 1 assert original_result == reset_result @@ -269,19 +406,19 @@ def test_get_batch_cursor_can_be_reset(file_dir: Path) -> None: # Edge cases -- boundaries def test_get_batch_start_at_beyond_end_returns_empty_list(file_dir: Path) -> None: """Ensure that nothing is returned if start_at is too high.""" - cursor = BatchCursor(file_dir, batch_size=5, start_at=999) + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=5, start_at=999) assert cursor.get_batch() == [] def test_get_batch_empty_directory_returns_empty_list(tmp_path: Path) -> None: """Ensure that an empty dir returns nothing.""" - cursor = BatchCursor(tmp_path, batch_size=5) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=5) assert cursor.get_batch() == [] def test_get_batch_batch_size_larger_than_remaining_files(file_dir: Path) -> None: """Ensure that batches are sized correctly for partial batches.""" - cursor = BatchCursor(file_dir, batch_size=10, start_at=10) + cursor = NumericFileSequenceBatcher(directory=file_dir, batch_size=10, start_at=10) result = cursor.get_batch() # should have 00010 - 00015 assert result[-1].name == "report_00015.csv" @@ -294,7 +431,7 @@ def test_get_batch_start_at_skips_to_next_available_when_gap(tmp_path: Path) -> """Ensure that gaps in the sequence are dealt with correctly.""" # Files exist for 1,2,3 then jump to 10,11,12 — no 4-9 make_sequence(tmp_path, "data", "csv.gz", [1, 2, 3, 10, 11, 12]) - cursor = BatchCursor(tmp_path, batch_size=5, start_at=5) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=5, start_at=5) # retrieve 5 files, starting at 00005 assert cursor.get_batch() == [tmp_path / f"data_{n:05}.csv.gz" for n in [10, 11, 12]] assert cursor.start_at == 13 # noqa: PLR2004 @@ -304,7 +441,7 @@ def test_get_batch_start_at_skips_to_next_available_when_gap(tmp_path: Path) -> def test__get_batchsequential_calls_across_gap(tmp_path: Path) -> None: """Ensure that files are correctly retrieved across gaps in the sequence.""" make_sequence(tmp_path, "data", "csv.gz", [1, 2, 3, 10, 11, 12]) - cursor = BatchCursor(tmp_path, batch_size=2) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=2) assert cursor.get_batch() == [tmp_path / f"data_{n:05}.csv.gz" for n in [1, 2]] assert cursor.start_at == 3 # noqa: PLR2004 @@ -321,7 +458,7 @@ def test__get_batchsequential_calls_across_gap(tmp_path: Path) -> None: def test_get_batch_sequential_calls_across_gap_with_end_at(tmp_path: Path) -> None: """Ensure that files are correctly retrieved across gaps in the sequence when end_at is specified.""" make_sequence(tmp_path, "data", "csv.gz", [1, 2, 3, 5, 8, 11, 15]) - cursor = BatchCursor(tmp_path, batch_size=2, end_at=10) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=2, end_at=10) assert cursor.get_batch() == [tmp_path / f"data_{n:05}.csv.gz" for n in [1, 2]] assert cursor.start_at == 3 # noqa: PLR2004 @@ -335,35 +472,10 @@ def test_get_batch_sequential_calls_across_gap_with_end_at(tmp_path: Path) -> No assert cursor.start_at == 9 # noqa: PLR2004 -@pytest.fixture -def mixed_dir(tmp_path: Path) -> Path: - """Directory containing valid files alongside files that should be ignored.""" - make_sequence(tmp_path, "data", "txt", range(1, 6)) - more_files = [ - "data_123.txt", - "data_000001.txt", - "data_000100.txt.gz", - "data_000200.txt.tar.gz", - "data_000400.csv.gz", - "file_000300.txt.tar.gz", - # no numbers - "README.md", - # contains non-\w character - ".hidden_00001.txt", - # no extension - "data_00001", - # files in nested dirs -- will not be found - "nested/data_00010.txt", - "nested/dir1/data_00020.txt", - ] - make_files(tmp_path, more_files) - return tmp_path - - # File-name pattern filtering def test_get_batch_ignores_invalid_filenames(mixed_dir: Path) -> None: """Ensure that filenames are matched correctly.""" - cursor = BatchCursor(mixed_dir, batch_size=20) + cursor = NumericFileSequenceBatcher(directory=mixed_dir, batch_size=20) generated_file_names = [f"data_{n:05}.txt" for n in range(1, 6)] file_names = sorted( [ @@ -383,21 +495,33 @@ def test_get_batch_mixed_extensions_sorted_correctly(tmp_path: Path) -> None: """Ensure that files with a mix of extensions are sorted numerially.""" names = ["data_00001.csv", "data_00001.tar.gz", "data_00002.tar.gz", "data_00003.txt"] make_files(tmp_path, names) - cursor = BatchCursor(tmp_path, batch_size=10) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=10) assert [p.name for p in cursor.get_batch()] == names +def test_get_batch_exits_early_with_no_matching_files( + tmp_path: Path, non_matching_file_names: list[str], caplog +) -> None: + """Ensure that the batcher exits from get_batch early if there are no files that match the file_regex.""" + make_files(tmp_path, non_matching_file_names) + cursor = NumericFileSequenceBatcher(directory=tmp_path) + assert cursor.get_batch() == [] + assert len(caplog.records) == 1 + assert caplog.records[-1].levelno == logging.WARNING + assert caplog.records[-1].message == f"No matching files found in {tmp_path!s}" + + # Dynamic / live-directory behaviour def test_get_batch_picks_up_newly_added_files(tmp_path: Path) -> None: """Ensure that adding files to a dir during batching picks up new files correctly.""" # dir contains log_00001.txt -> log_00003.txt make_sequence(tmp_path, "log", "txt", range(1, 4)) - cursor = BatchCursor(tmp_path, batch_size=10) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=10) assert cursor.get_batch() == [tmp_path / f"log_{n:05}.txt" for n in [1, 2, 3]] (tmp_path / "log_00004.txt").touch() # Reset cursor to re-scan from the start - cursor.start_at = 0 + cursor.start_at = MIN_START_AT assert cursor.get_batch() == [tmp_path / f"log_{n:05}.txt" for n in [1, 2, 3, 4]] @@ -405,7 +529,7 @@ def test_get_batch_new_files_within_current_window_are_included(tmp_path: Path) """Ensure that all new files within the current batching params are included, regardless of sequence position.""" # dir contains log_00001.txt -> log_00005.txt make_sequence(tmp_path, "log", "txt", range(1, 6)) - cursor = BatchCursor(tmp_path, batch_size=3, end_at=13) + cursor = NumericFileSequenceBatcher(directory=tmp_path, batch_size=3, end_at=13) assert cursor.get_batch() == [tmp_path / f"log_{n:05}.txt" for n in [1, 2, 3]] assert cursor.start_at == 4 # noqa: PLR2004 diff --git a/tests/utils/test_gz.py b/tests/utils/test_gz.py index 0f8acda8..6674cb10 100644 --- a/tests/utils/test_gz.py +++ b/tests/utils/test_gz.py @@ -144,14 +144,18 @@ def test_compress_files_invalid_directory(tmp_path: Path) -> None: compress_files(non_existent, "*") -def test_decompress_file_creates_file(temporary_gz: Path, caplog: pytest.LogCaptureFixture) -> None: +@pytest.mark.parametrize("input_type", [str, Path]) +def test_decompress_file_creates_file( + temporary_gz: Path, caplog: pytest.LogCaptureFixture, input_type: type[str] | type[Path] +) -> None: """Test that running decompress file actually decompresses a file.""" # Ensure no decompressed file exists initially decompressed_file = temporary_gz.with_suffix("") assert not decompressed_file.exists() assert temporary_gz.exists() - decompress_file(temporary_gz) + # cast to either string or Path + decompress_file(input_type(temporary_gz)) # Verify the decompressed file was created assert decompressed_file.exists() From 8900c3beac8a80ed57fbaa19829b6b0e938d47ea Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Tue, 16 Jun 2026 14:56:37 -0700 Subject: [PATCH 04/12] Test fixes; remove a couple of unneeded ruff directives --- .../parsers/uniprot/idmapping.py | 16 ++++++++-------- .../utils/download/sync_client.py | 4 ++-- tests/audit/test_metrics.py | 2 +- tests/audit/test_rejects.py | 4 ++-- tests/integration/conftest.py | 2 +- tests/pipelines/test_all_the_bacteria.py | 2 +- tests/pipelines/test_core.py | 6 +++--- tests/pipelines/test_cts_defaults.py | 2 +- tests/pipelines/test_uniprot.py | 10 +++------- tests/pipelines/test_uniref.py | 12 ++++-------- tests/readers/test_dsv.py | 2 +- tests/readers/test_dsv_read_with_validation.py | 2 +- tests/utils/download/test_clients.py | 4 ++-- tests/validation/test_dataframe_validator.py | 2 +- 14 files changed, 31 insertions(+), 39 deletions(-) diff --git a/src/cdm_data_loaders/parsers/uniprot/idmapping.py b/src/cdm_data_loaders/parsers/uniprot/idmapping.py index 3c888a51..7ebd09b4 100644 --- a/src/cdm_data_loaders/parsers/uniprot/idmapping.py +++ b/src/cdm_data_loaders/parsers/uniprot/idmapping.py @@ -24,6 +24,7 @@ """ import datetime +from logging import Logger, getLogger from uuid import uuid4 import click @@ -34,7 +35,6 @@ from cdm_data_loaders.core.constants import CDM_LAKE_S3, INVALID_DATA_FIELD_NAME from cdm_data_loaders.core.pipeline_run import PipelineRun from cdm_data_loaders.readers.dsv import read -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger from cdm_data_loaders.utils.s3 import list_matching_objects from cdm_data_loaders.utils.spark_delta import APPEND, set_up_workspace, write_delta from cdm_data_loaders.validation.dataframe_validator import DataFrameValidator, Validator @@ -48,7 +48,7 @@ COLUMNS = [ID, DB, XREF] -logger = get_cdm_logger() +logger: Logger = getLogger(__name__) ID_MAPPING_SCHEMA = [StructField(n, StringType(), nullable=False) for n in COLUMNS] @@ -100,8 +100,8 @@ def read_and_write(spark: SparkSession, pipeline_run: PipelineRun, id_mapping_ts :param spark: spark sesh :type spark: SparkSession - :param delta_ns: namespace to write to - :type delta_ns: str + :param pipeline_run: ID of the pipeline run + :type pipeline_run: PipelineRun :param id_mapping_tsv: path to the ID mapping file :type id_mapping_tsv: str :param mode: write mode (append or overwrite) @@ -123,7 +123,7 @@ def read_and_write(spark: SparkSession, pipeline_run: PipelineRun, id_mapping_ts "--namespace", default="uniprot", show_default=True, - help="Delta Lake database name", + help="Database name", ) @click.option( "--tenant-name", @@ -135,12 +135,12 @@ def cli(source: str, namespace: str, tenant_name: str | None) -> None: :param source: full path to the source directory containing ID mapping file(s) :type source: str - :param namespace: Delta Lake database name + :param namespace: Database name :type namespace: str :param tenant_name: Tenant warehouse to save processed data to; defaults to saving data to the user warehouse if a tenant is not specified :type tenant_name: str | None """ - (spark, delta_ns) = set_up_workspace(APP_NAME, namespace, tenant_name) + (spark, database_name) = set_up_workspace(APP_NAME, namespace, tenant_name) # TODO: other locations / local files? bucket_list = list_matching_objects(source) @@ -148,7 +148,7 @@ def cli(source: str, namespace: str, tenant_name: str | None) -> None: # file names are in the 'Key' value # 'tenant-general-warehouse/kbase/datasets/uniprot/id_mapping/id_mapping_part_001.tsv.gz' file_path = f"{CDM_LAKE_S3}/{file['Key']}" - pipeline_run = PipelineRun(str(uuid4()), APP_NAME, file_path, delta_ns) + pipeline_run = PipelineRun(str(uuid4()), APP_NAME, file_path, database_name) logger.info("Reading in mappings from %s", file_path) read_and_write(spark, pipeline_run, file_path) diff --git a/src/cdm_data_loaders/utils/download/sync_client.py b/src/cdm_data_loaders/utils/download/sync_client.py index 96a64629..5589be80 100644 --- a/src/cdm_data_loaders/utils/download/sync_client.py +++ b/src/cdm_data_loaders/utils/download/sync_client.py @@ -19,7 +19,7 @@ """ -from logging import Logger, getLogger +from logging import WARNING, Logger, getLogger from pathlib import Path from typing import Any @@ -96,7 +96,7 @@ def __init__( max=max_backoff, ), reraise=True, - before_sleep=before_sleep_log(logger, logging.WARNING), + before_sleep=before_sleep_log(logger, WARNING), ) def download( diff --git a/tests/audit/test_metrics.py b/tests/audit/test_metrics.py index 2a92ac32..842a5a28 100644 --- a/tests/audit/test_metrics.py +++ b/tests/audit/test_metrics.py @@ -33,7 +33,7 @@ @pytest.mark.parametrize("pipeline", [PIPELINE_RUN[PIPELINE], "pipe_2"]) @pytest.mark.parametrize("source", [PIPELINE_RUN[SOURCE], "/src/two"]) @pytest.mark.parametrize("add_default_data", [True, False]) -def test_write_metrics( # noqa: PLR0913 +def test_write_metrics( spark: SparkSession, pipeline: str, source: str, diff --git a/tests/audit/test_rejects.py b/tests/audit/test_rejects.py index 5c66468b..95a2e705 100644 --- a/tests/audit/test_rejects.py +++ b/tests/audit/test_rejects.py @@ -29,7 +29,7 @@ @pytest.mark.requires_spark -def test_write_rejects( # noqa: PLR0913 +def test_write_rejects( spark: SparkSession, pipeline_run: PipelineRun, caplog: pytest.LogCaptureFixture, @@ -86,7 +86,7 @@ def test_write_rejects( # noqa: PLR0913 @pytest.mark.requires_spark -def test_write_rejects_no_rejects( # noqa: PLR0913 +def test_write_rejects_no_rejects( spark: SparkSession, pipeline_run: PipelineRun, caplog: pytest.LogCaptureFixture, diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 34377a44..adcfd3a5 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -196,7 +196,7 @@ def stage_files_to_ceph( return keys -def seed_lakehouse( # noqa: PLR0913 +def seed_lakehouse( s3: botocore.client.BaseClient, bucket: PurePosixPath, accession: str, diff --git a/tests/pipelines/test_all_the_bacteria.py b/tests/pipelines/test_all_the_bacteria.py index 06c5f458..e52e7072 100644 --- a/tests/pipelines/test_all_the_bacteria.py +++ b/tests/pipelines/test_all_the_bacteria.py @@ -490,7 +490,7 @@ def test_osf_file_downloader_success( ], ) @pytest.mark.parametrize("use_destination", VALID_DESTINATIONS) -def test_osf_file_downloader_error_handling( # noqa: PLR0913 +def test_osf_file_downloader_error_handling( atb_file_list: list[dict[str, Any]], expected_exceptions: list[str], expected_paths: dict[str, bool], diff --git a/tests/pipelines/test_core.py b/tests/pipelines/test_core.py index 62b86778..34945dc1 100644 --- a/tests/pipelines/test_core.py +++ b/tests/pipelines/test_core.py @@ -76,7 +76,7 @@ def config(request: pytest.FixtureRequest) -> BatchedFileInputSettings: return make_batched_settings(**request.param) -def assert_pipeline_run_correctly( # noqa: PLR0913 +def assert_pipeline_run_correctly( mock_dlt: MagicMock, fake_resource: MagicMock, destination: str, @@ -454,7 +454,7 @@ def test_run_pipeline_graceful_fail( @pytest.mark.parametrize("slack_configured", [True, False]) @pytest.mark.parametrize("success", [True, False]) -def test_run_pipeline_slack_configured( # noqa: PLR0913 +def test_run_pipeline_slack_configured( test_bfi_settings: BatchedFileInputSettings, mock_dlt: MagicMock, mock_send_slack_message: MagicMock, @@ -691,7 +691,7 @@ def test_stream_xml_file_resource_processes_all_files_across_batches( assert caplog.messages == [f"Reading from {f!s}" for f in fake_files] -def test_stream_xml_file_resource_multiple_batches_with_output( # noqa: PLR0913 +def test_stream_xml_file_resource_multiple_batches_with_output( mock_dlt: MagicMock, patched_io: tuple[MagicMock, MagicMock], fake_files: list[Path], diff --git a/tests/pipelines/test_cts_defaults.py b/tests/pipelines/test_cts_defaults.py index ee3de206..c5439e96 100644 --- a/tests/pipelines/test_cts_defaults.py +++ b/tests/pipelines/test_cts_defaults.py @@ -294,7 +294,7 @@ def test_settings_trailing_slash_stripped( "use_output_dir_for_pipeline_metadata", ARG_ALIASES["use_output_dir_for_pipeline_metadata"], ) -def test_cli_app_run_alt_settings( # noqa: PLR0913 +def test_cli_app_run_alt_settings( settings_cls: type[CtsSettings], dev_mode: str, input_dir: str, diff --git a/tests/pipelines/test_uniprot.py b/tests/pipelines/test_uniprot.py index 56e3028b..452df6e5 100644 --- a/tests/pipelines/test_uniprot.py +++ b/tests/pipelines/test_uniprot.py @@ -71,7 +71,7 @@ def test_make_settings_all_params_set() -> None: "use_output_dir_for_pipeline_metadata", ARG_ALIASES["use_output_dir_for_pipeline_metadata"], ) -def test_cli_app_run_alt_settings( # noqa: PLR0913 +def test_cli_app_run_alt_settings( dev_mode: str, input_dir: str, output: str, @@ -136,9 +136,8 @@ def test_run_uniprot_pipeline_args_set_correctly(test_settings: UniProtSettings) assert mock_run_pipeline.call_count == 1 _, kwargs = mock_run_pipeline.call_args - assert kwargs.keys() == {"settings", "resource", "pipeline_kwargs", "pipeline_run_kwargs"} + assert kwargs.keys() == {"settings", "resource", "pipeline_kwargs"} assert kwargs["pipeline_kwargs"] == {"pipeline_name": "uniprot_kb", "dataset_name": "uniprot_kb"} - assert kwargs["pipeline_run_kwargs"] == {"table_format": "delta"} assert kwargs["settings"] == test_settings assert isinstance(kwargs["resource"], Callable) @@ -164,10 +163,7 @@ def test_run_uniprot_pipeline_sets_core_run_pipeline_args_correctly( pipeline_name="uniprot_kb", dataset_name="uniprot_kb", ) - mock_dlt.pipeline.return_value.run.assert_called_once_with( - expected_resource, - table_format="delta", - ) + mock_dlt.pipeline.return_value.run.assert_called_once_with(expected_resource) def test_parse_uniprot_resource(test_settings: UniProtSettings) -> None: diff --git a/tests/pipelines/test_uniref.py b/tests/pipelines/test_uniref.py index fe9deb68..f1998fa3 100644 --- a/tests/pipelines/test_uniref.py +++ b/tests/pipelines/test_uniref.py @@ -166,7 +166,7 @@ def test_make_settings_all_params_set() -> None: "use_output_dir_for_pipeline_metadata", ARG_ALIASES["use_output_dir_for_pipeline_metadata"], ) -def test_cli_app_run_alt_settings( # noqa: PLR0913 +def test_cli_app_run_alt_settings( dev_mode: str, input_dir: str, output: str, @@ -233,12 +233,11 @@ def test_run_uniref_pipeline_args_set_correctly(test_settings: UnirefSettings) - assert mock_run_pipeline.call_count == 1 _, kwargs = mock_run_pipeline.call_args - assert kwargs.keys() == {"settings", "resource", "pipeline_kwargs", "pipeline_run_kwargs"} + assert kwargs.keys() == {"settings", "resource", "pipeline_kwargs"} assert kwargs["pipeline_kwargs"] == { "pipeline_name": f"uniref_{test_settings.uniref_variant}", "dataset_name": "uniprot_kb", } - assert kwargs["pipeline_run_kwargs"] == {"table_format": "delta"} assert kwargs["settings"] == test_settings assert isinstance(kwargs["resource"], Callable) @@ -264,10 +263,7 @@ def test_run_uniref_pipeline_sets_core_run_pipeline_args_correctly( pipeline_name=f"uniref_{test_settings.uniref_variant}", dataset_name="uniprot_kb", ) - mock_dlt.pipeline.return_value.run.assert_called_once_with( - expected_resource, - table_format="delta", - ) + mock_dlt.pipeline.return_value.run.assert_called_once_with(expected_resource) def test_parse_uniref_resource(test_settings: UnirefSettings) -> None: @@ -320,7 +316,7 @@ def fake_pipeline_run(resource, **kwargs) -> list: # noqa: ANN001, ANN003 assert isinstance(resource, Callable) # after draining the generator, resource should be a list # the resource should be the output of parse_uniref assert resource.name == "parse_uniref" - assert kwargs == {"table_format": "delta"} + assert kwargs == {} return list(resource) mock_stream.side_effect = fake_stream_xml_file diff --git a/tests/readers/test_dsv.py b/tests/readers/test_dsv.py index 07d9814a..dd1f5c34 100644 --- a/tests/readers/test_dsv.py +++ b/tests/readers/test_dsv.py @@ -92,7 +92,7 @@ def test_read_tsv_csv(spark: SparkSession, csv_schema: list[StructField], all_li @pytest.mark.requires_spark @pytest.mark.parametrize("mode", INGEST_MODES) @pytest.mark.parametrize("csv_lines", [VALID, MISSING_REQUIRED, TYPE_MISMATCH, TOO_FEW_COLS, TOO_MANY_COLS, ALL_LINES]) -def test_csv_read_modes( # noqa: PLR0913 +def test_csv_read_modes( spark: SparkSession, mode: str, csv_lines: str, diff --git a/tests/readers/test_dsv_read_with_validation.py b/tests/readers/test_dsv_read_with_validation.py index 32cf9372..c69c1928 100644 --- a/tests/readers/test_dsv_read_with_validation.py +++ b/tests/readers/test_dsv_read_with_validation.py @@ -69,7 +69,7 @@ def read_with_validation( @pytest.mark.requires_spark @pytest.mark.parametrize("mode", INGEST_MODES) @pytest.mark.parametrize("csv_lines", [VALID, MISSING_REQUIRED, TYPE_MISMATCH, TOO_FEW_COLS, TOO_MANY_COLS, ALL_LINES]) -def test_csv_read_with_validation_errors( # noqa: PLR0913 +def test_csv_read_with_validation_errors( spark: SparkSession, mode: str, csv_lines: str, diff --git a/tests/utils/download/test_clients.py b/tests/utils/download/test_clients.py index 0bc8120b..4cb7e473 100644 --- a/tests/utils/download/test_clients.py +++ b/tests/utils/download/test_clients.py @@ -88,7 +88,7 @@ def handler(_: httpx.Request) -> httpx.Response: ], ) @pytest.mark.asyncio -async def test_download_validate_response( # noqa: PLR0913 +async def test_download_validate_response( status: int, status_text: str | None, error: type[Exception] | None, @@ -243,7 +243,7 @@ def handler(_: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("max_attempts", range(1, 5)) @pytest.mark.parametrize("response_type", ["error", "timeout"]) @pytest.mark.asyncio -async def test_timeout_and_server_error_retries( # noqa: PLR0913 +async def test_timeout_and_server_error_retries( tmp_path: Path, sample_content: bytes, max_attempts: int, diff --git a/tests/validation/test_dataframe_validator.py b/tests/validation/test_dataframe_validator.py index 925ca22d..ff8a5245 100644 --- a/tests/validation/test_dataframe_validator.py +++ b/tests/validation/test_dataframe_validator.py @@ -38,7 +38,7 @@ def test_validate_dataframe_empty_df(pipeline_run: PipelineRun, empty_df: DataFr @pytest.mark.requires_spark -def test_validate_dataframe_no_validation( # noqa: PLR0913 +def test_validate_dataframe_no_validation( spark: SparkSession, csv_schema: list[StructField], annotated_df_data: list[dict[str, Any]], From 96afbfb2193668930ef9cadfb4b81d2c7cf85d8a Mon Sep 17 00:00:00 2001 From: i alarmed alien Date: Wed, 17 Jun 2026 06:11:51 -0700 Subject: [PATCH 05/12] Apply suggestions from code review Co-authored-by: i alarmed alien Co-authored-by: Matt Dawson --- src/cdm_data_loaders/pipelines/all_the_bacteria.py | 7 +++---- src/cdm_data_loaders/pipelines/core.py | 5 +++-- src/cdm_data_loaders/pipelines/ncbi_ftp_download.py | 6 ++++-- src/cdm_data_loaders/pipelines/ncbi_rest_api.py | 7 +++++-- src/cdm_data_loaders/utils/batcher.py | 2 +- tests/utils/test_batcher.py | 6 +++--- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/cdm_data_loaders/pipelines/all_the_bacteria.py b/src/cdm_data_loaders/pipelines/all_the_bacteria.py index 671dc1e1..8f82817d 100644 --- a/src/cdm_data_loaders/pipelines/all_the_bacteria.py +++ b/src/cdm_data_loaders/pipelines/all_the_bacteria.py @@ -33,6 +33,8 @@ from cdm_data_loaders.utils.download.sync_client import FileDownloader from cdm_data_loaders.utils.s3 import stream_to_s3 +logger = logging.getLogger(__name__) + DATASET_NAME = "all_the_bacteria" ALL_FILES_TSV_FILE_ID = "R6gcp" ALL_ATB_FILE_NAME = "all_atb_files.tsv" @@ -116,7 +118,7 @@ def load_patterns(pattern_file: Path) -> re.Pattern | None: if patterns: return re.compile("^(" + "|".join(patterns) + ")$") except Exception: - logging.getLogger(__name__).exception("Could not load patterns from %s", str(pattern_file)) + logger.exception("Could not load patterns from %s", str(pattern_file)) return None @@ -130,7 +132,6 @@ def download_atb_index_tsv(settings: AtbSettings) -> Path: :return: path to the downloaded file :rtype: Path """ - logger = logging.getLogger(__name__) # get the all_atb_files.tsv file info from the OSF API and retrieve the download link osf_client = RESTClient( base_url="https://api.osf.io/v2/", @@ -181,7 +182,6 @@ def get_file_download_links(settings: AtbSettings, atb_files_tsv: Path) -> Gener :yield: list of fields to download :rtype: Generator[list[dict[str, Any]], Any] """ - logger = logging.getLogger(__name__) pattern_to_match = settings.pattern_matches with atb_files_tsv.open() as index_file: reader = csv.DictReader(index_file, delimiter="\t") @@ -212,7 +212,6 @@ def osf_file_downloader(settings: AtbSettings, atb_file_list: list[dict[str, Any :param atb_file_list: info about files to transfer, as a list of dictionaries :type atb_file_list: list[dict[str, Any]] """ - logger = logging.getLogger(__name__) client = FileDownloader() successful_downloads = [] for f in atb_file_list: diff --git a/src/cdm_data_loaders/pipelines/core.py b/src/cdm_data_loaders/pipelines/core.py index 23e9638e..3b6420e3 100644 --- a/src/cdm_data_loaders/pipelines/core.py +++ b/src/cdm_data_loaders/pipelines/core.py @@ -16,6 +16,8 @@ from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher from cdm_data_loaders.utils.xml_utils import stream_xml_file +logger = logging.getLogger(__name__) + def construct_env_var() -> None: """Use environment variables to construct a new environment variable.""" @@ -36,7 +38,6 @@ def sync_configs(settings: CtsSettings, dlt_config: Any) -> None: def dump_settings(settings: CtsSettings) -> None: """Dump the pipeline settings to the logger.""" settings_minus_dlt_config = settings.model_dump(exclude={"dlt_config"}) - logger: logging.Logger = logging.getLogger(__name__) logger.info("Pipeline settings:") logger.info(settings_minus_dlt_config) @@ -57,7 +58,7 @@ def run_cli(settings_cls: type[CtsSettings], pipeline_fn: Callable[[Any], None]) logging.getLogger(__name__).exception("Error initialising config") raise except Exception: - logging.getLogger(__name__).exception("Unexpected error setting up config") + logger.exception("Unexpected error setting up config") raise dump_settings(settings) diff --git a/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py b/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py index ea4054c4..0de5efa3 100644 --- a/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py +++ b/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py @@ -33,6 +33,9 @@ from cdm_data_loaders.utils.ftp_client import ThreadLocalFTP from cdm_data_loaders.utils.s3 import get_s3_client, upload_file +logger: Logger = getLogger(__name__) + + DEFAULT_STAGING_KEY_PREFIX: PurePosixPath = PurePosixPath("staging") @@ -102,7 +105,7 @@ def _upload_assembly_dir( if upload_file(f, str(dest_prefix), show_progress=False): count += 1 else: - getLogger(__name__).warning("Failed to upload %s to %s", f, dest_prefix) + logger.warning("Failed to upload %s to %s", f, dest_prefix) f.unlink() shutil.rmtree(assembly_dir, ignore_errors=True) return count @@ -127,7 +130,6 @@ def download_batch( :param limit: optional limit for testing :return: report dict with overall stats """ - logger: Logger = getLogger(__name__) with manifest_path.open() as f: assembly_paths = [PurePosixPath(line.strip()) for line in f if line.strip() and not line.startswith("#")] diff --git a/src/cdm_data_loaders/pipelines/ncbi_rest_api.py b/src/cdm_data_loaders/pipelines/ncbi_rest_api.py index 3082064a..8a398f97 100644 --- a/src/cdm_data_loaders/pipelines/ncbi_rest_api.py +++ b/src/cdm_data_loaders/pipelines/ncbi_rest_api.py @@ -52,6 +52,9 @@ REST_CLIENT_HOOKS = {} +logger: logging.Logger = logging.getLogger(__name__) + + ARG_ALIAS_BATCH_SIZE = ["-b", "--batch-size", "--batch_size"] ARG_ALIAS_QUERY_TYPE = ["-q", "--query-type", "--query_type"] @@ -255,7 +258,7 @@ def get_dataset_reports(assembly_id_list: list[str]) -> dict[str, None | dict[st if not assembly_id_list: return {} - logging.getLogger(__name__).info("fetching dataset reports for:\n%s", ", ".join(sorted(assembly_id_list))) + logger.info("fetching dataset reports for:\n%s", ", ".join(sorted(assembly_id_list))) assembly_dataset_reports = [] for page in ncbi_genome_client.paginate( @@ -276,7 +279,7 @@ def get_dataset_reports(assembly_id_list: list[str]) -> dict[str, None | dict[st def get_annotation_report(assembly_id: str) -> list[dict[str, Any]] | None: """Fetch the annotation report for an assembly from the NCBI datasets REST API.""" - logging.getLogger(__name__).info("fetching annotation report for %s", assembly_id) + logger.info("fetching annotation report for %s", assembly_id) page_data = [] for page in ncbi_genome_client.paginate( diff --git a/src/cdm_data_loaders/utils/batcher.py b/src/cdm_data_loaders/utils/batcher.py index 00312114..e657e16d 100644 --- a/src/cdm_data_loaders/utils/batcher.py +++ b/src/cdm_data_loaders/utils/batcher.py @@ -1,4 +1,4 @@ -"""File system-related utilities.""" +"""Utilities for splitting a selection into batches.""" import re from logging import Logger, getLogger diff --git a/tests/utils/test_batcher.py b/tests/utils/test_batcher.py index e47c8799..8581103e 100644 --- a/tests/utils/test_batcher.py +++ b/tests/utils/test_batcher.py @@ -134,7 +134,7 @@ def test_init_batcher_defaults() -> None: assert bc.start_at == MIN_START_AT assert bc.end_at == MIN_END_AT assert bc.file_regex == FILE_NAME_REGEX - assert bc.directory == Path("path/to/dir") + assert bc.directory == Path("path") / "to" / "dir" def test_init_batcher_values() -> None: @@ -143,13 +143,13 @@ def test_init_batcher_values() -> None: batch_size = 10 end_at = 26 bc = NumericFileSequenceBatcher( - directory=Path("path/to/dir"), start_at=start_at, batch_size=batch_size, end_at=end_at + directory=Path("path") / "to" / "dir", start_at=start_at, batch_size=batch_size, end_at=end_at ) assert bc.start_at == start_at assert bc.batch_size == batch_size assert bc.end_at == end_at assert bc.file_regex == FILE_NAME_REGEX - assert bc.directory == Path("path/to/dir") + assert bc.directory == Path("path") / "to" / "dir" def test_init_batcher_no_directory() -> None: From 2be18305a8c0bb32d552e4a660f3f048529ed388 Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Wed, 17 Jun 2026 06:34:50 -0700 Subject: [PATCH 06/12] Tidy up a few more extra getLogger calls --- .../parsers/gene_association_file.py | 5 ++--- .../parsers/uniprot/idmapping.py | 16 ++++++++-------- .../pipelines/all_the_bacteria.py | 5 +++-- src/cdm_data_loaders/pipelines/core.py | 8 +++----- .../pipelines/ncbi_ftp_download.py | 1 - src/cdm_data_loaders/pipelines/ncbi_rest_api.py | 7 +++---- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/cdm_data_loaders/parsers/gene_association_file.py b/src/cdm_data_loaders/parsers/gene_association_file.py index 548de565..7aa493fa 100644 --- a/src/cdm_data_loaders/parsers/gene_association_file.py +++ b/src/cdm_data_loaders/parsers/gene_association_file.py @@ -22,10 +22,10 @@ - Schema conforms to the CDM-style structured annotation model. """ -import logging import os import sys import urllib.request +from logging import Logger, getLogger import click from delta import configure_spark_with_delta_pip @@ -45,8 +45,7 @@ ) from pyspark.sql.types import StringType -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) +logger: Logger = getLogger(__name__) # --- Constants --- SUBJECT = "subject" diff --git a/src/cdm_data_loaders/parsers/uniprot/idmapping.py b/src/cdm_data_loaders/parsers/uniprot/idmapping.py index 7ebd09b4..c994713d 100644 --- a/src/cdm_data_loaders/parsers/uniprot/idmapping.py +++ b/src/cdm_data_loaders/parsers/uniprot/idmapping.py @@ -25,6 +25,7 @@ import datetime from logging import Logger, getLogger +from typing import Final from uuid import uuid4 import click @@ -40,16 +41,15 @@ from cdm_data_loaders.validation.dataframe_validator import DataFrameValidator, Validator from cdm_data_loaders.validation.df_nullable_fields import validate as check_nullable_fields -APP_NAME = "uniprot_idmapping" -NOW = datetime.datetime.now(tz=datetime.UTC) -DB = "db" -XREF = "xref" -ID = "id" -COLUMNS = [ID, DB, XREF] - - logger: Logger = getLogger(__name__) + +APP_NAME: Final[str] = "uniprot_idmapping" +NOW = datetime.datetime.now(tz=datetime.UTC) +DB: Final[str] = "db" +XREF: Final[str] = "xref" +ID: Final[str] = "id" +COLUMNS = [ID, DB, XREF] ID_MAPPING_SCHEMA = [StructField(n, StringType(), nullable=False) for n in COLUMNS] diff --git a/src/cdm_data_loaders/pipelines/all_the_bacteria.py b/src/cdm_data_loaders/pipelines/all_the_bacteria.py index 8f82817d..d631b803 100644 --- a/src/cdm_data_loaders/pipelines/all_the_bacteria.py +++ b/src/cdm_data_loaders/pipelines/all_the_bacteria.py @@ -11,9 +11,9 @@ """ import csv -import logging import re from collections.abc import Generator +from logging import Logger, getLogger from pathlib import Path from typing import Any @@ -33,7 +33,8 @@ from cdm_data_loaders.utils.download.sync_client import FileDownloader from cdm_data_loaders.utils.s3 import stream_to_s3 -logger = logging.getLogger(__name__) +logger: Logger = getLogger(__name__) + DATASET_NAME = "all_the_bacteria" ALL_FILES_TSV_FILE_ID = "R6gcp" diff --git a/src/cdm_data_loaders/pipelines/core.py b/src/cdm_data_loaders/pipelines/core.py index 3b6420e3..acce94de 100644 --- a/src/cdm_data_loaders/pipelines/core.py +++ b/src/cdm_data_loaders/pipelines/core.py @@ -1,9 +1,9 @@ """Common reusable pipeline elements.""" import datetime -import logging import os from collections.abc import Callable, Generator +from logging import Logger, getLogger from typing import Any import dlt @@ -16,7 +16,7 @@ from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher from cdm_data_loaders.utils.xml_utils import stream_xml_file -logger = logging.getLogger(__name__) +logger: Logger = getLogger(__name__) def construct_env_var() -> None: @@ -55,7 +55,7 @@ def run_cli(settings_cls: type[CtsSettings], pipeline_fn: Callable[[Any], None]) settings = settings_cls(dlt_config=dlt.config) sync_configs(settings, dlt.config) except (SettingsError, ValidationError, ValueError): - logging.getLogger(__name__).exception("Error initialising config") + logger.exception("Error initialising config") raise except Exception: logger.exception("Unexpected error setting up config") @@ -101,7 +101,6 @@ def run_pipeline( pipeline = dlt.pipeline(destination=destination, **pipeline_kwargs) slack_hook: str | None = pipeline.runtime_config.slack_incoming_hook - logger: logging.Logger = logging.getLogger(__name__) if not slack_hook: logger.info("Slack webhook not configured; no Slack alerts will be sent.") @@ -138,7 +137,6 @@ def stream_xml_file_resource( :param log_interval: log a progress message every N entries :type log_interval: int """ - logger = logging.getLogger(__name__) timestamp = datetime.datetime.now(tz=datetime.UTC) batch_params: dict[str, Any] = {} if settings.start_at: diff --git a/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py b/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py index 0de5efa3..636eed42 100644 --- a/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py +++ b/src/cdm_data_loaders/pipelines/ncbi_ftp_download.py @@ -268,7 +268,6 @@ def download_and_stage( # noqa: PLR0913, PLR0915 :param dry_run: when ``True``, download but skip all S3 uploads :return: download report extended with ``staged_objects``, ``staging_key_prefix``, ``dry_run`` """ - logger: Logger = getLogger(__name__) if manifest_s3_key is not None and manifest_local_path is not None: msg = "Provide exactly one of manifest_s3_key or manifest_local_path, not both" raise ValueError(msg) diff --git a/src/cdm_data_loaders/pipelines/ncbi_rest_api.py b/src/cdm_data_loaders/pipelines/ncbi_rest_api.py index 8a398f97..37c7d43c 100644 --- a/src/cdm_data_loaders/pipelines/ncbi_rest_api.py +++ b/src/cdm_data_loaders/pipelines/ncbi_rest_api.py @@ -5,12 +5,12 @@ batch_001.txt, batch_002.txt, batch_003.txt. """ -import logging import os import re from collections.abc import Generator from functools import partial from itertools import islice +from logging import Logger, getLogger from pathlib import Path from typing import Any, Final from urllib.parse import parse_qs, urlparse @@ -34,6 +34,8 @@ from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_SETTINGS_CONFIG_DICT, CtsSettings from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher +logger: Logger = getLogger(__name__) + DATASET_NAME: Final[str] = "ncbi_rest_api" NCBI_API_KEY = os.environ.get("NCBI_API_KEY") or "DEMO_KEY" @@ -52,9 +54,6 @@ REST_CLIENT_HOOKS = {} -logger: logging.Logger = logging.getLogger(__name__) - - ARG_ALIAS_BATCH_SIZE = ["-b", "--batch-size", "--batch_size"] ARG_ALIAS_QUERY_TYPE = ["-q", "--query-type", "--query_type"] From 255f0b7eb46bb48ca742625ef07a04e72204314f Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Wed, 17 Jun 2026 13:49:13 -0700 Subject: [PATCH 07/12] updating slack messaging to be a bit more circumspect --- src/cdm_data_loaders/pipelines/core.py | 35 +++++++++++-- tests/pipelines/test_core.py | 68 ++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/cdm_data_loaders/pipelines/core.py b/src/cdm_data_loaders/pipelines/core.py index acce94de..732c60c4 100644 --- a/src/cdm_data_loaders/pipelines/core.py +++ b/src/cdm_data_loaders/pipelines/core.py @@ -4,7 +4,7 @@ import os from collections.abc import Callable, Generator from logging import Logger, getLogger -from typing import Any +from typing import Any, Final import dlt from dlt.common.runtime.slack import send_slack_message @@ -16,9 +16,36 @@ from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher from cdm_data_loaders.utils.xml_utils import stream_xml_file +WEBHOOK_NOT_CONFIGURED: Final[str] = "Slack webhook not configured" +NO_MESSAGE: Final[str] = "No message supplied" + + logger: Logger = getLogger(__name__) +def send_slack_message_carefully(slack_hook: str, message: str, is_markdown: bool = False) -> None: # noqa: FBT001, FBT002 + """Carefully send a slack message by wrapping it in a try/except. + + :param slack_hook: _description_ + :type slack_hook: str + :param message: _description_ + :type message: str + :param is_markdown: _description_, defaults to False + :type is_markdown: bool, optional + """ + if not slack_hook: + logger.warning("Cannot send slack message: %s", WEBHOOK_NOT_CONFIGURED) + return + if not message: + logger.warning("Cannot send slack message: %s", NO_MESSAGE) + return + + try: + send_slack_message(slack_hook, message, is_markdown) + except Exception: + logger.exception("Failed to send slack message") + + def construct_env_var() -> None: """Use environment variables to construct a new environment variable.""" b_var = os.environ.get("VARIABLE_B") @@ -103,7 +130,7 @@ def run_pipeline( slack_hook: str | None = pipeline.runtime_config.slack_incoming_hook if not slack_hook: - logger.info("Slack webhook not configured; no Slack alerts will be sent.") + logger.info("No Slack alerts will be sent: %s", WEBHOOK_NOT_CONFIGURED) try: load_info = pipeline.run(resource, **(pipeline_run_kwargs or {})) @@ -111,13 +138,13 @@ def run_pipeline( err_msg = f"Pipeline failed: {e!s}" logger.exception(err_msg) if slack_hook: - send_slack_message(slack_hook, err_msg) + send_slack_message_carefully(slack_hook, err_msg) return logger.info(load_info) logger.info("Work complete!") if slack_hook: - send_slack_message(slack_hook, "Pipeline completed successfully!") + send_slack_message_carefully(slack_hook, "Pipeline completed successfully!") def stream_xml_file_resource( diff --git a/tests/pipelines/test_core.py b/tests/pipelines/test_core.py index 34945dc1..9a8504b8 100644 --- a/tests/pipelines/test_core.py +++ b/tests/pipelines/test_core.py @@ -6,7 +6,7 @@ from collections.abc import Generator from copy import deepcopy from pathlib import Path -from typing import Any +from typing import Any, Final from unittest.mock import MagicMock, call, patch import pytest @@ -15,9 +15,12 @@ from cdm_data_loaders.pipelines import core from cdm_data_loaders.pipelines.core import ( + NO_MESSAGE, + WEBHOOK_NOT_CONFIGURED, construct_env_var, run_cli, run_pipeline, + send_slack_message_carefully, stream_xml_file_resource, sync_configs, ) @@ -162,6 +165,64 @@ def test_construct_env_var_overwrites_existing_hook() -> None: assert os.environ["RUNTIME__SLACK_INCOMING_HOOK"] == "https://hooks.slack.com/services/B/T/C/" +# send slack message carefully +SLACK_HOOK: Final[str] = "https://slack.hook/what/ever" +TEST_MESSAGE: Final[str] = "3... 2... 1... testing?" + + +@pytest.mark.parametrize( + ("slack_hook", "message", "err_msg"), + [ + ("", TEST_MESSAGE, WEBHOOK_NOT_CONFIGURED), + (SLACK_HOOK, "", NO_MESSAGE), + ("", "", WEBHOOK_NOT_CONFIGURED), + ], +) +def test_send_slack_message_carefully_params_fail( + slack_hook: str, + message: str, + err_msg: str, + caplog: pytest.LogCaptureFixture, + mock_send_slack_message: MagicMock, +) -> None: + """Test sending a slack message ever so carefully, but with incorrect parameters.""" + send_slack_message_carefully(slack_hook, message) + mock_send_slack_message.assert_not_called() + assert len(caplog.records) == 1 + assert caplog.records[-1].levelno == logging.WARNING + assert caplog.records[-1].message == f"Cannot send slack message: {err_msg}" + + +@pytest.mark.parametrize("markdown", [True, False, None]) +def test_send_slack_message_carefully_markdown_params( + markdown: None | bool, mock_send_slack_message: MagicMock +) -> None: + """Test that the markdown param is correctly passed on to send_slack_message. + + :param markdown: markdown parameter + :type markdown: None | bool + """ + if markdown is None: + send_slack_message_carefully(SLACK_HOOK, TEST_MESSAGE) + else: + send_slack_message_carefully(SLACK_HOOK, TEST_MESSAGE, markdown) + + if markdown: + mock_send_slack_message.assert_called_once_with(SLACK_HOOK, TEST_MESSAGE, True) # noqa: FBT003 + else: + mock_send_slack_message.assert_called_once_with(SLACK_HOOK, TEST_MESSAGE, False) # noqa: FBT003 + + +def test_send_slack_message_fail_error_oh_no(monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture) -> None: + """Ensure that errors are caught and don't crash the whole goddamn ship of fools.""" + slack_mock = MagicMock(side_effect=ValueError("Oh no! An error!")) + monkeypatch.setattr(core, "send_slack_message", slack_mock) + send_slack_message_carefully(SLACK_HOOK, TEST_MESSAGE) + assert len(caplog.records) == 1 + assert caplog.records[-1].levelno == logging.ERROR + assert caplog.records[-1].message == "Failed to send slack message" + + # sync_configs def test_sync_configs_mutates_dlt_config_in_place(dlt_config: dict[str, Any], test_cts_settings: CtsSettings) -> None: """sync_configs mutates the supplied dlt_config dict in-place.""" @@ -463,6 +524,7 @@ def test_run_pipeline_slack_configured( caplog: pytest.LogCaptureFixture, ) -> None: """Test that a slack message is sent if the slack_incoming_hook runtime config value is available.""" + error = None # set up slack config if slack_configured: slack_hook = "http://some.url.slack.com" @@ -482,10 +544,10 @@ def test_run_pipeline_slack_configured( ) else: mock_send_slack_message.assert_called_once_with("http://some.url.slack.com", f"Pipeline failed: {error!s}") - assert "Slack webhook not configured; no Slack alerts will be sent" not in caplog.messages + assert f"No Slack alerts will be sent: {WEBHOOK_NOT_CONFIGURED}" not in caplog.messages else: mock_send_slack_message.assert_not_called() - assert "Slack webhook not configured; no Slack alerts will be sent." in caplog.messages + assert f"No Slack alerts will be sent: {WEBHOOK_NOT_CONFIGURED}" in caplog.messages if success: # log messages on success From 1820085d7f8dc1c1f8d1b00c43c055ea2e2c14c2 Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Wed, 17 Jun 2026 14:31:51 -0700 Subject: [PATCH 08/12] Fixing test fails --- tests/pipelines/test_core.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/pipelines/test_core.py b/tests/pipelines/test_core.py index 9a8504b8..9023c801 100644 --- a/tests/pipelines/test_core.py +++ b/tests/pipelines/test_core.py @@ -540,10 +540,16 @@ def test_run_pipeline_slack_configured( if slack_configured: if success: mock_send_slack_message.assert_called_once_with( - "http://some.url.slack.com", "Pipeline completed successfully!" + "http://some.url.slack.com", + "Pipeline completed successfully!", + False, # noqa: FBT003 ) else: - mock_send_slack_message.assert_called_once_with("http://some.url.slack.com", f"Pipeline failed: {error!s}") + mock_send_slack_message.assert_called_once_with( + "http://some.url.slack.com", + f"Pipeline failed: {error!s}", + False, # noqa: FBT003 + ) assert f"No Slack alerts will be sent: {WEBHOOK_NOT_CONFIGURED}" not in caplog.messages else: mock_send_slack_message.assert_not_called() From ad829c6061465756117d3faba85b734ed87ea34c Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Thu, 18 Jun 2026 14:38:14 -0700 Subject: [PATCH 09/12] Add in root logger stuff and terrible tests. Update other tests to take account of root logger init code in the cli_app initialisation. --- Dockerfile | 17 +- logger_config.json | 24 + .../parsers/uniprot/uniprot_kb.py | 34 +- src/cdm_data_loaders/pipelines/core.py | 2 + .../pipelines/cts_defaults.py | 8 +- src/cdm_data_loaders/utils/cdm_logger.py | 240 +++----- tests/parsers/uniprot/test_uniprot_kb.py | 21 +- tests/pipelines/conftest.py | 7 + tests/pipelines/test_cts_defaults.py | 4 + tests/pipelines/test_ncbi_rest_api.py | 5 + tests/pipelines/test_uniprot.py | 4 + tests/pipelines/test_uniref.py | 4 + tests/utils/test_cdm_logger.py | 549 +++++++++--------- 13 files changed, 432 insertions(+), 487 deletions(-) create mode 100644 logger_config.json diff --git a/Dockerfile b/Dockerfile index 0a7c8a78..01f5c1e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,14 +55,27 @@ RUN --mount=type=cache,target=/root/.cache/uv \ # Then, add the rest of the project source code and install it # Installing separately from its dependencies allows optimal layer caching -COPY --chown=nonroot:nonroot . /app +# COPY --chown=nonroot:nonroot . /app +COPY --chown=nonroot:nonroot .dlt /app/.dlt +COPY --chown=nonroot:nonroot docs /app/docs +COPY --chown=nonroot:nonroot scripts /app/scripts +COPY --chown=nonroot:nonroot src /app/src +COPY --chown=nonroot:nonroot tests /app/tests +COPY --chown=nonroot:nonroot README.md /app/README.md +COPY --chown=nonroot:nonroot pyproject.toml /app/pyproject.toml +COPY --chown=nonroot:nonroot uv.lock /app/uv.lock + RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked --no-editable -# Place executables in the environment at the front of the path + # Place executables in the environment at the front of the path ENV PATH="/app/.venv/bin:$PATH" RUN chmod +x ./scripts/entrypoint.sh + +# make sure that the nonroot user owns the app directory +RUN chown nonroot:nonroot /app + # Use the non-root user to run our application USER nonroot ENTRYPOINT ["./scripts/entrypoint.sh"] diff --git a/logger_config.json b/logger_config.json new file mode 100644 index 00000000..b80f0427 --- /dev/null +++ b/logger_config.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "INFO", + "stream": "ext://sys.stdout" + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "/output_dir/log_file.txt" + } + }, + "loggers": { + "root": { + "level": "INFO", + "handlers": [ + "console", + "file" + ] + } + } +} diff --git a/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py b/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py index fbf29c61..aa0bc969 100644 --- a/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py +++ b/src/cdm_data_loaders/parsers/uniprot/uniprot_kb.py @@ -138,23 +138,17 @@ def parse_cross_references(entry: Element) -> list[dict[str, Any]]: description_suffix = f" for UniProt:{molecules[0].get('id')}" if len(molecules) > 0 else "" prop_list = [{"type": p.get("type"), "value": p.get("value")} for p in dbxref.findall("ns:property", NS)] - if prop_list and db in ("Ensembl", "EMBL", "RefSeq", "MANE-Select"): - if db == "Ensembl": - refs.extend(parse_ensembl_dbxref(xref, prop_list, description_suffix)) - continue - - if db == "EMBL": - # embl / genbank - refs.extend(parse_embl_dbxref(xref, prop_list, description_suffix)) - continue - - if db == "RefSeq": - refs.extend(parse_refseq_dbxref(xref, prop_list, description_suffix)) - continue + prop_list_dispatch_hash = { + "Ensembl": parse_ensembl_dbxref, + # embl/genbank + "EMBL": parse_embl_dbxref, + "RefSeq": parse_refseq_dbxref, + "MANE-Select": parse_mane_dbxref, + } - if db == "MANE-Select": - refs.extend(parse_mane_dbxref(xref, prop_list, description_suffix)) - continue + if prop_list and db in prop_list_dispatch_hash: + refs.extend(prop_list_dispatch_hash[db](xref, prop_list, description_suffix)) + continue if db == "GO": refs.extend([{DB: "GO", XREF: xref.removeprefix("GO:")}]) @@ -364,15 +358,15 @@ def dump_xml_element(element: Element) -> str: def parse_uniprot_entry( entry: Element, - current_timestamp: datetime.datetime, + timestamp: datetime.datetime, file_path: str | Path, ) -> dict[str, Any]: """Parse a UniProt entry to yield the appropriate CDM schema tables. :param entry: an entry from a UniProt XML file :type entry: Element - :param current_timestamp: current timestamp - :type current_timestamp: datetime.datetime + :param timestamp: current timestamp + :type timestamp: datetime.datetime :param file_path: path of the file currently being parsed :type file_path: str | Path :return: dictionary of parsed data, indexed by table name @@ -401,7 +395,7 @@ def parse_uniprot_entry( # TODO: add in "created" field when merging datasets? "created": None, "data_source": f"UniProt/{entry.attrib.get('dataset')}", - "updated": current_timestamp, + "updated": timestamp, } ref_data = parse_references(entry) diff --git a/src/cdm_data_loaders/pipelines/core.py b/src/cdm_data_loaders/pipelines/core.py index 732c60c4..098daeb1 100644 --- a/src/cdm_data_loaders/pipelines/core.py +++ b/src/cdm_data_loaders/pipelines/core.py @@ -14,6 +14,7 @@ from cdm_data_loaders.pipelines.cts_defaults import DEFAULT_PIPELINE_BATCH_SIZE, BatchedFileInputSettings, CtsSettings from cdm_data_loaders.utils.batcher import NumericFileSequenceBatcher +from cdm_data_loaders.utils.cdm_logger import init_logger from cdm_data_loaders.utils.xml_utils import stream_xml_file WEBHOOK_NOT_CONFIGURED: Final[str] = "Slack webhook not configured" @@ -81,6 +82,7 @@ def run_cli(settings_cls: type[CtsSettings], pipeline_fn: Callable[[Any], None]) try: settings = settings_cls(dlt_config=dlt.config) sync_configs(settings, dlt.config) + init_logger(settings) except (SettingsError, ValidationError, ValueError): logger.exception("Error initialising config") raise diff --git a/src/cdm_data_loaders/pipelines/cts_defaults.py b/src/cdm_data_loaders/pipelines/cts_defaults.py index d6d70315..8adf054a 100644 --- a/src/cdm_data_loaders/pipelines/cts_defaults.py +++ b/src/cdm_data_loaders/pipelines/cts_defaults.py @@ -5,9 +5,11 @@ import dlt.common.configuration.accessors from frozendict import frozendict from pydantic import AliasChoices, Field, computed_field, field_validator, model_validator -from pydantic_settings import BaseSettings, CliSuppress, SettingsConfigDict +from pydantic_settings import CliSuppress, SettingsConfigDict from cdm_data_loaders.utils.batcher import MIN_START_AT +from cdm_data_loaders.utils.cdm_logger import ARG_ALIASES as LOGGER_ARG_ALIASES +from cdm_data_loaders.utils.cdm_logger import DEFAULT_LOG_CONFIG_FILE, LoggerSettings # TODO: frozendict can be moved to the stdlib implementation when py 3.15 is released. @@ -22,6 +24,7 @@ { "dev_mode": False, "input_dir": INPUT_MOUNT, + "log_config_file": DEFAULT_LOG_CONFIG_FILE, # N.b. this gets replaced by destination.local_fs.bucket_url "output": "", "use_destination": "local_fs", @@ -44,6 +47,7 @@ "batch_size": ["-b", "--batch_size", "--batch-size"], "dev_mode": ["--dev_mode", "--dev-mode", "--dev"], "input_dir": ["-i", "--input_dir", "--input-dir"], + "log_config_file": LOGGER_ARG_ALIASES, "output": ["-o", "--output"], "start_at": ["-s", "--start_at", "--start-at"], "use_destination": ["-d", "--use_destination", "--use-destination"], @@ -65,7 +69,7 @@ ) -class CtsSettings(BaseSettings): +class CtsSettings(LoggerSettings): """Configuration for running a basic import pipeline.""" model_config = SettingsConfigDict(**DEFAULT_SETTINGS_CONFIG_DICT) diff --git a/src/cdm_data_loaders/utils/cdm_logger.py b/src/cdm_data_loaders/utils/cdm_logger.py index cb91cae4..a4585868 100644 --- a/src/cdm_data_loaders/utils/cdm_logger.py +++ b/src/cdm_data_loaders/utils/cdm_logger.py @@ -5,221 +5,115 @@ import json import logging import logging.config -import logging.handlers -import os from pathlib import Path +from typing import Any, Final +import yaml +from dlt.common.runtime.json_logging import config_root_logger from frozendict import frozendict +from pydantic import AliasChoices, Field +from pydantic_settings import BaseSettings -DEFAULT_LOGGER_NAME = "cdm_data_loader" -GENERIC_ERROR_MESSAGE = "An error of unknown origin occurred." +ROOT_LOGGER_CONFIGURED: bool = False -LOG_FILENAME = "cdm_data_loader.log" -MAX_LOG_FILE_SIZE = 2**30 # 1 GiB -MAX_LOG_BACKUPS = 5 -VALID_LOG_LEVELS = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} -LOGGING_CONFIG_FILENAME = "logging_config.json" +# no default logger config file -- falls back to LOGGING_CONFIG if file not found. +DEFAULT_LOG_CONFIG_FILE: Final = None -JSON_LOG_CONFIG = '{"time": "%(asctime)s", "level": "%(levelname)s", "module": "%(module)s", "msg": "%(message)s"}' - -_module_logger = logging.getLogger(__name__) - -# Immutable fallback config used when no external config file can be found. -# Note: disable_existing_loggers is intentionally absent — it is always -# injected at runtime by _load_logging_config() to guarantee it is False. +# Immutable fallback config used when no external config file can be found LOGGING_CONFIG = frozendict( { "version": 1, "handlers": { "console": { "class": "logging.StreamHandler", - "formatter": "json", - "level": "INFO", "stream": "ext://sys.stdout", }, }, - "formatters": {"json": {"format": JSON_LOG_CONFIG}}, - "loggers": {DEFAULT_LOGGER_NAME: {"level": "INFO", "handlers": ["console"]}}, + "loggers": {"root": {"level": "INFO", "handlers": ["console"]}}, + "disable_existing_loggers": False, } ) +ARG_ALIASES = ["--log-config-file", "--log_config_file"] -def _load_config_from_path(path: Path) -> dict: - """Attempt to load and parse a JSON logging config from the given path. - :param path: path to a JSON logging config file - :type path: Path - :return: parsed config dict - :rtype: dict - :raises FileNotFoundError: if no file exists at the given path - :raises ValueError: if the file content is not valid JSON - """ - with path.open() as f: - return json.load(f) +class LoggerSettings(BaseSettings): + """Configuration for a class with a logger config.""" + log_config_file: str | None = Field( + default=DEFAULT_LOG_CONFIG_FILE, + description="Location of configuration file for the logger", + validation_alias=AliasChoices(*[alias.strip("-") for alias in ARG_ALIASES]), + ) -def _load_logging_config(config_file: str | Path | None = None) -> dict: - """Resolve and load a logging config, working through a priority chain until a source succeeds. - - Resolution order: - 1. ``config_file`` argument (if provided) - 2. ``LOG_CONFIG_FILE`` environment variable (if set) - 3. ``logging_config.json`` in the current working directory - 4. Built-in ``LOGGING_CONFIG`` frozendict - If a file is found but sets ``disable_existing_loggers`` to ``True``, - a warning is emitted and the value is overridden. The key is always - set to ``False`` in the returned dict, regardless of source, to prevent - ``dictConfig`` from silently disabling pre-existing loggers. +def _load_config_from_path(path: str | Path) -> dict[str, Any]: + """Attempt to load and parse a logging config from the given path. - :param config_file: explicit path to a logging config file, defaults to None - :type config_file: str | Path | None, optional - :return: logging config dict ready to pass to dictConfig - :rtype: dict + :param path: path to a JSON or YAML logging config file + :type path: str | Path + :return: parsed config dict + :rtype: dict[str, Any] """ - candidates: list[tuple[Path, str]] = [] - - if config_file is not None: - candidates.append((Path(config_file), "config_file argument")) - - env_path = os.getenv("LOG_CONFIG_FILE") - if env_path: - candidates.append((Path(env_path), "LOG_CONFIG_FILE env var")) - - candidates.append((Path.cwd() / LOGGING_CONFIG_FILENAME, "current working directory")) - - for path, source in candidates: - try: - config = _load_config_from_path(path) - if config.get("disable_existing_loggers", True): - _module_logger.warning( - "Logging config loaded from %s (%s) sets disable_existing_loggers " - "to True. Overriding to prevent existing loggers being silently disabled.", - path, - source, - ) - _module_logger.info("Loaded logging config from %s (%s).", path, source) - break - except FileNotFoundError: - _module_logger.warning("Logging config not found at %s (%s). Trying next source.", path, source) - except ValueError: - _module_logger.warning("Logging config at %s (%s) is not valid JSON. Trying next source.", path, source) - else: - _module_logger.warning("No logging config file found. Falling back to built-in config.") - config = dict(LOGGING_CONFIG) + if not isinstance(path, Path): + path = Path(path) - config["disable_existing_loggers"] = False - return config + suffix = path.suffix.lower() + if suffix in (".yml", ".yaml"): + with path.open() as f: + return yaml.safe_load(f) -def _json_formatter() -> logging.Formatter: - """Construct the standard CDM JSON log formatter. + if suffix == ".json": + with path.open() as f: + return json.load(f) - :return: configured Formatter instance - :rtype: logging.Formatter - """ - return logging.Formatter(JSON_LOG_CONFIG) + err_msg = f"Unsupported config file format: {path.name}" + raise ValueError(err_msg) -def _set_level_safe(logger: logging.Logger, level: str) -> None: - """Set the log level on a logger, raising a descriptive error for invalid values. - - :param logger: the logger to configure - :type logger: logging.Logger - :param level: log level string - :type level: str - :raises ValueError: if the level string is not a recognised logging level - """ - normalised = level.upper() - if normalised not in VALID_LOG_LEVELS: - msg = f"Invalid log level {level!r}. Must be one of: {', '.join(sorted(VALID_LOG_LEVELS))}" - raise ValueError(msg) - logger.setLevel(normalised) - - -def get_cdm_logger() -> logging.Logger: +def get_cdm_logger(logger_name: str, settings: LoggerSettings | None = None) -> logging.Logger: """Retrieve the default CDM logger, initialising it if necessary. - Prefers the 'dlt' logger if it has already been configured, otherwise - falls back to the CDM logger, creating it if needed. - :return: initialised logger :rtype: logging.Logger """ - all_loggers = logging.root.manager.loggerDict - if "dlt" in all_loggers: - return logging.getLogger("dlt") - if DEFAULT_LOGGER_NAME in all_loggers: - return logging.getLogger(DEFAULT_LOGGER_NAME) - return init_logger() + init_logger(settings) + return logging.getLogger(logger_name) -def _attach_file_handler(logger: logging.Logger, log_file: str | Path) -> None: - """Attach a file handler to the given logger if no file handler is already present. +def init_logger(settings: LoggerSettings | None = None) -> None: + """Initialise logger configuration.""" + if not settings: + # init settings (pulling in log_config_file from cmd line args / env vars) if not provided + settings = LoggerSettings() - Checks for any existing handler whose class name contains 'FileHandler' - to avoid attaching duplicate file handlers of any type. Creates the parent - directory of log_file if it does not already exist. - - :param logger: the logger to attach the handler to - :type logger: logging.Logger - :param log_file: path to the log file - :type log_file: str | Path - """ - if any("FileHandler" in type(h).__name__ for h in logger.handlers): - return - - log_path = Path(log_file) - log_path.parent.mkdir(parents=True, exist_ok=True) + if settings and settings.log_config_file: + config = _load_config_from_path(settings.log_config_file) + else: + logging.getLogger(__name__).warning("No logging config file found. Falling back to built-in config.") + config = dict(LOGGING_CONFIG) - handler = logging.handlers.RotatingFileHandler( - log_path, - maxBytes=MAX_LOG_FILE_SIZE, - backupCount=MAX_LOG_BACKUPS, - ) - handler.setFormatter(_json_formatter()) - logger.addHandler(handler) - - -def init_logger( - log_level: str | None = None, - config_file: str | Path | None = None, - enable_file_logging: bool = False, # noqa: FBT001, FBT002 - log_file: str | Path | None = None, -) -> logging.Logger: - """Initialise the logger for the module. - - Loads config by working through the following priority chain: - 1. ``config_file`` argument (if provided) - 2. ``LOG_CONFIG_FILE`` environment variable (if set) - 3. ``logging_config.json`` in the current working directory - 4. Built-in ``LOGGING_CONFIG`` frozendict - - If ``log_level`` is specified or the ``LOG_LEVEL`` env var is set, the - logger is set to that level. File logging is opt-in via - ``enable_file_logging`` or the ``ENABLE_FILE_LOGGING`` env var. - - :param log_level: logger level, defaults to None - :type log_level: str | None, optional - :param config_file: explicit path to a logging config file, defaults to None - :type config_file: str | Path | None, optional - :param enable_file_logging: attach a file handler, defaults to False - :type enable_file_logging: bool, optional - :param log_file: path to the log file, defaults to LOG_FILENAME in the CWD - :type log_file: str | Path, optional - :return: initialised logger - :rtype: logging.Logger - """ - logging.config.dictConfig(_load_logging_config(config_file)) + # make sure there's nothing too crazy going on + config["disable_existing_loggers"] = False + logging.config.dictConfig(config) - logger = logging.getLogger(DEFAULT_LOGGER_NAME) + configure_root_logger_from_dlt() - new_log_level = log_level or os.getenv("LOG_LEVEL") - if new_log_level: - _set_level_safe(logger, new_log_level) - if enable_file_logging or os.getenv("ENABLE_FILE_LOGGING", "").lower() == "true": - _attach_file_handler(logger, log_file or LOG_FILENAME) +def configure_root_logger_from_dlt() -> None: + """Configure the root logger using settings from the dlt logger. - return logger + Copies over the log formatter used by the dlt logger. + """ + global ROOT_LOGGER_CONFIGURED # noqa: PLW0603 + if ROOT_LOGGER_CONFIGURED is False and "dlt" in logging.Logger.manager.loggerDict: + dlt_logger = logging.getLogger("dlt") + # this passes dlt's formatter up to the root logger + config_root_logger() + # pass messages from the dlt logger up to the root + dlt_logger.propagate = True + # clear the dlt logger handlers and let everything be handled by the root logger instead. + dlt_logger.handlers.clear() + ROOT_LOGGER_CONFIGURED = True diff --git a/tests/parsers/uniprot/test_uniprot_kb.py b/tests/parsers/uniprot/test_uniprot_kb.py index 56ef0764..53940137 100644 --- a/tests/parsers/uniprot/test_uniprot_kb.py +++ b/tests/parsers/uniprot/test_uniprot_kb.py @@ -875,12 +875,21 @@ def test_parse_organism(xml: str, expected: list[dict[str, str]]) -> None: # TODO: put together a full example! ], ) -def test_parse_uniprot_entry(xml: str, expected: dict[str, list[dict[str, Any]]]) -> None: +@pytest.mark.parametrize("args_style", ["positional", "kwargs"]) +def test_parse_uniprot_entry(args_style: str, xml: str, expected: dict[str, list[dict[str, Any]]]) -> None: """Test the parsing of generic cross references.""" xml_str = entry_wrap(xml) - parsed_entry = parse_uniprot_entry( - fromstring(xml_str, parser=XMLParser(remove_blank_text=True)), - datetime.datetime.now(tz=datetime.UTC), - FILE_PATH, - ) + if args_style == "positional": + parsed_entry = parse_uniprot_entry( + fromstring(xml_str, parser=XMLParser(remove_blank_text=True)), + datetime.datetime.now(tz=datetime.UTC), + FILE_PATH, + ) + else: + parsed_entry = parse_uniprot_entry( + file_path=FILE_PATH, + timestamp=datetime.datetime.now(tz=datetime.UTC), + entry=fromstring(xml_str, parser=XMLParser(remove_blank_text=True)), + ) + assert parsed_entry == expected diff --git a/tests/pipelines/conftest.py b/tests/pipelines/conftest.py index b1059f2c..84ae0b7c 100644 --- a/tests/pipelines/conftest.py +++ b/tests/pipelines/conftest.py @@ -77,6 +77,7 @@ def _generate_dlt_config() -> dict[str, Any]: { "dev_mode": "false", "input_dir": "/dir/path", + "log_config_file": "some/path", "output": "/some/dir", "use_destination": "local_fs", "use_output_dir_for_pipeline_metadata": "true", @@ -179,6 +180,12 @@ def fake_files() -> list[Path]: return [Path(f"/fake/input/part_{n}.xml") for n in [1, 2, 3, 4, 5]] +@pytest.fixture(autouse=True) +def mock_init_logger(monkeypatch: pytest.MonkeyPatch) -> None: + """Mock the init_logger call in core to prevent the logger from trying to initialise itself every time.""" + monkeypatch.setattr(core, "init_logger", MagicMock()) + + @pytest.fixture def mock_dlt(monkeypatch: pytest.MonkeyPatch, dlt_config: dict[str, Any]) -> MagicMock: """Patch dlt in core, wiring pipeline.return_value to a fresh MagicMock.""" diff --git a/tests/pipelines/test_cts_defaults.py b/tests/pipelines/test_cts_defaults.py index c5439e96..f94eb704 100644 --- a/tests/pipelines/test_cts_defaults.py +++ b/tests/pipelines/test_cts_defaults.py @@ -287,6 +287,7 @@ def test_settings_trailing_slash_stripped( @pytest.mark.parametrize("settings_cls", SETTINGS_CLASSES) @pytest.mark.parametrize("dev_mode", ARG_ALIASES["dev_mode"]) @pytest.mark.parametrize("input_dir", ARG_ALIASES["input_dir"]) +@pytest.mark.parametrize("log_config_file", ARG_ALIASES["log_config_file"]) @pytest.mark.parametrize("output", ARG_ALIASES["output"]) @pytest.mark.parametrize("start_at", ARG_ALIASES["start_at"]) @pytest.mark.parametrize("use_destination", ARG_ALIASES["use_destination"]) @@ -298,6 +299,7 @@ def test_cli_app_run_alt_settings( settings_cls: type[CtsSettings], dev_mode: str, input_dir: str, + log_config_file: str, output: str, start_at: str, use_destination: str, @@ -311,6 +313,8 @@ def test_cli_app_run_alt_settings( TEST_BATCH_FILE_SETTINGS["dev_mode"], input_dir, TEST_BATCH_FILE_SETTINGS["input_dir"], + log_config_file, + TEST_BATCH_FILE_SETTINGS["log_config_file"], output, TEST_BATCH_FILE_SETTINGS["output"], use_destination, diff --git a/tests/pipelines/test_ncbi_rest_api.py b/tests/pipelines/test_ncbi_rest_api.py index bf9b0811..86771d48 100644 --- a/tests/pipelines/test_ncbi_rest_api.py +++ b/tests/pipelines/test_ncbi_rest_api.py @@ -240,6 +240,7 @@ def test_settings_all_params_set(settings: frozendict, reconciled: frozendict) - @pytest.mark.parametrize("batch_size", ARG_ALIAS_BATCH_SIZE) @pytest.mark.parametrize("dev_mode", ARG_ALIASES["dev_mode"]) @pytest.mark.parametrize("input_dir", ARG_ALIASES["input_dir"]) +@pytest.mark.parametrize("log_config_file", ARG_ALIASES["log_config_file"]) @pytest.mark.parametrize("output", ARG_ALIASES["output"]) @pytest.mark.parametrize("use_destination", ARG_ALIASES["use_destination"]) @pytest.mark.parametrize( @@ -251,6 +252,7 @@ def test_cli_all_variants( batch_size: str, dev_mode: str, input_dir: str, + log_config_file: str, output: str, use_destination: str, use_output_dir_for_pipeline_metadata: str, @@ -269,6 +271,8 @@ def test_cli_all_variants( TEST_NCBI_SETTINGS["dev_mode"], input_dir, TEST_NCBI_SETTINGS["input_dir"], + log_config_file, + TEST_NCBI_SETTINGS["log_config_file"], output, TEST_NCBI_SETTINGS["output"], use_destination, @@ -369,6 +373,7 @@ def test_run_ncbi_pipeline_sets_core_run_pipeline_args_correctly( { "dev_mode": bool(dev_mode), "input_dir": "tests/data/ncbi_rest_api/input", + "log_config_file": None, "output": "/some/dir", "pipeline_dir": "/some/dir/.dlt_conf" if use_pipeline_dir else None, "raw_data_dir": "/some/dir/raw_data", diff --git a/tests/pipelines/test_uniprot.py b/tests/pipelines/test_uniprot.py index 452df6e5..b7862c28 100644 --- a/tests/pipelines/test_uniprot.py +++ b/tests/pipelines/test_uniprot.py @@ -64,6 +64,7 @@ def test_make_settings_all_params_set() -> None: @pytest.mark.parametrize("dev_mode", ARG_ALIASES["dev_mode"]) @pytest.mark.parametrize("input_dir", ARG_ALIASES["input_dir"]) +@pytest.mark.parametrize("log_config_file", ARG_ALIASES["log_config_file"]) @pytest.mark.parametrize("output", ARG_ALIASES["output"]) @pytest.mark.parametrize("start_at", ARG_ALIASES["start_at"]) @pytest.mark.parametrize("use_destination", ARG_ALIASES["use_destination"]) @@ -74,6 +75,7 @@ def test_make_settings_all_params_set() -> None: def test_cli_app_run_alt_settings( dev_mode: str, input_dir: str, + log_config_file: str, output: str, start_at: str, use_destination: str, @@ -86,6 +88,8 @@ def test_cli_app_run_alt_settings( TEST_SETTINGS["dev_mode"], input_dir, TEST_SETTINGS["input_dir"], + log_config_file, + TEST_SETTINGS["log_config_file"], output, TEST_SETTINGS["output"], start_at, diff --git a/tests/pipelines/test_uniref.py b/tests/pipelines/test_uniref.py index f1998fa3..13b1514a 100644 --- a/tests/pipelines/test_uniref.py +++ b/tests/pipelines/test_uniref.py @@ -158,6 +158,7 @@ def test_make_settings_all_params_set() -> None: @pytest.mark.parametrize("dev_mode", ARG_ALIASES["dev_mode"]) @pytest.mark.parametrize("input_dir", ARG_ALIASES["input_dir"]) +@pytest.mark.parametrize("log_config_file", ARG_ALIASES["log_config_file"]) @pytest.mark.parametrize("output", ARG_ALIASES["output"]) @pytest.mark.parametrize("start_at", ARG_ALIASES["start_at"]) @pytest.mark.parametrize("uniref_variant", UNIREF_VARIANT_ALIASES) @@ -169,6 +170,7 @@ def test_make_settings_all_params_set() -> None: def test_cli_app_run_alt_settings( dev_mode: str, input_dir: str, + log_config_file: str, output: str, start_at: str, uniref_variant: str, @@ -182,6 +184,8 @@ def test_cli_app_run_alt_settings( TEST_SETTINGS["dev_mode"], input_dir, TEST_SETTINGS["input_dir"], + log_config_file, + TEST_SETTINGS["log_config_file"], output, TEST_SETTINGS["output"], start_at, diff --git a/tests/utils/test_cdm_logger.py b/tests/utils/test_cdm_logger.py index 852f7be5..c3cca9cb 100644 --- a/tests/utils/test_cdm_logger.py +++ b/tests/utils/test_cdm_logger.py @@ -1,391 +1,372 @@ -"""Tests for cdm_data_loaders/utils/cdm_logger.py.""" +"""Tests for the CDM logger.""" import json import logging -import logging.handlers -from collections.abc import Generator -from copy import deepcopy +from contextlib import nullcontext from pathlib import Path -from typing import Any +from typing import Any, Generator +from unittest.mock import MagicMock, patch import pytest +import yaml from frozendict import frozendict import cdm_data_loaders.utils.cdm_logger as cdm_logger_module from cdm_data_loaders.utils.cdm_logger import ( - DEFAULT_LOGGER_NAME, - JSON_LOG_CONFIG, - LOG_FILENAME, - LOGGING_CONFIG, - MAX_LOG_BACKUPS, - MAX_LOG_FILE_SIZE, - _attach_file_handler, - _load_logging_config, - _set_level_safe, + LoggerSettings, + _load_config_from_path, + configure_root_logger_from_dlt, get_cdm_logger, - init_logger, ) -# Add near the top of the test file, alongside the other imports -MODULE_LOGGER_NAME = "cdm_data_loaders.utils.cdm_logger" +def get_logger_dict() -> dict[str, Any]: + """Get the dictionary of existing loggers.""" + return logging.Logger.manager.loggerDict -VALID_JSON_CONFIG = frozendict( - { - "version": 1, - "disable_existing_loggers": False, - "handlers": { - "CONFIGURE_HANDLER_NAME": { - "class": "logging.StreamHandler", - "formatter": "json", - "level": "INFO", - "stream": "ext://sys.stdout", - } - }, - "formatters": {"json": {"format": JSON_LOG_CONFIG}}, - "loggers": {DEFAULT_LOGGER_NAME: {"level": "INFO", "handlers": ["CONFIGURE_HANDLER_NAME"]}}, - } -) +@pytest.fixture(autouse=True) +def reset_module_state(monkeypatch: pytest.MonkeyPatch) -> Generator[None, Any]: + """Reset global module state and root logging config between every test.""" + # Reset the guard flag so each test starts from a clean slate + monkeypatch.setattr(cdm_logger_module, "ROOT_LOGGER_CONFIGURED", False) -def _write_config(path: Path, test_name: str, config: dict[str, Any] | None = None) -> Path: - """Write a JSON logging config to path, using VALID_JSON_CONFIG by default.""" - # edit the config to ensure that it is recognisable as being from a specific source - if not config: - config = deepcopy(dict(VALID_JSON_CONFIG)) - # switch out the handler name for a test-specific name - config["loggers"][DEFAULT_LOGGER_NAME]["handlers"] = [test_name] - config["handlers"][test_name] = config["handlers"].pop("CONFIGURE_HANDLER_NAME") + # Clear root logger handlers so dictConfig calls don't accumulate + root_logger: logging.Logger = logging.getLogger("root") + root_logger.handlers.clear() - path.write_text(json.dumps(config if config is not None else VALID_JSON_CONFIG)) - return path + yield + # Teardown: reset again after test + root_logger.handlers.clear() -@pytest.fixture(autouse=True) -def reset_logging() -> Generator[None, Any]: - """Remove CDM and dlt loggers from the manager and clear their handlers before and after tests.""" - def _clean() -> None: - for name in (DEFAULT_LOGGER_NAME, "dlt"): - logger = logging.root.manager.loggerDict.pop(name, None) - if isinstance(logger, logging.Logger): - logger.handlers.clear() +@pytest.fixture +def mock_config_root_logger() -> Generator[MagicMock, Any]: + """Patch dlt's config_root_logger so tests are hermetic.""" + with patch("cdm_data_loaders.utils.cdm_logger.config_root_logger") as mock: + yield mock - _clean() - yield - _clean() + +@pytest.fixture +def mock_dlt_logger(monkeypatch: pytest.MonkeyPatch) -> MagicMock: + dlt_mock = MagicMock() + dlt_mock.propagate = False + dlt_mock.handlers = [1, 2, 3, 4] + + monkeypatch.setitem(logging.Logger.manager.loggerDict, "dlt", dlt_mock) + return dlt_mock @pytest.fixture def clean_env(monkeypatch: pytest.MonkeyPatch) -> None: """Remove logging-related env vars during tests.""" - monkeypatch.delenv("LOG_LEVEL", raising=False) monkeypatch.delenv("LOG_CONFIG_FILE", raising=False) - monkeypatch.delenv("ENABLE_FILE_LOGGING", raising=False) + monkeypatch.delenv("LOG-CONFIG-FILE", raising=False) @pytest.fixture -def empty_cwd(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: +def empty_cwd(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, clean_env: None) -> None: """Remove LOG_CONFIG_FILE env vars and chdir to an empty temporary directory with no config file.""" monkeypatch.chdir(tmp_path) - monkeypatch.delenv("LOG_CONFIG_FILE", raising=False) -@pytest.fixture -def cdm_logger(clean_env: None, empty_cwd: None) -> logging.Logger: # noqa: ARG001 - """Return a CDM logger initialised with LOGGING_CONFIG.""" - return init_logger() +YAML_CONFIG = frozendict( + { + "version": 1, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + } + }, + "loggers": {"root": {"level": "DEBUG", "handlers": ["console"]}}, + } +) + + +JSON_CONFIG = frozendict( + { + "version": 1, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + } + }, + "loggers": {"root": {"level": "WARNING", "handlers": ["console"]}}, + } +) @pytest.fixture -def dlt_logger() -> logging.Logger: - """Register a pre-configured dlt logger in the logging manager.""" - logger = logging.getLogger("dlt") - logger.setLevel(logging.WARNING) - return logger +def yaml_config_file(tmp_path: Path) -> Path: + """Write a minimal valid YAML logging config and return its path.""" + p = tmp_path / "logging.yaml" + p.write_text(yaml.dump(dict(YAML_CONFIG))) + return p @pytest.fixture -def config_in_cwd(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: - """Write a valid config file in a temporary dir and chdir into it, simulating a config found in the CWD.""" - path = _write_config(tmp_path / cdm_logger_module.LOGGING_CONFIG_FILENAME, "config_in_cwd") - monkeypatch.chdir(tmp_path) - monkeypatch.delenv("LOG_CONFIG_FILE", raising=False) - return path +def json_config_file(tmp_path: Path) -> Path: + """Write a minimal valid JSON logging config and return its path.""" + p = tmp_path / "logging.json" + p.write_text(json.dumps(dict(JSON_CONFIG))) + return p @pytest.fixture -def config_at_explicit_path(tmp_path: Path) -> Path: - """Write a valid config file in a temporary directory.""" - return _write_config(tmp_path / "custom_logging_config.json", "config_at_explicit_path") +def settings_with_yaml(yaml_config_file: Path) -> LoggerSettings: + """Logger settings object for the YAML config file.""" + return LoggerSettings(log_config_file=str(yaml_config_file)) -# _load_logging_config — resolution order -@pytest.mark.usefixtures("config_in_cwd") -def test_load_logging_config_uses_explicit_path_first(config_at_explicit_path: Path) -> None: - """Ensure that the config file argument is used in preference to other choices.""" - config = _load_logging_config(config_file=config_at_explicit_path) - assert config["disable_existing_loggers"] is False - assert config["loggers"][DEFAULT_LOGGER_NAME]["handlers"] == ["config_at_explicit_path"] - assert set(config["handlers"]) == {"config_at_explicit_path"} +@pytest.fixture +def settings_with_json(json_config_file: Path) -> LoggerSettings: + """Logger settings object for the json config file.""" + return LoggerSettings(log_config_file=str(json_config_file)) -@pytest.mark.usefixtures("config_in_cwd") -def test_load_logging_config_uses_env_var_over_cwd( - config_at_explicit_path: Path, monkeypatch: pytest.MonkeyPatch -) -> None: - """Ensure that the LOG_CONFIG_FILE env var is used in preference to the LOGGING_CONFIG fallback.""" - monkeypatch.setenv("LOG_CONFIG_FILE", str(config_at_explicit_path)) - config = _load_logging_config() - assert config["loggers"][DEFAULT_LOGGER_NAME]["handlers"] == ["config_at_explicit_path"] - assert set(config["handlers"]) == {"config_at_explicit_path"} +@pytest.fixture +def settings_without_config() -> LoggerSettings: + """Logger settings object with no log config file.""" + return LoggerSettings(log_config_file=None) -@pytest.mark.usefixtures("config_in_cwd") -def test_load_logging_config_uses_cwd_when_no_arg_or_env(monkeypatch: pytest.MonkeyPatch) -> None: - """When no argument or env var is provided, the logging_config.json in the current working directory is loaded.""" - monkeypatch.delenv("LOG_CONFIG_FILE", raising=False) - config = _load_logging_config() - assert config["disable_existing_loggers"] is False - assert config["loggers"][DEFAULT_LOGGER_NAME]["handlers"] == ["config_in_cwd"] - assert set(config["handlers"]) == {"config_in_cwd"} +FILE_CONTENT = frozendict( + { + "valid.json": json.dumps(dict(JSON_CONFIG)), + "valid.yml": yaml.dump(dict(YAML_CONFIG)), + "valid.yaml": yaml.dump({"version": 1, "loggers": {}}), + "invalid.json": "{not valid json", + "invalid.yaml": ": : invalid: yaml: : :", + } +) +# LoggerSettings -@pytest.mark.usefixtures("empty_cwd") -def test_load_logging_config_falls_back_to_frozendict_when_all_sources_fail(caplog: pytest.LogCaptureFixture) -> None: - """Ensure that logging falls back to the default frozendict if no other sources are found.""" - with caplog.at_level(logging.WARNING, logger=MODULE_LOGGER_NAME): - config = _load_logging_config() - assert config == {**LOGGING_CONFIG, "disable_existing_loggers": False} - log_message = caplog.records[-1] - assert log_message.message == "No logging config file found. Falling back to built-in config." +def test_logger_settings_with_log_config_file(yaml_config_file: Path) -> None: + """LoggerSettings stores the log_config_file path passed directly to the constructor.""" + settings = LoggerSettings(log_config_file=str(yaml_config_file)) + assert settings.log_config_file == str(yaml_config_file) -@pytest.mark.usefixtures("config_in_cwd") -def test_load_logging_config_skips_bad_argument_path_and_tries_next( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture -) -> None: - """Ensure that a non-existent or invalid config file is ignored and the next source tried.""" - monkeypatch.delenv("LOG_CONFIG_FILE", raising=False) - with caplog.at_level(logging.DEBUG, logger=MODULE_LOGGER_NAME): - config = _load_logging_config(config_file=tmp_path / "nonexistent.json") - assert config["disable_existing_loggers"] is False - assert any("nonexistent.json" in m for m in caplog.messages) + +@pytest.mark.parametrize("log_config_file_is_none", [True, False]) +def test_logger_settings_with_without_params(log_config_file_is_none: bool) -> None: + """LoggerSettings.log_config_file is None when no value is provided.""" + settings = LoggerSettings(log_config_file=None) if log_config_file_is_none else LoggerSettings() + assert settings.log_config_file is None -@pytest.mark.usefixtures("config_in_cwd") -def test_load_logging_config_skips_bad_env_var_path_and_tries_next( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture +@pytest.mark.parametrize( + "env_var_name", + ["log-config-file", "log_config_file", "LOG_CONFIG_FILE", "LOG-CONFIG-FILE"], +) +def test_logger_settings_alias_accepted_via_environment( + env_var_name: str, monkeypatch: pytest.MonkeyPatch, yaml_config_file: Path +) -> None: + """Both hyphenated and underscored environment variable aliases are resolved to log_config_file.""" + monkeypatch.setenv(env_var_name, str(yaml_config_file)) + settings = LoggerSettings() # pyright: ignore[reportCallIssue] + assert settings.log_config_file == str(yaml_config_file) + + +# _load_config_from_path + + +@pytest.mark.parametrize( + ("file_name", "expected"), + [ + ("logger_config", pytest.raises(ValueError, match=r"Unsupported config file format: logger_config")), + ("logger.toml", pytest.raises(ValueError, match=r"Unsupported config file format: logger.toml")), + ("path/to/logger.cfg", pytest.raises(ValueError, match=r"Unsupported config file format: logger.cfg")), + ("path/to/file.json", pytest.raises(FileNotFoundError, match="No such file or directory")), + ("valid.json", nullcontext(dict(JSON_CONFIG))), + ("valid.yaml", nullcontext({"version": 1, "loggers": {}})), + ("valid.yml", nullcontext(dict(YAML_CONFIG))), + ( + "invalid.json", + pytest.raises(json.decoder.JSONDecodeError, match="Expecting property name enclosed in double quotes"), + ), + ("invalid.yaml", pytest.raises(yaml.YAMLError, match=r"expected , but found \':\'\n")), + ], +) +def test_load_config_from_path_pass_fail( + tmp_path: Path, file_name: str, expected: nullcontext | pytest.RaisesExc ) -> None: - """Ensure that a non-existent or invalid config file is ignored and the next source tried.""" - monkeypatch.setenv("LOG_CONFIG_FILE", str(tmp_path / "nonexistent.json")) - with caplog.at_level(logging.WARNING, logger=MODULE_LOGGER_NAME): - config = _load_logging_config() - assert config["disable_existing_loggers"] is False - assert any("nonexistent.json" in m for m in caplog.messages) + """Test the loading of a config file, both successfully and with failure states.""" + p = tmp_path / file_name + if file_name in FILE_CONTENT: + p.write_text(FILE_CONTENT[file_name]) + + with expected as e: + assert _load_config_from_path(p) == e -@pytest.mark.usefixtures("config_in_cwd") -def test_load_logging_config_skips_invalid_json_and_tries_next( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture +# configure_root_logger_from_dlt +def test_configure_root_logger_from_dlt_configures_dlt_on_first_call( + mock_config_root_logger: MagicMock, mock_dlt_logger: MagicMock ) -> None: - """Ensure that a non-existent or invalid config file is ignored and the next source tried.""" - bad_path = tmp_path / "bad_config.json" - bad_path.write_text("this is not json {{{") - monkeypatch.delenv("LOG_CONFIG_FILE", raising=False) - with caplog.at_level(logging.WARNING, logger=MODULE_LOGGER_NAME): - config = _load_logging_config(config_file=bad_path) - assert config["disable_existing_loggers"] is False - assert any("bad_config.json" in m for m in caplog.messages) + """The first call sets dlt logger propagation, invokes config_root_logger, and sets the guard.""" + # check what is initialised in the way of loggers + assert "dlt" in logging.Logger.manager.loggerDict + configure_root_logger_from_dlt() -def test_load_logging_config_overrides_disable_existing_loggers_when_true_in_file( - tmp_path: Path, caplog: pytest.LogCaptureFixture, request: pytest.FixtureRequest -) -> None: - """If a config file sets disable_existing_loggers to True, _load_logging_config should override it.""" - path = _write_config( - tmp_path / "bad.json", request.node.originalname, {**VALID_JSON_CONFIG, "disable_existing_loggers": True} - ) - with caplog.at_level(logging.WARNING, logger=MODULE_LOGGER_NAME): - config = _load_logging_config(config_file=path) - assert config["disable_existing_loggers"] is False - assert any( - "sets disable_existing_loggers to True. Overriding to prevent existing loggers being silently disabled" in m - for m in caplog.messages - ) - - -@pytest.mark.usefixtures("clean_env") -def test_init_logger_accepts_explicit_config_file(config_at_explicit_path: Path) -> None: - """init_logger should accept a config_file argument and pass it through to _load_logging_config without error.""" - logger = init_logger(config_file=config_at_explicit_path) - assert isinstance(logger, logging.Logger) + mock_config_root_logger.assert_called_once() + dlt_logger = logging.getLogger("dlt") + assert dlt_logger == mock_dlt_logger + assert dlt_logger.propagate is True + assert dlt_logger.handlers == [] + assert cdm_logger_module.ROOT_LOGGER_CONFIGURED is True -@pytest.mark.usefixtures("empty_cwd") -def test_init_logger_uses_log_config_file_env_var( - config_at_explicit_path: Path, monkeypatch: pytest.MonkeyPatch +def test_configure_root_logger_from_dlt_does_not_reconfigure_on_subsequent_calls( + mock_config_root_logger: MagicMock, ) -> None: - """When LOG_CONFIG_FILE is set in the environment and no config_file argument is passed, init_logger should load config from that path.""" - monkeypatch.setenv("LOG_CONFIG_FILE", str(config_at_explicit_path)) - monkeypatch.delenv("LOG_LEVEL", raising=False) - logger = init_logger() - assert isinstance(logger, logging.Logger) - + """Calling configure_root_logger_from_dlt a second time is a no-op; config_root_logger is only called once.""" + configure_root_logger_from_dlt() + configure_root_logger_from_dlt() -def test_init_logger_happy_path(cdm_logger: logging.Logger) -> None: - """init_logger should return a Logger with the CDM default name.""" - assert isinstance(cdm_logger, logging.Logger) - assert cdm_logger.name == DEFAULT_LOGGER_NAME - # calling init_logger more than once returns the same object - assert init_logger() is init_logger() - assert cdm_logger.level == logging.INFO + mock_config_root_logger.assert_called_once() -@pytest.mark.parametrize("level", ["DEBUG", "Info", "warning"]) -@pytest.mark.usefixtures("clean_env", "empty_cwd") -def test_init_logger_explicit_level_argument(level: str) -> None: - """Ensure that the log level can be set explicitly.""" - logger = init_logger(log_level=level) - assert logger.level == getattr(logging, level.upper()) +def test_configure_root_logger_skips_when_guard_already_set(mock_config_root_logger: MagicMock, monkeypatch) -> None: + """When ROOT_LOGGER_CONFIGURED is already True, config_root_logger is never called.""" + monkeypatch.setattr(cdm_logger_module, "ROOT_LOGGER_CONFIGURED", True) + configure_root_logger_from_dlt() -@pytest.mark.usefixtures("clean_env", "empty_cwd") -def test_init_logger_env_var_sets_level(monkeypatch: pytest.MonkeyPatch) -> None: - """When LOG_LEVEL is set in the environment, init_logger should apply it to the logger level.""" - monkeypatch.setenv("LOG_LEVEL", "ERROR") - assert init_logger().level == logging.ERROR + mock_config_root_logger.assert_not_called() -@pytest.mark.usefixtures("clean_env", "empty_cwd") -def test_init_logger_argument_takes_priority_over_env_var(monkeypatch: pytest.MonkeyPatch) -> None: - """An explicit log_level argument should take precedence over the LOG_LEVEL env var when both are set.""" - monkeypatch.setenv("LOG_LEVEL", "ERROR") - assert init_logger(log_level="DEBUG").level == logging.DEBUG +# get_cdm_logger +def test_get_cdm_logger_calls_init_logger_with_args() -> None: + """get_cdm_logger should call init_logger to ensure logging is initialised.""" + settings = LoggerSettings() + with patch("cdm_data_loaders.utils.cdm_logger.init_logger") as mock_init: + logger = get_cdm_logger("test.logger", settings=settings) -def test_init_logger_log_level_gates_emission_correctly( - cdm_logger: logging.Logger, caplog: pytest.LogCaptureFixture -) -> None: - """Messages at or above the configured level should be captured; messages below it should be suppressed.""" - with caplog.at_level(logging.INFO, logger=DEFAULT_LOGGER_NAME): - cdm_logger.info("should appear") - cdm_logger.debug("should not appear") + mock_init.assert_called_once_with(settings) + assert isinstance(logger, logging.Logger) + assert logger.name == "test.logger" - assert "should appear" in caplog.messages - assert "should not appear" not in caplog.messages +def test_get_cdm_logger_without_settings_passes_none_to_init_logger() -> None: + """get_cdm_logger called with no settings argument should pass None to init_logger.""" + with patch("cdm_data_loaders.utils.cdm_logger.init_logger") as mock_init: + logger = get_cdm_logger("another.logger") -# console -def test_init_logger_console_handler_configured_correctly(cdm_logger: logging.Logger) -> None: - """The logger should have a StreamHandler at INFO level by default. + mock_init.assert_called_once_with(None) + assert isinstance(logger, logging.Logger) + assert logger.name == "another.logger" - No RotatingFileHandler should be attached unless explicitly requested. - """ - stream_handlers = [h for h in cdm_logger.handlers if type(h) is logging.StreamHandler] - assert len(stream_handlers) == 1 - assert stream_handlers[0].level == logging.INFO - rotating_handlers = [h for h in cdm_logger.handlers if isinstance(h, logging.handlers.RotatingFileHandler)] - assert rotating_handlers == [] +def test_get_cdm_logger_returns_logger_with_correct_name( + settings_with_yaml: LoggerSettings, mock_config_root_logger: MagicMock +) -> None: + """The returned object is a Logger whose name matches the argument passed in.""" + result = get_cdm_logger("my.service", settings=settings_with_yaml) -@pytest.mark.usefixtures("clean_env", "empty_cwd") -def test_get_cdm_logger_creates_cdm_logger_when_none_exists() -> None: - """When neither a dlt nor a CDM logger exists, get_cdm_logger should initialise and return a new CDM logger.""" - logger = get_cdm_logger() - assert isinstance(logger, logging.Logger) - assert logger.name == DEFAULT_LOGGER_NAME - assert DEFAULT_LOGGER_NAME in logging.root.manager.loggerDict - # get_cdm_logger should returns the existing logger rather than creating a new one. - assert get_cdm_logger() is init_logger() + assert isinstance(result, logging.Logger) + assert result.name == "my.service" -@pytest.mark.usefixtures("clean_env", "empty_cwd", "dlt_logger") -def test_get_cdm_logger_prefers_dlt_over_cdm_logger() -> None: - """When a dlt logger is present in the logging manager, get_cdm_logger should return it even if a CDM logger also exists.""" - init_logger() - assert get_cdm_logger().name == "dlt" +def test_get_cdm_logger_uses_yaml_config_when_provided( + settings_with_yaml: LoggerSettings, mock_config_root_logger: MagicMock +) -> None: + """When settings point to a YAML file, dictConfig receives the parsed YAML content.""" + with patch("cdm_data_loaders.utils.cdm_logger.logging.config.dictConfig") as mock_dict_config: + get_cdm_logger("svc", settings=settings_with_yaml) + applied_config = mock_dict_config.call_args[0][0] + assert applied_config["disable_existing_loggers"] is False + assert applied_config["loggers"]["root"]["level"] == "DEBUG" -@pytest.mark.parametrize("level", ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) -def test_set_level_safe_accepts_all_valid_levels(cdm_logger: logging.Logger, level: str) -> None: - """_set_level_safe should accept all standard logging level strings.""" - _set_level_safe(cdm_logger, level) - assert cdm_logger.level == getattr(logging, level) +def test_get_cdm_logger_uses_json_config_when_provided( + settings_with_json: LoggerSettings, mock_config_root_logger: MagicMock +) -> None: + """When settings point to a JSON file, dictConfig receives the parsed JSON content.""" + with patch("cdm_data_loaders.utils.cdm_logger.logging.config.dictConfig") as mock_dict_config: + get_cdm_logger("svc", settings=settings_with_json) -@pytest.mark.parametrize("level", ["debug", "Info", "wARNING"]) -def test_set_level_safe_is_case_insensitive(cdm_logger: logging.Logger, level: str) -> None: - """Ensure that _set_level_safe normalises case issues.""" - _set_level_safe(cdm_logger, level) - assert cdm_logger.level == getattr(logging, level.upper()) + applied_config = mock_dict_config.call_args[0][0] + assert applied_config["loggers"]["root"]["level"] == "WARNING" -def test_set_level_safe_raises_descriptive_error_for_invalid_level(cdm_logger: logging.Logger) -> None: - """_set_level_safe should raise a ValueError for an unrecognised level string. +def test_get_cdm_logger_falls_back_to_builtin_config_when_no_settings( + mock_config_root_logger: MagicMock, caplog: pytest.LogCaptureFixture +) -> None: + """Passing settings=None applies the built-in INFO config and emits a warning.""" + with ( + patch("cdm_data_loaders.utils.cdm_logger.logging.config.dictConfig") as mock_dict_config, + caplog.at_level(logging.WARNING, logger="cdm_data_loaders.utils.cdm_logger"), + ): + get_cdm_logger("svc", settings=None) - The error message should echo back the bad value and list the valid options. - """ - with pytest.raises(ValueError, match=r"Invalid log level 'VERBOS'\. Must be one of"): - _set_level_safe(cdm_logger, "VERBOS") + applied_config = mock_dict_config.call_args[0][0] + assert applied_config["loggers"]["root"]["level"] == "INFO" + assert any("Falling back to built-in config" in m for m in caplog.messages) -# file handler -def test_attach_file_handler_adds_correctly_configured_handler(cdm_logger: logging.Logger, tmp_path: Path) -> None: - """_attach_file_handler should add a single RotatingFileHandler with a log file at the specified path.""" - log_path = tmp_path / LOG_FILENAME - _attach_file_handler(cdm_logger, log_path) +def test_get_cdm_logger_falls_back_to_builtin_config_when_log_config_file_is_none( + settings_without_config: LoggerSettings, mock_config_root_logger: MagicMock, caplog: pytest.LogCaptureFixture +) -> None: + """Settings with log_config_file=None applies the built-in INFO config and emits a warning.""" + with ( + patch("cdm_data_loaders.utils.cdm_logger.logging.config.dictConfig") as mock_dict_config, + caplog.at_level(logging.WARNING, logger="cdm_data_loaders.utils.cdm_logger"), + ): + get_cdm_logger("svc", settings=settings_without_config) - rotating_handlers = [h for h in cdm_logger.handlers if isinstance(h, logging.handlers.RotatingFileHandler)] - assert len(rotating_handlers) == 1 + applied_config = mock_dict_config.call_args[0][0] + assert applied_config["loggers"]["root"]["level"] == "INFO" + assert any("Falling back to built-in config" in m for m in caplog.messages) - handler = rotating_handlers[0] - assert handler.maxBytes == MAX_LOG_FILE_SIZE - assert handler.backupCount == MAX_LOG_BACKUPS - assert handler.formatter._fmt == JSON_LOG_CONFIG # noqa: SLF001 - assert log_path.exists() +def test_get_cdm_logger_disable_existing_loggers_always_set_false( + settings_with_yaml: LoggerSettings, mock_config_root_logger: MagicMock +) -> None: + """disable_existing_loggers is always injected as False to prevent silently killing pre-existing loggers.""" + with patch("cdm_data_loaders.utils.cdm_logger.logging.config.dictConfig") as mock_dict_config: + get_cdm_logger("svc", settings=settings_with_yaml) - # try attaching a second handler - _attach_file_handler(cdm_logger, log_path) + applied_config = mock_dict_config.call_args[0][0] + assert applied_config["disable_existing_loggers"] is False - assert len([h for h in cdm_logger.handlers if isinstance(h, logging.handlers.RotatingFileHandler)]) == 1 +def test_get_cdm_logger_calls_configure_root_logger_from_dlt( + settings_with_yaml: LoggerSettings, mock_config_root_logger: MagicMock +) -> None: + """get_cdm_logger always delegates to configure_root_logger_from_dlt after applying config.""" + with patch("cdm_data_loaders.utils.cdm_logger.configure_root_logger_from_dlt") as mock_configure: + get_cdm_logger("svc", settings=settings_with_yaml) -def test_attach_file_handler_creates_missing_parent_directory(cdm_logger: logging.Logger, tmp_path: Path) -> None: - """_attach_file_handler should create the parent directory of the log file path if it does not exist.""" - log_path = tmp_path / "nested" / "dirs" / LOG_FILENAME - assert not log_path.parent.exists() - _attach_file_handler(cdm_logger, log_path) - assert log_path.parent.exists() - assert log_path.exists() + mock_configure.assert_called_once() -def test_attach_file_handler_detects_any_file_handler_type(cdm_logger: logging.Logger, tmp_path: Path) -> None: - """_attach_file_handler should not add a second file handler.""" - existing = logging.FileHandler(tmp_path / "other.log") - cdm_logger.addHandler(existing) +def test_get_cdm_logger_dlt_already_initialised_does_not_reconfigure( + settings_with_yaml: LoggerSettings, mock_config_root_logger: MagicMock +) -> None: + """When the dlt logger is already initialised, repeated calls to get_cdm_logger do not re-invoke config_root_logger.""" + cdm_logger_module.ROOT_LOGGER_CONFIGURED = True - _attach_file_handler(cdm_logger, tmp_path / LOG_FILENAME) + get_cdm_logger("svc", settings=settings_with_yaml) + get_cdm_logger("svc", settings=settings_with_yaml) - file_handlers = [h for h in cdm_logger.handlers if "FileHandler" in type(h).__name__] - assert len(file_handlers) == 1 + mock_config_root_logger.assert_not_called() -@pytest.mark.parametrize("env_value", ["true", "TRUE", "True"]) -@pytest.mark.usefixtures("clean_env") -def test_init_logger_file_handler_added_when_requested( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, env_value: str +def test_get_cdm_logger_dlt_not_yet_initialised_configures_on_first_call( + settings_with_yaml: LoggerSettings, mock_config_root_logger: MagicMock ) -> None: - """A file handler should be attached when enable_file_logging=True or ENABLE_FILE_LOGGING env var is set.""" - log_path = tmp_path / LOG_FILENAME - - logger_arg = init_logger(enable_file_logging=True, log_file=log_path) - assert any("FileHandler" in type(h).__name__ for h in logger_arg.handlers) + """When the dlt logger has not yet been initialised, the first call to get_cdm_logger configures it and sets the guard.""" + assert cdm_logger_module.ROOT_LOGGER_CONFIGURED is False - logger_arg.handlers.clear() - logging.root.manager.loggerDict.pop(DEFAULT_LOGGER_NAME, None) + get_cdm_logger("svc", settings=settings_with_yaml) - monkeypatch.setenv("ENABLE_FILE_LOGGING", env_value) - logger_env = init_logger(log_file=log_path) - assert any("FileHandler" in type(h).__name__ for h in logger_env.handlers) + mock_config_root_logger.assert_called_once() + assert cdm_logger_module.ROOT_LOGGER_CONFIGURED is True From f88249cdc36a3ed70aa9c448f7c692bc4e3cf48f Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Thu, 18 Jun 2026 15:01:15 -0700 Subject: [PATCH 10/12] fixing extra tab and removing get_cdm_logger call --- Dockerfile | 3 +-- src/cdm_data_loaders/utils/spark_delta.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 01f5c1e9..d4f7760c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,6 @@ RUN --mount=type=cache,target=/root/.cache/uv \ # Then, add the rest of the project source code and install it # Installing separately from its dependencies allows optimal layer caching -# COPY --chown=nonroot:nonroot . /app COPY --chown=nonroot:nonroot .dlt /app/.dlt COPY --chown=nonroot:nonroot docs /app/docs COPY --chown=nonroot:nonroot scripts /app/scripts @@ -68,7 +67,7 @@ COPY --chown=nonroot:nonroot uv.lock /app/uv.lock RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked --no-editable - # Place executables in the environment at the front of the path +# Place executables in the environment at the front of the path ENV PATH="/app/.venv/bin:$PATH" RUN chmod +x ./scripts/entrypoint.sh diff --git a/src/cdm_data_loaders/utils/spark_delta.py b/src/cdm_data_loaders/utils/spark_delta.py index 113347d8..1479da2c 100644 --- a/src/cdm_data_loaders/utils/spark_delta.py +++ b/src/cdm_data_loaders/utils/spark_delta.py @@ -1,12 +1,13 @@ """Utilities for interacting with Spark and delta tables.""" +from logging import Logger, getLogger + from berdl_notebook_utils.setup_spark_session import get_spark_session from berdl_notebook_utils.spark.database import create_namespace_if_not_exists from pyspark.sql import DataFrame, DataFrameWriter, SparkSession -from cdm_data_loaders.utils.cdm_logger import get_cdm_logger +logger: Logger = getLogger(__name__) -logger = get_cdm_logger() APPEND = "append" OVERWRITE = "overwrite" From 3132682f141aefae993ebb6ddd34ae4508845977 Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Thu, 18 Jun 2026 15:26:57 -0700 Subject: [PATCH 11/12] Version bump to 0.1.10 --- CHANGELOG.md | 6 +++ pyproject.toml | 2 +- uv.lock | 137 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b732f52..24f70e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CDM Data Loaders Changelog - [CDM Data Loaders Changelog](#cdm-data-loaders-changelog) + - [v0.1.10](#v0110) - [v0.1.9](#v019) - [v0.1.8](#v018) - [v0.1.7](#v017) @@ -13,6 +14,11 @@ - [v0.1.0](#v010) +### v0.1.10 + +- Add in the ability to configure loggers using a file +- Bugfix for UniProt pipeline + ### v0.1.9 - Splitting out NCBI REST API pipeline into annotation and dataset components. diff --git a/pyproject.toml b/pyproject.toml index 3d92bcac..1cf83055 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "click>=8.4.1", "defusedxml>=0.7.1", "delta-spark>=4.2.0", - "dlt[deltalake,duckdb,filesystem,parquet,pyiceberg]>=1.27.2", + "dlt[deltalake,duckdb,filesystem,hub,parquet,pyiceberg]>=1.27.2", "frictionless[aws]>=5.19.0", "frozendict>=2.4.7", "ipykernel>=7.2.0", diff --git a/uv.lock b/uv.lock index 7dc078e2..81d4e0d8 100644 --- a/uv.lock +++ b/uv.lock @@ -492,7 +492,7 @@ dependencies = [ { name = "click" }, { name = "defusedxml" }, { name = "delta-spark" }, - { name = "dlt", extra = ["deltalake", "duckdb", "filesystem", "parquet", "pyiceberg"] }, + { name = "dlt", extra = ["deltalake", "duckdb", "filesystem", "hub", "parquet", "pyiceberg"] }, { name = "frictionless", extra = ["aws"] }, { name = "frozendict" }, { name = "ipykernel" }, @@ -530,7 +530,7 @@ requires-dist = [ { name = "click", specifier = ">=8.4.1" }, { name = "defusedxml", specifier = ">=0.7.1" }, { name = "delta-spark", specifier = ">=4.2.0" }, - { name = "dlt", extras = ["deltalake", "duckdb", "filesystem", "parquet", "pyiceberg"], specifier = ">=1.27.2" }, + { name = "dlt", extras = ["deltalake", "duckdb", "filesystem", "hub", "parquet", "pyiceberg"], specifier = ">=1.27.2" }, { name = "frictionless", extras = ["aws"], specifier = ">=5.19.0" }, { name = "frozendict", specifier = ">=2.4.7" }, { name = "ipykernel", specifier = ">=7.2.0" }, @@ -813,6 +813,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, ] +[[package]] +name = "cron-descriptor" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/75/b44b05ae7d1e49b59a27a917c44e04ce9aa4cebdd05dac42ba7df06a91b4/cron_descriptor-2.1.0.tar.gz", hash = "sha256:ecddb8b2f6c5286398949aaefe185364666af74f33b01877c61378e1fd4e38e6", size = 50221, upload-time = "2026-06-02T16:26:28.223Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/41/c476c41cb88dbbba83e0685dd331b9d32599f1e4a94ca223b02437a3d83b/cron_descriptor-2.1.0-py3-none-any.whl", hash = "sha256:e280efae0e375e2cbc62846f833888f12c7921d2d11ab0dca598f94c51af8639", size = 74734, upload-time = "2026-06-02T16:26:26.919Z" }, +] + +[[package]] +name = "croniter" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, +] + [[package]] name = "cryptography" version = "49.0.0" @@ -1021,6 +1045,10 @@ filesystem = [ { name = "botocore" }, { name = "s3fs" }, ] +hub = [ + { name = "dlthub" }, + { name = "dlthub-client" }, +] parquet = [ { name = "pyarrow" }, ] @@ -1031,6 +1059,38 @@ pyiceberg = [ { name = "sqlalchemy" }, ] +[[package]] +name = "dlthub" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-jose" }, + { name = "ruamel-yaml" }, + { name = "sqlglot" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/f1/16f1c80212f46d244bf71de6ea4a97a89abd8b5c04e847d398ba094a7495/dlthub-0.27.0.tar.gz", hash = "sha256:60ac1cf07956efcc5115f84e858416c266facf95bfd17ca8b5c6d3bb11891aeb", size = 170995, upload-time = "2026-05-18T15:57:20.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/44/0841ea6b10191bc6338bdf51e600563ad56e95a61d08e03fb4d1f84e9d18/dlthub-0.27.0-py3-none-any.whl", hash = "sha256:f5b156afdb5240fbace44461fb099fa64557ca19a30aeb55a0f21ba655720667", size = 228555, upload-time = "2026-05-18T15:57:18.813Z" }, +] + +[[package]] +name = "dlthub-client" +version = "0.27.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cron-descriptor" }, + { name = "croniter" }, + { name = "httpx" }, + { name = "pathspec" }, + { name = "pyjwt" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/28/a8b3a1b3a5557ca0bf819fce52697ccdb9731ea73bda96b352a1f65ef3f1/dlthub_client-0.27.7.tar.gz", hash = "sha256:85ee09a03e4c4a505c1d12fd1f514d03bd26e1cec74d7b6b1f6397e926577177", size = 132851, upload-time = "2026-06-11T19:43:57.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/bd/026ee4fa2d489a9aaf8cf058ed15173baa2d4cec1cfaccefe42f80fdd1f9/dlthub_client-0.27.7-py3-none-any.whl", hash = "sha256:35b32a8350bab9feed00c1ed1cb5349b7819ba0fd84dfcd71e561db54550fb3e", size = 296295, upload-time = "2026-06-11T19:43:56.71Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -1071,6 +1131,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/82/4d52f3f9f9703a226b26b80bdae3f6905aeefe5221bf1815fc93ff02ca25/duckdb-1.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:0f8722346024e5d9f02b58bf7b0491a629f97fdc8a04a10e432940f471ee387a", size = 14449863, upload-time = "2026-06-17T10:48:50.18Z" }, ] +[[package]] +name = "ecdsa" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/ca/8de7744cb3bc966c85430ca2d0fcaeea872507c6a4cf6e007f7fe269ed9d/ecdsa-0.19.2.tar.gz", hash = "sha256:62635b0ac1ca2e027f82122b5b81cb706edc38cd91c63dda28e4f3455a2bf930", size = 202432, upload-time = "2026-03-26T09:58:17.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/79/119091c98e2bf49e24ed9f3ae69f816d715d2904aefa6a2baa039a2ba0b0/ecdsa-0.19.2-py2.py3-none-any.whl", hash = "sha256:840f5dc5e375c68f36c1a7a5b9caad28f95daa65185c9253c0c08dd952bb7399", size = 150818, upload-time = "2026-03-26T09:58:15.808Z" }, +] + [[package]] name = "elementpath" version = "5.1.2" @@ -2675,6 +2747,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +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 = "pathvalidate" version = "3.3.1" @@ -2731,7 +2812,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess" }, + { name = "ptyprocess", marker = "(platform_python_implementation != 'PyPy' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -2969,6 +3050,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, ] +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -3339,6 +3429,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] +[[package]] +name = "python-jose" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ecdsa" }, + { name = "pyasn1" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" }, +] + [[package]] name = "python-multipart" version = "0.0.32" @@ -3731,6 +3835,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, ] +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, +] + [[package]] name = "ruff" version = "0.15.17" @@ -3935,11 +4060,11 @@ wheels = [ [[package]] name = "sqlglot" -version = "30.11.0" +version = "28.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/de/1542b04dcac0f85706fb8ce1ce7d2abcc230cdf10ea9e3aa7345393e5a90/sqlglot-30.11.0.tar.gz", hash = "sha256:1a23c6e2adb41da61fda46b1848d2fa26341d447fc0f0cd5ca21160362100991", size = 5893125, upload-time = "2026-06-11T17:11:37.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/8d/9ce5904aca760b81adf821c77a1dcf07c98f9caaa7e3b5c991c541ff89d2/sqlglot-28.0.0.tar.gz", hash = "sha256:cc9a651ef4182e61dac58aa955e5fb21845a5865c6a4d7d7b5a7857450285ad4", size = 5520798, upload-time = "2025-11-17T10:34:57.016Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/86/53edf106e8cd3c883ccd0c6b470bf00ddf877a86e667665343b2d597329d/sqlglot-30.11.0-py3-none-any.whl", hash = "sha256:cffdee57d1f2f5472dc9f13087e618cf795841172b7d5ef78b63a051a52d2710", size = 698721, upload-time = "2026-06-11T17:11:35.737Z" }, + { url = "https://files.pythonhosted.org/packages/56/6d/86de134f40199105d2fee1b066741aa870b3ce75ee74018d9c8508bbb182/sqlglot-28.0.0-py3-none-any.whl", hash = "sha256:ac1778e7fa4812f4f7e5881b260632fc167b00ca4c1226868891fb15467122e4", size = 536127, upload-time = "2025-11-17T10:34:55.192Z" }, ] [[package]] From 85dbb7585691ce012588a9a2780f1f2f91916306 Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Thu, 18 Jun 2026 15:28:33 -0700 Subject: [PATCH 12/12] Version bump to 0.1.10 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1cf83055..21e83f85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cdm-data-loaders" -version = "0.1.9" +version = "0.1.10" description = "Data loaders and wranglers for the CDM." requires-python = ">= 3.13" readme = "README.md" diff --git a/uv.lock b/uv.lock index 81d4e0d8..4880b60f 100644 --- a/uv.lock +++ b/uv.lock @@ -484,7 +484,7 @@ wheels = [ [[package]] name = "cdm-data-loaders" -version = "0.1.9" +version = "0.1.10" source = { editable = "." } dependencies = [ { name = "bioregistry" },