diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..1a77661 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,41 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: prefix-dev/setup-pixi@v0.8.0 + with: + pixi-version: latest + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.ref_name }} + - name: Build package + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.ref_name }} + run: pixi run build-package + - name: Generate JSON schema + run: pixi run json-schema > biomero-schema.json + - name: Upload schema to release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload ${{ github.ref_name }} biomero-schema.json --clobber + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..edab21d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: windows-latest + env: + PYTHONUTF8: "1" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: prefix-dev/setup-pixi@v0.8.0 + with: + pixi-version: latest + - run: pixi run test + - run: pixi run test-validate + - run: pixi run test-parse + - run: pixi run test-pparse + - run: pixi run test-jparse diff --git a/.gitignore b/.gitignore index abf3f24..afcaad7 100644 --- a/.gitignore +++ b/.gitignore @@ -177,7 +177,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # Ruff stuff: .ruff_cache/ diff --git a/README.md b/README.md index 3d4751b..1552636 100644 --- a/README.md +++ b/README.md @@ -100,23 +100,23 @@ schema = { "name": "string", // Required. GitHub workflow repository name (without prefix). E.g. NucleiTracking-ImageJ "description": "string" // Required. Description of workflow. - "schema-version": "string" // Required. Semver of schema version. + "schema-version": "string" // Required. Schema format identifier. Use the current schema version constant, e.g. "biomero-0.1". "authors": // Optional. Authors list. [ { "name": "string", // Required. Full name of author. - "email": "optional", // Optional. Email address of author. - "affiliations": "string[]" // Optional. List of affiliations matching "id" of an instituttion in instititions list. + "email": "string", // Optional. Email address of author. + "affiliations": "string[]" // Optional. List of affiliations matching "id" of an institution in institutions list. } ], "institutions": // Optional. Institutions list. [ { "id": "string", // Required. Unique institute identifier. - "name": "string" // Optional. Name of the institions. Defaults to id. + "name": "string" // Optional. Name of the institution. Defaults to id. } ], - "citations": // Optional. List of citations for the tool. Defaults to empty list. + "citations": // Required. List of citations for the tool. At least one citation required. [ { "name": "string", // Required. Name of the tool being cited. @@ -127,16 +127,16 @@ schema = ], // corresponding to: https://neubias-wg5.github.io/problem_class_ground_truth.html#steps-section "problem-class": // Optional. Biaflows problem class ("object-segmentation" | "pixel-classification" | "object-counting" | "object-detection" | "filament-tree-tracing" | "filament-networks-tracing" | "landmark-detection" | "particle-tracking" | "object-tracking"). - "container-image": // Required. Base cotnainer description. + "container-image": // Required. Base container description. { "image": "string", // Required. Image to match the name of your workflow GitHub repository (lower case only). E.g. neubiaswg5/w_nucleitracking-imagej:1.0.0 - "type": "string", // Required. "oci" | "singularity" (lower case only). + "type": "string", // Required. "oci" | "singularity" | "docker" (lower case only). "platforms": "string"[] // Optional. Build-time multi-platform targets. }, "configuration": // Optional. Technical configuration. { "input_folder": "string", // Optional. Full path where the input folder must be mounted in the container. Defaults to "/inputs". - "output_folder": "string", // Optional. Full path where teh output folder must be mounted in the container. Defaults to "/outputs". + "output_folder": "string", // Optional. Full path where the output folder must be mounted in the container. Defaults to "/outputs". "resources": // Optional. { "networking": "boolean", // Optional. Whether internet connection is needed. Defaults to False. @@ -158,14 +158,22 @@ schema = // references to "@id" get the value of "id" in lowercase // references to "@ID" get the value of "id" in uppercase "id": "string", // Required. Unique parameter identifier. - "type": "string", // Required. Data type of the parameter (Number|String|ineger|flaot|boolean|string|file|image|array). - "name": "string", // Optional. Human-readable display name appearing in BIAFLOWS UI (paramater dialog box). Defaults to "@id". - "description": "string", // Optional. Description of paramater. Context help in BIAFLOWS UI (paramater dialog box). Soft Defaults to "". + "type": "string", // Required. Data type of the parameter (Number|String|integer|float|boolean|string|file|image|array|measurement|executable). + "name": "string", // Optional. Human-readable display name appearing in BIAFLOWS UI (parameter dialog box). Defaults to "@id". + "description": "string", // Optional. Description of parameter. Context help in BIAFLOWS UI (parameter dialog box). Soft Defaults to "". "value-key": "string", // Optional. Substitution key in CLI. Defaults to "[@ID]". "command-line-flag": "string", // Optional. CLI flag. Defaults to "--@id". - "default-value": "string|number|boolean", // Optional. Default value in BIAFLOWS UI (paramater dialog box). Soft Defaults to empty string. + "default-value": "string|number|boolean", // Optional. Default value in BIAFLOWS UI (parameter dialog box). Soft Defaults to empty string. "optional": "boolean", // Optional. If true, parameter not required. Soft Defaults to False. "set-by-server": "boolean", // Optional. If true, parameter is server-assigned. Soft Defaults to False. + "value-choices": "array", // Optional. List of allowed values for this parameter. + "value-choices-labels": "string[]", // Optional. Display labels for value-choices, index-aligned. When null, value is used as label. + "mode": "string", // Optional. UI display mode — "beginner" | "advanced". Advanced params are collapsed by default in the UI. + "file-count": "string", // Optional. For file-type inputs: "single" | "multiple". + "format": "string|string[]", // Optional. Type-specific. File extension(s) — see file/image/array sections below. + "sub-type": "string|string[]", // Optional. Type-specific. Image sub-type(s) — see image section below. + "output-dir-set": "boolean", // Optional. If true, this parameter specifies the output directory. Biomero will supply the data/out path. + "file-attachment": "boolean", // Optional. If true, this is a user-supplied OMERO file-attachment input (annotation ID). Biomero will download the file from OMERO and transfer it to the HPC at runtime, then inject the resolved path as the CLI argument. } ] "outputs": // Optional. List of output parameter descriptors. @@ -174,14 +182,20 @@ schema = // references to "@id" get the value of "id" in lowercase // references to "@ID" get the value of "id" in uppercase "id": "string", // Required. Unique parameter identifier. - "type": "string", // Required. Data type of the parameter (Number|String). - "name": "string", // Optional. Human-readable display name appearing in BIAFLOWS UI (paramater dialog box). Defaults to "@id". - "description": "string", // Optional. Description of paramater. Context help in BIAFLOWS UI (paramater dialog box). Soft Defaults to "". + "type": "string", // Required. Data type of the parameter (Number|String|integer|float|boolean|string|file|image|array|measurement|executable). + "name": "string", // Optional. Human-readable display name appearing in BIAFLOWS UI (parameter dialog box). Defaults to "@id". + "description": "string", // Optional. Description of parameter. Context help in BIAFLOWS UI (parameter dialog box). Soft Defaults to "". "value-key": "string", // Optional. Substitution key in CLI. Defaults to "[@ID]". "command-line-flag": "string", // Optional. CLI flag. Defaults to "--@id". - "default-value": "string|number|boolean", // Optional. Default value in BIAFLOWS UI (paramater dialog box). Soft Defaults to empty string. + "default-value": "string|number|boolean", // Optional. Default value in BIAFLOWS UI (parameter dialog box). Soft Defaults to empty string. "optional": "boolean", // Optional. If true, parameter not required. Soft Defaults to False. "set-by-server": "boolean", // Optional. If true, parameter is server-assigned. Soft Defaults to False. + "value-choices": "array", // Optional. List of allowed values for this parameter. + "value-choices-labels": "string[]", // Optional. Display labels for value-choices, index-aligned. When null, value is used as label. + "mode": "string", // Optional. UI display mode — "beginner" | "advanced". Advanced params are collapsed by default in the UI. + "file-count": "string", // Optional. For file-type outputs: "single" | "multiple". + "format": "string|string[]", // Optional. Type-specific. File extension(s) — see file/image/array sections below. + "sub-type": "string|string[]", // Optional. Type-specific. Image sub-type(s) — see image section below. } ] "command-line": "string" // Required. e.g. "python wrapper.py CYTOMINE_HOST CYTOMINE_PUBLIC_KEY CYTOMINE_PRIVATE_KEY CYTOMINE_ID_PROJECT CYTOMINE_ID_SOFTWARE IJ_RADIUS IJ_THRESHOLD". @@ -189,17 +203,24 @@ schema = file = { - "format": "string" // Required. Extension of the file type (.csv). + "format": "string" // Optional. Extension of the file type (e.g. .csv). } image = { - "sub-type": "string", // Required. Image type (grayscale|color|binary|labeled|class). - "format": "string" // Required. Extension of the image type (tif, png, jpg, jpeg, tiff, ometiff). + "sub-type": "string|string[]", // Optional. Image type (grayscale|color|binary|labeled|class|plate). Can be a single value or list. + "format": "string|string[]" // Optional. Extension of the image type (tif|png|jpg|jpeg|tiff|ometiff|zarr|omezarr|ome.zarr|ome-zarr). Can be a single value or list. } array = { - "format": "string" // Required. Extension of the file type (npy, npz) + "format": "string" // Optional. Extension of the file type (npy, npz) } ``` + +## Computed Fields + +The following fields are automatically computed from the schema and included in the JSON output: + +- **`requires-zarr`** (`boolean`): `true` when any image input uses a ZARR format (`zarr`, `omezarr`, `ome.zarr`, `ome-zarr`) or has `plate` sub-type. +- **`requires-plate`** (`boolean`): `true` when any image input has `plate` sub-type. diff --git a/pixi.lock b/pixi.lock index 47721b3..aa8ee3d 100644 --- a/pixi.lock +++ b/pixi.lock @@ -5,6 +5,8 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: osx-64: - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda @@ -25,19 +27,75 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/47/ffe8cd7a6a02833b10623bf765fbb57ce977e9a4318ca0e8cf97e9c3d2b3/rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/e1/342c4434df56aa537f6ce7647eefee521d96fbb828b08acd709865767652/setuptools_scm-10.0.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/60/73603fbcdbe5e803855bcce4414f94eaeed449083bd8183e67161af78188/vcs_versioning-1.1.1-py3-none-any.whl + - pypi: ./ + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.4-h4b44e0e_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/e1/342c4434df56aa537f6ce7647eefee521d96fbb828b08acd709865767652/setuptools_scm-10.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/60/73603fbcdbe5e803855bcce4414f94eaeed449083bd8183e67161af78188/vcs_versioning-1.1.1-py3-none-any.whl - pypi: ./ packages: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl @@ -52,17 +110,38 @@ packages: version: 25.4.0 sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl + name: attrs + version: 26.1.0 + sha256: c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 + requires_python: '>=3.9' - pypi: ./ name: biomero-schema - version: 0.1.0 - sha256: d248370949a5bab66da767b52308edcc36a49c0b2244ecbb50e8230e4f361e15 + version: 0.1.dev49+g1d4a8f686.d20260602 + sha256: 86a6d44ceb3f8a707ef0222cf5594910cff85ad6a45527537b0e342de2edd648 requires_dist: - pydantic>=2.0.0 - click>=8.0.0 - jsonschema>=4.0.0 - rich>=13.0.0 - requires_python: '>=3.8' - editable: true + - pyyaml>=6.0.3 + - pytest>=8.0.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + name: build + version: 1.5.0 + sha256: 13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f + requires_dist: + - packaging>=24.0 + - pyproject-hooks + - colorama ; os_name == 'nt' + - importlib-metadata>=4.6 ; python_full_version < '3.10.2' + - tomli>=1.1.0 ; python_full_version < '3.11' + - keyring ; extra == 'keyring' + - uv>=0.1.18 ; extra == 'uv' + - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' + - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c md5: 97c4b3bd8a90722104798175a1bdddbf @@ -73,6 +152,18 @@ packages: purls: [] size: 132607 timestamp: 1757437730085 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 + md5: 4cb8e6b48f67de0b018719cdf1136306 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 56115 + timestamp: 1771350256444 - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 md5: f9e5fbc24009179e8b0409624691758a @@ -82,6 +173,15 @@ packages: purls: [] size: 155907 timestamp: 1759649036195 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda + sha256: 6f4ff81534c19e76acf52fcabf4a258088a932b8f1ac56e9a59e98f6051f8e46 + md5: 56fb2c6c73efc627b40c77d14caecfba + depends: + - __win + license: ISC + purls: [] + size: 131388 + timestamp: 1776865633471 - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl name: click version: 8.3.0 @@ -89,6 +189,23 @@ packages: requires_dist: - colorama ; sys_platform == 'win32' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + name: click + version: 8.3.3 + sha256: a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 + requires_dist: + - colorama ; sys_platform == 'win32' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + name: colorama + version: 0.4.6 + sha256: 4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + name: iniconfig + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl name: jsonschema version: 4.25.1 @@ -116,6 +233,33 @@ packages: - uri-template ; extra == 'format-nongpl' - webcolors>=24.6.0 ; extra == 'format-nongpl' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl + name: jsonschema + version: 4.26.0 + sha256: d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce + requires_dist: + - attrs>=22.2.0 + - jsonschema-specifications>=2023.3.6 + - referencing>=0.28.4 + - rpds-py>=0.25.0 + - fqdn ; extra == 'format' + - idna ; extra == 'format' + - isoduration ; extra == 'format' + - jsonpointer>1.13 ; extra == 'format' + - rfc3339-validator ; extra == 'format' + - rfc3987 ; extra == 'format' + - uri-template ; extra == 'format' + - webcolors>=1.11 ; extra == 'format' + - fqdn ; extra == 'format-nongpl' + - idna ; extra == 'format-nongpl' + - isoduration ; extra == 'format-nongpl' + - jsonpointer>1.13 ; extra == 'format-nongpl' + - rfc3339-validator ; extra == 'format-nongpl' + - rfc3986-validator>0.1.0 ; extra == 'format-nongpl' + - rfc3987-syntax>=1.1.0 ; extra == 'format-nongpl' + - uri-template ; extra == 'format-nongpl' + - webcolors>=24.6.0 ; extra == 'format-nongpl' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl name: jsonschema-specifications version: 2025.9.1 @@ -135,6 +279,20 @@ packages: purls: [] size: 72450 timestamp: 1752719744781 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda + sha256: 2d81d647c1f01108803457cac999b947456f44dd0a3c2325395677feacaeca67 + md5: 264e350e035092b5135a2147c238aec4 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.8.0.* + license: MIT + license_family: MIT + purls: [] + size: 71094 + timestamp: 1777846223617 - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda sha256: 277dc89950f5d97f1683f26e362d6dca3c2efa16cb2f6fdb73d109effa1cd3d0 md5: d214916b24c625bcc459b245d509f22e @@ -145,6 +303,18 @@ packages: purls: [] size: 52573 timestamp: 1760295626449 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 + md5: 720b39f5ec0610457b725eb3f396219a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 45831 + timestamp: 1769456418774 - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda sha256: 7e22fd1bdb8bf4c2be93de2d4e718db5c548aa082af47a7430eb23192de6bb36 md5: 8468beea04b9065b9807fc8b9cdc5894 @@ -156,6 +326,19 @@ packages: purls: [] size: 104826 timestamp: 1749230155443 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + sha256: d636d1a25234063642f9c531a7bb58d84c1c496411280a36ea000bd122f078f1 + md5: 8f83619ab1588b98dd99c90b0bfc5c6d + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 106486 + timestamp: 1775825663227 - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda sha256: 98299c73c7a93cd4f5ff8bb7f43cd80389f08b5a27a296d806bdef7841cc9b9e md5: 18b81186a6adb43f000ad19ed7b70381 @@ -166,6 +349,18 @@ packages: purls: [] size: 77667 timestamp: 1748393757154 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + sha256: 40dcd0b9522a6e0af72a9db0ced619176e7cfdb114855c7a64f278e73f8a7514 + md5: e4a9fc2bba3b022dad998c78856afe47 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 89411 + timestamp: 1769482314283 - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda sha256: 466366b094c3eb4b1d77320530cbf5400e7a10ab33e4824c200147488eebf7a6 md5: 156bfb239b6a67ab4a01110e6718cbc4 @@ -176,6 +371,17 @@ packages: purls: [] size: 980121 timestamp: 1753948554003 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda + sha256: 7a6256ea136936df4c4f3b227ba1e273b7d61152f9811b52157af497f07640b0 + md5: 4152b5a8d2513fd7ae9fb9f221a5595d + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1301855 + timestamp: 1775753831574 - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09 md5: 003a54a4e32b02f7355b50a837e699da @@ -188,6 +394,20 @@ packages: purls: [] size: 57133 timestamp: 1727963183990 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 58347 + timestamp: 1774072851498 - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl name: markdown-it-py version: 4.0.0 @@ -246,6 +466,35 @@ packages: purls: [] size: 2747108 timestamp: 1759326402264 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + sha256: feb5815125c60f2be4a411e532db1ed1cd2d7261a6a43c54cb6ae90724e2e154 + md5: 05c7d624cff49dbd8db1ad5ba537a8a3 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9410183 + timestamp: 1775589779763 +- pypi: https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl + name: packaging + version: '26.2' + sha256: 5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl name: pydantic version: 2.12.3 @@ -258,6 +507,18 @@ packages: - email-validator>=2.0.0 ; extra == 'email' - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + name: pydantic + version: 2.13.3 + sha256: 6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927 + requires_dist: + - annotated-types>=0.6.0 + - pydantic-core==2.46.3 + - typing-extensions>=4.14.1 + - typing-inspection>=0.4.2 + - email-validator>=2.0.0 ; extra == 'email' + - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl name: pydantic-core version: 2.41.4 @@ -265,6 +526,13 @@ packages: requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl + name: pydantic-core + version: 2.46.3 + sha256: 8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl name: pygments version: 2.19.2 @@ -272,6 +540,38 @@ packages: requires_dist: - colorama>=0.4.6 ; extra == 'windows-terminal' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + name: pygments + version: 2.20.0 + sha256: 81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + name: pytest + version: 9.0.3 + sha256: 2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1.0.1 + - packaging>=22 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda build_number: 102 sha256: 2470866eee70e75d6be667aa537424b63f97c397a0a90f05f2bab347b9ed5a51 @@ -297,6 +597,31 @@ packages: size: 14427639 timestamp: 1761177864469 python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.4-h4b44e0e_100_cp314.conda + build_number: 100 + sha256: e258d626b0ba778abb319f128de4c1211306fe86fe0803166817b1ce2514c920 + md5: 40b6a8f438afb5e7b314cc5c4a43cd84 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.14.* *_cp314 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + size: 18055445 + timestamp: 1775615317758 + python_site_packages_path: Lib/site-packages - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda build_number: 8 sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 @@ -308,6 +633,16 @@ packages: purls: [] size: 6989 timestamp: 1752805904792 +- pypi: https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl + name: pyyaml + version: 6.0.3 + sha256: 4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda sha256: 53017e80453c4c1d97aaf78369040418dea14cf8f46a2fa999f31bd70b36c877 md5: 342570f8e02f2f022147a7f841475784 @@ -336,11 +671,93 @@ packages: - markdown-it-py>=2.2.0 - pygments>=2.13.0,<3.0.0 requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + name: rich + version: 15.0.0 + sha256: 33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + requires_python: '>=3.9.0' - pypi: https://files.pythonhosted.org/packages/08/47/ffe8cd7a6a02833b10623bf765fbb57ce977e9a4318ca0e8cf97e9c3d2b3/rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl name: rpds-py version: 0.28.0 sha256: dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472 requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl + name: rpds-py + version: 0.30.0 + sha256: 95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl + name: setuptools + version: 82.0.1 + sha256: a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb + requires_dist: + - pytest>=6,!=8.1.* ; extra == 'test' + - virtualenv>=13.0.0 ; extra == 'test' + - wheel>=0.44.0 ; extra == 'test' + - pip>=19.1 ; extra == 'test' + - packaging>=24.2 ; extra == 'test' + - jaraco-envs>=2.2 ; extra == 'test' + - pytest-xdist>=3 ; extra == 'test' + - jaraco-path>=3.7.2 ; extra == 'test' + - build[virtualenv]>=1.0.3 ; extra == 'test' + - filelock>=3.4.0 ; extra == 'test' + - ini2toml[lite]>=0.14 ; extra == 'test' + - tomli-w>=1.0.0 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-perf ; sys_platform != 'cygwin' and extra == 'test' + - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test' + - pytest-home>=0.5 ; extra == 'test' + - pytest-subprocess ; extra == 'test' + - pyproject-hooks!=1.1 ; extra == 'test' + - jaraco-test>=5.5 ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pygments-github-lexers==0.0.5 ; extra == 'doc' + - sphinx-favicon ; extra == 'doc' + - sphinx-inline-tabs ; extra == 'doc' + - sphinx-reredirects ; extra == 'doc' + - sphinxcontrib-towncrier ; extra == 'doc' + - sphinx-notfound-page>=1,<2 ; extra == 'doc' + - pyproject-hooks!=1.1 ; extra == 'doc' + - towncrier<24.7 ; extra == 'doc' + - packaging>=24.2 ; extra == 'core' + - more-itertools>=8.8 ; extra == 'core' + - jaraco-text>=3.7 ; extra == 'core' + - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core' + - wheel>=0.43.0 ; extra == 'core' + - jaraco-functools>=4 ; extra == 'core' + - more-itertools ; extra == 'core' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - ruff>=0.13.0 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=2.2 ; extra == 'enabler' + - pytest-mypy ; extra == 'type' + - mypy==1.18.* ; extra == 'type' + - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type' + - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5c/e1/342c4434df56aa537f6ce7647eefee521d96fbb828b08acd709865767652/setuptools_scm-10.0.5-py3-none-any.whl + name: setuptools-scm + version: 10.0.5 + sha256: f611037d8aae618221503b8fa89319f073438252ae3420e01c9ceec249131a0a + requires_dist: + - vcs-versioning>=1.0.0.dev0 + - packaging>=20 + - setuptools + - tomli>=1 ; python_full_version < '3.11' + - typing-extensions ; python_full_version < '3.11' + - rich ; extra == 'rich' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda sha256: b24468006a96b71a5f4372205ea7ec4b399b0f2a543541e86f883de54cd623fc md5: 9864891a6946c2fe037c02fca7392ab4 @@ -352,6 +769,18 @@ packages: purls: [] size: 3259809 timestamp: 1748387843735 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: TCL + license_family: BSD + purls: [] + size: 3526350 + timestamp: 1769460339384 - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl name: typing-extensions version: 4.15.0 @@ -371,6 +800,69 @@ packages: purls: [] size: 122968 timestamp: 1742727099393 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a + md5: 1e610f2416b6acdd231c5f573d754a0f + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19356 + timestamp: 1767320221521 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 + md5: 37eb311485d2d8b2c419449582046a42 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_34 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 683233 + timestamp: 1767320219644 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 + md5: 242d9f25d2ae60c76b38a5e42858e51d + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 115235 + timestamp: 1767320173250 +- pypi: https://files.pythonhosted.org/packages/e6/60/73603fbcdbe5e803855bcce4414f94eaeed449083bd8183e67161af78188/vcs_versioning-1.1.1-py3-none-any.whl + name: vcs-versioning + version: 1.1.1 + sha256: b541e2ba79fc6aaa3850f8a7f88af43d97c1c80649c01142ee4146eddbc599e4 + requires_dist: + - packaging>=20 + - tomli>=1 ; python_full_version < '3.11' + - typing-extensions ; python_full_version < '3.11' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda sha256: c171c43d0c47eed45085112cb00c8c7d4f0caa5a32d47f2daca727e45fb98dca md5: cd60a4a5a8d6a476b30d8aa4bb49251a @@ -382,3 +874,16 @@ packages: purls: [] size: 485754 timestamp: 1742433356230 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 + md5: 053b84beec00b71ea8ff7a4f84b55207 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 388453 + timestamp: 1764777142545 diff --git a/pyproject.toml b/pyproject.toml index b28a857..8195bef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,28 @@ [build-system] -requires = ["setuptools>=45", "wheel"] +requires = ["setuptools>=45", "wheel", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" [project] name = "biomero-schema" -version = "0.1.0" +dynamic = ["version"] description = "CLI tool to validate and parse JSON files against a schema" authors = [{name = "Schema Validator", email = "validator@example.com"}] readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.11" dependencies = [ "pydantic>=2.0.0", "click>=8.0.0", "jsonschema>=4.0.0", - "rich>=13.0.0" + "rich>=13.0.0", + "pyyaml>=6.0.3" ] +[project.optional-dependencies] +test = ["pytest>=8.0.0"] + +[tool.pytest.ini_options] +testpaths = ["tests"] + [project.scripts] biomero-schema = "biomero_schema.cli:cli" @@ -27,14 +34,21 @@ where = ["src"] [tool.pixi.workspace] channels = ["conda-forge"] -platforms = ["osx-64"] +platforms = ["osx-64", "win-64"] [tool.pixi.pypi-dependencies] biomero-schema = { path = ".", editable = true } +pytest = ">=8.0.0" +setuptools-scm = ">=8" +build = ">=1.0.0" [tool.pixi.tasks] json-schema = { cmd = ["biomero-schema", "schema"], description = "Print the json schema generated from pydantic model" } +build-package = { cmd = ["python", "-m", "build"], description = "Build the package wheel and sdist" } test-parse = { cmd = ["biomero-schema", "parse", "tests/example_workflow.json"], description = "Parse and run pydantic validation on test file" } test-pparse = { cmd = ["biomero-schema", "parse", "tests/example_workflow.json", "--pretty"], description = "Parse, validate, and show detailed info on test file" } test-jparse = { cmd = ["biomero-schema", "parse", "tests/example_workflow.json", "--json"], description = "Parse, validate, and show test file json contents" } test-validate = { cmd = ["biomero-schema", "validate", "tests/example_workflow.json"], description = "Run json schema validation on test file" } +test = { cmd = ["pytest", "-v"], description = "Run all tests" } + +[tool.setuptools_scm] diff --git a/src/biomero_schema/__init__.py b/src/biomero_schema/__init__.py index 6e0e97e..ff4c6d7 100644 --- a/src/biomero_schema/__init__.py +++ b/src/biomero_schema/__init__.py @@ -1,2 +1,11 @@ """Biomero schema package.""" -__version__ = "0.1.0" +from importlib.metadata import version, PackageNotFoundError + +try: + __version__ = version("biomero-schema") +except PackageNotFoundError: + __version__ = "unknown" + +from biomero_schema.models import BIOMERO_SCHEMA_VERSION + +__all__ = ["__version__", "BIOMERO_SCHEMA_VERSION"] diff --git a/src/biomero_schema/cli.py b/src/biomero_schema/cli.py index 2056bed..c509803 100644 --- a/src/biomero_schema/cli.py +++ b/src/biomero_schema/cli.py @@ -1,23 +1,29 @@ """CLI for biomero schema validation and parsing.""" import json +import os.path import sys from typing import Any, Dict +import yaml import click import jsonschema from rich.console import Console from rich.pretty import pprint -from .models import WorkflowSchema +from biomero_schema.models import WorkflowSchema console = Console() -def load_json_file(file_path: str) -> Dict[str, Any]: - """Load JSON file and return parsed content.""" +def load_descriptor_file(file_path: str) -> Dict[str, Any]: + """Load descriptor file and return parsed content.""" try: + ext = os.path.splitext(file_path)[1] with open(file_path) as f: - return json.load(f) + if ext in (".yaml", ".yml"): + return yaml.safe_load(f) + else: + return json.load(f) except FileNotFoundError: console.print(f"[red]Error: File '{file_path}' not found[/red]") sys.exit(1) @@ -40,14 +46,14 @@ def schema(): @cli.command() -@click.argument("json_file", type=click.Path(exists=True)) -def validate(json_file: str): +@click.argument("descriptor_file", type=click.Path(exists=True)) +def validate(descriptor_file: str): """Validate a JSON file against the JSON schema.""" - console.print(f"[blue]Validating {json_file}...[/blue]") + console.print(f"[blue]Validating {descriptor_file}...[/blue]") # Load schema and JSON file schema = WorkflowSchema.model_json_schema() - data = load_json_file(json_file) + data = load_descriptor_file(descriptor_file) try: jsonschema.validate(data, schema) @@ -64,15 +70,15 @@ def validate(json_file: str): @cli.command() -@click.argument("json_file", type=click.Path(exists=True)) +@click.argument("descriptor_file", type=click.Path(exists=True)) @click.option("--pretty", is_flag=True, help="Pretty print the parsed object") @click.option("--json", "output_json", is_flag=True, help="Output as JSON") -def parse(json_file: str, pretty: bool, output_json: bool): - """Parse a JSON file into a Pydantic representation.""" - console.print(f"[blue]Parsing {json_file}...[/blue]") +def parse(descriptor_file: str, pretty: bool, output_json: bool): + """Parse a descriptor file into a Pydantic representation.""" + console.print(f"[blue]Parsing {descriptor_file}...[/blue]") - # Load and validate JSON file - data = load_json_file(json_file) + # Load and validate descriptor file + data = load_descriptor_file(descriptor_file) try: # Parse into Pydantic model diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index d3890ab..afbe8aa 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -1,6 +1,12 @@ """Pydantic models for the workflow schema.""" from typing import List, Optional, Union, Literal -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, computed_field, ConfigDict + +_ZARR_FORMATS = frozenset({'zarr', 'omezarr', 'ome.zarr', 'ome-zarr'}) + +# The schema format identifier embedded in every biomero-schema descriptor. +# Increment the minor version on breaking schema changes. +BIOMERO_SCHEMA_VERSION = "biomero-0.1" class Author(BaseModel): @@ -27,7 +33,7 @@ class Citation(BaseModel): class ContainerImage(BaseModel): """Container image model.""" image: str = Field(..., description="Image to match the name of your workflow GitHub repository (lower case only)") - type: Literal["oci", "singularity"] = Field(..., description="Container type") + type: Literal["oci", "singularity", "docker"] = Field(..., description="Container type") platforms: Optional[List[str]] = Field(None, description="Build-time multi-platform targets") @@ -63,7 +69,7 @@ class FileParameter(BaseModel): class ImageParameter(BaseModel): """Image parameter specific fields.""" sub_type: Literal["grayscale", "color", "binary", "labeled", "class"] = Field(..., alias="sub-type", description="Image type") - format: Literal["tif", "png", "jpg", "jpeg", "tiff", "ometiff"] = Field(..., description="Extension of the image type") + format: Literal["tif", "png", "jpg", "jpeg", "tiff", "ometiff", "zarr", "omezarr"] = Field(..., description="Extension of the image type") class ArrayParameter(BaseModel): @@ -74,7 +80,7 @@ class ArrayParameter(BaseModel): class Parameter(BaseModel): """Parameter model.""" id: str = Field(..., description="Unique parameter identifier") - type: Literal["Number", "String", "integer", "float", "boolean", "string", "file", "image", "array"] = Field(..., description="Data type of the parameter") + type: Literal["Number", "String", "integer", "float", "boolean", "string", "file", "image", "array", "measurement", "executable"] = Field(..., description="Data type of the parameter") name: Optional[str] = Field(None, description="Human-readable display name appearing in BIAFLOWS UI (parameter dialog box). Defaults to '@id'") description: Optional[str] = Field(None, description="Description of parameter. Context help in BIAFLOWS UI (parameter dialog box). Soft Defaults to ''") value_key: Optional[str] = Field(None, alias="value-key", description="Substitution key in CLI. Defaults to '[@ID]'") @@ -87,16 +93,23 @@ class Parameter(BaseModel): ) optional: Optional[bool] = Field(None, description="If true, parameter not required. Soft Defaults to False") set_by_server: Optional[bool] = Field(None, alias="set-by-server", description="If true, parameter is server-assigned. Soft Defaults to False") - + value_choices: Optional[List[Union[str, int, float, bool]]] = Field(None, alias="value-choices", description="List of allowed values for this parameter") + output_dir_set: Optional[bool] = Field(None, alias="output-dir-set", description="If true, this parameter specifies the output directory (bilayers output_dir_set flag). Biomero will supply the data/out path.") + file_attachment: Optional[bool] = Field(None, alias="file-attachment", description="If true, this is a user-supplied OMERO file-attachment input (annotation ID). Biomero will download the file from OMERO and transfer it to the HPC at runtime, then inject the resolved path as the CLI argument.") + + mode: Optional[Literal["beginner", "advanced"]] = Field(None, description="UI display mode — 'advanced' params are collapsed by default in the UI") + # Type-specific fields - format: Optional[str] = Field(None, description="Format for file/image/array types") - sub_type: Optional[str] = Field(None, alias="sub-type", description="Sub-type for image parameters") + format: Optional[Union[str, List[str]]] = Field(None, description="Format for file/image/array types") + file_count: Optional[Literal["single", "multiple"]] = Field(None, alias="file-count", description="For file-type inputs: whether user selects a single file or multiple files") + sub_type: Optional[Union[str, List[str]]] = Field(None, alias="sub-type", description="Sub-type for image parameters") + value_choices_labels: Optional[List[Optional[str]]] = Field(None, alias="value-choices-labels", description="Display labels for value_choices, index-aligned. When None, value is used as label.") class OutputParameter(BaseModel): """Output parameter model.""" id: str = Field(..., description="Unique parameter identifier") - type: Literal["Number", "String"] = Field(..., description="Data type of the parameter") + type: Literal["Number", "String", "integer", "float", "boolean", "string", "file", "image", "array", "measurement", "executable"] = Field(..., description="Data type of the parameter") name: Optional[str] = Field(None, description="Human-readable display name appearing in BIAFLOWS UI (parameter dialog box). Defaults to '@id'") description: Optional[str] = Field(None, description="Description of parameter. Context help in BIAFLOWS UI (parameter dialog box). Soft Defaults to ''") value_key: Optional[str] = Field(None, alias="value-key", description="Substitution key in CLI. Defaults to '[@ID]'") @@ -109,13 +122,26 @@ class OutputParameter(BaseModel): ) optional: Optional[bool] = Field(None, description="If true, parameter not required. Soft Defaults to False") set_by_server: Optional[bool] = Field(None, alias="set-by-server", description="If true, parameter is server-assigned. Soft Defaults to False") + value_choices: Optional[List[Union[str, int, float, bool]]] = Field(None, alias="value-choices", description="List of allowed values for this parameter") + + mode: Optional[Literal["beginner", "advanced"]] = Field(None, description="UI display mode — 'advanced' params are collapsed by default in the UI") + + # Type-specific fields + format: Optional[Union[str, List[str]]] = Field(None, description="Format for file/image/array types") + file_count: Optional[Literal["single", "multiple"]] = Field( + None, + alias="file-count", + description="Emits a single file or multiple files", + ) + sub_type: Optional[Union[str, List[str]]] = Field(None, alias="sub-type", description="Sub-type for image parameters") + value_choices_labels: Optional[List[Optional[str]]] = Field(None, alias="value-choices-labels", description="Display labels for value_choices, index-aligned. When None, value is used as label.") class WorkflowSchema(BaseModel): """Main workflow schema model.""" name: str = Field(..., description="GitHub workflow repository name (without prefix). E.g. NucleiTracking-ImageJ") description: str = Field(..., description="Description of workflow") - schema_version: str = Field(..., alias="schema-version", description="Semver of schema version") + schema_version: str = Field(..., alias="schema-version", description="Schema format identifier, e.g. 'biomero-0.1'") authors: List[Author] = Field([], description="Authors list") institutions: List[Institution] = Field([], description="Institutions list") citations: List[Citation] = Field([], min_length=1, description="List of citations for the tool. At least one required") @@ -136,6 +162,37 @@ class WorkflowSchema(BaseModel): outputs: List[OutputParameter] = Field([], description="List of output parameter descriptors") command_line: str = Field(..., alias="command-line", description="Command line template") - class Config: - populate_by_name = True - validate_by_name = True + @computed_field(alias="requires-zarr") + @property + def requires_zarr(self) -> bool: + """True when any image input uses a ZARR format or has plate subtype.""" + for inp in self.inputs: + if inp.type != 'image': + continue + fmt = inp.format or [] + if isinstance(fmt, str): + fmt = [fmt] + if any(f in _ZARR_FORMATS for f in fmt): + return True + sub = inp.sub_type or [] + if isinstance(sub, str): + sub = [sub] + if 'plate' in sub: + return True + return False + + @computed_field(alias="requires-plate") + @property + def requires_plate(self) -> bool: + """True when any image input has plate subtype.""" + for inp in self.inputs: + if inp.type != 'image': + continue + sub = inp.sub_type or [] + if isinstance(sub, str): + sub = [sub] + if 'plate' in sub: + return True + return False + + model_config = ConfigDict(populate_by_name=True, validate_by_alias=True) diff --git a/tests/example_workflow.json b/tests/example_workflow.json index 3218453..7201064 100644 --- a/tests/example_workflow.json +++ b/tests/example_workflow.json @@ -1,7 +1,7 @@ { "name": "NucleiTracking-ImageJ", "description": "ImageJ workflow for nuclei tracking in time-lapse microscopy images", - "schema-version": "1.0.0", + "schema-version": "biomero-0.1", "authors": [ { "name": "John Doe", diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py new file mode 100644 index 0000000..02c96a5 --- /dev/null +++ b/tests/test_computed_fields.py @@ -0,0 +1,242 @@ +"""Tests for WorkflowSchema.requires_zarr and requires_plate computed fields.""" +import json +import pytest +from pathlib import Path +from biomero_schema.models import WorkflowSchema + + +TESTS_DIR = Path(__file__).parent + + +def load_example() -> dict: + return json.loads((TESTS_DIR / "example_workflow.json").read_text()) + + +def make_schema(image_inputs: list) -> WorkflowSchema: + """Build a minimal WorkflowSchema with the given image input list.""" + base = load_example() + base["inputs"] = image_inputs + return WorkflowSchema.model_validate(base) + + +def image_input(fmt=None, subtype=None) -> dict: + inp = { + "id": "input_image", + "type": "image", + "name": "Input", + "value-key": "[INPUT_IMAGE]", + "command-line-flag": "--input", + } + if fmt is not None: + inp["format"] = fmt + if subtype is not None: + inp["sub-type"] = subtype + return inp + + +# --------------------------------------------------------------------------- +# requires_zarr +# --------------------------------------------------------------------------- + +class TestRequiresZarr: + def test_false_for_tiff(self): + schema = make_schema([image_input(fmt="tif")]) + assert schema.requires_zarr is False + + def test_false_for_ometiff(self): + schema = make_schema([image_input(fmt="ometiff")]) + assert schema.requires_zarr is False + + def test_false_for_png(self): + schema = make_schema([image_input(fmt="png")]) + assert schema.requires_zarr is False + + def test_false_for_no_image_inputs(self): + base = load_example() + base["inputs"] = [ + {"id": "x", "type": "float", "value-key": "[X]", "command-line-flag": "--x"} + ] + schema = WorkflowSchema.model_validate(base) + assert schema.requires_zarr is False + + def test_true_for_zarr(self): + schema = make_schema([image_input(fmt="zarr")]) + assert schema.requires_zarr is True + + def test_true_for_omezarr(self): + schema = make_schema([image_input(fmt="omezarr")]) + assert schema.requires_zarr is True + + def test_true_for_ome_zarr_dot(self): + schema = make_schema([image_input(fmt="ome.zarr")]) + assert schema.requires_zarr is True + + def test_true_for_ome_zarr_dash(self): + schema = make_schema([image_input(fmt="ome-zarr")]) + assert schema.requires_zarr is True + + def test_true_for_format_list_containing_zarr(self): + schema = make_schema([image_input(fmt=["tif", "zarr"])]) + assert schema.requires_zarr is True + + def test_true_when_plate_subtype(self): + """Plate implies ZARR.""" + schema = make_schema([image_input(fmt="omezarr", subtype="plate")]) + assert schema.requires_zarr is True + + def test_false_for_zarr_format_on_non_image_type(self): + """format field on a non-image input should not trigger zarr.""" + base = load_example() + base["inputs"] = [ + {"id": "x", "type": "file", "format": "zarr", "value-key": "[X]", "command-line-flag": "--x"} + ] + schema = WorkflowSchema.model_validate(base) + assert schema.requires_zarr is False + + +# --------------------------------------------------------------------------- +# requires_plate +# --------------------------------------------------------------------------- + +class TestRequiresPlate: + def test_false_by_default(self): + schema = make_schema([image_input(fmt="tif", subtype="grayscale")]) + assert schema.requires_plate is False + + def test_false_for_no_subtype(self): + schema = make_schema([image_input(fmt="zarr")]) + assert schema.requires_plate is False + + def test_true_for_plate_subtype(self): + schema = make_schema([image_input(fmt="omezarr", subtype="plate")]) + assert schema.requires_plate is True + + def test_true_for_plate_in_subtype_list(self): + schema = make_schema([image_input(fmt="omezarr", subtype=["plate"])]) + assert schema.requires_plate is True + + def test_false_for_other_subtype(self): + schema = make_schema([image_input(fmt="tif", subtype="grayscale")]) + assert schema.requires_plate is False + + +# --------------------------------------------------------------------------- +# Parameter.mode and Parameter.value_choices_labels +# --------------------------------------------------------------------------- + +def param_input(mode=None, choices=None, choice_labels=None) -> dict: + """Build a minimal string parameter for injection into a schema.""" + inp = { + "id": "my_param", + "type": "string", + "name": "My Param", + "value-key": "[MY_PARAM]", + "command-line-flag": "--my_param", + } + if mode is not None: + inp["mode"] = mode + if choices is not None: + inp["value-choices"] = choices + if choice_labels is not None: + inp["value-choices-labels"] = choice_labels + return inp + + +class TestParameterMode: + def _get_param(self, mode=None): + base = load_example() + base["inputs"] = [image_input(fmt="tif"), param_input(mode=mode)] + schema = WorkflowSchema.model_validate(base) + return next(p for p in schema.inputs if p.id == "my_param") + + def test_mode_defaults_to_none(self): + assert self._get_param().mode is None + + def test_mode_beginner(self): + assert self._get_param(mode="beginner").mode == "beginner" + + def test_mode_advanced(self): + assert self._get_param(mode="advanced").mode == "advanced" + + def test_mode_serializes_via_model_dump(self): + base = load_example() + base["inputs"] = [image_input(fmt="tif"), param_input(mode="advanced")] + schema = WorkflowSchema.model_validate(base) + dumped = schema.model_dump(by_alias=True) + param = next(p for p in dumped["inputs"] if p["id"] == "my_param") + assert param["mode"] == "advanced" + + +class TestParameterValueChoicesLabels: + def _make(self, choices=None, labels=None): + base = load_example() + base["inputs"] = [image_input(fmt="tif"), + param_input(choices=choices, choice_labels=labels)] + schema = WorkflowSchema.model_validate(base) + return next(p for p in schema.inputs if p.id == "my_param") + + def test_value_choices_labels_defaults_to_none(self): + param = self._make(choices=["a", "b"]) + assert param.value_choices_labels is None + + def test_value_choices_labels_set(self): + param = self._make(choices=["a", "b"], labels=["Label A", "Label B"]) + assert param.value_choices_labels == ["Label A", "Label B"] + + def test_value_choices_labels_serializes_via_model_dump(self): + base = load_example() + base["inputs"] = [image_input(fmt="tif"), + param_input(choices=["a", "b"], + choice_labels=["Label A", "Label B"])] + schema = WorkflowSchema.model_validate(base) + dumped = schema.model_dump(by_alias=True) + param = next(p for p in dumped["inputs"] if p["id"] == "my_param") + assert param["value-choices-labels"] == ["Label A", "Label B"] + + def test_value_choices_labels_none_omitted_in_model_dump(self): + base = load_example() + base["inputs"] = [image_input(fmt="tif"), param_input(choices=["a"])] + schema = WorkflowSchema.model_validate(base) + dumped = schema.model_dump(by_alias=True) + param = next(p for p in dumped["inputs"] if p["id"] == "my_param") + assert param.get("value-choices-labels") is None + + def test_plate_also_sets_zarr(self): + """Plate always implies requires_zarr too.""" + schema = make_schema([image_input(fmt="omezarr", subtype="plate")]) + assert schema.requires_zarr is True + assert schema.requires_plate is True + + +# --------------------------------------------------------------------------- +# Serialisation — flags appear in model_dump(by_alias=True) +# --------------------------------------------------------------------------- + +class TestSerialisedOutput: + def test_keys_present_in_dump(self): + schema = make_schema([image_input(fmt="zarr", subtype="plate")]) + data = schema.model_dump(by_alias=True) + assert "requires-zarr" in data + assert "requires-plate" in data + + def test_values_correct_in_dump(self): + schema = make_schema([image_input(fmt="zarr", subtype="plate")]) + data = schema.model_dump(by_alias=True) + assert data["requires-zarr"] is True + assert data["requires-plate"] is True + + def test_false_values_in_dump(self): + schema = make_schema([image_input(fmt="tif")]) + data = schema.model_dump(by_alias=True) + assert data["requires-zarr"] is False + assert data["requires-plate"] is False + + +# --------------------------------------------------------------------------- +# Round-trip through example_workflow.json (tiff input → both False) +# --------------------------------------------------------------------------- + +def test_example_workflow_no_zarr(): + schema = WorkflowSchema.model_validate(load_example()) + assert schema.requires_zarr is False + assert schema.requires_plate is False