diff --git a/poetry.lock b/poetry.lock index 61792cf7..e5fdeaa7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -58,6 +58,17 @@ files = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +[[package]] +name = "argparse-dataclass" +version = "2.0.0" +description = "Declarative CLIs with argparse and dataclasses" +optional = false +python-versions = ">=3.8" +files = [ + {file = "argparse_dataclass-2.0.0-py3-none-any.whl", hash = "sha256:3ffc8852a88d9d98d1364b4441a712491320afb91fb56049afd8a51d74bb52d2"}, + {file = "argparse_dataclass-2.0.0.tar.gz", hash = "sha256:09ab641c914a2f12882337b9c3e5086196dbf2ee6bf0ef67895c74002cc9297f"}, +] + [[package]] name = "arrow" version = "1.3.0" @@ -321,6 +332,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "conda-inject" +version = "1.3.1" +description = "Helper functions for injecting a conda environment into the current python environment (by modifying sys.path, without actually changing the current python environment)." +optional = false +python-versions = ">=3.9,<4.0" +files = [ + {file = "conda_inject-1.3.1-py3-none-any.whl", hash = "sha256:0a106bb0ef3553e82b6e7ef343162305c44dad7789c1909eed1abe83548c7fc6"}, + {file = "conda_inject-1.3.1.tar.gz", hash = "sha256:9e8d902230261beba74083aae12c2c5a395e29b408469fefadc8aaf51ee441e5"}, +] + +[package.dependencies] +pyyaml = ">=6.0,<7.0" + [[package]] name = "configargparse" version = "1.7" @@ -875,6 +900,59 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "immutables" +version = "0.20" +description = "Immutable Collections" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "immutables-0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dea0ae4d7f31b145c18c16badeebc2f039d09411be4a8febb86e1244cf7f1ce0"}, + {file = "immutables-0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2dd0dcef2f8d4523d34dbe1d2b7804b3d2a51fddbd104aad13f506a838a2ea15"}, + {file = "immutables-0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:393dde58ffd6b4c089ffdf4cef5fe73dad37ce4681acffade5f5d5935ec23c93"}, + {file = "immutables-0.20-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1214b5a175df783662b7de94b4a82db55cc0ee206dd072fa9e279fb8895d8df"}, + {file = "immutables-0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2761e3dc2a6406943ce77b3505e9b3c1187846de65d7247548dc7edaa202fcba"}, + {file = "immutables-0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2bcea81e7516bd823b4ed16f4f794531097888675be13e833b1cc946370d5237"}, + {file = "immutables-0.20-cp310-cp310-win32.whl", hash = "sha256:d828e7580f1fa203ddeab0b5e91f44bf95706e7f283ca9fbbcf0ae08f63d3084"}, + {file = "immutables-0.20-cp310-cp310-win_amd64.whl", hash = "sha256:380e2957ba3d63422b2f3fbbff0547c7bbe6479d611d3635c6411005a4264525"}, + {file = "immutables-0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:532be32c7a25dae6cade28825c76d3004cf4d166a0bfacf04bda16056d59ba26"}, + {file = "immutables-0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5302ce9c7827f8300f3dc34a695abb71e4a32bab09e65e5ad6e454785383347f"}, + {file = "immutables-0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b51aec54b571ae466113509d4dc79a2808dc2ae9263b71fd6b37778cb49eb292"}, + {file = "immutables-0.20-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f56aea56e597ecf6631f24a4e26007b6a5f4fe30278b96eb90bc1f60506164"}, + {file = "immutables-0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:085ac48ee3eef7baf070f181cae574489bbf65930a83ec5bbd65c9940d625db3"}, + {file = "immutables-0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f063f53b5c0e8f541ae381f1d828f3d05bbed766a2d6c817f9218b8b37a4cb66"}, + {file = "immutables-0.20-cp311-cp311-win32.whl", hash = "sha256:b0436cc831b47e26bef637bcf143cf0273e49946cfb7c28c44486d70513a3080"}, + {file = "immutables-0.20-cp311-cp311-win_amd64.whl", hash = "sha256:5bb32aee1ea16fbb90f58f8bd96016bca87aba0a8e574e5fa218d0d83b142851"}, + {file = "immutables-0.20-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ba726b7a3a696b9d4b122fa2c956bc68e866f3df1b92765060c88c64410ff82"}, + {file = "immutables-0.20-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5a88adf1dcc9d8ab07dba5e74deefcd5b5e38bc677815cbf9365dc43b69f1f08"}, + {file = "immutables-0.20-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1009a4e00e2e69a9b40c2f1272795f5a06ad72c9bf4638594d518e9cbd7a721a"}, + {file = "immutables-0.20-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96899994842c37cf4b9d6d2bedf685aae7810bd73f1538f8cba5426e2d65cb85"}, + {file = "immutables-0.20-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a606410b2ccb6ae339c3f26cccc9a92bcb16dc06f935d51edfd8ca68cf687e50"}, + {file = "immutables-0.20-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8e82754f72823085643a2c0e6a4c489b806613e94af205825fa81df2ba147a0"}, + {file = "immutables-0.20-cp312-cp312-win32.whl", hash = "sha256:525fb361bd7edc8a891633928d549713af8090c79c25af5cc06eb90b48cb3c64"}, + {file = "immutables-0.20-cp312-cp312-win_amd64.whl", hash = "sha256:a82afc3945e9ceb9bcd416dc4ed9b72f92760c42787e26de50610a8b81d48120"}, + {file = "immutables-0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f17f25f21e82a1c349a61191cfb13e442a348b880b74cb01b00e0d1e848b63f4"}, + {file = "immutables-0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:65954eb861c61af48debb1507518d45ae7d594b4fba7282785a70b48c5f51f9b"}, + {file = "immutables-0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62f8a7a22939278127b7a206d05679b268b9cf665437125625348e902617cbad"}, + {file = "immutables-0.20-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac86f4372f4cfaa00206c12472fd3a78753092279e0552b7e1880944d71b04fe"}, + {file = "immutables-0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e771198edc11a9e02ffa693911b3918c6cde0b64ad2e6672b076dbe005557ad8"}, + {file = "immutables-0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc739fc07cff5df2e4f31addbd48660b5ac0da56e9f719f8bb45da8ddd632c63"}, + {file = "immutables-0.20-cp38-cp38-win32.whl", hash = "sha256:c086ccb44d9d3824b9bf816365d10b1b82837efc7119f8bab56bd7a27ed805a9"}, + {file = "immutables-0.20-cp38-cp38-win_amd64.whl", hash = "sha256:9cd2ee9c10bf00be3c94eb51854bc0b761326bd0a7ea0dad4272a3f182269ae6"}, + {file = "immutables-0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4f78cb748261f852953620ed991de74972446fd484ec69377a41e2f1a1beb75"}, + {file = "immutables-0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6449186ea91b7c17ec8e7bd9bf059858298b1db5c053f5d27de8eba077578ce"}, + {file = "immutables-0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85dd9765b068f7beb297553fddfcf7f904bd58a184c520830a106a58f0c9bfb4"}, + {file = "immutables-0.20-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f349a7e0327b92dcefb863e49ace086f2f26e6689a4e022c98720c6e9696e763"}, + {file = "immutables-0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e3a5462f6d3549bbf7d02ce929fb0cb6df9539445f0589105de4e8b99b906e69"}, + {file = "immutables-0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc51a01a64a6d2cd7db210a49ad010c2ac2e9e026745f23fd31e0784096dcfff"}, + {file = "immutables-0.20-cp39-cp39-win32.whl", hash = "sha256:83794712f0507416f2818edc63f84305358b8656a93e5b9e2ab056d9803c7507"}, + {file = "immutables-0.20-cp39-cp39-win_amd64.whl", hash = "sha256:2837b1078abc66d9f009bee9085cf62515d5516af9a5c9ea2751847e16efd236"}, + {file = "immutables-0.20.tar.gz", hash = "sha256:1d2f83e6a6a8455466cd97b9a90e2b4f7864648616dfa6b19d18f49badac3876"}, +] + +[package.extras] +test = ["flake8 (>=5.0,<6.0)", "mypy (>=1.4,<2.0)", "pycodestyle (>=2.9,<3.0)", "pytest (>=7.4,<8.0)"] + [[package]] name = "importlib-metadata" version = "7.1.0" @@ -1470,9 +1548,9 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, {version = ">=1.20.3", markers = "python_version < \"3.10\""}, {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -2804,6 +2882,116 @@ messaging = ["slacker"] pep = ["eido", "peppy"] reports = ["pygments"] +[[package]] +name = "snakemake" +version = "8.10.7" +description = "Workflow management system to create reproducible and scalable data analyses" +optional = false +python-versions = ">=3.11" +files = [ + {file = "snakemake-8.10.7-py3-none-any.whl", hash = "sha256:faf0642d04986ec669d51c6a6e66794e503c826902d581b025c31c9de6c79845"}, + {file = "snakemake-8.10.7.tar.gz", hash = "sha256:c64e0ddfe2af6c06f0c50f5451b8cffad0775a829364754e40da418b4c1118d6"}, +] + +[package.dependencies] +appdirs = "*" +conda-inject = ">=1.3.1,<2.0" +configargparse = "*" +connection-pool = ">=0.0.3" +datrie = "*" +docutils = "*" +dpath = ">=2.1.6,<3.0.0" +gitpython = "*" +humanfriendly = "*" +immutables = "*" +jinja2 = ">=3.0,<4.0" +jsonschema = "*" +nbformat = "*" +packaging = "*" +psutil = "*" +pulp = ">=2.3.1,<2.9" +pyyaml = "*" +requests = ">=2.8.1,<3.0" +reretry = "*" +smart-open = ">=3.0,<8.0" +snakemake-interface-common = ">=1.17.0,<2.0" +snakemake-interface-executor-plugins = ">=9.1.0,<10.0" +snakemake-interface-report-plugins = ">=1.0.0,<2.0.0" +snakemake-interface-storage-plugins = ">=3.1.0,<4.0" +stopit = "*" +tabulate = "*" +throttler = "*" +toposort = ">=1.10,<2.0" +wrapt = "*" +yte = ">=1.5.1,<2.0" + +[package.extras] +messaging = ["slack-sdk"] +pep = ["eido", "peppy"] +reports = ["pygments"] + +[[package]] +name = "snakemake-interface-common" +version = "1.17.2" +description = "Common functions and classes for Snakemake and its plugins" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "snakemake_interface_common-1.17.2-py3-none-any.whl", hash = "sha256:ca5043ee707c071d9fed7e659df4803e8eeeaf1fe0d0bdb6716deb0141208748"}, + {file = "snakemake_interface_common-1.17.2.tar.gz", hash = "sha256:7a2bba88df98c1a0a5cec89b835c62dd2e6e72c1fb8fd104fe73405c800b87c0"}, +] + +[package.dependencies] +argparse-dataclass = ">=2.0.0,<3.0.0" +ConfigArgParse = ">=1.7,<2.0" + +[[package]] +name = "snakemake-interface-executor-plugins" +version = "9.1.1" +description = "This package provides a stable interface for interactions between Snakemake and its executor plugins." +optional = false +python-versions = "<4.0,>=3.11" +files = [ + {file = "snakemake_interface_executor_plugins-9.1.1-py3-none-any.whl", hash = "sha256:8f164978829e11c821d31b380501a80956bff67023935d0320d4cc00c9b0aa3b"}, + {file = "snakemake_interface_executor_plugins-9.1.1.tar.gz", hash = "sha256:357c3b1d633b26241693a4e5ce291fbe198c03a54a30acfa86dd97dc252fa2c6"}, +] + +[package.dependencies] +argparse-dataclass = ">=2.0.0,<3.0.0" +snakemake-interface-common = ">=1.12.0,<2.0.0" +throttler = ">=1.2.2,<2.0.0" + +[[package]] +name = "snakemake-interface-report-plugins" +version = "1.0.0" +description = "The interface for Snakemake report plugins." +optional = false +python-versions = ">=3.11,<4.0" +files = [ + {file = "snakemake_interface_report_plugins-1.0.0-py3-none-any.whl", hash = "sha256:e39cf2f27a36bda788dd97ede8fd056f887e00dca2d14ffea91dbc696d1f17cd"}, + {file = "snakemake_interface_report_plugins-1.0.0.tar.gz", hash = "sha256:02311cdc4bebab2a1c28469b5e6d5c6ac6e9c66998ad4e4b3229f1472127490f"}, +] + +[package.dependencies] +snakemake-interface-common = ">=1.16.0,<2.0.0" + +[[package]] +name = "snakemake-interface-storage-plugins" +version = "3.2.2" +description = "This package provides a stable interface for interactions between Snakemake and its storage plugins." +optional = false +python-versions = "<4.0,>=3.11" +files = [ + {file = "snakemake_interface_storage_plugins-3.2.2-py3-none-any.whl", hash = "sha256:d06211b965c165db719cfe598e7aee3c153081ad6c79a3819380fbd2e4fe80c1"}, + {file = "snakemake_interface_storage_plugins-3.2.2.tar.gz", hash = "sha256:fc8a70ef5b1fd054bc64270925228e2054158da9bcf8fa8bd4be36d93a82678b"}, +] + +[package.dependencies] +reretry = ">=0.11.8,<0.12.0" +snakemake-interface-common = ">=1.12.0,<2.0.0" +throttler = ">=1.2.2,<2.0.0" +wrapt = ">=1.15.0,<2.0.0" + [[package]] name = "sniffio" version = "1.3.1" @@ -3735,4 +3923,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "7f0725bfb02f1e5d834ec6333115310f872507bc2b2f88a38b7d75d4fb31276a" +content-hash = "3d33761ab1df0ce91093a91e61dc0ed9266155fa50bfbb3bddf844b304561f0b" diff --git a/pyproject.toml b/pyproject.toml index e2cd32f6..e663971a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,8 +36,8 @@ python = ">=3.8,<4.0" # keep upper limit pybids = ">=0.16.0,<0.17" snakemake = [ - { version = ">=5.28.0,<8", python = ">=3.8" }, - { version = ">=7.18.2,<8", python = ">=3.11" }, + { version = ">=5.28.0,<8", python = "<3.11" }, + { version = ">=7.18.2", python = ">=3.11" }, ] typing-extensions = ">=3.10.0" # minimum 22.2 to get "alias" parameter on attrs.field diff --git a/snakebids/core/datasets.py b/snakebids/core/datasets.py index 33e84506..ba9fe902 100644 --- a/snakebids/core/datasets.py +++ b/snakebids/core/datasets.py @@ -13,7 +13,6 @@ import more_itertools as itx from bids import BIDSLayout from pvandyken.deprecated import deprecated -from snakemake.io import expand as sn_expand from typing_extensions import Self, TypedDict import snakebids.utils.sb_itertools as sb_it @@ -21,6 +20,7 @@ from snakebids.exceptions import DuplicateComponentError from snakebids.io.console import get_console_size from snakebids.io.printing import format_zip_lists, quote_wrap +from snakebids.snakemake_compat import expand as sn_expand from snakebids.types import ZipList from snakebids.utils.containers import ImmutableList, MultiSelectDict, UserDictPy38 from snakebids.utils.utils import get_wildcard_dict, property_alias, zip_list_eq diff --git a/snakebids/core/input_generation.py b/snakebids/core/input_generation.py index d86a19a7..c365ecba 100644 --- a/snakebids/core/input_generation.py +++ b/snakebids/core/input_generation.py @@ -17,7 +17,6 @@ import more_itertools as itx from bids import BIDSLayout, BIDSLayoutIndexer -from snakemake.script import Snakemake from snakebids.core._querying import ( FilterSpecError, @@ -32,6 +31,7 @@ DuplicateComponentError, RunError, ) +from snakebids.snakemake_compat import Snakemake from snakebids.types import InputConfig, InputsConfig, ZipList from snakebids.utils.snakemake_io import glob_wildcards from snakebids.utils.utils import ( diff --git a/snakebids/plugins/snakemake.py b/snakebids/plugins/snakemake.py index bd85fa9a..6ceac2b2 100644 --- a/snakebids/plugins/snakemake.py +++ b/snakebids/plugins/snakemake.py @@ -9,8 +9,6 @@ import attrs import more_itertools as itx -import snakemake -from snakemake.io import load_configfile from typing_extensions import overload, override from snakebids import bidsapp @@ -20,6 +18,8 @@ from snakebids.plugins.cli_config import CliConfig from snakebids.plugins.component_edit import ComponentEdit from snakebids.plugins.pybidsdb import Pybidsdb +from snakebids.snakemake_compat import load_configfile +from snakebids.snakemake_compat import main as snakemake_main from snakebids.utils.output import ( prepare_bidsapp_output, write_output_mode, @@ -66,7 +66,7 @@ def __call__( values: str | Sequence[Any] | None, option_string: str | None = None, ): - snakemake.main(["-h"]) # type: ignore + snakemake_main(["-h"]) # type: ignore def _get_file_paths( @@ -330,7 +330,7 @@ def finalize_config(self, config: dict[str, Any]): @bidsapp.hookimpl def run(self, config: dict[str, Any]): """Run snakemake with the given config, after applying plugins.""" - snakemake.main( # type: ignore + snakemake_main( # type: ignore [ *filter( None, diff --git a/snakebids/project_template/{% if build_system != 'poetry' %}pyproject.toml{% endif %}.jinja b/snakebids/project_template/{% if build_system != 'poetry' %}pyproject.toml{% endif %}.jinja index 77f5cdbd..6f27bdea 100644 --- a/snakebids/project_template/{% if build_system != 'poetry' %}pyproject.toml{% endif %}.jinja +++ b/snakebids/project_template/{% if build_system != 'poetry' %}pyproject.toml{% endif %}.jinja @@ -28,13 +28,14 @@ classifiers = [ requires-python = "{{ python_version }}" dependencies = [ - "snakemake >= {{ snakemake_version }},<8", "snakebids {{ snakebids_version | toml_encode }}", + "snakemake>={{ snakemake_version }},<8; python_version < \"3.11\"", + "snakemake>=8.1.2; python_version >= \"3.11\"", {#- newer pulps are incompatible with old snakemakes, and we need to support old snakemakes for python versions <3.11. So cap pulp to the last working version #} - "pulp < 2.8.0", + "pulp < 2.8.0; python_version < \"3.11\"", ] [project.scripts] diff --git a/snakebids/project_template/{% if build_system == 'poetry' %}pyproject.toml{% endif %}.jinja b/snakebids/project_template/{% if build_system == 'poetry' %}pyproject.toml{% endif %}.jinja index f68c30d7..09c77f78 100644 --- a/snakebids/project_template/{% if build_system == 'poetry' %}pyproject.toml{% endif %}.jinja +++ b/snakebids/project_template/{% if build_system == 'poetry' %}pyproject.toml{% endif %}.jinja @@ -22,13 +22,16 @@ classifiers = [ [tool.poetry.dependencies] python = "{{ python_version }}" -snakemake = ">={{ snakemake_version }},<8" +snakemake = [ + { version=">={{ snakemake_version }},<8", python = "<3.11" }, + { version=">=8.1.2", python = ">=3.11" }, +] snakebids = {{ snakebids_version | format_poetry }} {#- newer pulps are incompatible with old snakemakes, and we need to support old snakemakes for python versions <3.11. So cap pulp to the last working version #} -pulp = "<2.8.0" +pulp = { version="<2.8.0", python = "<3.11" } pandas = [ { version = "<=2.0.3", python = "<3.9" }, { version = ">=2.1.1", python = ">=3.12" }, diff --git a/snakebids/snakemake_compat.py b/snakebids/snakemake_compat.py new file mode 100644 index 00000000..48abe810 --- /dev/null +++ b/snakebids/snakemake_compat.py @@ -0,0 +1,23 @@ +# type: ignore + +try: + from snakemake.cli import main + from snakemake.common import configfile + from snakemake.common.configfile import load_configfile +except ImportError: + import snakemake.io as configfile + from snakemake import main + from snakemake.io import load_configfile + +from snakemake.exceptions import WildcardError +from snakemake.io import expand +from snakemake.script import Snakemake + +__all__ = [ + "load_configfile", + "main", + "expand", + "Snakemake", + "WildcardError", + "configfile", +] diff --git a/snakebids/snakemake_compat.pyi b/snakebids/snakemake_compat.pyi new file mode 100644 index 00000000..887438ef --- /dev/null +++ b/snakebids/snakemake_compat.pyi @@ -0,0 +1,47 @@ +from pathlib import Path +from typing import Any, Callable, Iterable, Sequence + +from snakemake.common import configfile as configfile # type: ignore + +class WildcardError(Exception): ... + +def load_configfile(configpath: str) -> dict[str, Any]: + "Load a JSON or YAML configfile as a dict, then checks that it's a dict." + +def expand( + filepatterns: Sequence[Path | str] | Path | str, + func: Callable[[Iterable[str]], Iterable[Iterable[str]]] | None = ..., + /, + *, + allow_missing: bool | Sequence[str] | str = ..., + **wildcards: Sequence[str] | str, +) -> list[str]: + """ + Expand wildcards in given filepatterns. + + Arguments + *args -- first arg: filepatterns as list or one single filepattern, + second arg (optional): a function to combine wildcard values + (itertools.product per default) + **wildcards -- the wildcards as keyword arguments + with their values as lists. If allow_missing=True is included + wildcards in filepattern without values will stay unformatted. + """ + ... + +class Namedlist[T](list[T | Iterable[T]]): ... + +class InputFiles[T](Namedlist[T]): + def __getattribute__(self, __name: str) -> str: ... + +class OutputFiles[T](Namedlist[T]): + def __getattribute__(self, __name: str) -> str: ... + +class Params[T](Namedlist[T]): ... + +class Snakemake: + input: InputFiles[str] + output: OutputFiles[str] + params: Params[str] + +def main(argv: list[str] = ...) -> None: ... diff --git a/snakebids/tests/test_datasets.py b/snakebids/tests/test_datasets.py index 2be0fd10..343ddf6f 100644 --- a/snakebids/tests/test_datasets.py +++ b/snakebids/tests/test_datasets.py @@ -12,7 +12,6 @@ import pytest from hypothesis import assume, given from hypothesis import strategies as st -from snakemake.exceptions import WildcardError from snakebids.core.datasets import ( BidsComponent, @@ -22,6 +21,7 @@ ) from snakebids.exceptions import DuplicateComponentError from snakebids.paths._presets import bids +from snakebids.snakemake_compat import WildcardError from snakebids.tests import strategies as sb_st from snakebids.tests.helpers import expand_zip_list, get_bids_path, get_zip_list, setify from snakebids.types import Expandable, ZipList diff --git a/snakebids/tests/test_generate_inputs.py b/snakebids/tests/test_generate_inputs.py index 6d1faf7d..cb28cff7 100644 --- a/snakebids/tests/test_generate_inputs.py +++ b/snakebids/tests/test_generate_inputs.py @@ -23,7 +23,6 @@ from hypothesis import strategies as st from pyfakefs.fake_filesystem import FakeFilesystem from pytest_mock import MockerFixture -from snakemake.io import expand as sb_expand from snakebids.core._querying import PostFilter, UnifiedFilter, get_matching_files from snakebids.core.datasets import BidsComponent, BidsDataset @@ -38,6 +37,7 @@ ) from snakebids.exceptions import ConfigError, PybidsError, RunError from snakebids.paths._presets import bids +from snakebids.snakemake_compat import expand as sb_expand from snakebids.tests import strategies as sb_st from snakebids.tests.helpers import ( Benchmark, diff --git a/snakebids/tests/test_plugins/test_snakemake.py b/snakebids/tests/test_plugins/test_snakemake.py index 07b6c38d..de4fd01c 100644 --- a/snakebids/tests/test_plugins/test_snakemake.py +++ b/snakebids/tests/test_plugins/test_snakemake.py @@ -60,9 +60,9 @@ def test_errors_if_no_configfile_found(self, fakefs_tmpdir: Path): class TestAddCliArguments: def test_snakemake_help_arg_added(self, mocker: MockerFixture): - from snakebids.plugins.snakemake import snakemake + import snakebids.plugins.snakemake - mock = mocker.patch.object(snakemake, "main") + mock = mocker.patch.object(snakebids.plugins.snakemake, "snakemake_main") smk = SnakemakeBidsApp.create_empty() parser = argparse.ArgumentParser() smk.add_cli_arguments(parser) @@ -151,14 +151,13 @@ def test_force_output_is_set(self, value: bool): def get_io_mocks(mocker: MockerFixture): - import snakemake # noqa: I001 - import snakemake.io + import snakebids.snakemake_compat # noqa: I001 import snakebids.plugins.snakemake as sn_app mocker.stopall() mocker.patch.object(sn_app.impm, "version", return_value="version") mocker.patch.object( - snakemake.io, + snakebids.snakemake_compat.configfile, "open", mocker.mock_open(read_data='{"a_key": "a value"}'), ) @@ -166,7 +165,7 @@ def get_io_mocks(mocker: MockerFixture): "write_output_mode": mocker.patch.object(sn_app, "write_output_mode"), "prepare_output": mocker.patch.object(sn_app, "prepare_bidsapp_output"), "write_config": mocker.patch.object(sn_app, "write_config"), - "snakemake": mocker.patch.object(snakemake, "main"), + "snakemake": mocker.patch.object(sn_app, "snakemake_main"), } diff --git a/typings/snakemake/exceptions.pyi b/typings/snakemake/exceptions.pyi deleted file mode 100644 index b367988e..00000000 --- a/typings/snakemake/exceptions.pyi +++ /dev/null @@ -1 +0,0 @@ -class WildcardError(Exception): ... diff --git a/typings/snakemake/io.pyi b/typings/snakemake/io.pyi deleted file mode 100644 index 437189ea..00000000 --- a/typings/snakemake/io.pyi +++ /dev/null @@ -1,442 +0,0 @@ -""" -This type stub file was generated by pyright. -""" -from __future__ import annotations - -import os -from contextlib import contextmanager -from pathlib import Path -from typing import Any, Callable, Generic, Iterable, List, Sequence, TypeVar, overload - -__author__ = ... -__copyright__ = ... -__email__ = ... -__license__ = ... - -class Mtime: - __slots__ = ... - def __init__(self, local=..., local_target=..., remote=...) -> None: ... - def local_or_remote(self, follow_symlinks=...): ... - def remote(self): ... - def local(self, follow_symlinks=...): ... - -def lutime(f, times): ... - -if os.chmod in os.supports_follow_symlinks: - def lchmod(f, mode): ... - -else: - def lchmod(f, mode): ... - -class ExistsDict(dict): - def __init__(self, cache) -> None: ... - def __getitem__(self, path): ... - def __contains__(self, path): ... - -class IOCache: - def __init__(self, max_wait_time) -> None: ... - def mtime_inventory(self, jobs): ... - async def collect_mtime(self, path): ... - def clear(self): ... - def deactivate(self): ... - -def IOFile(file, rule=...): ... - -class _IOFile(str): - """ - A file that is either input or output of a rule. - """ - - __slots__ = ... - def __new__(cls, file): ... - def new_from(self, new_value): ... - def iocache(func): ... - def inventory(self): ... - @_refer_to_remote - def get_inventory_parent(self): # -> _IOFile | None: - """If eligible for inventory, get the parent of a given path. - - This code does not work on local Windows paths, - but inventory is disabled on Windows. - """ - ... - @contextmanager - def open( - self, mode=..., buffering=..., encoding=..., errors=..., newline=... - ): # -> Generator[TextIOWrapper, None, None]: - """Open this file. If necessary, download it from remote first. - - This can (and should) be used in a `with`-statement. - """ - ... - def contains_wildcard(self): ... - @property - def is_remote(self): ... - @property - def is_ancient(self): ... - @property - def is_directory(self): ... - @property - def is_temp(self): ... - @property - def is_multiext(self): ... - @property - def multiext_prefix(self): ... - def update_remote_filepath(self): ... - @property - def should_keep_local(self): ... - @property - def should_stay_on_remote(self): ... - @property - def remote_object(self): ... - @property - @_refer_to_remote - def file(self): ... - def check(self): ... - @property - def exists(self): ... - def parents(self, omit=...): # -> Generator[_IOFile, None, None]: - """Yield all parent paths, omitting the given number of ancestors.""" - ... - @property - @iocache - def exists_local(self): ... - @property - @iocache - def exists_remote(self): ... - @property - def protected(self): # -> bool: - """Returns True if the file is protected. Always False for symlinks.""" - ... - @property - @iocache - def mtime(self): ... - @property - def mtime_uncached(self): # -> Mtime: - """Obtain mtime. - - Usually, this will be one stat call only. For symlinks and directories - it will be two, for symlinked directories it will be three, - for remote files it will additionally query the remote - location. - """ - ... - @property - def flags(self): ... - def is_fifo(self): # -> bool: - """Return True if file is a FIFO according to the filesystem.""" - ... - @property - @iocache - @_refer_to_remote - def size(self): ... - @property - def size_local(self): ... - def is_checksum_eligible(self): ... - def checksum(self, force=...): # -> str | None: - """Return checksum if file is small enough, else None. - Returns None if file does not exist. If force is True, - omit eligibility check.""" - ... - def is_same_checksum(self, other_checksum, force=...): ... - def check_broken_symlink(self): # -> None: - """Raise WorkflowError if file is a broken symlink.""" - ... - @_refer_to_remote - def is_newer(self, time): # -> Literal[False]: - """Returns true of the file (which is an input file) is newer than time, or if it is - a symlink that points to a file newer than time.""" - ... - def download_from_remote(self): ... - def upload_to_remote(self): ... - def prepare(self): ... - def protect(self): ... - def remove(self, remove_non_empty_dir=...): ... - def touch(self, times=...): # -> None: - """times must be 2-tuple: (atime, mtime)""" - ... - def touch_or_create(self): ... - def apply_wildcards(self, wildcards, fill_missing=..., fail_dynamic=...): ... - def get_wildcard_names(self): ... - def regex(self): ... - def wildcard_constraints(self): ... - def constant_prefix(self): ... - def constant_suffix(self): ... - def match(self, target): ... - def format_dynamic(self): ... - def clone_flags(self, other): ... - def clone_remote_object(self, other): ... - def set_flags(self, flags): ... - def __eq__(self, other) -> bool: ... - def __hash__(self) -> int: ... - -_double_slash_regex = ... -_wildcard_regex = ... - -def wait_for_files( - files, latency_wait=..., force_stay_on_remote=..., ignore_pipe_or_service=... -): # -> None: - """Wait for given files to be present in the filesystem.""" - ... - -def get_wildcard_names(pattern): ... -def contains_wildcard(path): ... -def contains_wildcard_constraints(pattern): ... -def remove(file, remove_non_empty_dir=...): ... -def get_wildcard_constraints(pattern): ... -def regex(filepattern): ... -def apply_wildcards( - pattern, - wildcards, - fill_missing=..., - fail_dynamic=..., - dynamic_fill=..., - keep_dynamic=..., -): ... -def not_iterable(value): ... -def is_callable(value): ... - -class AnnotatedString(str): - def __init__(self, value) -> None: ... - def new_from(self, new_value): ... - -def flag(value, flag_type, flag_value=...): ... -def is_flagged(value, flag): ... -def get_flag_value(value, flag_type): ... -def ancient(value): # -> AnnotatedString | list[Unknown]: - """ - A flag for an input file that shall be considered ancient; i.e. its timestamp shall have no effect on which jobs to run. - """ - ... - -def directory(value): # -> AnnotatedString | list[Unknown]: - """ - A flag to specify that output is a directory, rather than a file or named pipe. - """ - ... - -def temp(value): # -> AnnotatedString | list[Unknown]: - """ - A flag for an input or output file that shall be removed after usage. - """ - ... - -def pipe(value): ... -def service(value): ... -def temporary(value): # -> AnnotatedString | list[Unknown]: - """An alias for temp.""" - ... - -def protected(value): # -> AnnotatedString | list[Unknown]: - """A flag for a file that shall be write-protected after creation.""" - ... - -def dynamic(value): # -> AnnotatedString | list[Unknown]: - """ - A flag for a file that shall be dynamic, i.e. the multiplicity - (and wildcard values) will be expanded after a certain - rule has been run""" - ... - -def touch(value): ... -def ensure(value, non_empty=..., sha256=...): ... -def unpack(value): ... -def repeat(value, n_repeat): # -> AnnotatedString | list[Unknown]: - """Flag benchmark records with the number of repeats.""" - ... - -def checkpoint_target(value): ... -def sourcecache_entry(value, orig_path_or_uri): ... - -ReportObject = ... - -def report( - value, - caption=..., - category=..., - subcategory=..., - labels=..., - patterns=..., - htmlindex=..., -): # -> AnnotatedString | list[Unknown]: - """Flag output file or directory as to be included into reports. - - In the case of a directory, files to include can be specified via a glob pattern (default: *). - - Arguments - value -- File or directory. - caption -- Path to a .rst file with a textual description of the result. - category -- Name of the (optional) category in which the result should be displayed in the report. - subcategory -- Name of the (optional) subcategory - columns -- Dict of strings (may contain wildcard expressions) that will be used as columns when displaying result tables - patterns -- Wildcard patterns for selecting files if a directory is given (this is used as - input for snakemake.io.glob_wildcards). Pattern shall not include the path to the - directory itself. - """ - ... - -def local(value): # -> AnnotatedString | list[Unknown]: - """Mark a file as a local file. This disables the application of a default remote - provider. - """ - ... - -def expand( - filepatterns: Sequence[Path | str] | Path | str, - func: Callable[[Iterable[str]], Iterable[Iterable[str]]] | None = ..., - /, - *, - allow_missing: bool | Sequence[str] | str = ..., - **wildcards: Sequence[str] | str, -) -> list[str]: - """ - Expand wildcards in given filepatterns. - - Arguments - *args -- first arg: filepatterns as list or one single filepattern, - second arg (optional): a function to combine wildcard values - (itertools.product per default) - **wildcards -- the wildcards as keyword arguments - with their values as lists. If allow_missing=True is included - wildcards in filepattern without values will stay unformatted. - """ - ... - -def multiext(prefix, *extensions): # -> list[AnnotatedString | list[Unknown]]: - """Expand a given prefix with multiple extensions (e.g. .txt, .csv, _peaks.bed, ...).""" - ... - -def limit(pattern, **wildcards): - """ - Limit wildcards to the given values. - - Arguments: - **wildcards -- the wildcards as keyword arguments - with their values as lists - """ - ... - -def glob_wildcards(pattern, files=..., followlinks=...): # -> Wildcards: - """ - Glob the values of the wildcards by matching the given pattern to the filesystem. - Returns a named tuple with a list of values for each wildcard. - """ - ... - -def update_wildcard_constraints( - pattern, wildcard_constraints, global_wildcard_constraints -): # -> AnnotatedString | str: - """Update wildcard constraints - - Args: - pattern (str): pattern on which to update constraints - wildcard_constraints (dict): dictionary of wildcard:constraint key-value pairs - global_wildcard_constraints (dict): dictionary of wildcard:constraint key-value pairs - """ - ... - -def split_git_path(path): ... -def get_git_root(path): - """ - Args: - path: (str) Path a to a directory/file that is located inside the repo - Returns: - path to the root folder for git repo - """ - ... - -def get_git_root_parent_directory(path, input_path): - """ - This function will recursively go through parent directories until a git - repository is found or until no parent directories are left, in which case - an error will be raised. This is needed when providing a path to a - file/folder that is located on a branch/tag not currently checked out. - - Args: - path: (str) Path a to a directory that is located inside the repo - input_path: (str) origin path, used when raising WorkflowError - Returns: - path to the root folder for git repo - """ - ... - -def git_content(git_file): - """ - This function will extract a file from a git repository, one located on - the filesystem. - The expected format is git+file:///path/to/your/repo/path_to_file@version - - Args: - env_file (str): consist of path to repo, @, version, and file information - Ex: git+file:///home/smeds/snakemake-wrappers/bio/fastqc/wrapper.py@0.19.3 - Returns: - file content or None if the expected format isn't meet - """ - ... - -def strip_wildcard_constraints(pattern): # -> str: - """Return a string that does not contain any wildcard constraints.""" - ... - -_T = TypeVar("_T") - -class Namedlist(List[_T | Iterable[_T]]): - """ - A list that additionally provides functions to name items. Further, - it is hashable, however, the hash does not consider the item names. - """ - - def __init__( - self, - toclone=..., - fromdict=..., - plainstr=..., - strip_constraints=..., - custom_map=..., - ) -> None: - """ - Create the object. - - Arguments - toclone -- another Namedlist that shall be cloned - fromdict -- a dict that shall be converted to a - Namedlist (keys become names) - """ - ... - def items(self): ... - def keys(self): ... - def get(self, key: str, default_value: _T = ...) -> _T: ... - def __getitem__(self, key: str) -> _T: ... - def __hash__(self) -> int: ... - def __str__(self) -> str: ... - -class InputFiles(Namedlist[_T]): - @property - def size(self): ... - @property - def size_mb(self): ... - def __getattribute__(self, __name: str) -> str: ... - -class OutputFiles(Namedlist[_T]): - def __getattribute__(self, __name: str) -> str: ... - -class Wildcards(Namedlist[_T]): ... -class Params(Namedlist[_T]): ... -class Resources(Namedlist[_T]): ... -class Log(Namedlist[_T]): ... - -def load_configfile(configpath: str) -> dict[str, Any]: - "Loads a JSON or YAML configfile as a dict, then checks that it's a dict." - ... - -class PeriodicityDetector: - def __init__(self, min_repeat=..., max_repeat=...) -> None: - """ - Args: - max_repeat (int): The maximum length of the periodic substring. - min_repeat (int): The minimum length of the periodic substring. - """ - ... - def is_periodic(self, value): # -> str | Any | None: - """Returns the periodic substring or None if not periodic.""" - ... diff --git a/typings/snakemake/script.pyi b/typings/snakemake/script.pyi deleted file mode 100644 index f73bc8e1..00000000 --- a/typings/snakemake/script.pyi +++ /dev/null @@ -1,365 +0,0 @@ -""" -This type stub file was generated by pyright. -""" -from __future__ import annotations - -import os -import typing -from abc import ABC, abstractmethod -from pathlib import Path -from typing import List, Optional, Pattern, Tuple, Union - -from snakemake import sourcecache -from snakemake.io import InputFiles, OutputFiles, Params -from snakemake.sourcecache import SourceCache - -__author__ = ... -__copyright__ = ... -__email__ = ... -__license__ = ... -PY_PREAMBLE_RE = ... -PathLike = Union[str, Path, os.PathLike] - -class Snakemake: - def __init__( - self, - input_, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - bench_iteration, - scriptdir=..., - ) -> None: ... - input: "InputFiles[str]" - output: "OutputFiles[str]" - params: "Params[str]" - def log_fmt_shell(self, stdout=..., stderr=..., append=...): # -> str: - """ - Return a shell redirection string to be used in `shell()` calls - - This function allows scripts and wrappers to support optional `log` files - specified in the calling rule. If no `log` was specified, then an - empty string "" is returned, regardless of the values of `stdout`, - `stderr`, and `append`. - - Parameters - --------- - - stdout : bool - Send stdout to log - - stderr : bool - Send stderr to log - - append : bool - Do not overwrite the log file. Useful for sending an output of - multiple commands to the same log. Note however that the log will - not be truncated at the start. - - The following table describes the output: - - -------- -------- -------- ----- ------------- - stdout stderr append log return value - -------- -------- -------- ----- ------------ - True True True fn >> fn 2>&1 - True False True fn >> fn - False True True fn 2>> fn - True True False fn > fn 2>&1 - True False False fn > fn - False True False fn 2> fn - any any any None "" - -------- -------- -------- ----- ----------- - """ - ... - -class REncoder: - """Encoding Python data structures into R.""" - - @classmethod - def encode_numeric(cls, value): ... - @classmethod - def encode_value(cls, value): ... - @classmethod - def encode_list(cls, l): ... - @classmethod - def encode_items(cls, items): ... - @classmethod - def encode_dict(cls, d): ... - @classmethod - def encode_namedlist(cls, namedlist): ... - -class JuliaEncoder: - """Encoding Python data structures into Julia.""" - - @classmethod - def encode_value(cls, value): ... - @classmethod - def encode_list(cls, l): ... - @classmethod - def encode_items(cls, items): ... - @classmethod - def encode_positional_items(cls, namedlist): ... - @classmethod - def encode_dict(cls, d): ... - @classmethod - def encode_namedlist(cls, namedlist): ... - -class BashEncoder: - """bash docs for associative arrays - https://www.gnu.org/software/bash/manual/html_node/Arrays.html#Arrays""" - - def __init__( - self, namedlists: List[str] = ..., dicts: List[str] = ..., prefix: str = ... - ) -> None: - """namedlists is a list of strings indicating the snakemake object's member - variables which are encoded as Namedlist. - dicts is a list of strings indicating the snakemake object's member variables - that are encoded as dictionaries. - Prefix is the prefix for the bash variable name(s) e.g., snakemake_input - """ - ... - def encode_snakemake(self, smk: Snakemake) -> str: - """Turn a snakemake object into a collection of bash associative arrays""" - ... - @staticmethod - def dict_to_aa(d: dict) -> str: - """Converts a dictionary to an associative array""" - ... - @classmethod - def encode_namedlist(cls, named_list) -> str: - """Convert a namedlist into a bash associative array - This produces the array component of the variable. - e.g. ( [var1]=val1 [var2]=val2 ) - to make it a correct bash associative array, you need to name it with - name= - """ - ... - -class ScriptBase(ABC): - editable = ... - def __init__( - self, - path, - cache_path: typing.Optional[str], - source, - basedir, - input_, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - conda_env, - conda_base_path, - container_img, - singularity_args, - env_modules, - bench_record, - jobid, - bench_iteration, - cleanup_scripts, - shadow_dir, - is_local, - ) -> None: ... - def evaluate(self, edit=...): ... - @property - def local_path(self): ... - @abstractmethod - def get_preamble(self): ... - @abstractmethod - def write_script(self, preamble, fd): ... - @abstractmethod - def execute_script(self, fname, edit=...): ... - -class PythonScript(ScriptBase): - @staticmethod - def generate_preamble( - path, - cache_path: typing.Optional[str], - source, - basedir, - input_, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - conda_env, - container_img, - singularity_args, - env_modules, - bench_record, - jobid, - bench_iteration, - cleanup_scripts, - shadow_dir, - is_local, - preamble_addendum=..., - ): ... - def get_preamble(self): ... - def write_script(self, preamble, fd): ... - def execute_script(self, fname, edit=...): ... - -class RScript(ScriptBase): - @staticmethod - def generate_preamble( - path, - source, - basedir, - input_, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - conda_env, - container_img, - singularity_args, - env_modules, - bench_record, - jobid, - bench_iteration, - cleanup_scripts, - shadow_dir, - preamble_addendum=..., - ): ... - def get_preamble(self): ... - def write_script(self, preamble, fd): ... - def execute_script(self, fname, edit=...): ... - -class RMarkdown(ScriptBase): - def get_preamble(self): ... - def write_script(self, preamble, fd): ... - def execute_script(self, fname, edit=...): ... - -class JuliaScript(ScriptBase): - def get_preamble(self): ... - def write_script(self, preamble, fd): ... - def execute_script(self, fname, edit=...): ... - -class RustScript(ScriptBase): - @staticmethod - def generate_preamble( - path, - source, - basedir, - input_, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - conda_env, - container_img, - singularity_args, - env_modules, - bench_record, - jobid, - bench_iteration, - cleanup_scripts, - shadow_dir, - is_local, - preamble_addendum=..., - ): ... - def get_preamble(self): ... - def write_script(self, preamble, fd): ... - def execute_script(self, fname, edit=...): ... - def combine_preamble_and_source(self, preamble: str) -> str: - """The manifest info needs to be moved to before the preamble. - Also, because rust-scipt relies on inner docs, there can't be an empty line - between the manifest and preamble. - """ - ... - @staticmethod - def default_dependencies() -> str: ... - @staticmethod - def default_features() -> str: ... - @staticmethod - def extract_manifest(source: str) -> Tuple[str, str]: ... - -class BashScript(ScriptBase): - @staticmethod - def generate_preamble( - path, - source, - basedir, - input_, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - conda_env, - container_img, - singularity_args, - env_modules, - bench_record, - jobid, - bench_iteration, - cleanup_scripts, - shadow_dir, - is_local, - ) -> str: ... - def get_preamble(self): ... - def write_script(self, preamble, fd): ... - def combine_preamble_and_source(self, preamble: str): ... - def execute_script(self, fname, edit=...): ... - -def strip_re(regex: Pattern, s: str) -> Tuple[str, str]: - """Strip a substring matching a regex from a string and return the stripped part - and the remainder of the original string. - Returns an empty string and the original string if the regex is not found - """ - ... - -def get_source( - path, sourcecache: sourcecache.SourceCache, basedir=..., wildcards=..., params=... -): ... -def get_language(source_file, source): ... -def script( - path, - basedir, - input, - output, - params, - wildcards, - threads, - resources, - log, - config, - rulename, - conda_env, - conda_base_path, - container_img, - singularity_args, - env_modules, - bench_record, - jobid, - bench_iteration, - cleanup_scripts, - shadow_dir, - runtime_sourcecache_path, -): # -> None: - """ - Load a script from the given basedir + path and execute it. - """ - ...