From bd6f78f79d03fc0c30d35f8597b16fb9a1e103b0 Mon Sep 17 00:00:00 2001 From: Peter Van Dyken Date: Thu, 14 Sep 2023 23:21:34 -0400 Subject: [PATCH] Overhaul of snakebids create Use copier instead of cookiecutter - Better feature set - Better documentation - More polished (e.g. typed codebase) Change starting workflow into a "welcome" workflow - Pregenerated empty data file based on the tutorial - workflow reads in the datafiles using generate_inputs and prints a welcome message Allow choice of build systems. App will be immediately installable and publishable End-to-end testing of app creation, dependency installation, and initial run --- .github/workflows/test.yml | 22 ++ containers/test-template/Dockerfile | 18 ++ containers/test-template/test-template.sh | 32 +++ poetry.lock | 222 +++++++++++++----- pyproject.toml | 14 +- snakebids/admin.py | 28 ++- snakebids/jinja2_ext/__init__.py | 0 snakebids/jinja2_ext/colorama.py | 11 + snakebids/jinja2_ext/vcs.py | 79 +++++++ snakebids/plugins/validator.py | 2 +- .../{{{ name_slug }} => }/README.md.jinja | 0 snakebids/project_template/copier.yaml | 87 ++++++- .../tests/data/dataset_description.json | 18 ++ snakebids/project_template/tests/data/sub-001 | 1 + ...'poetry' %}pyproject.toml{% endif %}.jinja | 48 ++++ ...'poetry' %}pyproject.toml{% endif %}.jinja | 34 +++ ...setuptools' %}MANIFEST.in{% endif %}.jinja | 5 + .../.gitignore | 0 .../Makefile | 0 .../conf.py.jinja | 0 .../getting_started/installation.md.jinja | 0 .../index.md | 0 .../requirements.txt.jinja | 0 .../usage/app_cli.md.jinja | 0 .../usage/snakemake_cli.md | 0 .../{{ name_slug }}/setup.py.jinja | 46 ---- .../{{name_slug}}/workflow/Snakefile | 53 ----- .../{{name_slug}}/__init__.py | 0 .../{{name_slug}}/config/snakebids.yml | 21 +- .../pipeline_description.json.jinja | 0 .../{{name_slug}}/run.py.jinja | 2 +- .../{{name_slug}}/workflow/Snakefile | 39 +++ snakebids/tests/helpers.py | 20 +- snakebids/tests/test_template.py | 88 ++++--- 34 files changed, 681 insertions(+), 209 deletions(-) create mode 100644 containers/test-template/Dockerfile create mode 100755 containers/test-template/test-template.sh create mode 100644 snakebids/jinja2_ext/__init__.py create mode 100644 snakebids/jinja2_ext/colorama.py create mode 100644 snakebids/jinja2_ext/vcs.py rename snakebids/project_template/{{{ name_slug }} => }/README.md.jinja (100%) create mode 100644 snakebids/project_template/tests/data/dataset_description.json create mode 120000 snakebids/project_template/tests/data/sub-001 create mode 100644 snakebids/project_template/{% if build_system != 'poetry' %}pyproject.toml{% endif %}.jinja create mode 100644 snakebids/project_template/{% if build_system == 'poetry' %}pyproject.toml{% endif %}.jinja create mode 100644 snakebids/project_template/{% if build_system == 'setuptools' %}MANIFEST.in{% endif %}.jinja rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/.gitignore (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/Makefile (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/conf.py.jinja (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/getting_started/installation.md.jinja (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/index.md (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/requirements.txt.jinja (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/usage/app_cli.md.jinja (100%) rename snakebids/project_template/{{{ name_slug }} => }/{% if create_doc_template %}docs{% endif %}/usage/snakemake_cli.md (100%) delete mode 100644 snakebids/project_template/{{ name_slug }}/setup.py.jinja delete mode 100644 snakebids/project_template/{{ name_slug }}/{{name_slug}}/workflow/Snakefile create mode 100644 snakebids/project_template/{{name_slug}}/__init__.py rename snakebids/project_template/{{{ name_slug }} => }/{{name_slug}}/config/snakebids.yml (92%) rename snakebids/project_template/{{{ name_slug }} => }/{{name_slug}}/pipeline_description.json.jinja (100%) rename snakebids/project_template/{{{ name_slug }} => }/{{name_slug}}/run.py.jinja (85%) create mode 100644 snakebids/project_template/{{name_slug}}/workflow/Snakefile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83fa32fb..401f97f2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,6 +58,7 @@ jobs: - name: Install library run: poetry install --no-interaction --no-ansi + #---------------------------------------------- # run python style checks #---------------------------------------------- @@ -113,6 +114,27 @@ jobs: - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-root --no-ansi + + - name: Cache Template Testing Containers + uses: actions/cache@v3 + with: + path: container-test-template-cache + key: ${{ runner.os }}-go-build-cache-${{ hashFiles('containers/test-template/**') }} + + - name: Inject container-test-template-cache into docker + uses: reproducible-containers/buildkit-cache-dance@v2.1.2 + with: + cache-source: container-test-template-cache + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: containers/test-template + cache-from: type=gha + cache-to: type=gha,mode=max + push: false + load: true + tags: snakebids/test-template + platforms: linux/amd64 #---------------------------------------------- # install your root project, if required #---------------------------------------------- diff --git a/containers/test-template/Dockerfile b/containers/test-template/Dockerfile new file mode 100644 index 00000000..5fd54422 --- /dev/null +++ b/containers/test-template/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11.5 + +# Install and uninstall snakebids to cache it and it's dependences +RUN python -m pip install pipx && \ + pipx install poetry && \ + pipx install hatch && \ + pipx install pdm && \ + mkdir prebuild && \ + cd prebuild && \ + pip wheel snakebids && \ + cd .. && \ + rm -rf prebuild + +COPY ./test-template.sh /run/test-template.sh +ENV PATH="/root/.local/bin:$PATH" + +WORKDIR /work +ENTRYPOINT [ "/run/test-template.sh" ] diff --git a/containers/test-template/test-template.sh b/containers/test-template/test-template.sh new file mode 100755 index 00000000..14d44733 --- /dev/null +++ b/containers/test-template/test-template.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +set -eu + +method="$1" +script_name="$2" + +cp -r /app/* /work +script="'${script_name}' tests/data tests/result participant -c1 --skip-bids-validation" +case "$method" in + "setuptools" ) + python -m venv .venv + .venv/bin/python -m pip install . + PATH=".venv/bin:$PATH" eval "$script" + ;; + "poetry" ) + poetry install + eval "poetry run $script" + ;; + "hatch" ) + hatch env create + eval "hatch env run -- $script" + ;; + "pdm" ) + pdm install + eval "pdm run $script" + ;; + * ) + >&2 echo "Invalid method" + exit 1 + ;; +esac diff --git a/poetry.lock b/poetry.lock index 666a4455..e484bc10 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,19 @@ # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "appdirs" version = "1.4.4" @@ -359,13 +373,13 @@ rich = "*" [[package]] name = "copier" -version = "8.1.0" +version = "8.3.0" description = "A library for rendering project templates." optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<4.0" files = [ - {file = "copier-8.1.0-py3-none-any.whl", hash = "sha256:56537163b1b24441a63504ebf56b8db588dee20b48ef4fc374a6c5a2b43010c1"}, - {file = "copier-8.1.0.tar.gz", hash = "sha256:902b4eb65fafe7a1621991234d2ebf3bc3fc9323e64e3a2560a00c05c73f6229"}, + {file = "copier-8.3.0-py3-none-any.whl", hash = "sha256:a708e9b7c0eb7363ee5935f7a96a63a6f39b6da1d36f1c931d1ebd7711bb2ecf"}, + {file = "copier-8.3.0.tar.gz", hash = "sha256:051149721c811bfa84023fca5c23827917ac5f42ab6c2696dcb522b17aee7cae"}, ] [package.dependencies] @@ -378,11 +392,12 @@ jinja2-ansible-filters = ">=1.3.1" packaging = ">=23.0" pathspec = ">=0.9.0" plumbum = ">=1.6.9" -pydantic = ">=1.10.2,<2" +pydantic = ">=2.0.3" pygments = ">=2.7.1" pyyaml = ">=5.3.1" pyyaml-include = ">=1.2" questionary = ">=1.8.1" +typing-extensions = {version = ">=3.7.4,<5.0.0", markers = "python_version < \"3.9\""} [[package]] name = "datrie" @@ -1013,6 +1028,21 @@ PyYAML = "*" [package.extras] test = ["pytest", "pytest-cov"] +[[package]] +name = "jinja2-time" +version = "0.2.0" +description = "Jinja2 Extension for Dates and Times" +optional = false +python-versions = "*" +files = [ + {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"}, + {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, +] + +[package.dependencies] +arrow = "*" +jinja2 = "*" + [[package]] name = "jsonschema" version = "4.19.0" @@ -1756,55 +1786,140 @@ tutorial = ["ipykernel", "jinja2", "jupyter-client", "markupsafe", "nbconvert"] [[package]] name = "pydantic" -version = "1.10.12" -description = "Data validation and settings management using python type hints" +version = "2.3.0" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"}, + {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.6.3" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.6.3" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"}, + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"}, + {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"}, + {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"}, + {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"}, + {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"}, + {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"}, + {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"}, + {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"}, + {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"}, + {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"}, + {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"}, + {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"}, + {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"}, + {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"}, + {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"}, + {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyfakefs" @@ -2778,27 +2893,26 @@ files = [ [[package]] name = "ubelt" -version = "1.3.3" +version = "1.3.2" description = "A Python utility belt containing simple tools, a stdlib like feel, and extra batteries." optional = false python-versions = ">=3.6" files = [ - {file = "ubelt-1.3.3-py3-none-any.whl", hash = "sha256:7e5a0975609312db93a76ae2a1e81ded50f2aca1be28c9b1783e7fd9b49f5f47"}, - {file = "ubelt-1.3.3.tar.gz", hash = "sha256:482c5c34844a887c02ffed69b2305bd09872755223d638383b2df8bc69f4dbdd"}, + {file = "ubelt-1.3.2-py3-none-any.whl", hash = "sha256:abd5a4998ce84abab30b55c2e99a3e805f00bd8db14bdeb470838a134a340a6d"}, + {file = "ubelt-1.3.2.tar.gz", hash = "sha256:15e09c5628aefb82d2022e4fb12b35cf30fd27112f8c492212208972a24acc19"}, ] [package.dependencies] "jaraco.windows" = {version = "*", markers = "platform_system == \"Windows\""} -pydantic = {version = "<2.0", markers = "platform_system == \"Windows\" and platform_python_implementation == \"PyPy\""} [package.extras] -all = ["Pygments", "colorama", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "jaraco.windows", "numpy", "numpy", "numpy", "numpy", "numpy", "numpy", "pydantic (<2.0)", "pytest", "pytest", "pytest", "pytest", "pytest", "pytest", "pytest-cov", "pytest-cov", "pytest-cov", "pytest-cov", "pytest-timeout", "python-dateutil", "requests", "xdoctest", "xxhash", "xxhash", "xxhash", "xxhash", "xxhash", "xxhash"] -all-strict = ["Pygments (==2.2.0)", "colorama (==0.4.3)", "coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "jaraco.windows (==3.9.1)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.1)", "numpy (==1.23.5)", "pydantic (<2.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "pytest-timeout (==1.4.2)", "python-dateutil (==2.8.1)", "requests (==2.25.1)", "xdoctest (==0.14.0)", "xxhash (==1.3.0)", "xxhash (==1.3.0)", "xxhash (==1.4.3)", "xxhash (==2.0.2)", "xxhash (==3.0.0)", "xxhash (==3.2.0)"] +all = ["Pygments", "colorama", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "jaraco.windows", "numpy", "numpy", "numpy", "numpy", "numpy", "numpy", "pytest", "pytest", "pytest", "pytest", "pytest", "pytest", "pytest-cov", "pytest-cov", "pytest-cov", "pytest-cov", "pytest-timeout", "python-dateutil", "requests", "xdoctest", "xxhash", "xxhash", "xxhash", "xxhash", "xxhash", "xxhash"] +all-strict = ["Pygments (==2.2.0)", "colorama (==0.4.3)", "coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "jaraco.windows (==3.9.1)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.1)", "numpy (==1.23.5)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "pytest-timeout (==1.4.2)", "python-dateutil (==2.8.1)", "requests (==2.25.1)", "xdoctest (==0.14.0)", "xxhash (==1.3.0)", "xxhash (==1.3.0)", "xxhash (==1.4.3)", "xxhash (==2.0.2)", "xxhash (==3.0.0)", "xxhash (==3.2.0)"] docs = ["Pygments", "myst-parser", "sphinx", "sphinx-autoapi", "sphinx-autobuild", "sphinx-reredirects", "sphinx-rtd-theme", "sphinxcontrib-napoleon"] docs-strict = ["Pygments (==2.9.0)", "myst-parser (==0.16.1)", "sphinx (==4.3.2)", "sphinx-autoapi (==1.8.4)", "sphinx-autobuild (==2021.3.14)", "sphinx-reredirects (==0.0.1)", "sphinx-rtd-theme (==1.0.0)", "sphinxcontrib-napoleon (==0.7)"] optional = ["Pygments", "colorama", "numpy", "numpy", "numpy", "numpy", "numpy", "numpy", "python-dateutil", "xxhash", "xxhash", "xxhash", "xxhash", "xxhash", "xxhash"] optional-strict = ["Pygments (==2.2.0)", "colorama (==0.4.3)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.1)", "numpy (==1.23.5)", "python-dateutil (==2.8.1)", "xxhash (==1.3.0)", "xxhash (==1.3.0)", "xxhash (==1.4.3)", "xxhash (==2.0.2)", "xxhash (==3.0.0)", "xxhash (==3.2.0)"] -runtime-strict = ["jaraco.windows (==3.9.1)", "pydantic (<2.0)"] +runtime-strict = ["jaraco.windows (==3.9.1)"] tests = ["coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "coverage", "pytest", "pytest", "pytest", "pytest", "pytest", "pytest", "pytest-cov", "pytest-cov", "pytest-cov", "pytest-cov", "pytest-timeout", "requests", "xdoctest"] tests-strict = ["coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "pytest-timeout (==1.4.2)", "requests (==2.25.1)", "xdoctest (==0.14.0)"] @@ -2968,4 +3082,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "2887048ebff450d3870d0beea93528797497aa5fc4e90791bb3b43180340a097" +content-hash = "432a0ee0e3998c707ae8e5d84398fddb0983e68a7df16f34d57857517dc14b7a" diff --git a/pyproject.toml b/pyproject.toml index 74364e16..6883b534 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,8 @@ scipy = [ { version = ">=1.10.0,<=1.10.1", python = "<3.9" }, { version = ">=1.10.0", python = ">=3.9" } ] -copier = "^8.1.0" +copier = ">=8.2.0" +jinja2-time = "^0.2.0" [tool.poetry.group.dev.dependencies] black = "^23.1.0" @@ -93,8 +94,8 @@ build-backend = "poetry_dynamic_versioning.backend" [tool.poe.tasks] setup = "pre-commit install" -quality = { shell = "isort snakebids && black snakebids && ruff snakebids && pyright" } -fix = { shell = "ruff --fix snakebids && isort snakebids && black snakebids"} +quality.shell = "isort snakebids && black snakebids && ruff snakebids && pyright" +fix.shell = "ruff --fix snakebids && isort snakebids && black snakebids" test = """ pytest --doctest-modules --ignore=docs \ --ignore=snakebids/project_template --benchmark-disable @@ -102,6 +103,13 @@ pytest --doctest-modules --ignore=docs \ mkinit = "mkinit --recursive --nomods --black -i snakebids" benchmark = "pytest --benchmark-only --benchmark-autosave" +[tool.poe.tasks.build-container] +args = [{ name = "container_id", positional = true, required = true }] +cmd = """ + docker build 'containers/${container_id}' \ + --tag 'snakebids/${container_id}:dev' +""" + [tool.isort] profile = "black" multi_line_output = 3 diff --git a/snakebids/admin.py b/snakebids/admin.py index 4b75f21f..18186214 100644 --- a/snakebids/admin.py +++ b/snakebids/admin.py @@ -1,10 +1,12 @@ """Script to generate a Snakebids project.""" import argparse +import sys from pathlib import Path +import copier # type: ignore import more_itertools as itx -from cookiecutter.main import cookiecutter # type: ignore +from colorama import Fore, Style import snakebids from snakebids.app import SnakeBidsApp @@ -12,10 +14,28 @@ def create_app(args: argparse.Namespace) -> None: - cookiecutter( - str(Path(itx.first(snakebids.__path__)) / "project_template"), - output_dir=args.output_dir, + output = Path(args.output_dir).resolve() + if not output.parent.exists(): + print( + f"{Fore.RED}{Style.BRIGHT}{output.parent}{Style.RESET_ALL}{Fore.RED} does " + f"not exist{Fore.RESET}", + file=sys.stderr, + ) + sys.exit(1) + print( + f"Creating Snakebids app at {Fore.GREEN}{output}{Fore.RESET}", file=sys.stderr ) + print(file=sys.stderr) + try: + copier.run_copy( # type: ignore + str(Path(itx.first(snakebids.__path__), "project_template")), + output, + data={"app_full_name": output.name}, + unsafe=True, + ) + except KeyboardInterrupt: + print(f"{Fore.RED}Aborted!{Fore.RESET}", file=sys.stderr) + sys.exit(1) def create_descriptor(args: argparse.Namespace) -> None: diff --git a/snakebids/jinja2_ext/__init__.py b/snakebids/jinja2_ext/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/snakebids/jinja2_ext/colorama.py b/snakebids/jinja2_ext/colorama.py new file mode 100644 index 00000000..927b40bf --- /dev/null +++ b/snakebids/jinja2_ext/colorama.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import jinja2.parser +from colorama import Fore +from jinja2.ext import Extension + + +class ColoramaExtension(Extension): + def __init__(self, env: jinja2.Environment): + super().__init__(env) + env.globals["Fore"] = Fore # type: ignore diff --git a/snakebids/jinja2_ext/vcs.py b/snakebids/jinja2_ext/vcs.py new file mode 100644 index 00000000..b7478ca6 --- /dev/null +++ b/snakebids/jinja2_ext/vcs.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import re +import subprocess +import sys +from pathlib import Path + +import jinja2.parser +from jinja2 import nodes +from jinja2.ext import Extension + + +class GitConfigExtension(Extension): + tags = {"gitconfig"} # noqa: RUF012 + _config: dict[str, str] + + def __init__(self, env: jinja2.Environment) -> None: + self._config = {} + + try: + config_list = subprocess.check_output( + [executable(), "config", "-l"], stderr=subprocess.STDOUT + ).decode() + + m = re.findall("(?ms)^([^=]+)=(.*?)$", config_list) + if m: + for group in m: + self._config[group[0]] = group[1] + except (subprocess.CalledProcessError, OSError): + pass + + def get(self, key: str, default: str | None = None) -> str | None: + return self._config.get(key, default) + + def __getitem__(self, item: str) -> str: + return self._config[item] + + def parse(self, parser: jinja2.parser.Parser): + lineno = next(parser.stream).lineno + + node = parser.parse_expression() + + if not isinstance(node, nodes.Const): + raise ValueError("Argument to `gitconfig` must be a string") + call_method = self.call_method( + "get", + [node], + lineno=lineno, + ) + return nodes.Output([call_method], lineno=lineno) + + +def executable() -> str: + _executable = None + + if sys.platform == "win32": + # Finding git via where.exe + where = "%WINDIR%\\System32\\where.exe" + paths = subprocess.check_output( + [where, "git"], shell=True, encoding="oem" + ).split("\n") + for path in paths: + if not path: + continue + + _path = Path(path.strip()) + try: + _path.relative_to(Path.cwd()) + except ValueError: + _executable = str(_path) + + break + else: + _executable = "git" + + if _executable is None: # type: ignore + raise RuntimeError("Unable to find a valid git executable") + + return _executable diff --git a/snakebids/plugins/validator.py b/snakebids/plugins/validator.py index b21fb2d5..7dbe311e 100644 --- a/snakebids/plugins/validator.py +++ b/snakebids/plugins/validator.py @@ -56,7 +56,7 @@ def __call__(self, app: SnakeBidsApp) -> None: temp.flush() try: subprocess.check_call( - ["bids-validator", app.config["bids_dirs"], "-c", temp.name] + ["bids-validator", app.config["bids_dir"], "-c", temp.name] ) # If successfully bids-validation performed diff --git a/snakebids/project_template/{{ name_slug }}/README.md.jinja b/snakebids/project_template/README.md.jinja similarity index 100% rename from snakebids/project_template/{{ name_slug }}/README.md.jinja rename to snakebids/project_template/README.md.jinja diff --git a/snakebids/project_template/copier.yaml b/snakebids/project_template/copier.yaml index 9d314da2..4df5e52f 100644 --- a/snakebids/project_template/copier.yaml +++ b/snakebids/project_template/copier.yaml @@ -1,11 +1,12 @@ full_name: type: str help: What is your name? - placeholder: " optional" + default: '{% gitconfig "user.name" %}' email: type: str help: What is your email? + default: '{% gitconfig "user.email" %}' github: type: str @@ -14,7 +15,6 @@ github: app_full_name: type: str help: What is the name of your app? - default: "{{ dst_path }}" validator: '{% if not app_full_name %}Required{% endif %}' app_description: @@ -24,6 +24,25 @@ app_description: app_version: default: "0.1.0" help: Starting version number for your app + validator: '{% if not app_version %}Required{% endif %}' + +build_system: + help: > + What build system would you like to use? If you don't know what this means, + choose setuptools + choices: + - setuptools + - poetry + - hatch + - flit + +# Flit and setuptools requires specifying license via Trove classifiers; too +# complicated to get via interactive prompt +license: + default: >- + {% if build_system != "flit" and build_system != "setuptools" %}MIT{% endif %} + help: Usage license for your app + when: "{{ build_system != 'flit' and build_system != 'setuptools' }}" create_doc_template: type: bool @@ -34,12 +53,74 @@ bids_version: when: false snakebids_version: - default: "0.0.0" + default: "0.9.2" + when: false + +snakemake_version: + default: "7.20" when: false name_slug: default: "{{ app_full_name|lower|replace(' ', '_')|replace('-', '_') }}" when: false +test_run_cmd: + default: "{{ name_slug }} tests/data tests/result participant -c1" + when: false + +_message_after_copy: > + Snakebids project successfully created in + {{ Fore.GREEN }}{{ _copier_conf.dst_path }}{{ Fore.RESET }} + + + Just a few steps before coding: + + + {{ Fore.BLUE }}1.{{ Fore.RESET }} Change into the project directory: + + $ cd {{ _copier_conf.dst_path }} + + {% if build_system == "poetry" -%} + {{ Fore.BLUE }}2.{{ Fore.RESET }} Install dependencies. + + $ poetry install + + {%- else -%} + {{ Fore.BLUE + "2." + Fore.RESET + """ Install dependencies. This may vary + depending on your tooling. The following are examples (the first example + should work on any python environment; the others require 3rd party tools): + """ | wordwrap(80)}} + + * {{Fore.YELLOW}}Virtual Environment and setuptools:{{Fore.RESET}} + $ python -m venv .venv + $ source .venv/bin/activate + $ pip install . + + * {{Fore.YELLOW}}Hatch:{{Fore.RESET}} + $ hatch env create + + * {{Fore.YELLOW}}PDM:{{Fore.RESET}} + $ pdm install + {%- endif %} + + + {{ Fore.BLUE }}3.{{Fore.RESET }} Perform a test run. + + {% if build_system == "poetry" %} + $ poetry run {{ test_run_cmd }} + + {%- else %} + * {{Fore.YELLOW}}Virtual Environment and setuptools:{{Fore.RESET}} + $ {{ test_run_cmd }} + + * {{Fore.YELLOW}}Hatch:{{Fore.RESET}} + $ hatch env run -- {{ test_run_cmd }} + + * {{Fore.YELLOW}}PDM:{{Fore.RESET}} + $ pdm run {{ test_run_cmd }} + {%- endif %} + _jinja_extensions: - jinja2_time.TimeExtension + - snakebids.jinja2_ext.vcs.GitConfigExtension + - snakebids.jinja2_ext.colorama.ColoramaExtension diff --git a/snakebids/project_template/tests/data/dataset_description.json b/snakebids/project_template/tests/data/dataset_description.json new file mode 100644 index 00000000..cec4e821 --- /dev/null +++ b/snakebids/project_template/tests/data/dataset_description.json @@ -0,0 +1,18 @@ +{ + "Acknowledgements": "Snakebids Development Team", + "Authors": [ + "Peter Van Dyken", + "Ali Khan", + "Tristan Kuehn", + "Jason Kai" + ], + "DatasetType": "raw", + "BIDSVersion": "1.4.1", + "HowToAcknowledge": "https://zenodo.org/record/8274278", + "License": "PDLL", + "Name": "Snakebids Test T1w Dataset (empty files)", + "ReferencesAndLinks": [ + "https://github.com/akhanf/snakebids", + "List of papers or websites" + ] + } diff --git a/snakebids/project_template/tests/data/sub-001 b/snakebids/project_template/tests/data/sub-001 new file mode 120000 index 00000000..30ebd731 --- /dev/null +++ b/snakebids/project_template/tests/data/sub-001 @@ -0,0 +1 @@ +../../../../docs/tutorial/bids/sub-001 \ No newline at end of file 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 new file mode 100644 index 00000000..a70448d8 --- /dev/null +++ b/snakebids/project_template/{% if build_system != 'poetry' %}pyproject.toml{% endif %}.jinja @@ -0,0 +1,48 @@ +[project] +name = "{{ app_full_name }}" +version = "{{ app_version }}" +description = "{{ app_description }}" +readme = "README.md" +{% if license -%} +license = "{{ license }}" +{%- endif %} +{% if full_name or email -%} +authors = [ + { {{ '' }} + {%- if full_name -%} + name = "{{ full_name }}" + {%- endif -%} + {%- if full_name and email -%} + ,{{ ' ' }} + {%- endif -%} + {%- if email -%} + email = "{{ email }}" + {%- endif -%} + {{ ' ' }}} +] +{%- endif %} +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] + +requires-python = ">=3.8,<3.12" +dependencies = [ + "snakemake >= {{ snakemake_version }}", + "snakebids >= {{ snakebids_version }}", +] + +[project.scripts] +{{ name_slug }} = "{{ name_slug }}.run:main" + +[build-system] +{% if build_system == "flit" -%} +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" +{%- elif build_system == "hatch" -%} +requires = ["hatchling"] +build-backend = "hatchling.build" +{%- else -%} +requires = ["setuptools"] +build-backend = "setuptools.build_meta" +{%- endif %} 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 new file mode 100644 index 00000000..4c4b11b7 --- /dev/null +++ b/snakebids/project_template/{% if build_system == 'poetry' %}pyproject.toml{% endif %}.jinja @@ -0,0 +1,34 @@ +[tool.poetry] +name = "{{ app_full_name }}" +version = "{{ app_version }}" +description = "{{ app_description }}" +readme = "README.md" +{% if license -%} +license = "{{ license }}" +{%- endif %} +{% if full_name -%} +authors = [ + {% if email -%} + "{{ full_name }} <{{email}}>" + {%- else -%} + "{{ full_name }}" + {%- endif %} +] +{%- endif %} +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] + +[tool.poetry.dependencies] +python = ">=3.8,<3.12" +snakemake = ">={{ snakemake_version }}" +snakebids = ">={{ snakebids_version }}" + +[tool.poetry.scripts] +{{ name_slug }} = "{{ name_slug }}.run:main" + + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/snakebids/project_template/{% if build_system == 'setuptools' %}MANIFEST.in{% endif %}.jinja b/snakebids/project_template/{% if build_system == 'setuptools' %}MANIFEST.in{% endif %}.jinja new file mode 100644 index 00000000..cba9b38a --- /dev/null +++ b/snakebids/project_template/{% if build_system == 'setuptools' %}MANIFEST.in{% endif %}.jinja @@ -0,0 +1,5 @@ +include {{ name_slug }}/**/*.yaml +include {{ name_slug }}/**/*.yml +include {{ name_slug }}/**/*.json +include {{ name_slug }}/**/Snakefile +include {{ name_slug }}/**/*.smk diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/.gitignore b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/.gitignore similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/.gitignore rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/.gitignore diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/Makefile b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/Makefile similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/Makefile rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/Makefile diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/conf.py.jinja b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/conf.py.jinja similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/conf.py.jinja rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/conf.py.jinja diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/getting_started/installation.md.jinja b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/getting_started/installation.md.jinja similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/getting_started/installation.md.jinja rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/getting_started/installation.md.jinja diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/index.md b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/index.md similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/index.md rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/index.md diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/requirements.txt.jinja b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/requirements.txt.jinja similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/requirements.txt.jinja rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/requirements.txt.jinja diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/usage/app_cli.md.jinja b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/usage/app_cli.md.jinja similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/usage/app_cli.md.jinja rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/usage/app_cli.md.jinja diff --git a/snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/usage/snakemake_cli.md b/snakebids/project_template/{% if create_doc_template %}docs{% endif %}/usage/snakemake_cli.md similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{% if create_doc_template %}docs{% endif %}/usage/snakemake_cli.md rename to snakebids/project_template/{% if create_doc_template %}docs{% endif %}/usage/snakemake_cli.md diff --git a/snakebids/project_template/{{ name_slug }}/setup.py.jinja b/snakebids/project_template/{{ name_slug }}/setup.py.jinja deleted file mode 100644 index 13ef84b7..00000000 --- a/snakebids/project_template/{{ name_slug }}/setup.py.jinja +++ /dev/null @@ -1,46 +0,0 @@ -import json - -import setuptools - -with open("README.md", encoding="utf-8") as fh: - long_description = fh.read() - -with open("pipeline_description.json", encoding="utf-8") as fh: - pipeline = json.load(fh) -name = pipeline["GeneratedBy"][0]["Name"] -description = pipeline["Name"] -version = pipeline["GeneratedBy"][0]["Version"] -optional_vals = { - attr: pipeline["GeneratedBy"][0].get(key) - for attr, key in [ - ("url", "CodeURL"), - ("author", "Author"), - ("author_email", "AuthorEmail"), - ] - if key in pipeline -} - -setuptools.setup( - name=name, - version=version, - description=description, - long_description=long_description, - long_description_content_type="text/x-rst", - packages=setuptools.find_packages(), - include_package_data=True, - classifiers=[ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", - ], - entry_points={ - "console_scripts": [ - "{{name_slug}}={{name_slug}}.run:main" - ] - }, - install_requires=[ - "snakebids>={{snakebids_version}}", - "snakemake", - ], - python_requires=">=3.7", - **optional_vals, -) diff --git a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/workflow/Snakefile b/snakebids/project_template/{{ name_slug }}/{{name_slug}}/workflow/Snakefile deleted file mode 100644 index b934da01..00000000 --- a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/workflow/Snakefile +++ /dev/null @@ -1,53 +0,0 @@ -#---- begin snakebids boilerplate ---------------------------------------------- - -from snakebids import bids, generate_inputs, get_wildcard_constraints - -configfile: workflow.source_path('../config/snakebids.yml') - -# Get input wildcards -inputs = generate_inputs( - bids_dir=config["bids_dir"], - pybids_inputs=config["pybids_inputs"], - pybidsdb_dir=config.get("pybidsdb_dir"), - pybidsdb_reset=config.get("pybidsdb_reset"), - derivatives=config.get("derivatives", None), - participant_label=config.get("participant_label", None), - exclude_participant_label=config.get("exclude_participant_label", None), - validate=not config.get("plugins.validator.skip", False) -) - -#this adds constraints to the bids naming -wildcard_constraints: **get_wildcard_constraints(config['pybids_inputs']) - -#---- end snakebids boilerplate ------------------------------------------------ - - -rule smooth: - input: inputs['bold'].path - output: - bids( - root=config['root'], - datatype='func', - desc='smooth{fwhm}mm', - suffix='bold.nii.gz', - **inputs['bold'].wildcards - ) - container: config['singularity']['fsl'] - log: - bids( - root='logs', - suffix='smooth.log', - fwhm='{fwhm}', - **inputs['bold'].wildcards - ) - params: sigma = lambda wildcards: f'{float(wildcards.fwhm)/2.355:0.2f}' - shell: 'fslmaths {input} -s {params.sigma} {output}' - - -rule all: - input: - inputs['bold'].expand( - rules.smooth.output, - fwhm = config['smoothing_fwhm'], - ) - default_target: True diff --git a/snakebids/project_template/{{name_slug}}/__init__.py b/snakebids/project_template/{{name_slug}}/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/config/snakebids.yml b/snakebids/project_template/{{name_slug}}/config/snakebids.yml similarity index 92% rename from snakebids/project_template/{{ name_slug }}/{{name_slug}}/config/snakebids.yml rename to snakebids/project_template/{{name_slug}}/config/snakebids.yml index 9b36f37f..f2249d8f 100644 --- a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/config/snakebids.yml +++ b/snakebids/project_template/{{name_slug}}/config/snakebids.yml @@ -22,16 +22,16 @@ targets_by_analysis_level: # https://bids-standard.github.io/pybids/generated/bids.layout.BIDSLayout.html#bids.layout.BIDSLayout.get pybids_inputs: + t1w: + filters: + suffix: T1w + wildcards: + - subject bold: filters: - suffix: 'bold' - extension: '.nii.gz' - datatype: 'func' + suffix: bold wildcards: - subject - - session - - acquisition - - task - run #configuration for the command-line parameters to make available @@ -54,7 +54,7 @@ parse_args: help: Level of the analysis that will be performed. choices: *analysis_levels - --participant_label: + --participant-label: help: The label(s) of the participant(s) that should be analyzed. The label corresponds to sub- from the BIDS spec (so it does not include "sub-"). If this parameter is not @@ -62,7 +62,7 @@ parse_args: participants can be specified with a space separated list. nargs: '+' - --exclude_participant_label: + --exclude-participant-label: help: The label(s) of the participant(s) that should be excluded. The label corresponds to sub- from the BIDS spec (so it does not include "sub-"). If this parameter is not @@ -78,7 +78,7 @@ parse_args: # custom command-line parameters can then be added, these will get added to the config and also accessible to plugins # below are examples for plugin and custom parameters (e.g. config['smoothing_fwhm']) - --skip_bids_validation: + --skip-bids-validation: help: 'Skip validation of BIDS dataset. BIDS validation is performed by default using the bids-validator plugin (if installed/enabled) or with the pybids validator implementation (if bids-validator is not installed/enabled).' @@ -86,9 +86,6 @@ parse_args: action: "store_true" default: False - --smoothing_fwhm: - nargs: '+' - required: True #--- workflow specific configuration -- below is just an example: diff --git a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/pipeline_description.json.jinja b/snakebids/project_template/{{name_slug}}/pipeline_description.json.jinja similarity index 100% rename from snakebids/project_template/{{ name_slug }}/{{name_slug}}/pipeline_description.json.jinja rename to snakebids/project_template/{{name_slug}}/pipeline_description.json.jinja diff --git a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/run.py.jinja b/snakebids/project_template/{{name_slug}}/run.py.jinja similarity index 85% rename from snakebids/project_template/{{ name_slug }}/{{name_slug}}/run.py.jinja rename to snakebids/project_template/{{name_slug}}/run.py.jinja index f9426d23..1b782c1f 100755 --- a/snakebids/project_template/{{ name_slug }}/{{name_slug}}/run.py.jinja +++ b/snakebids/project_template/{{name_slug}}/run.py.jinja @@ -13,7 +13,7 @@ def get_parser(): def main(): app = SnakeBidsApp( - Path(__file__).resolve().parent.parent, # to get repository root + Path(__file__).resolve().parent, # to get repository root plugins=[BidsValidator()], ) app.run_snakemake() diff --git a/snakebids/project_template/{{name_slug}}/workflow/Snakefile b/snakebids/project_template/{{name_slug}}/workflow/Snakefile new file mode 100644 index 00000000..079a2d39 --- /dev/null +++ b/snakebids/project_template/{{name_slug}}/workflow/Snakefile @@ -0,0 +1,39 @@ +from snakebids import bids, generate_inputs, get_wildcard_constraints + +configfile: 'config/snakebids.yml' + +# Get input wildcards +inputs = generate_inputs( + bids_dir=config["bids_dir"], + pybids_inputs=config["pybids_inputs"], + pybidsdb_dir=config.get("pybidsdb_dir"), + pybidsdb_reset=config.get("pybidsdb_reset"), + derivatives=config.get("derivatives", None), + participant_label=config.get("participant_label", None), + exclude_participant_label=config.get("exclude_participant_label", None), + validate=not config.get("plugins.validator.skip", False) +) + + +rule all: + input: + inputs['t1w'].expand(), + inputs['bold'].expand() + default_target: True + params: + inputs=lambda wcards, input: "- " + "\n- ".join(input) + shell: + """ + cat < _F: + if sp.run(["docker"]).returncode != 0: + return pytest.mark.skip(reason="docker is not available on this machine")( + func + ) + if sp.run(["docker", "image", "inspect", container]).returncode != 0: + return pytest.mark.skip(reason=f"{container} is not built on this machine")( + func + ) + return func + + return inner def entity_to_wildcard(entities: str | Iterable[str], /): diff --git a/snakebids/tests/test_template.py b/snakebids/tests/test_template.py index 1f709796..3f0d138a 100644 --- a/snakebids/tests/test_template.py +++ b/snakebids/tests/test_template.py @@ -1,42 +1,68 @@ -from os.path import join +from __future__ import annotations + +import subprocess as sp from pathlib import Path +import copier # type: ignore import more_itertools as itx import pytest -from cookiecutter.main import cookiecutter # type: ignore import snakebids -from snakebids.app import SnakeBidsApp -from snakebids.cli import SnakebidsArgs +from snakebids.tests.helpers import needs_docker -def test_template_dry_runs_successfully(tmp_path: Path): +@needs_docker("snakebids/test-template:dev") +@pytest.mark.parametrize( + ["build", "venv"], + [ + ("setuptools", "setuptools"), + ("poetry", "poetry"), + ("hatch", "hatch"), + ("flit", "pdm"), + ], +) +def test_template_dry_runs_successfully(tmp_path: Path, build: str, venv: str): app_name = "snakebids_app" - cookiecutter( - join(itx.first(snakebids.__path__), "project_template"), - no_input=True, - output_dir=str(tmp_path), + data: dict[str, str | bool] = { + "full_name": "", + "email": "", + "app_full_name": app_name, + "github": "", + "app_description": "", + "build_system": build, + "app_version": "0.1.0", + "create_doc_template": False, + "license": "", + } + # poetry we need an author + if build == "poetry": + data["full_name"] = "John Doe" + data["email"] = "example@email.com" + + copier.run_copy( # type: ignore + str(Path(itx.first(snakebids.__path__), "project_template")), + tmp_path / app_name, + data=data, + unsafe=True, ) - app = SnakeBidsApp( - tmp_path / app_name / app_name, - args=SnakebidsArgs( - force=False, - outputdir=tmp_path / "out", - snakemake_args=["-n"], - args_dict={ - "bids_dir": Path("snakebids") / "tests" / "data" / "bids_bold", - "output_dir": tmp_path / "out", - "analysis_level": "participant", - "smoothing_fwhm": "1", - "filter_bold": None, - "wildcards_bold": None, - "path_bold": None, - "participant_label": None, - "exclude_participant_label": None, - "plugins.validator.skip": True, - }, - ), + cmd = sp.run( + [ + "docker", + "run", + "-v", + f"{tmp_path / app_name}:/app", + "--rm", + "snakebids/test-template:dev", + venv, + app_name, + ], + capture_output=True, + check=True, ) - with pytest.raises(SystemExit) as err: - app.run_snakemake() - assert err.value.code == 0 + # stdout: str = client.containers.run( + # "snakebids-test-template:dev", + # ["run/test-template.sh", "setuptools", app_name], + # volumes={str(tmp_path / app_name): { "bind": "/app", "mode": "rw"}}, + # auto_remove=True, + # ).decode() + assert "All set" in cmd.stdout.decode()