From 03d849e23f19bed0682e4d44f91afe35e38341c5 Mon Sep 17 00:00:00 2001 From: Joost de Folter Date: Tue, 5 May 2026 14:31:49 +0200 Subject: [PATCH 01/18] Extend to read bilayers yaml --- .gitignore | 2 +- pixi.lock | 352 +++++++++++++++++++++++++++++++++++++- pyproject.toml | 5 +- src/biomero_schema/cli.py | 34 ++-- 4 files changed, 374 insertions(+), 19 deletions(-) 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/pixi.lock b/pixi.lock index 47721b3..8fcf135 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 @@ -33,12 +35,50 @@ environments: - 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/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/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: ./ + 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/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/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/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/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/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: ./ packages: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl name: annotated-types @@ -52,17 +92,22 @@ 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 + sha256: 3fe5cc254b0ed72562b85beb6a731154c3741455246530df3ff89119776c0c5b requires_dist: - pydantic>=2.0.0 - click>=8.0.0 - jsonschema>=4.0.0 - rich>=13.0.0 + - pyyaml>=6.0.3 requires_python: '>=3.8' - editable: true - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c md5: 97c4b3bd8a90722104798175a1bdddbf @@ -73,6 +118,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 +139,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 +155,18 @@ 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/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl name: jsonschema version: 4.25.1 @@ -116,6 +194,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 +240,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 +264,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 +287,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 +310,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 +332,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 +355,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 +427,19 @@ 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/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl name: pydantic version: 2.12.3 @@ -258,6 +452,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 +471,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 +485,13 @@ 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' - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.0-hf88997e_102_cp314.conda build_number: 102 sha256: 2470866eee70e75d6be667aa537424b63f97c397a0a90f05f2bab347b9ed5a51 @@ -297,6 +517,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 +553,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 +591,25 @@ 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' - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda sha256: b24468006a96b71a5f4372205ea7ec4b399b0f2a543541e86f883de54cd623fc md5: 9864891a6946c2fe037c02fca7392ab4 @@ -352,6 +621,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 +652,60 @@ 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 - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda sha256: c171c43d0c47eed45085112cb00c8c7d4f0caa5a32d47f2daca727e45fb98dca md5: cd60a4a5a8d6a476b30d8aa4bb49251a @@ -382,3 +717,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..ac9a5c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,8 @@ 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.scripts] @@ -27,7 +28,7 @@ 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 } diff --git a/src/biomero_schema/cli.py b/src/biomero_schema/cli.py index 2056bed..fe9f22d 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("input_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 From 2f43d8b388ff436f40bf646f0f06e7f707268ad4 Mon Sep 17 00:00:00 2001 From: Joost de Folter Date: Thu, 7 May 2026 09:56:25 +0200 Subject: [PATCH 02/18] Add output types --- src/biomero_schema/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index ef39409..caeb69b 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -91,7 +91,7 @@ class Parameter(BaseModel): 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"] = 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]'") From deded21577b78beb9a4c00664674f3b7c3253c99 Mon Sep 17 00:00:00 2001 From: Joost de Folter Date: Thu, 7 May 2026 11:06:19 +0200 Subject: [PATCH 03/18] Revert outputparam change, add zarr types to format --- src/biomero_schema/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index caeb69b..c58da5d 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -63,7 +63,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 +74,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"] = 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]'") From c95d9715da802c7d695b737738576b48c392d819 Mon Sep 17 00:00:00 2001 From: Joost de Folter Date: Thu, 7 May 2026 11:10:11 +0200 Subject: [PATCH 04/18] Extend outputs as outputs must contain these options --- src/biomero_schema/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index c58da5d..44fbebd 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -74,7 +74,7 @@ class ArrayParameter(BaseModel): class Parameter(BaseModel): """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"] = 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]'") @@ -100,6 +100,10 @@ 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") + # 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") + class WorkflowSchema(BaseModel): """Main workflow schema model.""" From 0ca1ecee3a24eedd5022958f27a401dfa7f1c7fa Mon Sep 17 00:00:00 2001 From: Joost de Folter Date: Thu, 7 May 2026 11:45:27 +0200 Subject: [PATCH 05/18] Extend format and sub_type format --- src/biomero_schema/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index 44fbebd..3c91c98 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -84,8 +84,8 @@ class Parameter(BaseModel): set_by_server: Optional[bool] = Field(None, alias="set-by-server", description="If true, parameter is server-assigned. Soft Defaults to False") # 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") + sub_type: Optional[Union[str, List[str]]] = Field(None, alias="sub-type", description="Sub-type for image parameters") class OutputParameter(BaseModel): @@ -101,8 +101,8 @@ class OutputParameter(BaseModel): set_by_server: Optional[bool] = Field(None, alias="set-by-server", description="If true, parameter is server-assigned. Soft Defaults to False") # 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") + sub_type: Optional[Union[str, List[str]]] = Field(None, alias="sub-type", description="Sub-type for image parameters") class WorkflowSchema(BaseModel): From fa98f89ca4bd25060ba5f52ec638351c947dac84 Mon Sep 17 00:00:00 2001 From: Joost de Folter Date: Thu, 7 May 2026 14:28:12 +0200 Subject: [PATCH 06/18] Add docker type --- src/biomero_schema/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index 3c91c98..2157f69 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -27,7 +27,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") From d0cf1eeea207aa1095125ccb56383d04c7357768 Mon Sep 17 00:00:00 2001 From: Luik Date: Mon, 11 May 2026 17:40:32 +0200 Subject: [PATCH 07/18] Add value-choices to base model --- src/biomero_schema/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index 2157f69..60ef4cd 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -82,7 +82,8 @@ class Parameter(BaseModel): default_value: Optional[Union[str, float, bool]] = Field(None, alias="default-value", description="Default value in BIAFLOWS UI (parameter dialog box). Soft Defaults to empty string") 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") + # Type-specific fields format: Optional[Union[str, List[str]]] = Field(None, description="Format for file/image/array types") sub_type: Optional[Union[str, List[str]]] = Field(None, alias="sub-type", description="Sub-type for image parameters") @@ -99,6 +100,7 @@ class OutputParameter(BaseModel): default_value: Optional[Union[str, float, bool]] = Field(None, alias="default-value", description="Default value in BIAFLOWS UI (parameter dialog box). Soft Defaults to empty string") 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") # Type-specific fields format: Optional[Union[str, List[str]]] = Field(None, description="Format for file/image/array types") From e180ef56f9ed1de1bca165be66376514aad910d4 Mon Sep 17 00:00:00 2001 From: Luik Date: Wed, 13 May 2026 11:32:25 +0200 Subject: [PATCH 08/18] Add output_dir_set for params --- src/biomero_schema/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index 60ef4cd..eb67c69 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -83,6 +83,7 @@ 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.") # Type-specific fields format: Optional[Union[str, List[str]]] = Field(None, description="Format for file/image/array types") From 702f3c4b88c94348baf084a17ce634cc7730aecd Mon Sep 17 00:00:00 2001 From: Luik Date: Tue, 19 May 2026 15:59:28 +0200 Subject: [PATCH 09/18] Add computed fields for requires_zarr and requires_plate --- .github/workflows/test.yml | 15 ++++ pixi.lock | 52 ++++++++++- pyproject.toml | 8 ++ src/biomero_schema/models.py | 41 ++++++++- tests/test_computed_fields.py | 160 ++++++++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 tests/test_computed_fields.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..411e4e2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,15 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: prefix-dev/setup-pixi@v0.8.0 + with: + pixi-version: latest + - run: pixi run test diff --git a/pixi.lock b/pixi.lock index 8fcf135..5a20da9 100644 --- a/pixi.lock +++ b/pixi.lock @@ -28,13 +28,17 @@ environments: - 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/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/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 @@ -65,13 +69,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.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/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 @@ -100,13 +108,14 @@ packages: - pypi: ./ name: biomero-schema version: 0.1.0 - sha256: 3fe5cc254b0ed72562b85beb6a731154c3741455246530df3ff89119776c0c5b + sha256: 01f5a52d288ec62df162932a16cf3c9bd7fcc29eb7e6a2a0fc1ce096329c711e requires_dist: - pydantic>=2.0.0 - click>=8.0.0 - jsonschema>=4.0.0 - rich>=13.0.0 - pyyaml>=6.0.3 + - pytest>=8.0.0 ; extra == 'test' requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c @@ -167,6 +176,11 @@ packages: 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 @@ -440,6 +454,22 @@ packages: 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 @@ -492,6 +522,26 @@ packages: requires_dist: - colorama>=0.4.6 ; extra == 'windows-terminal' requires_python: '>=3.9' +- 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 diff --git a/pyproject.toml b/pyproject.toml index ac9a5c8..68a4a84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,12 @@ dependencies = [ "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" @@ -32,6 +38,7 @@ platforms = ["osx-64", "win-64"] [tool.pixi.pypi-dependencies] biomero-schema = { path = ".", editable = true } +pytest = ">=8.0.0" [tool.pixi.tasks] json-schema = { cmd = ["biomero-schema", "schema"], description = "Print the json schema generated from pydantic model" } @@ -39,3 +46,4 @@ test-parse = { cmd = ["biomero-schema", "parse", "tests/example_workflow.json"], 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" } diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index eb67c69..e69107d 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -1,6 +1,8 @@ """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'}) class Author(BaseModel): @@ -133,6 +135,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/test_computed_fields.py b/tests/test_computed_fields.py new file mode 100644 index 0000000..a13613b --- /dev/null +++ b/tests/test_computed_fields.py @@ -0,0 +1,160 @@ +"""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 + + 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 From 0d7b1a8198d0fa168982e191071e9c930aab3fc0 Mon Sep 17 00:00:00 2001 From: Luik Date: Wed, 20 May 2026 11:53:17 +0200 Subject: [PATCH 10/18] Add optional advanced mode and value choice labels --- src/biomero_schema/models.py | 6 +++ tests/test_computed_fields.py | 82 +++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index e69107d..5c221ef 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -87,9 +87,12 @@ class Parameter(BaseModel): 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.") + 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") 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): @@ -105,9 +108,12 @@ class OutputParameter(BaseModel): 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") 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): diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index a13613b..02c96a5 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -119,6 +119,88 @@ 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")]) From 55037b10d53e122f44a9c54ba014d45dc57e72dc Mon Sep 17 00:00:00 2001 From: Luik Date: Wed, 20 May 2026 17:50:07 +0200 Subject: [PATCH 11/18] Add other input support --- src/biomero_schema/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index b67a51a..fe6901d 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -76,7 +76,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]'") @@ -96,6 +96,7 @@ class Parameter(BaseModel): # 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="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.") @@ -103,7 +104,7 @@ class Parameter(BaseModel): class OutputParameter(BaseModel): """Output 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]'") From 51058e2494cb9773681ae75ae409629e9089b2d5 Mon Sep 17 00:00:00 2001 From: Luik Date: Thu, 21 May 2026 17:03:08 +0200 Subject: [PATCH 12/18] Add file attachment support --- src/biomero_schema/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index fe6901d..041d29d 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -91,6 +91,7 @@ class Parameter(BaseModel): 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") From 44cd23d7c52d5f879cbef6072b8c2f085384d79f Mon Sep 17 00:00:00 2001 From: Luik Date: Wed, 27 May 2026 11:30:23 +0200 Subject: [PATCH 13/18] Add file_count for outputs --- src/biomero_schema/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/biomero_schema/models.py b/src/biomero_schema/models.py index 041d29d..3d08af8 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -124,6 +124,11 @@ class OutputParameter(BaseModel): # 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.") From 51eb7eae4d87faf7964b38dcab1b1faa940c25ba Mon Sep 17 00:00:00 2001 From: Luik Date: Tue, 2 Jun 2026 13:16:03 +0200 Subject: [PATCH 14/18] Update README --- README.md | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3d4751b..a86774c 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ schema = "name": "string" // Optional. Name of the institions. 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,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|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". + "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 +180,18 @@ 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". } ] "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". @@ -194,8 +204,8 @@ file = 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 = @@ -203,3 +213,10 @@ array = "format": "string" // Required. 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. From 1d4a8f6860fcbf65e19317b5aa6736b2c445fc61 Mon Sep 17 00:00:00 2001 From: Luik Date: Tue, 2 Jun 2026 14:45:20 +0200 Subject: [PATCH 15/18] Github for version and pypi --- .github/workflows/python-publish.yml | 38 ++++++++++++ .github/workflows/test.yml | 2 + README.md | 14 +++-- pixi.lock | 89 +++++++++++++++++++++++++++- pyproject.toml | 9 ++- src/biomero_schema/cli.py | 2 +- 6 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..4324a26 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,38 @@ +# 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: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.ref_name }} + run: python -m build + - 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 index 411e4e2..fe52b43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,8 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: prefix-dev/setup-pixi@v0.8.0 with: pixi-version: latest diff --git a/README.md b/README.md index a86774c..8a50bf5 100644 --- a/README.md +++ b/README.md @@ -105,15 +105,15 @@ schema = [ { "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": // Required. List of citations for the tool. At least one citation required. @@ -170,6 +170,8 @@ schema = "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. } @@ -192,6 +194,8 @@ schema = "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". @@ -199,7 +203,7 @@ schema = file = { - "format": "string" // Required. Extension of the file type (.csv). + "format": "string" // Optional. Extension of the file type (e.g. .csv). } image = @@ -210,7 +214,7 @@ image = array = { - "format": "string" // Required. Extension of the file type (npy, npz) + "format": "string" // Optional. Extension of the file type (npy, npz) } ``` diff --git a/pixi.lock b/pixi.lock index 5a20da9..240acb2 100644 --- a/pixi.lock +++ b/pixi.lock @@ -43,8 +43,11 @@ environments: - 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 @@ -84,8 +87,11 @@ environments: - 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 @@ -107,8 +113,8 @@ packages: requires_python: '>=3.9' - pypi: ./ name: biomero-schema - version: 0.1.0 - sha256: 01f5a52d288ec62df162932a16cf3c9bd7fcc29eb7e6a2a0fc1ce096329c711e + version: 0.1.dev48+g51eb7eae4.d20260602 + sha256: be66ce090a506c690668ac8213d1fe8642adc24109af12264d5e40ed5b49a605 requires_dist: - pydantic>=2.0.0 - click>=8.0.0 @@ -116,7 +122,7 @@ packages: - rich>=13.0.0 - pyyaml>=6.0.3 - pytest>=8.0.0 ; extra == 'test' - requires_python: '>=3.8' + requires_python: '>=3.11' - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c md5: 97c4b3bd8a90722104798175a1bdddbf @@ -660,6 +666,74 @@ packages: 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 @@ -756,6 +830,15 @@ packages: 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 diff --git a/pyproject.toml b/pyproject.toml index 68a4a84..560977a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,14 @@ [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", @@ -39,6 +39,7 @@ platforms = ["osx-64", "win-64"] [tool.pixi.pypi-dependencies] biomero-schema = { path = ".", editable = true } pytest = ">=8.0.0" +setuptools-scm = ">=8" [tool.pixi.tasks] json-schema = { cmd = ["biomero-schema", "schema"], description = "Print the json schema generated from pydantic model" } @@ -47,3 +48,5 @@ test-pparse = { cmd = ["biomero-schema", "parse", "tests/example_workflow.json", 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/cli.py b/src/biomero_schema/cli.py index fe9f22d..c509803 100644 --- a/src/biomero_schema/cli.py +++ b/src/biomero_schema/cli.py @@ -70,7 +70,7 @@ def validate(descriptor_file: str): @cli.command() -@click.argument("input_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(descriptor_file: str, pretty: bool, output_json: bool): From 9a2808a508901ef81f9043bbe15449b5d2082597 Mon Sep 17 00:00:00 2001 From: Luik Date: Tue, 2 Jun 2026 14:55:08 +0200 Subject: [PATCH 16/18] Expand github actions to more tests and package --- .github/workflows/python-publish.yml | 21 ++++++++++++--------- .github/workflows/test.yml | 4 ++++ pixi.lock | 28 ++++++++++++++++++++++++++-- pyproject.toml | 2 ++ 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4324a26..1a77661 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -8,7 +8,7 @@ on: types: [published] permissions: - contents: read + contents: write jobs: deploy: @@ -19,18 +19,21 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 + - uses: prefix-dev/setup-pixi@v0.8.0 with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build + pixi-version: latest + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.ref_name }} - name: Build package env: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.ref_name }} - run: python -m build + 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: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe52b43..136abad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,3 +15,7 @@ jobs: 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/pixi.lock b/pixi.lock index 240acb2..aa8ee3d 100644 --- a/pixi.lock +++ b/pixi.lock @@ -27,6 +27,7 @@ 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 @@ -38,6 +39,7 @@ environments: - 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 @@ -70,6 +72,7 @@ environments: - 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 @@ -82,6 +85,7 @@ environments: - 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 @@ -113,8 +117,8 @@ packages: requires_python: '>=3.9' - pypi: ./ name: biomero-schema - version: 0.1.dev48+g51eb7eae4.d20260602 - sha256: be66ce090a506c690668ac8213d1fe8642adc24109af12264d5e40ed5b49a605 + version: 0.1.dev49+g1d4a8f686.d20260602 + sha256: 86a6d44ceb3f8a707ef0222cf5594910cff85ad6a45527537b0e342de2edd648 requires_dist: - pydantic>=2.0.0 - click>=8.0.0 @@ -123,6 +127,21 @@ packages: - 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 @@ -528,6 +547,11 @@ packages: 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 diff --git a/pyproject.toml b/pyproject.toml index 560977a..8195bef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,9 +40,11 @@ platforms = ["osx-64", "win-64"] 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" } From 3b6f4b41028a5a5abd07ede21c4e7f3d4afd2e8c Mon Sep 17 00:00:00 2001 From: Luik Date: Tue, 2 Jun 2026 15:02:09 +0200 Subject: [PATCH 17/18] Some encoding for cli results --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 136abad..edab21d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,8 @@ on: jobs: test: runs-on: windows-latest + env: + PYTHONUTF8: "1" steps: - uses: actions/checkout@v4 with: From 2491e07b7996011cdae2b490b48751090202f905 Mon Sep 17 00:00:00 2001 From: Luik Date: Tue, 2 Jun 2026 15:19:09 +0200 Subject: [PATCH 18/18] Change versioning of schema --- README.md | 2 +- src/biomero_schema/__init__.py | 11 ++++++++++- src/biomero_schema/models.py | 6 +++++- tests/example_workflow.json | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8a50bf5..1552636 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ 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. [ { 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/models.py b/src/biomero_schema/models.py index 3d08af8..afbe8aa 100644 --- a/src/biomero_schema/models.py +++ b/src/biomero_schema/models.py @@ -4,6 +4,10 @@ _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): """Author model.""" @@ -137,7 +141,7 @@ 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") 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",