diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3919e31..4126cee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.9", "3.13"] + python-version: ["3.11", "3.14"] os: [ubuntu-22.04] #, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version: "3.14" - name: Install dependencies run: | python -m pip install --upgrade pip @@ -47,7 +47,7 @@ jobs: - name: Run mypy run: poetry run mypy sentieon_cli --exclude scripts - name: Run flake8 - run: poetry run flake8 . --extend-ignore E231,E221, --exclude .github/scripts/license_message.py,sentieon_cli/scripts/gvcf_combine.py,sentieon_cli/scripts/vcf_mod.py,sentieon_cli/scripts/hybrid_anno.py,sentieon_cli/scripts/hybrid_select.py,sentieon_cli/archive.py,sentieon_cli/scripts/trimalt.py,playground,tests # false+ from python 3.12 + run: poetry run flake8 . - name: Run the automated tests run: poetry run pytest -v - name: Run doct tests diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8ca818..0e5e624 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,8 +14,8 @@ jobs: fail-fast: true max-parallel: 1 matrix: - python-version: ["3.9", "3.13"] - sentieon-version: ["202503.01"] + python-version: ["3.11", "3.14"] + sentieon-version: ["202503.02"] os: [ubuntu-22.04] #, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -97,8 +97,8 @@ jobs: fail-fast: true max-parallel: 1 matrix: - python-version: ["3.9", "3.13"] - sentieon-version: ["202503.01"] + python-version: ["3.11", "3.14"] + sentieon-version: ["202503.02"] os: [ubuntu-22.04] #, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -219,8 +219,8 @@ jobs: fail-fast: true max-parallel: 1 matrix: - python-version: ["3.9", "3.13"] - sentieon-version: ["202503.01"] + python-version: ["3.11", "3.14"] + sentieon-version: ["202503.02"] os: [ubuntu-22.04] #, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6a9085..3b02b53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.13" + python-version: "3.14" - name: Install dependencies run: | python -m pip install --upgrade pip @@ -29,4 +29,4 @@ jobs: poetry build -f sdist gh release upload ${{github.event.release.tag_name}} dist/*.tar.gz env: - GITHUB_TOKEN: ${{ github.TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ github.TOKEN }} diff --git a/README.md b/README.md index 93b1e68..49642e9 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ A command-line interface for the Sentieon software Download the latest tar.gz file from the GitHub release page, https://github.com/sentieon/sentieon-cli/releases/ and install the package with pip: ```sh -curl -LO https://github.com/Sentieon/sentieon-cli/releases/download/v1.5.2/sentieon_cli-1.5.2.tar.gz -pip install sentieon_cli-1.5.2.tar.gz +curl -LO https://github.com/Sentieon/sentieon-cli/releases/download/v1.6.0/sentieon_cli-1.6.0.tar.gz +pip install sentieon_cli-1.6.0.tar.gz ``` ## Installation with Poetry diff --git a/poetry.lock b/poetry.lock index bcd3313..96c75cf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,62 +1,66 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand. [[package]] name = "black" -version = "24.8.0" +version = "26.3.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev", "test"] files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"}, + {file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"}, + {file = "black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac"}, + {file = "black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a"}, + {file = "black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a"}, + {file = "black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff"}, + {file = "black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c"}, + {file = "black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5"}, + {file = "black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e"}, + {file = "black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5"}, + {file = "black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1"}, + {file = "black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f"}, + {file = "black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7"}, + {file = "black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983"}, + {file = "black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb"}, + {file = "black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54"}, + {file = "black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f"}, + {file = "black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56"}, + {file = "black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839"}, + {file = "black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2"}, + {file = "black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78"}, + {file = "black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568"}, + {file = "black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f"}, + {file = "black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c"}, + {file = "black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1"}, + {file = "black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b"}, + {file = "black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" -pathspec = ">=0.9.0" +pathspec = ">=1.0.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} +pytokens = ">=0.4.0,<0.5.0" [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""] [[package]] name = "click" -version = "8.1.8" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["dev", "test"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -68,96 +72,265 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev", "test"] +groups = ["dev", "test"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "sys_platform == \"win32\"", dev = "sys_platform == \"win32\" or platform_system == \"Windows\"", test = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] -name = "colorlog" -version = "6.9.0" -description = "Add colours to the output of Python's logging module." +name = "flake8" +version = "7.3.0" +description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = ">=3.6" -groups = ["main"] +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff"}, - {file = "colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2"}, + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -development = ["black", "flake8", "mypy", "pytest", "types-colorama"] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" [[package]] -name = "exceptiongroup" -version = "1.3.0" -description = "Backport of PEP 654 (exception groups)" +name = "flake8-pyproject" +version = "1.2.4" +description = "Flake8 plug-in loading the configuration from pyproject.toml" optional = false -python-versions = ">=3.7" -groups = ["dev", "test"] -markers = "python_version < \"3.11\"" +python-versions = ">=3.6" +groups = ["dev"] files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, + {file = "flake8_pyproject-1.2.4-py3-none-any.whl", hash = "sha256:ea34c057f9a9329c76d98723bb2bb498cc6ba8ff9872c4d19932d48c91249a77"}, ] [package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} +Flake8 = ">=5" [package.extras] -test = ["pytest (>=6)"] +dev = ["Flit (>=3.4)", "pyTest (>=7)", "pyTest-cov (>=7) ; python_version >= \"3.10\""] [[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.6.1" -groups = ["dev"] +python-versions = ">=3.10" +groups = ["dev", "test"] files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - [[package]] -name = "importlib-resources" -version = "5.13.0" -description = "Read resources from Python packages" +name = "levenshtein" +version = "0.27.3" +description = "Python extension for computing string edit distances and similarities." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "importlib_resources-5.13.0-py3-none-any.whl", hash = "sha256:9f7bd0c97b79972a6cce36a366356d16d5e13b09679c11a58f1014bfdf8e64b2"}, - {file = "importlib_resources-5.13.0.tar.gz", hash = "sha256:82d5c6cca930697dbbd86c93333bb2c2e72861d4789a11c2662b933e5ad2b528"}, + {file = "levenshtein-0.27.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d61eff70799fd5e710625da8a13e5adabd62bfd9f70abb9c531af6cad458cd27"}, + {file = "levenshtein-0.27.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:477efed87edf72ad0d3870038479ed2f63020a42e69c6a38a32a550e51f8e70e"}, + {file = "levenshtein-0.27.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ef99b9827d7d1100fc4398ac5522bd56766b894561c0cbdea0a01b93f24e642"}, + {file = "levenshtein-0.27.3-cp310-cp310-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9091e8ca9fff6088836abf372f8871fb480e44603defa526e1c3ae2f1d70acc5"}, + {file = "levenshtein-0.27.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ffdb2329712c5595eda3532a4f701f87f6c73a0f7aaac240681bf0b54310d63"}, + {file = "levenshtein-0.27.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:35856330eac1b968b45a5abbc4a3d14279bd9d1224be727cb1aac9ac4928a419"}, + {file = "levenshtein-0.27.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:5377e237f6a13f5b0618621cca7992848993470c011716c3ad09cdf19c3b13ab"}, + {file = "levenshtein-0.27.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e30614186eb5c43833b62ae7d893a116b88373eec8cf3f3d62ba51aa5962d8ea"}, + {file = "levenshtein-0.27.3-cp310-cp310-win32.whl", hash = "sha256:5499342fd6b003bd5abc28790c7b333884838f7fd8c50570a6520bbaf5e2a35b"}, + {file = "levenshtein-0.27.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e2792730388bec6a85d4d3e3a9b53b8a4b509722bea1a78a39a1a0a7d8f0e13"}, + {file = "levenshtein-0.27.3-cp310-cp310-win_arm64.whl", hash = "sha256:8a2a274b55562a49c6e9dadb16d05f6c27ffa98906b55d5c122893457ca6e464"}, + {file = "levenshtein-0.27.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:245b6ffb6e1b0828cafbce35c500cb3265d0962c121d090669f177968c5a2980"}, + {file = "levenshtein-0.27.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f44c98fa23f489eb7b2ad87d5dd24b6a784434bb5edb73f6b0513309c949690"}, + {file = "levenshtein-0.27.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f5f85a1fc96dfc147bba82b4c67d6346ea26c27ef77a6a9de689118e26dddbe"}, + {file = "levenshtein-0.27.3-cp311-cp311-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:18ceddd38d0e990d2c1c9b72f3e191dace87e2f8f0446207ce9e9cd2bfdfc8a1"}, + {file = "levenshtein-0.27.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:222b81adca29ee4128183328c6e1b25a48c817d14a008ab49e74be9df963b293"}, + {file = "levenshtein-0.27.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee3769ab6e89c24f901e6b7004100630e86721464d7d0384860a322d7953d3a5"}, + {file = "levenshtein-0.27.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:03eba8fda9f3f2b4b0760263fa20b20a90ab00cbeeab4d0d9d899b4f77912b0a"}, + {file = "levenshtein-0.27.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c870b19e2d5c7bc7f16213cc10312b82d873a4d46e1c6d51857a12ef39a76552"}, + {file = "levenshtein-0.27.3-cp311-cp311-win32.whl", hash = "sha256:1987622e9b8ba2ae47dc27469291da1f58462660fa34f4358e9d9c1830fb1355"}, + {file = "levenshtein-0.27.3-cp311-cp311-win_amd64.whl", hash = "sha256:a2b2aa81851e01bb09667b07e80c3fbf0f5a7c6ee9cd80caf43cce705e65832a"}, + {file = "levenshtein-0.27.3-cp311-cp311-win_arm64.whl", hash = "sha256:a084b335c54def1aef9a594b7163faa44dd00056323808bab783f43d8e4c1395"}, + {file = "levenshtein-0.27.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2de7f095b0ca8e44de9de986ccba661cd0dec3511c751b499e76b60da46805e9"}, + {file = "levenshtein-0.27.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9b8b29e5d5145a3c958664c85151b1bb4b26e4ca764380b947e6a96a321217c"}, + {file = "levenshtein-0.27.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc975465a51b1c5889eadee1a583b81fba46372b4b22df28973e49e8ddb8f54a"}, + {file = "levenshtein-0.27.3-cp312-cp312-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:57573ed885118554770979fdee584071b66103f6d50beddeabb54607a1213d81"}, + {file = "levenshtein-0.27.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23aff800a6dd5d91bb3754a6092085aa7ad46b28e497682c155c74f681cfaa2d"}, + {file = "levenshtein-0.27.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c08a952432b8ad9dccb145f812176db94c52cda732311ddc08d29fd3bf185b0a"}, + {file = "levenshtein-0.27.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3bfcb2d78ab9cc06a1e75da8fcfb7a430fe513d66cfe54c07e50f32805e5e6db"}, + {file = "levenshtein-0.27.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7235f6dcb31a217247468295e2dd4c6c1d3ac81629dc5d355d93e1a5f4c185"}, + {file = "levenshtein-0.27.3-cp312-cp312-win32.whl", hash = "sha256:ea80d70f1d18c161a209be556b9094968627cbaae620e102459ef9c320a98cbb"}, + {file = "levenshtein-0.27.3-cp312-cp312-win_amd64.whl", hash = "sha256:fbaa1219d9b2d955339a37e684256a861e9274a3fe3a6ee1b8ea8724c3231ed9"}, + {file = "levenshtein-0.27.3-cp312-cp312-win_arm64.whl", hash = "sha256:2edbaa84f887ea1d9d8e4440af3fdda44769a7855d581c6248d7ee51518402a8"}, + {file = "levenshtein-0.27.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e55aa9f9453fd89d4a9ff1f3c4a650b307d5f61a7eed0568a52fbd2ff2eba107"}, + {file = "levenshtein-0.27.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae4d484453c48939ecd01c5c213530c68dd5cd6e5090f0091ef69799ec7a8a9f"}, + {file = "levenshtein-0.27.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d18659832567ee387b266be390da0de356a3aa6cf0e8bc009b6042d8188e131f"}, + {file = "levenshtein-0.27.3-cp313-cp313-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027b3d142cc8ea2ab4e60444d7175f65a94dde22a54382b2f7b47cc24936eb53"}, + {file = "levenshtein-0.27.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdca6989368cc64f347f0423c528520f12775b812e170a0eb0c10e4c9b0f3ff"}, + {file = "levenshtein-0.27.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa00ab389386032b02a1c9050ec3c6aa824d2bbcc692548fdc44a46b71c058c6"}, + {file = "levenshtein-0.27.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:691c9003c6c481b899a5c2f72e8ce05a6d956a9668dc75f2a3ce9f4381a76dc6"}, + {file = "levenshtein-0.27.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12f7fc8bf0c24492fe97905348e020b55b9fc6dbaab7cd452566d1a466cb5e15"}, + {file = "levenshtein-0.27.3-cp313-cp313-win32.whl", hash = "sha256:9f4872e4e19ee48eed39f214eea4eca42e5ef303f8a4a488d8312370674dbf3a"}, + {file = "levenshtein-0.27.3-cp313-cp313-win_amd64.whl", hash = "sha256:83aa2422e9a9af2c9d3e56a53e3e8de6bae58d1793628cae48c4282577c5c2c6"}, + {file = "levenshtein-0.27.3-cp313-cp313-win_arm64.whl", hash = "sha256:d4adaf1edbcf38c3f2e290b52f4dcb5c6deff20308c26ef1127a106bc2d23e9f"}, + {file = "levenshtein-0.27.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:272e24764b8210337b65a1cfd69ce40df5d2de1a3baf1234e7f06d2826ba2e7a"}, + {file = "levenshtein-0.27.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:329a8e748a4e14d56daaa11f07bce3fde53385d05bad6b3f6dd9ee7802cdc915"}, + {file = "levenshtein-0.27.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5fea1a9c6b9cc8729e467e2174b4359ff6bac27356bb5f31898e596b4ce133a"}, + {file = "levenshtein-0.27.3-cp313-cp313t-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3a61aa825819b6356555091d8a575d1235bd9c3753a68316a261af4856c3b487"}, + {file = "levenshtein-0.27.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51de7a514e8183f0a82f2947d01b014d2391426543b1c076bf5a26328cec4e4"}, + {file = "levenshtein-0.27.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53cbf726d6e92040c9be7e594d959d496bd62597ea48eba9d96105898acbeafe"}, + {file = "levenshtein-0.27.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:191b358afead8561c4fcfed22f83c13bb6c8da5f5789e277f0c5aa1c45ca612f"}, + {file = "levenshtein-0.27.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ba1318d0635b834b8f0397014a7c43f007e65fce396a47614780c881bdff828b"}, + {file = "levenshtein-0.27.3-cp313-cp313t-win32.whl", hash = "sha256:8dd9e1db6c3b35567043e155a686e4827c4aa28a594bd81e3eea84d3a1bd5875"}, + {file = "levenshtein-0.27.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7813ecdac7a6223264ebfea0c8d69959c43d21a99694ef28018d22c4265c2af6"}, + {file = "levenshtein-0.27.3-cp313-cp313t-win_arm64.whl", hash = "sha256:8f05a0d23d13a6f802c7af595d0e43f5b9b98b6ed390cec7a35cb5d6693b882b"}, + {file = "levenshtein-0.27.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a6728bfae9a86002f0223576675fc7e2a6e7735da47185a1d13d1eaaa73dd4be"}, + {file = "levenshtein-0.27.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8e5037c4a6f97a238e24aad6f98a1e984348b7931b1b04b6bd02bd4f8238150d"}, + {file = "levenshtein-0.27.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6cf5ecf9026bf24cf66ad019c6583f50058fae3e1b3c20e8812455b55d597f1"}, + {file = "levenshtein-0.27.3-cp314-cp314-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9285084bd2fc19adb47dab54ed4a71f57f78fe0d754e4a01e3c75409a25aed24"}, + {file = "levenshtein-0.27.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce3bbbe92172a08b599d79956182c6b7ab6ec8d4adbe7237417a363b968ad87b"}, + {file = "levenshtein-0.27.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9dac48fab9d166ca90e12fb6cf6c7c8eb9c41aacf7136584411e20f7f136f745"}, + {file = "levenshtein-0.27.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d37a83722dc5326c93d17078e926c4732dc4f3488dc017c6839e34cd16af92b7"}, + {file = "levenshtein-0.27.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3466cb8294ce586e49dd467560a153ab8d296015c538223f149f9aefd3d9f955"}, + {file = "levenshtein-0.27.3-cp314-cp314-win32.whl", hash = "sha256:c848bf2457b268672b7e9e73b44f18f49856420ac50b2564cf115a6e4ef82688"}, + {file = "levenshtein-0.27.3-cp314-cp314-win_amd64.whl", hash = "sha256:742633f024362a4ed6ef9d7e75d68f74b041ae738985fcf55a0e6d1d4cade438"}, + {file = "levenshtein-0.27.3-cp314-cp314-win_arm64.whl", hash = "sha256:9eed6851224b19e8d588ddb8eb8a4ae3c2dcabf3d1213985f0b94a67e517b1df"}, + {file = "levenshtein-0.27.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:77de69a345c76227b51a4521cd85442eb3da54c7eb6a06663a20c058fc49e683"}, + {file = "levenshtein-0.27.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eba2756dc1f5b962b0ff80e49abb2153d5e809cc5e7fa5e85be9410ce474795d"}, + {file = "levenshtein-0.27.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c8fcb498287e971d84260f67808ff1a06b3f6212d80fea75cf5155db80606ff"}, + {file = "levenshtein-0.27.3-cp314-cp314t-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f067092c67464faab13e00a5c1a80da93baca8955d4d49579861400762e35591"}, + {file = "levenshtein-0.27.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92415f32c68491203f2855d05eef3277d376182d014cf0859c013c89f277fbbf"}, + {file = "levenshtein-0.27.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ef61eeaf1e0a42d7d947978d981fe4b9426b98b3dd8c1582c535f10dee044c3f"}, + {file = "levenshtein-0.27.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:103bb2e9049d1aa0d1216dd09c1c9106ecfe7541bbdc1a0490b9357d42eec8f2"}, + {file = "levenshtein-0.27.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a64ddd1986b2a4c468b09544382287315c53585eb067f6e200c337741e057ee"}, + {file = "levenshtein-0.27.3-cp314-cp314t-win32.whl", hash = "sha256:957244f27dc284ccb030a8b77b8a00deb7eefdcd70052a4b1d96f375780ae9dc"}, + {file = "levenshtein-0.27.3-cp314-cp314t-win_amd64.whl", hash = "sha256:ccd7eaa6d8048c3ec07c93cfbcdefd4a3ae8c6aca3a370f2023ee69341e5f076"}, + {file = "levenshtein-0.27.3-cp314-cp314t-win_arm64.whl", hash = "sha256:1d8520b89b7a27bb5aadbcc156715619bcbf556a8ac46ad932470945dca6e1bd"}, + {file = "levenshtein-0.27.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d2d7d22b6117a143f0cf101fe18a3ca90bd949fc33716a42d6165b9768d4a78c"}, + {file = "levenshtein-0.27.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:a55e7a2f317abd28576636e1f840fd268261f447c496a8481a9997a5ce889c59"}, + {file = "levenshtein-0.27.3-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa5f11952c38186bd4719e936eb4595b3d519218634924928787c36840256c"}, + {file = "levenshtein-0.27.3-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:559d3588e6766134d95f84f830cf40166360e1769d253f5f83474bff10a24341"}, + {file = "levenshtein-0.27.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:82d40da143c1b9e27adcd34a33dfcc4a0761aa717c5f618b9c6f57dec5d7a958"}, + {file = "levenshtein-0.27.3.tar.gz", hash = "sha256:1ac326b2c84215795163d8a5af471188918b8797b4953ec87aaba22c9c1f9fc0"}, ] [package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-ruff"] +rapidfuzz = ">=3.9.0,<4.0.0" [[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" +name = "librt" +version = "0.8.1" +description = "Mypyc runtime library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "test"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, + {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, + {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, + {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, + {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, + {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, + {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, + {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, + {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, + {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, + {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, + {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, + {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, + {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, + {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, + {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, + {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, + {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, + {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, + {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, + {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, + {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, + {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, + {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, + {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, + {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, + {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, + {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, + {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, + {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, + {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, + {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, + {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, + {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, + {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, + {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, + {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, + {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, + {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, + {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, + {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, + {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, + {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, +] + +[[package]] +name = "mappy" +version = "2.30" +description = "Minimap2 python binding" +optional = false +python-versions = "*" +groups = ["main"] files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "mappy-2.30.tar.gz", hash = "sha256:a25448004558a28cb0d74fb1e55b6ffe9a78aa15dd6b2763630fbbabbaa97a27"}, ] [[package]] @@ -174,55 +347,56 @@ files = [ [[package]] name = "mypy" -version = "1.14.1" +version = "1.19.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "test"] files = [ - {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, - {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, - {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, - {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, - {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, - {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, - {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, - {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, - {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, - {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, - {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, - {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, - {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, - {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, - {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, - {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +pathspec = ">=0.9.0" typing_extensions = ">=4.6.0" [package.extras] @@ -244,85 +418,168 @@ files = [ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] +[[package]] +name = "numpy" +version = "2.4.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb"}, + {file = "numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147"}, + {file = "numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920"}, + {file = "numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9"}, + {file = "numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470"}, + {file = "numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71"}, + {file = "numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15"}, + {file = "numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52"}, + {file = "numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd"}, + {file = "numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec"}, + {file = "numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67"}, + {file = "numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef"}, + {file = "numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e"}, + {file = "numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4"}, + {file = "numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18"}, + {file = "numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5"}, + {file = "numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97"}, + {file = "numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c"}, + {file = "numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc"}, + {file = "numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9"}, + {file = "numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5"}, + {file = "numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e"}, + {file = "numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3"}, + {file = "numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9"}, + {file = "numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee"}, + {file = "numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f"}, + {file = "numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f"}, + {file = "numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc"}, + {file = "numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476"}, + {file = "numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92"}, + {file = "numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687"}, + {file = "numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd"}, + {file = "numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d"}, + {file = "numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875"}, + {file = "numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070"}, + {file = "numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73"}, + {file = "numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368"}, + {file = "numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22"}, + {file = "numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a"}, + {file = "numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349"}, + {file = "numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c"}, + {file = "numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26"}, + {file = "numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02"}, + {file = "numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4"}, + {file = "numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168"}, + {file = "numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b"}, + {file = "numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950"}, + {file = "numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd"}, + {file = "numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24"}, + {file = "numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0"}, + {file = "numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0"}, + {file = "numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a"}, + {file = "numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc"}, + {file = "numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7"}, + {file = "numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657"}, + {file = "numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7"}, + {file = "numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093"}, + {file = "numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a"}, + {file = "numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611"}, + {file = "numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720"}, + {file = "numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5"}, + {file = "numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0"}, + {file = "numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b"}, + {file = "numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857"}, + {file = "numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5"}, + {file = "numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd"}, +] + [[package]] name = "packaging" -version = "23.2" +version = "26.0" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main", "dev", "test"] files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "test"] files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, ] +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.9.4" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev", "test"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, + {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "test"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pycodestyle" -version = "2.9.1" +version = "2.14.0" description = "Python style guide checker" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, ] [[package]] name = "pyflakes" -version = "2.5.0" +version = "3.4.0" description = "passive checker of Python programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, ] [[package]] @@ -339,92 +596,275 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" groups = ["dev", "test"] -markers = "python_version < \"3.11\"" files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, ] +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] -name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +name = "pytokens" +version = "0.4.1" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" groups = ["dev", "test"] files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, + {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, + {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, + {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, + {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, + {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, + {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, + {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, + {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, + {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, + {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, + {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, + {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, + {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, + {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, + {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, + {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, + {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, + {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, + {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, + {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, + {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, + {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, + {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, + {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, + {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, ] +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + [[package]] -name = "zipp" -version = "3.20.2" -description = "Backport of pathlib-compatible object wrapper for zip files" +name = "rapidfuzz" +version = "3.14.3" +description = "rapid fuzzy string matching" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9fcd4d751a4fffa17aed1dde41647923c72c74af02459ad1222e3b0022da3a1"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ad73afb688b36864a8d9b7344a9cf6da186c471e5790cbf541a635ee0f457f2"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5fb2d978a601820d2cfd111e2c221a9a7bfdf84b41a3ccbb96ceef29f2f1ac7"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d83b8b712fa37e06d59f29a4b49e2e9e8635e908fbc21552fe4d1163db9d2a1"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:dc8c07801df5206b81ed6bd6c35cb520cf9b6c64b9b0d19d699f8633dc942897"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c71ce6d4231e5ef2e33caa952bfe671cb9fd42e2afb11952df9fad41d5c821f9"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0e38828d1381a0cceb8a4831212b2f673d46f5129a1897b0451c883eaf4a1747"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da2a007434323904719158e50f3076a4dadb176ce43df28ed14610c773cc9825"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-win32.whl", hash = "sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:37d3c653af15cd88592633e942f5407cb4c64184efab163c40fcebad05f25141"}, + {file = "rapidfuzz-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:cc594bbcd3c62f647dfac66800f307beaee56b22aaba1c005e9c4c40ed733923"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea2d113e260a5da0c4003e0a5e9fdf24a9dc2bb9eaa43abd030a1e46ce7837d"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6c31a4aa68cfa75d7eede8b0ed24b9e458447db604c2db53f358be9843d81d3"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02821366d928e68ddcb567fed8723dad7ea3a979fada6283e6914d5858674850"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe8df315ab4e6db4e1be72c5170f8e66021acde22cd2f9d04d2058a9fd8162e"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:769f31c60cd79420188fcdb3c823227fc4a6deb35cafec9d14045c7f6743acae"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54fa03062124e73086dae66a3451c553c1e20a39c077fd704dc7154092c34c63"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:834d1e818005ed0d4ae38f6b87b86fad9b0a74085467ece0727d20e15077c094"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:948b00e8476a91f510dd1ec07272efc7d78c275d83b630455559671d4e33b678"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-win32.whl", hash = "sha256:43d0305c36f504232f18ea04e55f2059bb89f169d3119c4ea96a0e15b59e2a91"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:ef6bf930b947bd0735c550683939a032090f1d688dfd8861d6b45307b96fd5c5"}, + {file = "rapidfuzz-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:f3eb0ff3b75d6fdccd40b55e7414bb859a1cda77c52762c9c82b85569f5088e7"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329"}, + {file = "rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c"}, + {file = "rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15"}, + {file = "rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9"}, + {file = "rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c"}, + {file = "rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7cf174b52cb3ef5d49e45d0a1133b7e7d0ecf770ed01f97ae9962c5c91d97d23"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:442cba39957a008dfc5bdef21a9c3f4379e30ffb4e41b8555dbaf4887eca9300"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1faa0f8f76ba75fd7b142c984947c280ef6558b5067af2ae9b8729b0a0f99ede"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e6eefec45625c634926a9fd46c9e4f31118ac8f3156fff9494422cee45207e6"}, + {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56fefb4382bb12250f164250240b9dd7772e41c5c8ae976fd598a32292449cc5"}, + {file = "rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f"}, +] + +[package.extras] +all = ["numpy"] + +[[package]] +name = "scipy" +version = "1.17.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.11" groups = ["main"] -markers = "python_version < \"3.10\"" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec"}, + {file = "scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696"}, + {file = "scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee"}, + {file = "scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd"}, + {file = "scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c"}, + {file = "scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4"}, + {file = "scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444"}, + {file = "scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082"}, + {file = "scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff"}, + {file = "scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d"}, + {file = "scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8"}, + {file = "scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76"}, + {file = "scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086"}, + {file = "scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b"}, + {file = "scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21"}, + {file = "scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458"}, + {file = "scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb"}, + {file = "scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea"}, + {file = "scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87"}, + {file = "scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3"}, + {file = "scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c"}, + {file = "scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f"}, + {file = "scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d"}, + {file = "scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b"}, + {file = "scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6"}, + {file = "scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464"}, + {file = "scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950"}, + {file = "scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369"}, + {file = "scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448"}, + {file = "scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87"}, + {file = "scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a"}, + {file = "scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0"}, + {file = "scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce"}, + {file = "scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6"}, + {file = "scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e"}, + {file = "scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475"}, + {file = "scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50"}, + {file = "scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca"}, + {file = "scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c"}, + {file = "scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b"}, + {file = "scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866"}, + {file = "scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350"}, + {file = "scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118"}, + {file = "scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068"}, + {file = "scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118"}, + {file = "scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19"}, + {file = "scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39"}, + {file = "scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca"}, + {file = "scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad"}, + {file = "scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a"}, + {file = "scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4"}, + {file = "scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2"}, + {file = "scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484"}, + {file = "scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21"}, + {file = "scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0"}, ] +[package.dependencies] +numpy = ">=1.26.4,<2.7" + [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] +dev = ["click (<8.3.0)", "cython-lint (>=0.12.2)", "mypy (==1.10.0)", "pycodestyle", "ruff (>=0.12.0)", "spin", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)", "tabulate"] +test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest (>=8.0.0)", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["dev", "test"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] [metadata] lock-version = "2.1" -python-versions = ">= 3.8" -content-hash = "47eb87b3ebe7ff62dfa41dd69302f3ca080143817f584a5f65dea357fcac28be" +python-versions = ">= 3.11" +content-hash = "e4a334eb4d38c9b3b69b4edae5719276410a7846df76ced42958bda4bbbac238" diff --git a/pyproject.toml b/pyproject.toml index a60e999..81e278b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,14 @@ [project] name = "sentieon_cli" -version = "1.5.2" +version = "1.6.0" description = "entry point for sentieon command-line tools" authors = [ {name = "Don Freed", email = "don.freed@sentieon.com"}, {name = "Brent", email = "bpederse@gmail.com"}, ] readme = "README.md" -requires-python = ">= 3.8" +requires-python = ">= 3.11" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -18,9 +18,10 @@ classifiers = [ include = ["LICENSE"] dependencies = [ - "colorlog (>=6.7.0,<7.0.0)", - "importlib-resources (>=5.0,<6.0)", - "packaging (>=23.2,<26.0)", + "packaging (>=23.2)", + "mappy (>=2.30,<3.0)", + "scipy (>=1.17.1,<2.0.0)", + "levenshtein (>=0.27.3,<0.28.0)", ] [project.scripts] @@ -32,17 +33,41 @@ ignore_missing_imports = true [tool.black] line-length = 79 +[tool.flake8] +max-line-length = 79 +extend-ignore = ["E231", "E221"] +exclude = [ + ".github/scripts/license_message.py", + "sentieon_cli/scripts/combine_cnv.py", + "sentieon_cli/scripts/gvcf_combine.py", + "sentieon_cli/scripts/vcf_mod.py", + "sentieon_cli/scripts/hybrid_anno.py", + "sentieon_cli/scripts/hybrid_select.py", + "sentieon_cli/scripts/indel2cnv.py", + "sentieon_cli/scripts/trimalt.py", + "sentieon_cli/archive.py", + "playground", + "tests", +] + [tool.poetry.group.dev.dependencies] pytest = "^7.0.0" +pytest-asyncio = ">=0.21.0" black = "*" flake8 = "*" +Flake8-pyproject = "*" mypy = "*" [tool.poetry.group.test.dependencies] pytest = "^7.0.0" +pytest-asyncio = ">=0.21.0" black = "*" mypy = "*" +[tool.pytest.ini_options] +testpaths = ["tests"] +asyncio_mode = "strict" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/sentieon_cli/base_pangenome.py b/sentieon_cli/base_pangenome.py index 74182c9..b15dfc3 100644 --- a/sentieon_cli/base_pangenome.py +++ b/sentieon_cli/base_pangenome.py @@ -8,7 +8,7 @@ import pathlib from typing import List, Optional -from importlib_resources import files +from importlib.resources import files from . import command_strings as cmds from .job import Job diff --git a/sentieon_cli/command_strings.py b/sentieon_cli/command_strings.py index f8debd4..8269348 100644 --- a/sentieon_cli/command_strings.py +++ b/sentieon_cli/command_strings.py @@ -112,7 +112,8 @@ def cmd_pyexec_vcf_mod_haploid_patch( """ merge dnascope and dnascope-hp variants """ - assert tech.upper() in ("HIFI", "ONT") + if tech.upper() not in ("HIFI", "ONT"): + raise ValueError(f"Unsupported tech '{tech}', must be 'HIFI' or 'ONT'") cmd = f"sentieon pyexec {kwargs['vcf_mod_py']} -t {cores} " cmd += "haploid_patch " diff --git a/sentieon_cli/dag.py b/sentieon_cli/dag.py index 826e67a..b3997b6 100644 --- a/sentieon_cli/dag.py +++ b/sentieon_cli/dag.py @@ -29,10 +29,13 @@ def add_job(self, job: Job, dependencies: Optional[Set[Job]] = None): """Add a job to the DAG""" if dependencies: for dependency in dependencies: - assert ( - dependency in self.waiting_jobs - or dependency in self.ready_jobs - ) + if ( + dependency not in self.waiting_jobs + and dependency not in self.ready_jobs + ): + raise ValueError( + f"Dependency '{dependency}' is not in the DAG" + ) if isinstance(dependencies, set) and len(dependencies) > 0: self.waiting_jobs[job] = dependencies.copy() diff --git a/sentieon_cli/dnascope.py b/sentieon_cli/dnascope.py index dae9a48..09ed816 100644 --- a/sentieon_cli/dnascope.py +++ b/sentieon_cli/dnascope.py @@ -14,7 +14,7 @@ import packaging.version -from importlib_resources import files +from importlib.resources import files from . import command_strings as cmds from .dag import DAG @@ -49,7 +49,6 @@ total_memory, ) - ALN_MIN_VERSIONS = { "sentieon driver": packaging.version.Version("202308"), "samtools": packaging.version.Version("1.16"), @@ -256,7 +255,9 @@ def __init__(self) -> None: self.no_split_alignment = False def validate(self) -> None: - assert self.output_vcf + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) # uniquify pipeline attributes self.sr_r1_fastq = self.r1_fastq @@ -458,8 +459,12 @@ def build_dag(self) -> DAG: def sr_align_inputs(self) -> Tuple[List[pathlib.Path], Set[Job], Job]: """Align input BAM/CRAM/uBAM/uCRAM files with bwa""" - assert self.reference - assert self.model_bundle + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) if not self.skip_version_check: for cmd, min_version in ALN_MIN_VERSIONS.items(): @@ -526,8 +531,12 @@ def sr_align_fastq( self, ) -> Tuple[List[pathlib.Path], Set[Job], Optional[Job]]: """Align fastq files to the reference genome using bwa""" - assert self.reference - assert self.model_bundle + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) res: List[pathlib.Path] = [] jobs: Set[Job] = set() @@ -625,7 +634,9 @@ def dedup_and_metrics( Optional[Job], ]: """Perform dedup and metrics collection""" - assert self.output_vcf + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) suffix = "bam" if self.bam_format else "cram" # Create the metrics directory @@ -790,8 +801,12 @@ def sr_call_variants( deduped: List[pathlib.Path], ) -> Tuple[Job, Job, Job, Optional[Job], Optional[Job], Optional[Job]]: """Call SNVs, indels, and SVs using DNAscope""" - assert self.model_bundle - assert self.output_vcf + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) if not self.skip_version_check: for cmd, min_version in VARIANTS_MIN_VERSIONS.items(): diff --git a/sentieon_cli/dnascope_hybrid.py b/sentieon_cli/dnascope_hybrid.py index 284d3a7..79184c1 100644 --- a/sentieon_cli/dnascope_hybrid.py +++ b/sentieon_cli/dnascope_hybrid.py @@ -11,7 +11,7 @@ import packaging.version -from importlib_resources import files +from importlib.resources import files from .logging import get_logger from .archive import ar_load @@ -47,7 +47,6 @@ ) from .transfer import build_transfer_jobs - logger = get_logger(__name__) @@ -520,9 +519,15 @@ def build_dag(self) -> DAG: self.logger.info("Building the DAG") dag = DAG() - assert self.output_vcf - assert self.reference - assert self.model_bundle + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) rg_info = RgInfo( self.lr_aln_readgroups, @@ -712,9 +717,15 @@ def call_variants( """ Call SNVs and indels using the DNAscope hybrid pipeline """ - assert self.output_vcf - assert self.reference - assert self.model_bundle + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) ref_fai = pathlib.Path(str(self.reference) + ".fai") diff --git a/sentieon_cli/dnascope_longread.py b/sentieon_cli/dnascope_longread.py index 19aa5c7..3261cc5 100644 --- a/sentieon_cli/dnascope_longread.py +++ b/sentieon_cli/dnascope_longread.py @@ -4,6 +4,7 @@ import argparse import copy +from dataclasses import dataclass import json import pathlib import shutil @@ -12,7 +13,7 @@ import packaging.version -from importlib_resources import files +from importlib.resources import files from . import command_strings as cmds from .archive import ar_load @@ -80,6 +81,39 @@ } +@dataclass +class LRCallVariantsResult: + """Results from lr_call_variants""" + + first_calling_job: Job + diploid_transfer_jobs: List[Job] + diploid_concat_job: Optional[Job] + first_modelapply_job: Job + phaser_job: Job + bcftools_subset_phased_job: Optional[Job] + fai_to_bed_job: Optional[Job] + bcftools_subtract_job: Job + repeatmodel_job: Optional[Job] + bcftools_subset_unphased_job: Job + second_calling_job: Set[Job] + haploid_patch_job: Job + patch_transfer_jobs: List[List[Job]] + patch_concat_jobs: List[Job] + second_modelapply_job: Set[Job] + calling_unphased_job: Job + diploid_patch_job: Job + unphased_transfer_jobs: List[Job] + unphased_concat_job: Optional[Job] + modelapply_unphased_job: Job + merge_job: Job + gvcf_combine_job: Optional[Job] + haploid_calling_job: Optional[Job] + haploid_patch2_job: Optional[Job] + haploid_concat_job: Optional[Job] + haploid_gvcf_combine_job: Optional[Job] + haploid_gvcf_concat_job: Optional[Job] + + class DNAscopeLRPipeline(BasePipeline): """The DNAscope LongRead pipeline""" @@ -461,130 +495,120 @@ def build_dag(self) -> DAG: dag.add_job(hificnv_job, {merge_job}) if not self.skip_small_variants: - ( - first_calling_job, - diploid_transfer_jobs, - diploid_concat_job, - first_modelapply_job, - phaser_job, - bcftools_subset_phased_job, - fai_to_bed_job, - bcftools_subtract_job, - repeatmodel_job, - bcftools_subset_unphased_job, - second_calling_job, - haploid_patch_job, - patch_transfer_jobs, - patch_concat_jobs, - second_modelapply_job, - calling_unphased_job, - diploid_patch_job, - unphased_transfer_jobs, - unphased_concat_job, - modelapply_unphased_job, - merge_job, - gvcf_combine_job, - haploid_calling_job, - haploid_patch2_job, - haploid_concat_job, - haploid_gvcf_combine_job, - haploid_gvcf_concat_job, - ) = self.lr_call_variants(sample_input) - dag.add_job(first_calling_job, realign_jobs.union(align_jobs)) - - first_ma_deps = {first_calling_job} - if diploid_transfer_jobs and diploid_concat_job: - for job in diploid_transfer_jobs: - dag.add_job(job, {first_calling_job}) - dag.add_job(diploid_concat_job, set(diploid_transfer_jobs)) - first_ma_deps.add(diploid_concat_job) - - dag.add_job(first_modelapply_job, first_ma_deps) - dag.add_job(phaser_job, {first_modelapply_job}) - - haploid_patch_deps = set() - if bcftools_subset_phased_job: - dag.add_job(bcftools_subset_phased_job, {phaser_job}) - haploid_patch_deps.add(bcftools_subset_phased_job) - - subtract_deps = {phaser_job} - if fai_to_bed_job: - dag.add_job(fai_to_bed_job) - subtract_deps.add(fai_to_bed_job) - dag.add_job(bcftools_subtract_job, subtract_deps) - dag.add_job(bcftools_subset_unphased_job, {bcftools_subtract_job}) - - second_pass_deps = {phaser_job} - calling_unphased_deps = {bcftools_subtract_job} - haploid_calling_deps = set() - if repeatmodel_job: - dag.add_job(repeatmodel_job, {phaser_job}) - second_pass_deps.add(repeatmodel_job) - calling_unphased_deps.add(repeatmodel_job) - haploid_calling_deps.add(repeatmodel_job) - - merge_deps = set() - for job in second_calling_job: + cv = self.lr_call_variants(sample_input) + dag.add_job(cv.first_calling_job, realign_jobs.union(align_jobs)) + + first_ma_deps = {cv.first_calling_job} + if cv.diploid_transfer_jobs and cv.diploid_concat_job: + for job in cv.diploid_transfer_jobs: + dag.add_job(job, {cv.first_calling_job}) + dag.add_job( + cv.diploid_concat_job, set(cv.diploid_transfer_jobs) + ) + first_ma_deps.add(cv.diploid_concat_job) + + dag.add_job(cv.first_modelapply_job, first_ma_deps) + dag.add_job(cv.phaser_job, {cv.first_modelapply_job}) + + haploid_patch_deps: Set[Job] = set() + if cv.bcftools_subset_phased_job: + dag.add_job(cv.bcftools_subset_phased_job, {cv.phaser_job}) + haploid_patch_deps.add(cv.bcftools_subset_phased_job) + + subtract_deps: Set[Job] = {cv.phaser_job} + if cv.fai_to_bed_job: + dag.add_job(cv.fai_to_bed_job) + subtract_deps.add(cv.fai_to_bed_job) + dag.add_job(cv.bcftools_subtract_job, subtract_deps) + dag.add_job( + cv.bcftools_subset_unphased_job, + {cv.bcftools_subtract_job}, + ) + + second_pass_deps: Set[Job] = {cv.phaser_job} + calling_unphased_deps: Set[Job] = {cv.bcftools_subtract_job} + haploid_calling_deps: Set[Job] = set() + if cv.repeatmodel_job: + dag.add_job(cv.repeatmodel_job, {cv.phaser_job}) + second_pass_deps.add(cv.repeatmodel_job) + calling_unphased_deps.add(cv.repeatmodel_job) + haploid_calling_deps.add(cv.repeatmodel_job) + + merge_deps: Set[Job] = set() + for job in cv.second_calling_job: dag.add_job(job, second_pass_deps) haploid_patch_deps.add(job) - dag.add_job(haploid_patch_job, haploid_patch_deps) + dag.add_job(cv.haploid_patch_job, haploid_patch_deps) - second_ma_deps = {haploid_patch_job} - if patch_transfer_jobs: # pop_vcf + second_ma_deps = {cv.haploid_patch_job} + if cv.patch_transfer_jobs: # pop_vcf for patch_transfer_hap, patch_concat in zip( - patch_transfer_jobs, patch_concat_jobs + cv.patch_transfer_jobs, cv.patch_concat_jobs ): for job in patch_transfer_hap: - dag.add_job(job, {haploid_patch_job}) + dag.add_job(job, {cv.haploid_patch_job}) dag.add_job(patch_concat, set(patch_transfer_hap)) second_ma_deps.add(patch_concat) - for job in second_modelapply_job: + for job in cv.second_modelapply_job: dag.add_job(job, second_ma_deps) merge_deps.add(job) - dag.add_job(calling_unphased_job, calling_unphased_deps) + dag.add_job(cv.calling_unphased_job, calling_unphased_deps) dag.add_job( - diploid_patch_job, - {bcftools_subset_unphased_job, calling_unphased_job}, + cv.diploid_patch_job, + { + cv.bcftools_subset_unphased_job, + cv.calling_unphased_job, + }, ) - unphased_ma_deps = {diploid_patch_job} - if unphased_transfer_jobs and unphased_concat_job: # pop_vcf - for job in unphased_transfer_jobs: - dag.add_job(job, {diploid_patch_job}) - dag.add_job(unphased_concat_job, set(unphased_transfer_jobs)) - unphased_ma_deps.add(unphased_concat_job) + unphased_ma_deps = {cv.diploid_patch_job} + if cv.unphased_transfer_jobs and cv.unphased_concat_job: # pop_vcf + for job in cv.unphased_transfer_jobs: + dag.add_job(job, {cv.diploid_patch_job}) + dag.add_job( + cv.unphased_concat_job, set(cv.unphased_transfer_jobs) + ) + unphased_ma_deps.add(cv.unphased_concat_job) - dag.add_job(modelapply_unphased_job, unphased_ma_deps) - merge_deps.add(modelapply_unphased_job) - dag.add_job(merge_job, merge_deps) + dag.add_job(cv.modelapply_unphased_job, unphased_ma_deps) + merge_deps.add(cv.modelapply_unphased_job) + dag.add_job(cv.merge_job, merge_deps) - if gvcf_combine_job: # if gvcf - dag.add_job(gvcf_combine_job, {merge_job}) + if cv.gvcf_combine_job: + dag.add_job(cv.gvcf_combine_job, {cv.merge_job}) if ( - haploid_calling_job - and haploid_patch2_job - and haploid_concat_job + cv.haploid_calling_job + and cv.haploid_patch2_job + and cv.haploid_concat_job ): - # if haploid bed - dag.add_job(haploid_calling_job, haploid_calling_deps) - dag.add_job(haploid_patch2_job, {haploid_calling_job}) + dag.add_job(cv.haploid_calling_job, haploid_calling_deps) dag.add_job( - haploid_concat_job, {haploid_patch2_job, merge_job} + cv.haploid_patch2_job, + {cv.haploid_calling_job}, + ) + dag.add_job( + cv.haploid_concat_job, + {cv.haploid_patch2_job, cv.merge_job}, ) if ( - haploid_gvcf_combine_job - and haploid_gvcf_concat_job - and gvcf_combine_job + cv.haploid_gvcf_combine_job + and cv.haploid_gvcf_concat_job + and cv.gvcf_combine_job ): - # if gvcf - dag.add_job(haploid_gvcf_combine_job, {haploid_patch2_job}) dag.add_job( - haploid_gvcf_concat_job, - {haploid_gvcf_combine_job, gvcf_combine_job}, + cv.haploid_gvcf_combine_job, + {cv.haploid_patch2_job}, + ) + dag.add_job( + cv.haploid_gvcf_concat_job, + { + cv.haploid_gvcf_combine_job, + cv.gvcf_combine_job, + }, ) if not self.skip_svs: @@ -597,9 +621,15 @@ def lr_align_inputs(self) -> Tuple[List[pathlib.Path], Set[Job]]: """ Align reads to the reference genome using minimap2 """ - assert self.output_vcf - assert self.reference - assert self.model_bundle + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) if not self.skip_version_check: for cmd, min_version in ALN_MIN_VERSIONS.items(): if not check_version(cmd, min_version): @@ -646,8 +676,12 @@ def lr_align_fastq(self) -> Tuple[List[pathlib.Path], Set[Job]]: """ Align fastq to the reference genome using minimap2 """ - assert self.reference - assert self.model_bundle + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) res: List[pathlib.Path] = [] if self.fastq is None and self.readgroups is None: return (res, set()) @@ -843,41 +877,19 @@ def hificnv( def lr_call_variants( self, sample_input: List[pathlib.Path], - ) -> Tuple[ - Job, # first_calling_job - List[Job], # diploid_transfer_jobs - Optional[Job], # diploid_concat_job - Job, # first_modelapply_job - Job, # phaser_job - Optional[Job], # bcftools_subset_phased_job - Optional[Job], # fai_to_bed_job - Job, # bcftools_subtract_job - Optional[Job], # repeatmodel_job - Job, # bcftools_subset_unphased_job - Set[Job], # second_calling_job - Job, # haploid_patch_job - List[List[Job]], # patch_transfer_jobs - List[Job], # patch_concat_jobs - Set[Job], # second_modelapply_job - Job, # calling_unphased_job - Job, # diploid_patch_job - List[Job], # unphased_transfer_jobs - Optional[Job], # unphased_concat_job - Job, # modelapply_unphased_job - Job, # merge_job - Optional[Job], # gvcf_combine_job - Optional[Job], # haploid_calling_job - Optional[Job], # haploid_patch2_job - Optional[Job], # haploid_concat_job - Optional[Job], # haploid_gvcf_combine_job - Optional[Job], # haploid_gvcf_concat_job - ]: + ) -> LRCallVariantsResult: """ Call SNVs and indels using the DNAscope LongRead pipeline """ - assert self.model_bundle - assert self.reference - assert self.output_vcf + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) if not self.skip_version_check: for check_cmd, min_version in TOOL_MIN_VERSIONS.items(): if not check_version(check_cmd, min_version): @@ -1386,7 +1398,7 @@ def lr_call_variants( "haploid-gvcf-concat", 0, ) - return ( + return LRCallVariantsResult( first_calling_job, diploid_transfer_jobs, diploid_concat_job, @@ -1424,7 +1436,9 @@ def call_svs( """ Call SVs using Sentieon LongReadSV """ - assert self.model_bundle + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) if not self.skip_version_check: for cmd, min_version in SV_MIN_VERSIONS.items(): if not check_version(cmd, min_version): diff --git a/sentieon_cli/driver.py b/sentieon_cli/driver.py index b09a752..09d545f 100644 --- a/sentieon_cli/driver.py +++ b/sentieon_cli/driver.py @@ -518,6 +518,30 @@ def __init__( self.vcf = vcf +class PangenomeSV(BaseAlgo): + """algo PangenomeSV""" + + name = "PangenomeSV" + + def __init__( + self, + output: pathlib.Path, + gfa_file: pathlib.Path, + min_map_qual: Optional[int] = None, + min_sv_size: Optional[int] = None, + min_dp: Optional[int] = None, + min_af: Optional[float] = None, + prefix: Optional[str] = None, + ): + self.output = output + self.gfa_file = gfa_file + self.min_map_qual = min_map_qual + self.min_sv_size = min_sv_size + self.min_dp = min_dp + self.min_af = min_af + self.prefix = prefix + + class BaseDriver: """A base class for the Sentieon driver""" diff --git a/sentieon_cli/executor.py b/sentieon_cli/executor.py index 862fb7e..4d60f3e 100644 --- a/sentieon_cli/executor.py +++ b/sentieon_cli/executor.py @@ -73,13 +73,15 @@ def exit_gracefully(self, signum, frame) -> None: self.start_new_jobs = False for running_job in self.running: for subcommand in running_job[1].commands: - assert subcommand.proc + if not subcommand.proc: + continue subcommand.proc.send_signal(signal=signal.SIGTERM) time.sleep(10) for running_job in self.running: context = running_job[1] for subcommand in context.commands: - assert subcommand.proc + if not subcommand.proc: + continue if subcommand.proc.returncode is None: subcommand.proc.kill() asyncio.run(context.cleanup()) @@ -140,7 +142,13 @@ async def _execute(self) -> None: # Check if the command failed cmd_failed = False for subcommand in context.commands: - assert subcommand.proc + if not subcommand.proc: + logger.error( + "Sub-command has no process: %s", + subcommand, + ) + cmd_failed = True + continue ret = ( await subcommand.proc.wait() ) # Wait on all sub-commands diff --git a/sentieon_cli/job.py b/sentieon_cli/job.py index 971a94f..dea8343 100644 --- a/sentieon_cli/job.py +++ b/sentieon_cli/job.py @@ -59,7 +59,9 @@ def run(self, dry_run: bool = False): self.shell.run(context, stdout=sys.stdout, stderr=sys.stderr) ) for subcommand in context.commands: - assert subcommand.proc + if not subcommand.proc: + logger.error("subcommand has no process: %s", subcommand) + continue ret = asyncio.run(subcommand.proc.wait()) if ret != 0 and not subcommand.fail_ok: logger.error( diff --git a/sentieon_cli/logging.py b/sentieon_cli/logging.py index b314b03..bd5d15a 100644 --- a/sentieon_cli/logging.py +++ b/sentieon_cli/logging.py @@ -1,16 +1,12 @@ -import colorlog +import logging -handler = colorlog.StreamHandler() -handler.setFormatter( - colorlog.ColoredFormatter( - "%(log_color)s%(levelname)s:%(name)s:%(message)s" - ) -) +handler = logging.StreamHandler() +handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s:%(message)s")) def get_logger(name: str): - """Return a logger with a colorlog handler.""" - logger = colorlog.getLogger(name) + """Return a logger with a StreamHandler.""" + logger = logging.getLogger(name) logger.addHandler(handler) logger.propagate = False return logger diff --git a/sentieon_cli/pangenome.py b/sentieon_cli/pangenome.py index 5cba3f7..5c02025 100644 --- a/sentieon_cli/pangenome.py +++ b/sentieon_cli/pangenome.py @@ -14,7 +14,7 @@ import packaging.version -from importlib_resources import files +from importlib.resources import files from . import command_strings as cmds from .archive import ar_load @@ -41,7 +41,6 @@ from .shell_pipeline import Command, Pipeline from .util import __version__, check_version, parse_rg_line, path_arg, tmp - PANGENOME_MIN_VERSIONS = { "kmc": None, "sentieon driver": packaging.version.Version("202503.01"), @@ -327,8 +326,12 @@ def validate_segdup(self) -> None: def validate(self) -> None: """Validate pipeline inputs""" - assert self.output_vcf - assert self.reference + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) self.validate_bundle() self.validate_fastq_rg(r1_required=True) @@ -642,8 +645,12 @@ def build_haplotypes_job( self, output_gbz: pathlib.Path, kmer_file: pathlib.Path ) -> Job: """Build vg haplotypes job""" - assert self.hapl - assert self.gbz + if not self.hapl: + self.logger.error("hapl is required") + sys.exit(2) + if not self.gbz: + self.logger.error("gbz is required") + sys.exit(2) haplotypes_job = Job( cmds.cmd_vg_haplotypes( @@ -696,7 +703,9 @@ def build_pack_job( sample_gams: List[pathlib.Path], ) -> Job: """Compute read support with vg pack""" - assert self.gbz + if not self.gbz: + self.logger.error("gbz is required") + sys.exit(2) pack_job = Job( cmds.cmd_vg_pack( @@ -718,8 +727,12 @@ def build_call_job( sample_name="", ) -> Job: """Call SVs with vg call""" - assert self.gbz - assert self.snarls + if not self.gbz: + self.logger.error("gbz is required") + sys.exit(2) + if not self.snarls: + self.logger.error("snarls is required") + sys.exit(2) call_job = Job( cmds.cmd_sv_call( @@ -741,7 +754,9 @@ def build_surject_jobs( surject_paths_dict: pathlib.Path, ) -> Tuple[List[pathlib.Path], List[Job]]: """Surject gam files on the linear reference""" - assert self.xg + if not self.xg: + self.logger.error("xg is required") + sys.exit(2) aligned_bams: List[pathlib.Path] = [] surject_jobs: List[Job] = [] @@ -771,7 +786,9 @@ def build_dedup_job( aligned_bams: List[pathlib.Path], ) -> Tuple[List[Job], Job, Job]: """Mark duplicates""" - assert self.output_vcf + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) # Create .hdr files for each input file hdr_jobs: List[Job] = [] @@ -858,7 +875,9 @@ def build_metrics_job( deduped_bam: pathlib.Path, deduped_cram: pathlib.Path, ) -> Tuple[Job, Job]: - assert self.output_vcf + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) # Output metrics metric_base = self.output_vcf.name.replace(".vcf.gz", "") + ".txt" @@ -912,8 +931,12 @@ def build_smallvar_job( realigned_cram: pathlib.Path, ) -> Tuple[Job, Job]: """Small variant calling""" - assert self.model_bundle - assert self.output_vcf + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) model = self.model_bundle.joinpath("dnascope.model") interval_ctgs = list(["chr" + str(x) for x in range(1, 23)]) @@ -1024,7 +1047,9 @@ def build_cnv_jobs( realigned_cram: pathlib.Path, ) -> Tuple[Job, Job, Optional[Job], Optional[Job], Optional[Job]]: """Call CNVs with CNVscope""" - assert self.model_bundle + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) model = self.model_bundle.joinpath("cnv.model") @@ -1139,7 +1164,9 @@ def build_expansion_job( expansion_catalog: pathlib.Path, ) -> Job: """Identify repeat expansions""" - assert self.reference + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) expansion_job = Job( cmds.cmd_expansion_hunter( @@ -1185,8 +1212,12 @@ def build_segdup_job( genes: str, ) -> Job: """Call variants in difficult SegDups""" - assert self.reference - assert self.model_bundle + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) segdup_job = Job( cmds.cmd_segdup_caller( diff --git a/sentieon_cli/pipeline.py b/sentieon_cli/pipeline.py index 649f1c7..68c4a03 100644 --- a/sentieon_cli/pipeline.py +++ b/sentieon_cli/pipeline.py @@ -84,19 +84,28 @@ def add_arguments(cls, parser: argparse.ArgumentParser): def handle_arguments(self, args: argparse.Namespace): """Update self using the argparse object""" for k in self.params.keys(): - assert k in self.__dict__ + if k not in self.__dict__: + raise ValueError( + f"Parameter '{k}' is not an attribute of " + f"{self.__class__.__name__}" + ) if k in args.__dict__: val = getattr(args, k) if val is not None: setattr(self, k, val) for k in self.positionals.keys(): - assert k in self.__dict__ + if k not in self.__dict__: + raise ValueError( + f"Positional '{k}' is not an attribute of " + f"{self.__class__.__name__}" + ) if k in args.__dict__: setattr(self, k, getattr(args, k)) def setup_logging(self, args: argparse.Namespace) -> None: self.logger = get_logger(__name__) - assert self.logger.parent + if not self.logger.parent: + raise RuntimeError("Logger has no parent logger") self.logger.parent.setLevel(args.loglevel) self.logger.info("Starting sentieon-cli version: %s", __version__) diff --git a/sentieon_cli/scripts/combine_cnv.py b/sentieon_cli/scripts/combine_cnv.py new file mode 100644 index 0000000..eca4372 --- /dev/null +++ b/sentieon_cli/scripts/combine_cnv.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +""" +Combine CNVscope calls with indel2cnv-converted SV calls (both in CNV space). + +The converted CNV calls have REFSTART/REFSTOP defining the repeat array extent. +CNVscope loss calls overlapping these ranges are marked FILTER=SVdup (converted +call is preferred — it has precise breakpoints from the SV caller). +Gain calls are never marked. + +Both call sets use CN for evaluation: CNVscope has CN natively, converted calls +get CN = neutral_cn + CNDIFF. + +Usage: + python3 combine_cnv.py \\ + --cnv test/pred.HG002_30x_hs38.vcf.gz \\ + --converted test/hg002-svcaller.cnv.vcf.gz \\ + -o test/combined_cnv.vcf.gz +""" +import argparse +import sys +from collections import defaultdict + +import vcflib + + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +def load_converted_intervals(converted_vcf): + """Load REFSTART/REFSTOP intervals from converted calls, keyed by (chrom, direction).""" + intervals = defaultdict(list) + n = 0 + for chrom in converted_vcf.contigs: + for v in converted_vcf.range(chrom): + cndiff = v.info.get('CNDIFF', 0) + refstart = v.info.get('REFSTART', 0) + refstop = v.info.get('REFSTOP', 0) + if cndiff == 0 or refstart >= refstop: + continue + d = -1 if cndiff < 0 else 1 + intervals[(chrom, d)].append((refstart, refstop)) + n += 1 + for key in intervals: + intervals[key].sort() + return intervals, n + + +def overlaps_any(intervals, pos, end, min_frac): + """Check if [pos, end) overlaps any interval by >= min_frac of the call.""" + length = end - pos + if length <= 0: + return None + for rs, re_ in intervals: + if rs >= end: + break + if re_ <= pos: + continue + ovl = min(end, re_) - max(pos, rs) + if ovl / length >= min_frac: + return (rs, re_) + return None + + +def main(): + parser = argparse.ArgumentParser( + description='Combine CNVscope and converted SV calls (both CNV space)') + parser.add_argument('--cnv', required=True, help='CNVscope call VCF') + parser.add_argument('--converted', required=True, + help='indel2cnv.py output VCF (converted SV calls)') + parser.add_argument('-o', '--output', required=True, help='Output VCF (.vcf.gz)') + parser.add_argument('--min_overlap', type=float, default=0.5, + help='Min overlap fraction to mark SVdup (default: 0.5)') + parser.add_argument('--neutral_cn', type=int, default=2, + help='Neutral copy number (default: 2)') + args = parser.parse_args() + + conv_vcf = vcflib.VCF(args.converted, 'r') + cnv_vcf = vcflib.VCF(args.cnv, 'r') + + # Build dedup intervals from converted calls + eprint('Loading converted intervals...') + conv_intervals, n_conv = load_converted_intervals(conv_vcf) + eprint(f' {n_conv} intervals') + + # Reopen for full iteration + conv_vcf.close() + conv_vcf = vcflib.VCF(args.converted, 'r') + + # Merged header: CNV as base, add converted-specific headers + conv_extra = [h for h in conv_vcf.headers if h.startswith('##')] + new_hdrs = ( + '##INFO=', + '##INFO=', + '##FILTER=', + ) + out_vcf = vcflib.VCF(args.output, 'wb') + out_vcf.copy_header(cnv_vcf, update=tuple(conv_extra) + new_hdrs) + out_vcf.emit_header() + + all_contigs = list(cnv_vcf.contigs.keys()) + for c in conv_vcf.contigs: + if c not in cnv_vcf.contigs: + all_contigs.append(c) + + conv_stats = {'total': 0} + cnv_stats = {'kept': 0, 'marked': 0, 'neutral': 0, 'filtered': 0} + + seq = 0 + for chrom in all_contigs: + records = [] + + # Converted SV calls: emit all, add CN from CNDIFF + for v in conv_vcf.range(chrom): + cndiff = v.info.get('CNDIFF', 0) + v.info['CN'] = args.neutral_cn + cndiff + v.info['SOURCE'] = 'SV_CNV' + v.line = None + conv_stats['total'] += 1 + seq += 1 + records.append((v.pos, 0, seq, v)) + + # CNVscope calls: mark losses overlapping converted REFSTART/REFSTOP + for v in cnv_vcf.range(chrom): + if v.filter and set(v.filter).difference(('PASS',)): + cnv_stats['filtered'] += 1 + continue + cn = v.info.get('CN', args.neutral_cn) + if cn == args.neutral_cn: + cnv_stats['neutral'] += 1 + continue + v.info['SOURCE'] = 'CNV' + if cn < args.neutral_cn: + key = (chrom, -1) + interval = overlaps_any(conv_intervals.get(key, []), + v.pos, v.end, args.min_overlap) + if interval: + v.filter = ['SVdup'] + v.info['DEDUP'] = f'{chrom}:{interval[0]}-{interval[1]}' + cnv_stats['marked'] += 1 + v.line = None + seq += 1 + records.append((v.pos, 1, seq, v)) + continue + cnv_stats['kept'] += 1 + v.line = None + seq += 1 + records.append((v.pos, 1, seq, v)) + + records.sort() + for _, _, _, v in records: + out_vcf.emit(v) + + out_vcf.close() + conv_vcf.close() + cnv_vcf.close() + + eprint(f'Converted: {conv_stats["total"]} records') + eprint(f'CNV: {cnv_stats["kept"]} kept, {cnv_stats["marked"]} marked SVdup, ' + f'{cnv_stats["neutral"]} neutral, {cnv_stats["filtered"]} non-PASS') + eprint(f'Written to {args.output}') + + +if __name__ == '__main__': + main() diff --git a/sentieon_cli/scripts/indel2cnv.py b/sentieon_cli/scripts/indel2cnv.py new file mode 100755 index 0000000..c5fe617 --- /dev/null +++ b/sentieon_cli/scripts/indel2cnv.py @@ -0,0 +1,746 @@ +import collections +import Levenshtein +import math +import io +import vcflib +from scipy.signal import find_peaks +from multiprocessing import Pool +import argparse +import logging +import time +import resource +import sys +import platform +import mappy +import os +import re + +# Convert long INDELs (from assembly-based SV calls) into tandem CNV truth events. +# Pipeline: find_repeats (detect period) -> match_ref (locate repeat on reference) +# -> extend array boundaries -> emit DEL/DUP with REFSTART/REFSTOP for cnv_eval. +# Handles segmental duplications: insertion may be distant from (POSSHIFT2 > 0) or +# a truncated copy of (SVLEN < PERIOD) an existing tandem array — both are valid CNVs +# because reads from the new copy map back to the reference array, increasing depth. + +MIN_SEQ_LEN = 400 +REF_SEARCH_RNG = 50000 +REF_SEARCH_CNT = 6 +MAX_EXTEND_ITERS = 200 +MAX_EXTEND_SEQ = 20000 +MAX_SEQ_LEN = 200000 +MIN_PERIOD_RATIO = 0.1 # skip if matched portion < 10% of period (coincidental similarity) + +log = logging.getLogger(__name__) + +Contig = collections.namedtuple('Contig', 'length offset width skip') + +class Reference(object): + def __init__(self, path): + self.path = path + self.index = collections.OrderedDict() + with io.open(self.path+'.fai', 'rb') as fp: + for line in fp: + flds = line.rstrip().decode().split('\t') + if len(flds) != 5: + raise RuntimeError('Corrupted index') + self.index[flds[0]] = Contig._make(map(int,flds[1:])) + self.fp = io.open(path, 'rb') + + def __getstate__(self): + odict = self.__dict__.copy() + odict['fp'] = self.fp.tell() + return odict + + def __setstate__(self, ndict): + path = ndict['path'] + fp = io.open(path, 'rb') + fp.seek(ndict.pop('fp')) + ndict['fp'] = fp + self.__dict__.update(ndict) + + def get(self, c, s, e): + ci = self.index[c] + s = max(0, s) + e = min(e, ci.length) + if s >= e: + return '' + seq = b'' + while s < e: + o = ci.offset + s // ci.width * ci.skip + s % ci.width + n = ci.width - s % ci.width + n = min(n, e-s) + self.fp.seek(o) + seq += self.fp.read(n) + s += n + return seq.decode() + + def __iter__(self): + return iter(self.index.items()) + +def copy_variant(v, **overrides): + args = [getattr(v, s) for s in v.__slots__] + new_v = vcflib.Variant(*args) + new_v.samples = [dict(s) for s in v.samples] + new_v.alt = list(v.alt) + for k, val in overrides.items(): + setattr(new_v, k, val) + return new_v + +# Detect repeat period(s) in an INDEL sequence via autocorrelation-like scanning. +# Compares var_seq[0:n] vs var_seq[n:2n] at increasing n, finds peaks, refines with +# binary search, and explores harmonic multipliers (2x, 3x, ...) as candidate periods. +def find_repeats(var_seq, thresh): + if len(var_seq) < MIN_SEQ_LEN: + return [] + length = len(var_seq) + step = 200 if length > 2000 else 100 + start = step * 2 + scan_limit = min(length//2+step*5, length-step*1) + dist = [Levenshtein.ratio(var_seq[:n], var_seq[n:n*2]) for n in range(start, scan_limit, step)] + if not dist or max(dist) < thresh: + return [len(var_seq)] + peaks, _ = find_peaks(dist, distance=start//step, height=0.95) + if peaks.size == 0: + mid = length//2 + if Levenshtein.ratio(var_seq[:mid], var_seq[mid:mid*2]) > thresh: + peaks = [mid/step] + else: + peaks = [p+start//step for p in peaks] + if len(peaks) > 0: + est_period = min([peaks[i+1] - peaks[i] for i in range(len(peaks)-1)] + [peaks[0]]) + pidx = [int(round(p/est_period)) for p in peaks] + period = int((sum([pidx[i] * p for i, p in enumerate(peaks)])/sum([pidx[i]**2 for i, p in enumerate(peaks)]) if len(peaks) > 1 else peaks[0]) * step) + cnt = int(round(length/period)) + if cnt < 2: + return [len(var_seq)] + p_min, p_max = int(length/(cnt+0.5)), int(length/(cnt-0.5)) + p_step = max(1, (p_max - p_min)//10) + while True: + ratios = [max([Levenshtein.ratio(var_seq[i*p:(i+1)*p], var_seq[(i+1)*p:(i+2)*p]) for i in range(cnt-1)]) for p in range(p_min, p_max+1, p_step)] + best_ratio = max(ratios) + best_period = p_min + (ratios.index(best_ratio))*p_step + if p_step == 1: + break + p_min, p_max = best_period - 2 * p_step, best_period + 2 * p_step + p_step = max(1, (p_max - p_min)//10) + ratio_orig = max([Levenshtein.ratio(var_seq[i*period:(i+1)*period], var_seq[(i+1)*period:(i+2)*period]) for i in range(cnt-1)]) + if best_ratio > ratio_orig: + period = best_period + periods = {period: max(best_ratio, ratio_orig)} + i = 2 + while True: + cur_period = period * i + if cur_period * 2 > length: + break + cur_cnt = int(round(length/cur_period)) + if cur_cnt >= 2: + periods[cur_period] = max([Levenshtein.ratio(var_seq[j*cur_period:(j+1)*cur_period], var_seq[(j+1)*cur_period:(j+2)*cur_period]) for j in range(cur_cnt-1)]) + i += 1 + periods = {k: v for k, v in periods.items() if k >= MIN_SEQ_LEN} + if len(periods) > 1: + min_ratio = max(thresh, max(periods.values()) - 0.03) + periods = {k: v for k, v in periods.items() if v > min_ratio} + return list(periods.keys()) + return [len(var_seq)] + +# Align one repeat unit (seq) to a reference window around the INDEL position using mappy. +# If only part of seq aligned (partial match), extend the alignment boundary via binary +# search on Levenshtein ratio to handle split period boundaries. +# Then walk outward in steps of matched_ref_length to find the full tandem repeat array. +# Returns: (match_start, match_stop), (array_start, array_stop), (seq_start, seq_stop), ratio +def mm2_match(seq, ref, chrom, pos, half_win, thresh): + ref_search_start = max(0, pos - half_win) + ref_search_stop = pos + half_win + max_nm_ratio = 1 - thresh + min_match_length = MIN_SEQ_LEN + ref_seq = ref.get(chrom, ref_search_start, ref_search_stop) + if not ref_seq: + return None + aligner = mappy.Aligner(seq=ref_seq, preset='map-ont') + hits = list(aligner.map(seq)) + if not hits: + return None + # filter hits + filtered = [] + for h in hits: + if h.strand != 1 or h.is_primary == False: + continue + nm = h.NM if hasattr(h, 'NM') else (h.blen - h.mlen) + nm_rate = nm / h.blen if h.blen > 0 else 1.0 + if nm_rate >= max_nm_ratio or h.blen <= min_match_length: + continue + filtered.append((h, nm_rate)) + if not filtered: + return None + min_nm_rate = min(f[1] for f in filtered) + filtered = [(h, nr) for h, nr in filtered if nr <= min(min_nm_rate + 0.01, max_nm_ratio)] + best_hit, best_nm_rate = max(filtered, key=lambda x: x[0].blen) + seq_start, seq_stop = best_hit.q_st, best_hit.q_en + ref_start = best_hit.r_st + ref_search_start + ref_stop = best_hit.r_en + ref_search_start + matched_seq_length = seq_stop - seq_start + matched_ref_length = ref_stop - ref_start + best_matched_ref = ref.get(chrom, ref_start, ref_stop) + matched_seq = seq[seq_start:seq_stop] + best_ratio = Levenshtein.ratio(best_matched_ref, matched_seq) + if best_ratio < thresh: + return None + # Partial match extension: when mappy aligned only part of seq, the period boundary + # may be split (e.g. seq = [tail|head] of repeat). Extend the match into adjacent + # reference to recover the full period, using binary search on Levenshtein ratio. + if abs(len(seq) - matched_seq_length) > 50 and (len(seq)/matched_seq_length-1) < thresh and len(seq) <= MAX_EXTEND_SEQ: + if len(seq) - seq_stop < 10: + ref_after = ref.get(chrom, ref_stop, ref_stop + len(seq) - matched_seq_length) + step = min(100, len(ref_after)//5) + step = max(1, step) + start_i = step + end_i = len(ref_after) + seq_len_diff = len(seq) - seq_stop + last_valid_ratio = -1 + last_valid = 0 + last_invalid = -1 + best_ext = 0 + while True: + for l in range(start_i, end_i, step): + ratio = Levenshtein.ratio(best_matched_ref + ref_after[:l], seq[seq_start:]+seq[:l-seq_len_diff]) + if ratio > thresh: + last_valid = l + last_valid_ratio = ratio + else: + last_invalid = l + break + if last_invalid == -1 or step == 1: + best_ext = last_valid + break + step //= 2 + start_i = last_valid + end_i = last_invalid + if last_valid_ratio > 0: + best_ratio = last_valid_ratio + ref_stop += best_ext + best_matched_ref += ref_after[:best_ext] + matched_seq = seq[seq_start:] + seq[:best_ext - seq_len_diff] + matched_seq_length = len(matched_seq) + seq_stop = seq_start + matched_seq_length + matched_ref_length += best_ext + elif seq_start < 10: + ref_before = ref.get(chrom, ref_start - len(seq) + matched_seq_length, ref_start) + step = min(100, len(ref_before)//5) + step = max(1, step) + start_i = step + end_i = len(ref_before) + last_valid_ratio = -1 + last_valid = 0 + last_invalid = -1 + best_ext = 0 + while True: + for l in range(start_i, end_i, step): + ratio = Levenshtein.ratio(ref_before[-l:] + best_matched_ref, seq[-l+seq_start:] + seq[:seq_stop]) + if ratio > thresh: + last_valid = l + last_valid_ratio = ratio + else: + last_invalid = l + break + if last_invalid == -1 or step == 1: + best_ext = last_valid + break + step //= 2 + start_i = last_valid + end_i = last_invalid + if last_valid_ratio > 0: + best_ratio = last_valid_ratio + ref_start -= best_ext + best_matched_ref = ref_before[-best_ext:] + best_matched_ref + matched_seq = seq[-best_ext+seq_start:] + seq[:seq_stop] + matched_seq_length = len(matched_seq) + seq_start = seq_stop - len(matched_seq) + matched_ref_length += best_ext + # Walk outward from the match to find the full tandem repeat array on reference. + # REFSTART/REFSTOP define where read-depth change is expected (used by cnv_eval). + ref_start_ext = ref_start + ref_stop_ext = ref_stop + for _ in range(MAX_EXTEND_ITERS): + ref_start_ext -= matched_ref_length + if ref_start_ext < 0 or Levenshtein.ratio(ref.get(chrom, ref_start_ext, ref_start_ext+matched_ref_length), best_matched_ref) < thresh: + break + for _ in range(MAX_EXTEND_ITERS): + ref_stop_ext += matched_ref_length + if Levenshtein.ratio(ref.get(chrom, ref_stop_ext-matched_ref_length, ref_stop_ext), best_matched_ref) < thresh: + break + return (ref_start, ref_stop), (ref_start_ext, ref_stop_ext), (seq_start, seq_stop), best_ratio + +# Try to match one repeat unit in the reference immediately adjacent to the INDEL. +# For insertions: align alt_seq[:period] against ref_after[:p] + ref_before[-(period-p):] +# at varying split positions p (binary search). Handles wrap-around period boundaries. +# For deletions: the deleted ref sequence itself is the repeat unit. +def local_match(v, ref, period, thresh): + if len(v.ref) < len(v.alt[0]): + alt_seq = v.alt[0] + ref_before = ref.get(v.chrom, v.pos-period, v.pos) + ref_after = ref.get(v.chrom, v.pos, v.pos+period) + step = max(period//40, 100) + cnt = int(round(len(alt_seq)/period)) + offset = len(alt_seq) - cnt * period + if offset > 0: + ref_after = alt_seq[-offset:] + ref_after + elif offset < 0: + ref_after = ref_after[-offset:] + alt_period_seq = alt_seq[:period] + ratios = {p: Levenshtein.ratio(ref_after[:p] + ref_before[-(period-p):], alt_period_seq) for p in range(0, period, step)} + ratios[period] = Levenshtein.ratio(ref_after, alt_period_seq) + while True: + best_pos = max(ratios, key=ratios.get) + max_ratio = ratios[best_pos] + if max_ratio < 0.8: + return None + if step == 1: + break + step = step//2 + p_lo = max(0, best_pos - step) + p_hi = min(best_pos + step, period) + for p in (p_lo, p_hi): + if p not in ratios: + ratios[p] = Levenshtein.ratio(ref_after[:p] + ref_before[-(period-p):], alt_period_seq) + if max_ratio < thresh: + return None + ref_start = v.pos - (period - best_pos) + ref_end = v.pos + best_pos + ref_seq = ref_before[-(period-best_pos):] + alt_seq[:best_pos] if ref_start != v.pos else alt_period_seq + else: + ref_start = v.pos + ref_end = v.pos + period + ref_seq = v.ref[:period] + max_ratio = 1. + ref_start_ext = ref_start + ref_end_ext = ref_end + for _ in range(MAX_EXTEND_ITERS): + ref_start_ext -= period + if ref_start_ext < 0 or Levenshtein.ratio(ref.get(v.chrom, ref_start_ext, ref_start_ext+period), ref_seq) < thresh: + break + for _ in range(MAX_EXTEND_ITERS): + ref_end_ext += period + if Levenshtein.ratio(ref.get(v.chrom, ref_end_ext-period, ref_end_ext), ref_seq) < thresh: + break + return (ref_start, ref_end), (ref_start_ext, ref_end_ext), (0, period), max_ratio + +# Try local_match first (fast, exact adjacent match). Fall back to mm2_match +# (mappy alignment in a wider window) for segdups where the repeat may be distant. +def match_ref(v, ref, period, thresh): + local_result = local_match(v, ref, period, thresh) + if local_result is not None: + return local_result + return mm2_match(v.alt[0][:period], ref, v.chrom, v.pos, min(max(period, len(v.alt[0]), len(v.ref))*REF_SEARCH_CNT, REF_SEARCH_RNG), thresh) + +# Process one input INDEL variant into CNV call(s). +# Multi-allelic variants are split; nearby het INS with opposite phase are merged. +# For each allele: find_repeats -> match_ref -> pick best period -> emit DUP/DEL. +def proc_variant(v, ref, thresh): + stime = time.time() + result_vs = [] + # Split multi-allelic into per-allele variants, trim shared prefix/suffix + split_var = len(v.alt) > 1 + vs = [] + is_insert = [] + if split_var: + for i, a in enumerate(v.alt): + if a == '*': + continue + if len(a) < len(v.ref): + if len(v.ref) > MIN_SEQ_LEN and len(v.ref) - len(a) > MIN_SEQ_LEN: + v1 = copy_variant(v, alt=[a]) + if len(a) > 1: + if a == v1.ref[:len(a)] or len(a) < 10 or Levenshtein.ratio(a, v1.ref[:len(a)]) > thresh: + v1.pos += len(a) - 1 + v1.ref = v.ref[len(a):] + v1.alt = [a[-1]] + elif Levenshtein.ratio(a, v.ref[-len(a):]) > thresh: + v1.ref = v.ref[:-len(a)] + v1.alt = [v.ref[0]] + v1.samples[0]['GT'] = '0/1' + vs.append(v1) + is_insert.append(False) + elif len(a) - len(v.ref) > MIN_SEQ_LEN: + v1 = copy_variant(v) + v1.samples[0]['GT'] = '0/1' + if len(v.ref) < 6 or Levenshtein.ratio(v.alt[i][:len(v.ref)], v.ref) > min(thresh, 1-5/len(v.ref)): + v1.alt = [v1.alt[i][len(v.ref)-1:]] + v1.pos += len(v.ref) - 1 + v1.ref = v.ref[-1] + elif len(v.ref) < 6 or Levenshtein.ratio(v.alt[i][-len(v.ref):], v.ref) > min(thresh, 1-5/len(v.ref)): + v1.alt = [v1.alt[i][:-len(v.ref)]] + v1.ref = v.ref[0] + else: + v1.alt = [v1.alt[i]] + vs.append(v1) + is_insert.append(True) + else: + vs = [v] + is_insert = [len(v.alt[0]) > len(v.ref)] + periods = [] + for si, vv in enumerate(vs): + if not is_insert[si]: + del_len = len(vv.ref) - len(vv.alt[0]) + if del_len >= MIN_SEQ_LEN: + if del_len > MAX_SEQ_LEN: + gt = re.split(r"\||/", vv.samples[0]['GT'].replace('.', '0')) + gt_sum = sum(int(g) for g in gt) + end_pos = vv.pos + len(vv.ref) + vv.info = {'SVTYPE': 'DEL', 'CNDIFF': -gt_sum, 'POSSHIFT': 0, 'POSSHIFT2': 0, + 'DIST': 1.0, 'SVLEN': del_len, 'PERIOD': del_len, + 'REFSTART': vv.pos + 1, 'REFSTOP': end_pos, 'END': end_pos, + 'INDEL': '%s:%d' % (vv.chrom, vv.pos + 1)} + vv.end = end_pos - 1 + vv.ref = ref.get(vv.chrom, vv.pos, vv.pos + 1) + vv.alt = [''] + vv.line = None + result_vs.append(vv) + periods.append([]) + else: + periods.append(find_repeats(vv.ref[1:], thresh)) + else: + periods.append([]) + else: + if len(vv.alt[0]) - len(vv.ref) >= MIN_SEQ_LEN and len(vv.alt[0]) <= MAX_SEQ_LEN: + periods.append(find_repeats(vv.alt[0], thresh)) + else: + periods.append([]) + if len(sum(periods, [])) == 0: + return [], time.time() - stime + # If both alleles are same direction with matching periods, merge as homozygous + if (all(is_insert) or not any(is_insert)) and len(periods) > 1 and min(len(p) for p in periods) > 0: + updated_periods = [] + for p0 in periods[0]: + p1s = [p1 for p1 in periods[1] if abs(p0/p1-1) <= 0.05] + if p1s and Levenshtein.ratio(*(a[:min(p0, p1s[0])] for a in v.alt)) > 1 - (1-thresh)/2: + updated_periods.append(min(p0, p1s[0])) + if updated_periods: + periods = [updated_periods] + vs = [vs[1]] if len(vs[1].alt[0]) > len(vs[0].alt[0]) else [vs[0]] + vs[0].samples[0]['GT'] = '1/1' + + for si, sv in enumerate(vs): + period = periods[si] + if not period: + continue + results = [] + for j, p in enumerate(period): + try: + result = match_ref(sv, ref, p, thresh) + except Exception as e: + log.warning("match_ref failed for %s:%d period=%d: %s", sv.chrom, sv.pos, p, e) + result = None + if result: + results.append((j, *result)) + if not results: + continue + # Pick the best period: highest Levenshtein ratio, break ties by matched length + if len(results) == 1: + result = results[0] + else: + best_ratios = [r[4] for r in results] + best_ratio = max(best_ratios) + best_i = best_ratios.index(best_ratio) + result = results[best_i] + best_seq_length = result[3][1] - result[3][0] + for ri, r in enumerate(results): + if r and best_i != ri and best_ratio - r[4] < 0.01: + seq_length = r[3][1] - r[3][0] + if seq_length > best_seq_length: + result = r + best_i = ri + best_seq_length = seq_length + + ri, best_match, ref_range, seq_range, best_ratio = result + best_period = period[ri] + if is_insert[si]: + period_cnt = len(sv.alt[0]) / best_period + else: + overlap = max(0, min(sv.pos + len(sv.ref), ref_range[1]) - max(sv.pos, ref_range[0])) + period_cnt = overlap / best_period + period_cnt = int(round(period_cnt)) + seq_length = seq_range[1] - seq_range[0] + # Filter: matched portion must be large enough and a meaningful fraction of period + if seq_length < MIN_SEQ_LEN or seq_length / best_period < MIN_PERIOD_RATIO: + continue + gt = re.split(r"\||/", sv.samples[0]['GT'].replace('.', '0')) + gt_sum = sum(int(g) for g in gt) + is_dup = is_insert[si] + # POSSHIFT: distance from INDEL to matched repeat on reference + # POSSHIFT2: distance from INDEL to nearest edge of tandem array (0 = inside array) + # REFSTART/REFSTOP: tandem array boundaries (where depth change is expected) + indel_pos = '%s:%d' % (sv.chrom, sv.pos + 1) + sv.info = {'SVTYPE': 'DUP' if is_dup else 'DEL', + 'CNDIFF': period_cnt * (gt_sum if is_dup else -gt_sum), + 'POSSHIFT': best_match[0] - sv.pos, + 'POSSHIFT2': sv.pos - ref_range[1] if sv.pos > ref_range[1] else (ref_range[0] - sv.pos if ref_range[0] > sv.pos else 0), + 'DIST': best_ratio, + 'SVLEN': seq_length, + 'PERIOD': best_period, + 'REFSTART': ref_range[0] + 1, + 'REFSTOP': ref_range[1] + 1, + 'END': best_match[1] + 1, + 'INDEL': indel_pos} + sv.pos = best_match[0] + sv.end = best_match[1] + sv.ref = ref.get(sv.chrom, sv.pos, sv.pos+1) + sv.alt = [''] if is_dup else [''] + sv.line = None + result_vs.append(sv) + return result_vs, time.time() - stime + +def proc_batch(ref, variants, thresh): + results = [] + for v in variants: + try: + results.append(proc_variant(v, ref, thresh)) + except Exception as e: + log.error("Failed processing %s:%d: %s", v.chrom, v.pos, e) + results.append(([], 0.0)) + return results + +# Merge two adjacent het INS variants with opposite phase (1|0 + 0|1) into one hom variant. +def merge(ref, v1, v2): + if v1.chrom != v2.chrom or v2.pos - v1.pos > 10: + return None + if set([v1.samples[0]['GT'], v2.samples[0]['GT']]) != set(['1|0', '0|1']): + return None + if len(v1.alt) != len(v2.alt) or len(v2.alt) != 1: + return None + if len(v1.ref) > len(v1.alt[0]) or len(v1.ref) > len(v2.alt[0]): + return None + ref_gap = ref.get(v1.chrom, v1.end, v2.pos+1) + v1.ref += ref_gap + v1.alt[0] += ref_gap + v1.alt.append(v1.ref[:-1] + v2.alt[0]) + v1.samples[0]['GT'] = '1|1' + return v1 + +def gt_set(gt): + return [int(g) for g in re.split(r"\||/", gt) if g != '.'] + +# Adjust CNDIFF/SVLEN so that the repeat count fits the array size. +# E.g. CNDIFF=2 with SVLEN=2*period in a 4-copy array -> CNDIFF=1 with SVLEN=4*period. +def extend_v(v): + cndiff = v.info['CNDIFF'] + period = v.info['PERIOD'] + if not cndiff or not period: + return v + num_periods = round(v.info['SVLEN'] / period) + gt_sum = sum(gt_set(v.samples[0]['GT'])) or 1 + diff = cndiff // gt_sum + max_num_period = (v.info['REFSTOP'] - v.info['REFSTART']) / period + if max_num_period - round(max_num_period) < 0.1: + max_num_period = round(max_num_period) + else: + max_num_period = math.ceil(max_num_period) - 1 + best_d = -1 + for d in range(abs(diff), 0, -1): + sp = num_periods * abs(diff) / d + if abs(sp - round(sp)) > 0.1 or sp > max_num_period: + continue + best_d = d + if abs(diff) == best_d or best_d == -1: + return v + svlen_periods = round(num_periods * abs(diff) / best_d) + v.info['CNDIFF'] = best_d if cndiff > 0 else -best_d + v.info['SVLEN'] = svlen_periods * period + v.pos = v.info['REFSTART'] + v.end = v.pos + v.info['SVLEN'] + v.line = None + return v + +# Merge overlapping output CNV calls from the same tandem array. +# Handles: (1) opposite-phase same-type pairs -> hom, (2) opposite-type pairs -> net CNDIFF, +# (3) same-type DEL/DUP pairs with compatible periods -> combined CNDIFF. +def merge_output_vars(variants): + if not variants: + return [] + result = [] + last = variants[0] + for v in variants[1:]: + if last is None: + last = v + continue + merged = _try_merge_output(last, v) + if merged == 'drop': + last = None + elif merged: + last = merged + else: + result.append(last) + last = v + if last is not None: + result.append(last) + return result + +def _try_merge_output(v1, v2): + if v1.chrom != v2.chrom: + return None + rs1, re1 = v1.info['REFSTART'], v1.info['REFSTOP'] + rs2, re2 = v2.info['REFSTART'], v2.info['REFSTOP'] + overlap = min(re1, re2) - max(rs1, rs2) + if overlap <= 0: + return None + len1, len2 = re1 - rs1, re2 - rs2 + if len1 <= 0 or len2 <= 0: + return None + r1, r2 = overlap / len1, overlap / len2 + if max(r1, r2) < 0.9: + return None + # Opposite types (DUP+DEL or DEL+DUP): net the CNDIFF + if v1.alt[0] != v2.alt[0] and min(r1, r2) > 0.9: + net = v1.info['CNDIFF'] + v2.info['CNDIFF'] + if net == 0: + return 'drop' # cancel out + keep = v1 if abs(v1.info['CNDIFF']) > abs(v2.info['CNDIFF']) else v2 + keep.info['CNDIFF'] = net + keep.line = None + return keep + # Same type DEL + if v1.alt[0] == '' and v2.alt[0] == '': + keep = v1 if len1 >= len2 else v2 + if keep.info['CNDIFF'] == -2 or v1.samples[0]['GT'] == v2.samples[0]['GT']: + return keep + if min(r1, r2) > 0.9: + gt1, gt2 = gt_set(v1.samples[0]['GT']), gt_set(v2.samples[0]['GT']) + dot = '.' in v1.samples[0]['GT'] or '.' in v2.samples[0]['GT'] + if not dot: + gt = [gt1[i] | gt2[i] for i in range(min(len(gt1), len(gt2)))] + keep.samples[0]['GT'] = '|'.join(str(g) for g in gt) + else: + keep.samples[0]['GT'] = str(gt1[0] | gt2[0]) + '/.' + keep.info['CNDIFF'] = min(-2, v1.info['CNDIFF'] + v2.info['CNDIFF']) + keep.line = None + return keep + # Same type DUP with compatible periods + if v1.alt[0] == '' and v2.alt[0] == '': + p1, p2 = v1.info['PERIOD'], v2.info['PERIOD'] + r = max(p1, p2) / min(p1, p2) if min(p1, p2) > 0 else 999 + if abs(r - round(r)) > 0.1: + log.warning("Incompatible periods %d vs %d at %s:%d", p1, p2, v1.chrom, v1.pos) + return None + period = min(p1, p2) + keep, other = (v1, v2) if len1 >= len2 else (v2, v1) + # Normalize CNDIFF to common period; use round to avoid truncating partial matches + n1 = max(1, round(keep.info['SVLEN'] / period)) + n2 = max(1, round(other.info['SVLEN'] / period)) + cd1 = keep.info['CNDIFF'] * n1 + cd2 = other.info['CNDIFF'] * n2 + gt1, gt2 = gt_set(v1.samples[0]['GT']), gt_set(v2.samples[0]['GT']) + if gt1 == gt2 or (cd1 == cd2 and [1, 1] not in (gt1, gt2)): + dot = '.' in v1.samples[0]['GT'] or '.' in v2.samples[0]['GT'] + if not dot: + gt = [gt1[i] | gt2[i] for i in range(min(len(gt1), len(gt2)))] + keep.samples[0]['GT'] = '|'.join(str(g) for g in gt) + else: + keep.samples[0]['GT'] = str(gt1[0] | gt2[0]) + '/.' + keep.info['PERIOD'] = period + keep.info['SVLEN'] = period + keep.info['CNDIFF'] = cd1 + cd2 + keep = extend_v(keep) + keep.line = None + return keep + return None + +def main(args): + log_level = logging.DEBUG if args.verbose else logging.INFO + logging.basicConfig(level=log_level, format='%(asctime)s %(levelname)s %(message)s', + datefmt='%H:%M:%S') + ref = Reference(args.ref) + input_vcf = vcflib.VCF(args.input_vcf, 'r') + fout = vcflib.VCF(args.out_vcf, 'w') + update = ('##INFO=', + '##INFO=', + '##INFO=', + '##INFO=', + '##INFO=', + '##INFO=', + '##INFO=', + '##INFO=', + '##INFO=', + '##ALT=', + '##ALT=', + '##INFO=', + '##INFO=', + ) + fout.copy_header(input_vcf, update=update) + fout.emit_header() + all_contigs = [c for c, t in input_vcf.contigs.items()] + if args.contig: + input_contigs = args.contig.split(',') + contigs = [c for c in all_contigs if c in input_contigs] + if not contigs: + log.error("Contig %s not found in the VCF.", args.contig) + sys.exit(1) + else: + contigs = all_contigs + t0 = time.time() + variants = [] + for c in contigs: + vcf_c = input_vcf.range(c) + for v in vcf_c: + if (len(v.filter) == 0 or 'PASS' in v.filter) and max(len(a) for a in [v.ref] + v.alt) >= MIN_SEQ_LEN: + if variants: + v_new = merge(ref, variants[-1], v) + if v_new: + variants[-1] = v_new + continue + variants.append(v) + log.info("Loaded %d qualifying variants from %d contigs", len(variants), len(contigs)) + if args.threads == 1: + all_vars = [] + for v in variants: + vs, t = proc_variant(v, ref, args.thresh) + all_vars += vs + if vs: + for sv in vs: + log.info('%s:%d %s SVLEN=%d shift=%d,%d (%.3fs)', + sv.chrom, sv.pos, sv.alt[0], sv.info['SVLEN'], + sv.info['POSSHIFT'], sv.info['POSSHIFT2'], t) + else: + log.debug('%s:%d No CNV (%.3fs)', v.chrom, v.pos, t) + else: + n_threads = args.threads or os.cpu_count() + # Sort by expected difficulty (sequence length) descending so that + # expensive variants are dispatched first and don't pile up at the end. + # Use small batches (5) for better load balancing across workers. + variants.sort(key=lambda v: max(len(a) for a in [v.ref] + v.alt), reverse=True) + batch_size = 5 + args_in = [] + for i in range(0, len(variants), batch_size): + args_in.append((ref, variants[i:i+batch_size], args.thresh)) + all_vars = [] + with Pool(n_threads) as p: + for result in p.starmap(proc_batch, args_in): + for vs, t in result: + all_vars += vs + # Merge overlapping output calls (same array, different haplotypes) and write + for c in contigs: + out_c = sorted([v for v in all_vars if v.chrom == c], key=lambda v: v.pos) + out_c = merge_output_vars(out_c) + for v in sorted(out_c, key=lambda v: (v.pos, v.end)): + fout.emit(v) + t1 = time.time() + mm, ut, st = 0, 0, 0 + mem_scale = 1 if platform.system() == 'Darwin' else 1024 + for who in (resource.RUSAGE_SELF, resource.RUSAGE_CHILDREN): + ru = resource.getrusage(who) + mm += ru.ru_maxrss * mem_scale + ut += ru.ru_utime + st += ru.ru_stime + log.info('overall: %d mem %.3f user %.3f sys %.3f real', mm, ut, st, t1-t0) + fout.close() + input_vcf.close() + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Convert long INDELs to CNV calls") + parser.add_argument('ref', help='Reference FASTA') + parser.add_argument('input_vcf', help='Input VCF with long INDELs') + parser.add_argument('out_vcf', help='Output VCF file name') + parser.add_argument('--contig', help="Contigs to process (comma-separated)") + parser.add_argument('-t', '--threads', help="Concurrent processes (default: nproc)", default=None, type=int) + parser.add_argument('--thresh', help="Minimum Levenshtein similarity ratio", default=0.9, type=float) + parser.add_argument('-v', '--verbose', action='store_true', help="Verbose logging") + args = parser.parse_args() + main(args) diff --git a/sentieon_cli/scripts/trimalt.py b/sentieon_cli/scripts/trimalt.py index 167753e..a216200 100644 --- a/sentieon_cli/scripts/trimalt.py +++ b/sentieon_cli/scripts/trimalt.py @@ -31,13 +31,19 @@ def parse_kv(kv): # pop vcf only site, skip continue + alts = vals[4].split(',') ka, kr, kg, info = None, None, None, None - if len(vals) >= 8 and vals[7] != '.': + if '' in alts: + # is the last original alt allele + nonref_idx = alts.index('') + ka = [True] * (nonref_idx + 1) + ka += [False] * (len(alts) - nonref_idx - 1) + + elif len(vals) >= 8 and vals[7] != '.': info = list(map(parse_kv, vals[7].split(';'))) for k,v in info: if k == 'AF': ka = [x != '.' for x in v.split(',')] - kr = [True] + ka break if ka is None: @@ -48,14 +54,23 @@ def parse_kv(kv): sys.stdout.write(line) continue + kr = [True] + ka + if not info and len(vals) >= 8 and vals[7] != '.': + info = list(map(parse_kv, vals[7].split(';'))) + if vals[4] != '.': v = vals[4].split(',') v = [x for x,y in zip(v, ka) if y] r = vals[3] - #n = len(os.path.commonprefix([r[:0:-1]] + [a[:0:-1] for a in v])) - n = min(len(r), min(map(len, v))) - 1 + try: + n = min(len(r), min([len(x) for x in v if x != ''])) - 1 + except ValueError: + n = 0 if n > 0: - r, v = r[:-n], [a[:-n] for a in v] + r = r[:-n] + for i in range(len(v)): + if v[i] != '': + v[i] = v[i][:-n] vals[3] = r vals[4] = ','.join(v) diff --git a/sentieon_cli/scripts/vcf_mod.py b/sentieon_cli/scripts/vcf_mod.py index 148a99a..483538e 100644 --- a/sentieon_cli/scripts/vcf_mod.py +++ b/sentieon_cli/scripts/vcf_mod.py @@ -407,6 +407,10 @@ def patch1(vcfi1, vcfi2, vcfd1, vcfd2, vcfo1, vcfo2, **kwargs): d1 = None if d2 and d2.samples[0].get('GT') is None: d2 = None + if v1 and v1.samples[0].get('GT') is None: + v1 = None + if v2 and v2.samples[0].get('GT') is None: + v2 = None if not v1 or d1 and compatible(v1, d1): v1 = d1 if not v2 or d2 and compatible(v2, d2): diff --git a/sentieon_cli/sentieon_pangenome.py b/sentieon_cli/sentieon_pangenome.py index 7ac9cc1..6f94ab3 100644 --- a/sentieon_cli/sentieon_pangenome.py +++ b/sentieon_cli/sentieon_pangenome.py @@ -12,7 +12,7 @@ import packaging.version -from importlib_resources import files +from importlib.resources import files from . import command_strings as cmds from .archive import ar_load @@ -21,6 +21,8 @@ from .driver import ( AlignmentStat, BaseDistributionByCycle, + CNVModelApply, + CNVscope, CoverageMetrics, Dedup, DNAModelApply, @@ -30,6 +32,7 @@ InsertSizeMetricAlgo, LocusCollector, MeanQualityByCycle, + PangenomeSV, QualDistribution, WgsMetricsAlgo, ) @@ -98,6 +101,15 @@ class SentieonPangenome(BasePangenome): ), "type": path_arg(exists=True, is_file=True), }, + "call_svs": { + "help": "Call structural and copy number variants.", + "action": "store_true", + }, + "gvcf": { + "flags": ["-g", "--gvcf"], + "help": "Generate a gVCF output file.", + "action": "store_true", + }, "skip_metrics": { "help": "Skip metrics collection and multiQC", "action": "store_true", @@ -123,6 +135,10 @@ class SentieonPangenome(BasePangenome): "help": argparse.SUPPRESS, "action": "store_true", }, + "skip_small_variants": { + "help": argparse.SUPPRESS, + "action": "store_true", + }, } ) @@ -132,14 +148,17 @@ def __init__(self) -> None: super().__init__() self.readgroup: Optional[str] = None self.sample_input: List[pathlib.Path] = [] - self.bed: Optional[pathlib.Path] = None self.pop_vcf: Optional[pathlib.Path] = None + self.bed: Optional[pathlib.Path] = None + self.call_svs = False + self.gvcf = False self.skip_metrics = False self.skip_multiqc = False self.skip_contig_checks: bool = False self.skip_pangenome_name_checks: bool = False self.skip_pop_vcf_id_check: bool = False self.skip_model_apply = False + self.skip_small_variants = False def main(self, args: argparse.Namespace) -> None: """Run the pipeline""" @@ -258,8 +277,12 @@ def validate(self) -> None: sys.exit(2) def validate_bundle(self) -> None: - assert self.pop_vcf - assert self.gbz + if not self.pop_vcf: + self.logger.error("pop_vcf is required") + sys.exit(2) + if not self.gbz: + self.logger.error("gbz is required") + sys.exit(2) bundle_info_bytes = ar_load( str(self.model_bundle) + "/bundle_info.json" ) @@ -311,6 +334,13 @@ def validate_bundle(self) -> None: ) sys.exit(2) + if self.call_svs and "cnv.model" not in bundle_members: + self.logger.error( + "The model bundle does not contain a 'cnv.model' file " + "required for CNV calling with `--call_svs`." + ) + sys.exit(2) + if not self.skip_pop_vcf_id_check and not self.dry_run: pop_vcf_id = vcf_id(self.pop_vcf) if bundle_vcf_id != pop_vcf_id: @@ -373,9 +403,15 @@ def build_dag(self) -> DAG: def build_first_dag(self) -> DAG: """Build the main DAG for the Sentieon pangenome pipeline""" - assert self.reference - assert self.model_bundle - assert self.output_vcf + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) self.logger.info("Building the Sentieon pangenome DAG") dag = DAG() @@ -401,6 +437,13 @@ def build_first_dag(self) -> DAG: sample_gfa = self.tmp_dir.joinpath("sample-hap.gfa") sample_fasta = self.tmp_dir.joinpath("sample-hap.fa") mm2_bam = self.tmp_dir.joinpath("sample-mm2.bam") + if not self.r1_fastq: + # with bam/cram input, output the realigned bam/cram + mm2_bam = pathlib.Path( + str(self.output_vcf).replace( + ".vcf.gz", f"_mm2_deduped.{suffix}" + ) + ) raw_vcf = self.tmp_dir.joinpath("sample-dnascope.vcf.gz") transfer_vcf = self.tmp_dir.joinpath("sample-dnascope_transfer.vcf.gz") # - Ensure we have a bam file for t1k @@ -467,6 +510,8 @@ def build_first_dag(self) -> DAG: dnascope_dependencies.add(mm2_job) # With fastq input, perform dedup and metrics + cnv_input_bams: List[pathlib.Path] = [] + cnvscope_deps: Set[Job] = set() if self.r1_fastq: dnascope_bams.append(out_bwa_aln) dnascope_bams.append(out_mm2_aln) @@ -483,6 +528,9 @@ def build_first_dag(self) -> DAG: dag.add_job(mm2_dedup_job, {mm2_lc_job}) dnascope_dependencies.add(mm2_dedup_job) + cnv_input_bams = [out_bwa_aln] + cnvscope_deps = {bwa_dedup_job} + if not self.skip_metrics: metrics_job, rehead_job = self.build_metrics_job( [out_bwa_aln, out_mm2_aln], @@ -495,10 +543,43 @@ def build_first_dag(self) -> DAG: dag.add_job(multiqc_job, {rehead_job}) else: dnascope_bams.append(mm2_bam) + cnv_input_bams = list(self.sample_input) # DNAscope calling with bwa and mm2 input + sv_vcf = None + if self.call_svs: + sv_vcf = pathlib.Path( + str(self.output_vcf).replace(".vcf.gz", "_sv.vcf.gz") + ) + + if self.skip_small_variants and not self.call_svs: + return dag + + if self.skip_small_variants and self.call_svs and sv_vcf: + # SV calling only + dnascope_job = self.build_dnascope_job( + raw_vcf, + dnascope_bams, + sv_vcf=sv_vcf, + gfa_file=sample_gfa, + ) + dag.add_job(dnascope_job, dnascope_dependencies) + self._add_cnv_jobs( + dag, + sv_vcf, + cnv_input_bams, + cnvscope_deps, + dnascope_job, + ) + return dag + apply_dependencies = set() - dnascope_job = self.build_dnascope_job(raw_vcf, dnascope_bams) + dnascope_job = self.build_dnascope_job( + raw_vcf, + dnascope_bams, + sv_vcf=sv_vcf, + gfa_file=sample_gfa if self.call_svs else None, + ) dag.add_job(dnascope_job, dnascope_dependencies) apply_dependencies.add(dnascope_job) @@ -520,12 +601,19 @@ def build_first_dag(self) -> DAG: dag.add_job(concat_job, set(transfer_jobs)) apply_dependencies.add(concat_job) - if self.skip_model_apply: - return dag - - # DNAModelApply - apply_job = self.build_dnamodelapply_job(transfer_vcf) - dag.add_job(apply_job, apply_dependencies) + if not self.skip_model_apply: + # DNAModelApply + apply_job = self.build_dnamodelapply_job(transfer_vcf) + dag.add_job(apply_job, apply_dependencies) + + if self.call_svs and sv_vcf: + self._add_cnv_jobs( + dag, + sv_vcf, + cnv_input_bams, + cnvscope_deps, + dnascope_job, + ) return dag @@ -535,8 +623,12 @@ def build_alignment_job( sample_fastq: pathlib.Path, ) -> Job: """Build the alignment and extract jobs""" - assert self.reference - assert self.model_bundle + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) unzip = "igzip" if not shutil.which(unzip): @@ -570,8 +662,12 @@ def build_haplotypes_job( self, output_gbz: pathlib.Path, kmer_file: pathlib.Path ) -> Job: """Build vg haplotypes job""" - assert self.hapl - assert self.gbz + if not self.hapl: + self.logger.error("hapl is required") + sys.exit(2) + if not self.gbz: + self.logger.error("gbz is required") + sys.exit(2) haplotypes_job = Job( cmds.cmd_vg_haplotypes( @@ -629,8 +725,12 @@ def build_minimap2_lift_job( sample_gfa: pathlib.Path, ) -> Job: """Build minimap2 alignment with pgutil lift job""" - assert self.model_bundle - assert self.reference + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) rg = ( self.fastq_readgroup @@ -713,7 +813,9 @@ def build_metrics_job( sample_input: List[pathlib.Path], ) -> Tuple[Job, Job]: """Build a metrics job""" - assert self.output_vcf + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) # Create the metrics directory sample_name = self.output_vcf.name.replace(".vcf.gz", "") @@ -783,8 +885,12 @@ def build_dnascope_job( self, out_vcf: pathlib.Path, input_bams: List[pathlib.Path], + sv_vcf: Optional[pathlib.Path] = None, + gfa_file: Optional[pathlib.Path] = None, ) -> Job: - assert self.model_bundle + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) read_filters = [] if self.tech.upper() == "ULTIMA": @@ -798,14 +904,23 @@ def build_dnascope_job( interval=self.bed, read_filter=read_filters, ) - driver.add_algo( - DNAscope( - out_vcf, - model=self.model_bundle.joinpath("dnascope.model"), - pcr_indel_model=pcr_indel_model, - dbsnp=self.dbsnp, + if not self.skip_small_variants: + driver.add_algo( + DNAscope( + out_vcf, + model=self.model_bundle.joinpath("dnascope.model"), + pcr_indel_model=pcr_indel_model, + dbsnp=self.dbsnp, + emit_mode="gvcf" if self.gvcf else "variant", + ) + ) + if sv_vcf and gfa_file: + driver.add_algo( + PangenomeSV( + sv_vcf, + gfa_file=gfa_file, + ) ) - ) return Job( Pipeline(Command(*driver.build_cmd())), "dnascope-raw", @@ -813,8 +928,12 @@ def build_dnascope_job( ) def build_dnamodelapply_job(self, in_vcf) -> Job: - assert self.output_vcf - assert self.model_bundle + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) driver = Driver( reference=self.reference, @@ -832,3 +951,153 @@ def build_dnamodelapply_job(self, in_vcf) -> Job: "model-apply", self.cores, ) + + def _add_cnv_jobs( + self, + dag: DAG, + sv_vcf: pathlib.Path, + cnv_input_bams: List[pathlib.Path], + cnvscope_deps: Set[Job], + dnascope_job: Job, + ) -> None: + """Add CNV calling jobs to the DAG""" + if not self.model_bundle: + self.logger.error("model_bundle is required") + sys.exit(2) + if not self.reference: + self.logger.error("reference is required") + sys.exit(2) + if not self.output_vcf: + self.logger.error("output_vcf is required") + sys.exit(2) + + cnvscope_vcf = self.tmp_dir.joinpath("sample-cnvscope.vcf.gz") + cnv_apply_vcf = self.tmp_dir.joinpath("sample-cnv_model_apply.vcf.gz") + indel2cnv_vcf = self.tmp_dir.joinpath("sample-sv_cnv.vcf.gz") + cnv_vcf = pathlib.Path( + str(self.output_vcf).replace(".vcf.gz", "_cnv.vcf.gz") + ) + + # CNVscope on BWA deduped BAM + cnvscope_job = self._build_cnvscope_job(cnvscope_vcf, cnv_input_bams) + dag.add_job(cnvscope_job, cnvscope_deps) + + # CNVModelApply on CNVscope output + cnv_model_apply_job = self._build_cnv_model_apply_job( + cnv_apply_vcf, cnvscope_vcf + ) + dag.add_job(cnv_model_apply_job, {cnvscope_job}) + + # Convert PangenomeSV output to CNVs + indel2cnv_job = self._build_indel2cnv_job(indel2cnv_vcf, sv_vcf) + dag.add_job(indel2cnv_job, {dnascope_job}) + + # Combine CNVModelApply output with converted SVs + combine_job = self._build_combine_cnv_job( + cnv_vcf, cnv_apply_vcf, indel2cnv_vcf + ) + dag.add_job(combine_job, {cnv_model_apply_job, indel2cnv_job}) + + def _build_cnvscope_job( + self, + output_vcf: pathlib.Path, + input_bams: List[pathlib.Path], + ) -> Job: + """Build a CNVscope job""" + assert self.model_bundle is not None + driver = Driver( + reference=self.reference, + thread_count=self.cores, + input=input_bams, + interval=self.bed, + ) + driver.add_algo( + CNVscope( + output=output_vcf, + model=self.model_bundle.joinpath("cnv.model"), + ) + ) + return Job( + Pipeline(Command(*driver.build_cmd())), + "cnvscope", + self.cores, + ) + + def _build_cnv_model_apply_job( + self, + output_vcf: pathlib.Path, + input_vcf: pathlib.Path, + ) -> Job: + """Build a CNVModelApply job""" + assert self.model_bundle is not None + driver = Driver( + reference=self.reference, + thread_count=self.cores, + ) + driver.add_algo( + CNVModelApply( + output=output_vcf, + model=self.model_bundle.joinpath("cnv.model"), + vcf=input_vcf, + ) + ) + return Job( + Pipeline(Command(*driver.build_cmd())), + "cnv-model-apply", + self.cores, + ) + + def _build_indel2cnv_job( + self, + output_vcf: pathlib.Path, + input_vcf: pathlib.Path, + ) -> Job: + """Convert PangenomeSV INDELs to CNV calls""" + indel2cnv_script = pathlib.Path( + str(files("sentieon_cli.scripts").joinpath("indel2cnv.py")) + ) + # Run in background + return Job( + Pipeline( + Command( + "sentieon", + "pyexec", + str(indel2cnv_script), + str(self.reference), + str(input_vcf), + str(output_vcf), + "-t", + str(self.cores), + ) + ), + "indel2cnv", + 0, + ) + + def _build_combine_cnv_job( + self, + output_vcf: pathlib.Path, + cnv_vcf: pathlib.Path, + converted_vcf: pathlib.Path, + ) -> Job: + """Combine CNVscope and converted SV calls""" + combine_script = pathlib.Path( + str(files("sentieon_cli.scripts").joinpath("combine_cnv.py")) + ) + return Job( + Pipeline( + Command( + "sentieon", + "pyexec", + str(combine_script), + "--cnv", + str(cnv_vcf), + "--converted", + str(converted_vcf), + "-o", + str(output_vcf), + ) + ), + "combine-cnv", + 0, + ) diff --git a/sentieon_cli/shell_pipeline.py b/sentieon_cli/shell_pipeline.py index db2413a..02eb082 100644 --- a/sentieon_cli/shell_pipeline.py +++ b/sentieon_cli/shell_pipeline.py @@ -130,7 +130,8 @@ def __init__( file_output: Optional[pathlib.Path] = None, ): self.nodes = list(nodes) - assert self.nodes # Nodes cannot be empty + if not self.nodes: + raise ValueError("Pipeline nodes cannot be empty") self.skip_pipe = set(skip_pipe) if skip_pipe else set() self.file_input = file_input self.file_output = file_output @@ -302,7 +303,8 @@ async def setup(self, context: Context) -> str: # We run the open() in a thread to prevent blocking the async event # loop. def open_fifo_write(): - assert self.fifo_path + if not self.fifo_path: + raise RuntimeError("FIFO path not set") return open(self.fifo_path, "w") # Start the task that waits for the open, then runs the process @@ -339,7 +341,8 @@ async def setup(self, context: Context) -> str: self.fifo_path = context.get_new_fifo() def open_fifo_read(): - assert self.fifo_path + if not self.fifo_path: + raise RuntimeError("FIFO path not set") return open(self.fifo_path, "r") async def run_inner(): diff --git a/sentieon_cli/transfer.py b/sentieon_cli/transfer.py index d712dd9..a5fed93 100644 --- a/sentieon_cli/transfer.py +++ b/sentieon_cli/transfer.py @@ -8,7 +8,7 @@ import tempfile from typing import Dict, List, Optional, Set, Tuple -from importlib_resources import files +from importlib.resources import files from . import command_strings as cmds from .job import Job diff --git a/sentieon_cli/util.py b/sentieon_cli/util.py index edda2ed..cf58108 100644 --- a/sentieon_cli/util.py +++ b/sentieon_cli/util.py @@ -3,6 +3,7 @@ """ import argparse +from importlib.metadata import PackageNotFoundError, version import multiprocessing as mp import os import pathlib @@ -17,7 +18,10 @@ from .logging import get_logger -__version__ = "1.5.2" +try: + __version__ = version("sentieon_cli") +except PackageNotFoundError: + __version__ = "0.0.0" logger = get_logger(__name__) diff --git a/tests/test_all.py b/tests/test_all.py index 5c22617..71a3514 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -2,7 +2,7 @@ import os import shutil -from importlib_resources import files +from importlib.resources import files sys.path.insert( 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) diff --git a/tests/unit/test_sentieon_pangenome.py b/tests/unit/test_sentieon_pangenome.py index 7ec84da..b412389 100644 --- a/tests/unit/test_sentieon_pangenome.py +++ b/tests/unit/test_sentieon_pangenome.py @@ -129,3 +129,236 @@ def test_skip_model_apply(self): args_str = " ".join([str(arg) for arg in concat_job.shell.nodes[0].args]) assert str(pipeline.output_vcf) in args_str + def test_call_svs(self): + """Test that PangenomeSV is added when --call_svs is enabled""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + assert isinstance(dag, DAG) + + # The dnascope-raw job should exist + all_jobs = list(dag.waiting_jobs.keys()) + list( + dag.ready_jobs.keys() + ) + job_names = [job.name for job in all_jobs] + assert "dnascope-raw" in job_names + + # Find the dnascope-raw job and check its command includes + # both DNAscope and PangenomeSV + dnascope_job = None + for job in all_jobs: + if job.name == "dnascope-raw": + dnascope_job = job + break + assert dnascope_job is not None + cmd_str = str(dnascope_job.shell) + assert "--algo DNAscope" in cmd_str + assert "--algo PangenomeSV" in cmd_str + assert "--gfa_file" in cmd_str + + # SV output should use _sv.vcf.gz suffix + sv_vcf = str(pipeline.output_vcf).replace( + ".vcf.gz", "_sv.vcf.gz" + ) + assert sv_vcf in cmd_str + + def test_call_svs_disabled_by_default(self): + """Test that PangenomeSV is not added by default""" + pipeline = self.create_pipeline() + dag = pipeline.build_first_dag() + + all_jobs = list(dag.waiting_jobs.keys()) + list( + dag.ready_jobs.keys() + ) + dnascope_job = None + for job in all_jobs: + if job.name == "dnascope-raw": + dnascope_job = job + break + assert dnascope_job is not None + cmd_str = str(dnascope_job.shell) + assert "--algo DNAscope" in cmd_str + assert "--algo PangenomeSV" not in cmd_str + + def test_skip_small_variants(self): + """Test that DNAscope, transfer, and model-apply are skipped""" + pipeline = self.create_pipeline() + pipeline.skip_small_variants = True + dag = pipeline.build_first_dag() + + assert isinstance(dag, DAG) + + all_jobs = list(dag.waiting_jobs.keys()) + list( + dag.ready_jobs.keys() + ) + job_names = [job.name for job in all_jobs] + assert "dnascope-raw" not in job_names + assert "model-apply" not in job_names + assert "merge-trim-concat" not in job_names + + def test_skip_small_variants_with_call_svs(self): + """Test SV-only mode: driver runs PangenomeSV without DNAscope""" + pipeline = self.create_pipeline() + pipeline.skip_small_variants = True + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + assert isinstance(dag, DAG) + + all_jobs = list(dag.waiting_jobs.keys()) + list( + dag.ready_jobs.keys() + ) + job_names = [job.name for job in all_jobs] + + # The driver job should still run for SV calling + assert "dnascope-raw" in job_names + + # Transfer and model-apply should be skipped + assert "model-apply" not in job_names + assert "merge-trim-concat" not in job_names + + # The driver command should have PangenomeSV but NOT DNAscope + dnascope_job = None + for job in all_jobs: + if job.name == "dnascope-raw": + dnascope_job = job + break + assert dnascope_job is not None + cmd_str = str(dnascope_job.shell) + assert "--algo PangenomeSV" in cmd_str + assert "--algo DNAscope" not in cmd_str + + def _get_all_job_names(self, dag): + """Helper to get all job names from a DAG""" + all_jobs = list(dag.waiting_jobs.keys()) + list( + dag.ready_jobs.keys() + ) + return [job.name for job in all_jobs], all_jobs + + def test_cnv_jobs_with_call_svs(self): + """Test that CNV jobs are added when --call_svs is enabled""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + job_names, all_jobs = self._get_all_job_names(dag) + assert "cnvscope" in job_names + assert "cnv-model-apply" in job_names + assert "indel2cnv" in job_names + assert "combine-cnv" in job_names + + def test_cnv_jobs_not_present_without_call_svs(self): + """Test that CNV jobs are not added by default""" + pipeline = self.create_pipeline() + dag = pipeline.build_first_dag() + + job_names, _ = self._get_all_job_names(dag) + assert "cnvscope" not in job_names + assert "cnv-model-apply" not in job_names + assert "indel2cnv" not in job_names + assert "combine-cnv" not in job_names + + def test_cnv_jobs_with_skip_small_variants(self): + """Test CNV jobs are added in SV-only mode""" + pipeline = self.create_pipeline() + pipeline.skip_small_variants = True + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + job_names, _ = self._get_all_job_names(dag) + assert "cnvscope" in job_names + assert "cnv-model-apply" in job_names + assert "indel2cnv" in job_names + assert "combine-cnv" in job_names + + def test_cnvscope_command(self): + """Test CNVscope driver command has correct algo and model""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + _, all_jobs = self._get_all_job_names(dag) + cnvscope_job = None + for job in all_jobs: + if job.name == "cnvscope": + cnvscope_job = job + break + assert cnvscope_job is not None + cmd_str = str(cnvscope_job.shell) + assert "--algo CNVscope" in cmd_str + assert "cnv.model" in cmd_str + # Input should be the sample BAM (BAM input mode) + assert str(pipeline.sample_input[0]) in cmd_str + + def test_cnv_model_apply_command(self): + """Test CNVModelApply driver command""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + _, all_jobs = self._get_all_job_names(dag) + job = None + for j in all_jobs: + if j.name == "cnv-model-apply": + job = j + break + assert job is not None + cmd_str = str(job.shell) + assert "--algo CNVModelApply" in cmd_str + assert "cnv.model" in cmd_str + + def test_indel2cnv_command(self): + """Test indel2cnv script command""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + _, all_jobs = self._get_all_job_names(dag) + job = None + for j in all_jobs: + if j.name == "indel2cnv": + job = j + break + assert job is not None + cmd_str = str(job.shell) + assert "indel2cnv.py" in cmd_str + assert str(pipeline.reference) in cmd_str + + def test_combine_cnv_command(self): + """Test combine_cnv script command""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + dag = pipeline.build_first_dag() + + _, all_jobs = self._get_all_job_names(dag) + job = None + for j in all_jobs: + if j.name == "combine-cnv": + job = j + break + assert job is not None + cmd_str = str(job.shell) + assert "combine_cnv.py" in cmd_str + assert "--cnv" in cmd_str + assert "--converted" in cmd_str + # Output should use _cnv.vcf.gz suffix + cnv_vcf = str(pipeline.output_vcf).replace( + ".vcf.gz", "_cnv.vcf.gz" + ) + assert cnv_vcf in cmd_str + + def test_cnv_with_skip_model_apply(self): + """Test CNV jobs are added even with skip_model_apply""" + pipeline = self.create_pipeline() + pipeline.call_svs = True + pipeline.skip_model_apply = True + dag = pipeline.build_first_dag() + + job_names, _ = self._get_all_job_names(dag) + assert "model-apply" not in job_names + assert "cnvscope" in job_names + assert "cnv-model-apply" in job_names + assert "indel2cnv" in job_names + assert "combine-cnv" in job_names +