././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1785871590.9586556 galaxy_tool_util_models-26.1.1/0000755000175100017510000000000015234436347016204 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/HISTORY.rst0000644000175100017510000002026315234436334020076 0ustar00runnerrunnerHistory ------- .. to_doc ------------------- 26.1.1 (2026-08-04) ------------------- No recorded changes since last release ------------------- 26.1.0 (2026-08-02) ------------------- ========= Bug fixes ========= * Fix workflow implicit mapping of flat collections over paired_or_unpaired by `@jmchilton `_ in `#21933 `_ * [26.1 (spiritually)] Fix UDT collection outputs being silently dropped in YAML tool parser by `@jmchilton `_ in `#22761 `_ * Relax legacy doi:-prefixed citation handling (#22795) by `@mvdbeek `_ in `#22799 `_ * Add fixed color set from webcolors package by `@guerler `_ in `#22869 `_ * Always attempt to build tool request for new API endpoint by `@mvdbeek `_ in `#22894 `_ * Accept dce collection references in the tool request model by `@mvdbeek `_ in `#22926 `_ * Make the user-defined tool schema robust for LLM authoring (CustomToolAgent) by `@mvdbeek `_ in `#22968 `_ * Custom-tool agent: verified container resolution + authoring safeguards (with a reusable mulled-recommend CLI) by `@mvdbeek `_ in `#22981 `_ ============ Enhancements ============ * Various YAML Tool Hardening and Progress toward Tool State Goals by `@jmchilton `_ in `#21828 `_ * Migrate tool execution request from api/tools to api/jobs by `@guerler `_ in `#21842 `_ * Fix tool.parameters initialization by `@mvdbeek `_ in `#21914 `_ * Allow tools to specify time limits by `@jmchilton `_ in `#21927 `_ * Migrate Python packages to \`src\` layout and pure namespace packages by `@mr-c `_ in `#21977 `_ * Model subcollection mapping (map_over_type) and DCE in tool parameter schema by `@jmchilton `_ in `#21991 `_ * Enrich tool parameter JSON Schema with Galaxy metadata via model methods by `@mvdbeek `_ in `#22020 `_ * More Complete, Robust, and Tested JSON-Schema for Tool State Models by `@jmchilton `_ in `#22362 `_ * Clean up Pydantic v2 deprecation warnings by `@mvdbeek `_ in `#22463 `_ * Clean up various pytest warnings by `@mvdbeek `_ in `#22472 `_ * Narrow YAML schema by `@jmchilton `_ in `#22507 `_ * Tighten the workflow test schema by `@jmchilton `_ in `#22566 `_ * Push UserToolSource semantic validation onto the pydantic model by `@mvdbeek `_ in `#22615 `_ * Lift legacy UserToolSource representations on read by `@mvdbeek `_ in `#22624 `_ * Expose more metadata in ParsedTool abstraction by `@jmchilton `_ in `#22667 `_ ------------------- 26.0.1 (2026-06-04) ------------------- No recorded changes since last release ------------------- 26.0.0 (2026-04-08) ------------------- ========= Bug fixes ========= * Fix Pydantic UnsupportedFieldAttributeWarning for Field defaults in Annotated by `@mvdbeek `_ in `#21923 `_ * Backport paired or unpaired mapping fix by `@mvdbeek `_ in `#21936 `_ * Fix validation of certain classes of text validators in tools. by `@jmchilton `_ in `#22280 `_ ============ Enhancements ============ * Tool Request API by `@jmchilton `_ in `#20935 `_ * Update fastapi to 0.123.4 and ``get_openapi()`` fork by `@nsoranzo `_ in `#21384 `_ * Enable attaching sample sheet to landing requests by `@mvdbeek `_ in `#21489 `_ * Apply 2026 black style by `@galaxybot `_ in `#21618 `_ ============= Other changes ============= * Merge 25.1 into dev, fix openapi schema generation for TypedDict by `@mvdbeek `_ in `#21168 `_ * Fix tool request API for multiple select parameters with defaults. by `@jmchilton `_ in `#21416 `_ ------------------- 25.1.2 (2026-03-09) ------------------- No recorded changes since last release ------------------- 25.1.1 (2026-02-03) ------------------- No recorded changes since last release ------------------- 25.1.0 (2025-12-12) ------------------- ============ Enhancements ============ * Implement Sample Sheets by `@jmchilton `_ in `#19305 `_ * Prepare ``ToolBox.dynamic_tool_to_tool()`` for CWL formats by `@nsoranzo `_ in `#20364 `_ * Type annotation fixes for mypy 1.16.0 by `@nsoranzo `_ in `#20424 `_ * Add configfiles support and various enhancements for user defined tools by `@mvdbeek `_ in `#20761 `_ * Add support for picking ``from_work_dir`` directory by `@mvdbeek `_ in `#20916 `_ * Include format in internal json model by `@mvdbeek `_ in `#20917 `_ * Add resource docs and tweak tool source schema title generation by `@mvdbeek `_ in `#20951 `_ * Use workflow-style payload in data landing request by `@mvdbeek `_ in `#21107 `_ ------------------- 25.0.4 (2025-11-18) ------------------- No recorded changes since last release ------------------- 25.0.3 (2025-09-23) ------------------- ========= Bug fixes ========= * Fix collection element sorting in extended_metadata by `@mvdbeek `_ in `#20928 `_ ------------------- 25.0.2 (2025-08-13) ------------------- ========= Bug fixes ========= * Fix parameter models for optional color params. by `@jmchilton `_ in `#20705 `_ ------------------- 25.0.1 (2025-06-20) ------------------- No recorded changes since last release ------------------- 25.0.0 (2025-06-18) ------------------- First release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871579.0 galaxy_tool_util_models-26.1.1/LICENSE0000644000175100017510000000306615234436333017211 0ustar00runnerrunnerCopyright (c) 2005-2026 Galaxy Contributors (see CONTRIBUTORS.md) Galaxy is provided from 2026-02-25 onwards entirely under the MIT License. Some icons found in Galaxy are from the Silk Icons set, available under the Creative Commons Attribution 2.5 License, from: http://www.famfamfam.com/lab/icons/silk/ Other images and documentation are licensed under the Creative Commons Attribution 3.0 (CC BY 3.0) License. See: http://creativecommons.org/licenses/by/3.0/ -------------------------------------------------------------------------------- MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/Makefile0000644000175100017510000000545315234436334017647 0ustar00runnerrunner# Location of virtualenv used for development. VENV?=.venv # Open resource on Mac OS X or Linux OPEN_RESOURCE=bash -c 'open $$0 || xdg-open $$0' # Source virtualenv to execute command (flake8, sphinx, twine, etc...) IN_VENV=if [ -f $(VENV)/bin/activate ]; then . $(VENV)/bin/activate; fi; UPSTREAM?=galaxyproject SOURCE_DIR?=galaxy BUILD_SCRIPTS_DIR=scripts DEV_RELEASE?=0 VERSION?=$(shell DEV_RELEASE=$(DEV_RELEASE) python $(BUILD_SCRIPTS_DIR)/print_version_for_release.py) PROJECT_NAME?=galaxy-$(shell basename $(CURDIR)) PROJECT_NAME:=$(subst _,-,$(PROJECT_NAME)) BRANCH?=$(shell git rev-parse --abbrev-ref HEAD) TEST_DIR?=tests DIST=dist TESTS?=$(SOURCE_DIR) $(TEST_DIR) .PHONY: clean-pyc clean-build docs clean help: @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @echo "clean-pyc - remove Python file artifacts" @echo "clean-test - remove test and coverage artifacts" @echo "setup-venv - setup a development virtualenv in current directory." @echo "lint-dist - twine check dist results, including validating README content" @echo "dist - package project for PyPI distribution" clean: clean-build clean-pyc clean-tests clean-build: rm -fr build/ rm -fr $(DIST)/ find . -name "*.egg-info" -type d -exec rm -rf {} + clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-tests: rm -fr .tox/ setup-venv: uv sync --inexact --all-extras _test: uv run pytest $(TESTS) test: setup-venv _test _dist: uv build -o $(DIST) ls -l $(DIST) dist: clean _dist _setup-mypy-venv: setup-venv uv pip install -r ../../lib/galaxy/dependencies/pinned-typecheck-requirements.txt _mypy: uv run mypy . mypy: _setup-mypy-venv _mypy _setup-lint-venv: setup-venv uv pip install -r ../../lib/galaxy/dependencies/pinned-lint-requirements.txt _lint: uv run ruff check . lint: _setup-lint-venv _lint lint-dist: uvx --with-requirements dev-requirements.txt --from twine twine check $(DIST)/* # black doesn't actually work on symlinked files because they are outside # the current directory #_setup-format-venv: setup-venv # uv pip install isort black #_isort: # uv run isort --sp ../../.isort.cfg . #_black: # uv run black --config ../pyproject.toml . #format: _setup-format-venv _isort _black commit-version: $(IN_VENV) DEV_RELEASE=$(DEV_RELEASE) python $(BUILD_SCRIPTS_DIR)/commit_version.py $(VERSION) new-version: $(IN_VENV) DEV_RELEASE=$(DEV_RELEASE) python $(BUILD_SCRIPTS_DIR)/new_version.py $(VERSION) release-local: commit-version release-artifacts new-version push-release: # git push $(UPSTREAM) $(BRANCH) # git push upstream $(PROJECT_NAME)-$(VERSION) echo "Makefile doesn't manually push release." release: release-local push-release ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1785871590.9586112 galaxy_tool_util_models-26.1.1/PKG-INFO0000644000175100017510000002343715234436347017312 0ustar00runnerrunnerMetadata-Version: 2.4 Name: galaxy-tool-util-models Version: 26.1.1 Summary: Pydantic models for Galaxy tools Home-page: https://github.com/galaxyproject/galaxy Author: Galaxy Project and Community Author-email: galaxy-committers@lists.galaxyproject.org License: MIT Keywords: Galaxy Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Code Generators Classifier: Topic :: Software Development :: Testing Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pydantic>=2.7.4 Requires-Dist: pydantic-extra-types Requires-Dist: typing-extensions Provides-Extra: test Requires-Dist: pytest; extra == "test" Dynamic: license-file .. image:: https://badge.fury.io/py/galaxy-tool-util-models.svg :target: https://pypi.org/project/galaxy-tool-util-models/ Overview -------- Pydantic models to support Galaxy_ tool utilities. * Code: https://github.com/galaxyproject/galaxy/tree/dev/packages/tool_util_models .. _Galaxy: http://galaxyproject.org/ History ------- .. to_doc ------------------- 26.1.1 (2026-08-04) ------------------- No recorded changes since last release ------------------- 26.1.0 (2026-08-02) ------------------- ========= Bug fixes ========= * Fix workflow implicit mapping of flat collections over paired_or_unpaired by `@jmchilton `_ in `#21933 `_ * [26.1 (spiritually)] Fix UDT collection outputs being silently dropped in YAML tool parser by `@jmchilton `_ in `#22761 `_ * Relax legacy doi:-prefixed citation handling (#22795) by `@mvdbeek `_ in `#22799 `_ * Add fixed color set from webcolors package by `@guerler `_ in `#22869 `_ * Always attempt to build tool request for new API endpoint by `@mvdbeek `_ in `#22894 `_ * Accept dce collection references in the tool request model by `@mvdbeek `_ in `#22926 `_ * Make the user-defined tool schema robust for LLM authoring (CustomToolAgent) by `@mvdbeek `_ in `#22968 `_ * Custom-tool agent: verified container resolution + authoring safeguards (with a reusable mulled-recommend CLI) by `@mvdbeek `_ in `#22981 `_ ============ Enhancements ============ * Various YAML Tool Hardening and Progress toward Tool State Goals by `@jmchilton `_ in `#21828 `_ * Migrate tool execution request from api/tools to api/jobs by `@guerler `_ in `#21842 `_ * Fix tool.parameters initialization by `@mvdbeek `_ in `#21914 `_ * Allow tools to specify time limits by `@jmchilton `_ in `#21927 `_ * Migrate Python packages to \`src\` layout and pure namespace packages by `@mr-c `_ in `#21977 `_ * Model subcollection mapping (map_over_type) and DCE in tool parameter schema by `@jmchilton `_ in `#21991 `_ * Enrich tool parameter JSON Schema with Galaxy metadata via model methods by `@mvdbeek `_ in `#22020 `_ * More Complete, Robust, and Tested JSON-Schema for Tool State Models by `@jmchilton `_ in `#22362 `_ * Clean up Pydantic v2 deprecation warnings by `@mvdbeek `_ in `#22463 `_ * Clean up various pytest warnings by `@mvdbeek `_ in `#22472 `_ * Narrow YAML schema by `@jmchilton `_ in `#22507 `_ * Tighten the workflow test schema by `@jmchilton `_ in `#22566 `_ * Push UserToolSource semantic validation onto the pydantic model by `@mvdbeek `_ in `#22615 `_ * Lift legacy UserToolSource representations on read by `@mvdbeek `_ in `#22624 `_ * Expose more metadata in ParsedTool abstraction by `@jmchilton `_ in `#22667 `_ ------------------- 26.0.1 (2026-06-04) ------------------- No recorded changes since last release ------------------- 26.0.0 (2026-04-08) ------------------- ========= Bug fixes ========= * Fix Pydantic UnsupportedFieldAttributeWarning for Field defaults in Annotated by `@mvdbeek `_ in `#21923 `_ * Backport paired or unpaired mapping fix by `@mvdbeek `_ in `#21936 `_ * Fix validation of certain classes of text validators in tools. by `@jmchilton `_ in `#22280 `_ ============ Enhancements ============ * Tool Request API by `@jmchilton `_ in `#20935 `_ * Update fastapi to 0.123.4 and ``get_openapi()`` fork by `@nsoranzo `_ in `#21384 `_ * Enable attaching sample sheet to landing requests by `@mvdbeek `_ in `#21489 `_ * Apply 2026 black style by `@galaxybot `_ in `#21618 `_ ============= Other changes ============= * Merge 25.1 into dev, fix openapi schema generation for TypedDict by `@mvdbeek `_ in `#21168 `_ * Fix tool request API for multiple select parameters with defaults. by `@jmchilton `_ in `#21416 `_ ------------------- 25.1.2 (2026-03-09) ------------------- No recorded changes since last release ------------------- 25.1.1 (2026-02-03) ------------------- No recorded changes since last release ------------------- 25.1.0 (2025-12-12) ------------------- ============ Enhancements ============ * Implement Sample Sheets by `@jmchilton `_ in `#19305 `_ * Prepare ``ToolBox.dynamic_tool_to_tool()`` for CWL formats by `@nsoranzo `_ in `#20364 `_ * Type annotation fixes for mypy 1.16.0 by `@nsoranzo `_ in `#20424 `_ * Add configfiles support and various enhancements for user defined tools by `@mvdbeek `_ in `#20761 `_ * Add support for picking ``from_work_dir`` directory by `@mvdbeek `_ in `#20916 `_ * Include format in internal json model by `@mvdbeek `_ in `#20917 `_ * Add resource docs and tweak tool source schema title generation by `@mvdbeek `_ in `#20951 `_ * Use workflow-style payload in data landing request by `@mvdbeek `_ in `#21107 `_ ------------------- 25.0.4 (2025-11-18) ------------------- No recorded changes since last release ------------------- 25.0.3 (2025-09-23) ------------------- ========= Bug fixes ========= * Fix collection element sorting in extended_metadata by `@mvdbeek `_ in `#20928 `_ ------------------- 25.0.2 (2025-08-13) ------------------- ========= Bug fixes ========= * Fix parameter models for optional color params. by `@jmchilton `_ in `#20705 `_ ------------------- 25.0.1 (2025-06-20) ------------------- No recorded changes since last release ------------------- 25.0.0 (2025-06-18) ------------------- First release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/README.rst0000644000175100017510000000050215234436334017664 0ustar00runnerrunner .. image:: https://badge.fury.io/py/galaxy-tool-util-models.svg :target: https://pypi.org/project/galaxy-tool-util-models/ Overview -------- Pydantic models to support Galaxy_ tool utilities. * Code: https://github.com/galaxyproject/galaxy/tree/dev/packages/tool_util_models .. _Galaxy: http://galaxyproject.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/dev-requirements.txt0000644000175100017510000000006115234436334022235 0ustar00runnerrunner# For dev mypy # For release build twine==6.2.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/mypy.ini0000644000175100017510000006230515234436334017705 0ustar00runnerrunner[mypy] enable_error_code = ignore-without-code plugins = pydantic.mypy show_error_codes = True ignore_missing_imports = True check_untyped_defs = True exclude = (?x)( ^build/ | .*test/functional/tools/cwl_tools/ | .*tool_shed/test/test_data/repos/ ) pretty = True no_implicit_reexport = True no_implicit_optional = True strict_equality = True warn_redundant_casts = True warn_unreachable = True warn_unused_ignores = True platform = linux explicit_package_bases = True # green list - work on growing these please! [mypy-galaxy.jobs.job_destination] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.tool_util.toolbox.lineages.interface] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.tools.cache] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.tools.search] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.util.compression_utils] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.util.custom_logging] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.util.unittest_utils] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.util.xml_macros] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True [mypy-galaxy.managers.secured] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True # orange list - still need to enable some of the checks [mypy-galaxy.tools.wrappers] disallow_any_generics = True disallow_subclassing_any = True disallow_untyped_calls = False disallow_untyped_decorators = True disallow_untyped_defs = True warn_return_any = True warn_unreachable = False # red list - work on reducing these please! [mypy-galaxy.celery] warn_unreachable = False [mypy-galaxy.config] warn_unreachable = False [mypy-galaxy.datatypes.constructive_solid_geometry] warn_unreachable = False [mypy-galaxy.datatypes.data] warn_unreachable = False [mypy-galaxy.datatypes.mothur] warn_unreachable = False [mypy-galaxy.datatypes.phylip] warn_unreachable = False [mypy-galaxy.datatypes.qiime2] warn_unreachable = False [mypy-galaxy.managers.jobs] warn_unreachable = False [mypy-galaxy.tool_shed.metadata.metadata_generator] warn_unreachable = False [mypy-galaxy.tool_util.cwl.parser] warn_unreachable = False [mypy-galaxy.tool_util.cwl.cwltool_deps] warn_unreachable = False [mypy-galaxy.tool_util.data] warn_unreachable = False [mypy-galaxy.tool_util.toolbox.base] warn_unreachable = False [mypy-galaxy.tool_util.verify.interactor] warn_unreachable = False [mypy-galaxy.tools] warn_unreachable = False [mypy-galaxy.tools.parameters.basic] warn_unreachable = False [mypy-galaxy.webapps.galaxy.api.library_datasets] warn_unreachable = False [mypy-galaxy.webapps.galaxy.services.jobs] warn_unreachable = False [mypy-galaxy.workflow.run_request] warn_unreachable = False [mypy-galaxy_test.driver.driver_util] warn_unreachable = False [mypy-tool_shed.metadata.repository_metadata_manager] warn_unreachable = False [mypy-galaxy.util.oset] # lots of tricky code in here... check_untyped_defs = False [mypy-galaxy.webapps.reports.controllers.*] check_untyped_defs = False [mypy-galaxy.managers.deletable] check_untyped_defs = False [mypy-galaxy.datatypes.converters.interval_to_bedstrict_converter] check_untyped_defs = False [mypy-galaxy_test.base.ssh_util] check_untyped_defs = False [mypy-galaxy.util.heartbeat] check_untyped_defs = False [mypy-galaxy.tools.bundled.stats.aggregate_scores_in_intervals] check_untyped_defs = False [mypy-galaxy.tools.bundled.sr_mapping.srma_wrapper] check_untyped_defs = False [mypy-galaxy.tools.bundled.sr_mapping.bfast_wrapper] check_untyped_defs = False [mypy-galaxy.tools.bundled.sr_assembly.velveth_wrapper] check_untyped_defs = False [mypy-galaxy.tools.bundled.sr_assembly.velvetg_wrapper] check_untyped_defs = False [mypy-galaxy.tools.bundled.solid_tools.solid_qual_stats] check_untyped_defs = False [mypy-galaxy.tools.bundled.solid_tools.maq_cs_wrapper] check_untyped_defs = False [mypy-galaxy.tools.bundled.phenotype_association.pagetag] check_untyped_defs = False [mypy-galaxy.tools.bundled.maf.maf_to_bed] check_untyped_defs = False [mypy-galaxy.tools.bundled.filters.trimmer] check_untyped_defs = False [mypy-galaxy.tools.bundled.filters.sff_extract] check_untyped_defs = False [mypy-galaxy.tools.bundled.filters.lav_to_bed] check_untyped_defs = False [mypy-galaxy.tools.bundled.evolution.add_scores] check_untyped_defs = False [mypy-galaxy.tools.bundled.data_source.microbial_import_code] check_untyped_defs = False [mypy-galaxy.datatypes.converters.pileup_to_interval_index_converter] check_untyped_defs = False [mypy-galaxy.datatypes.converters.bgzip] check_untyped_defs = False [mypy-tool_shed.grids.repository_grid_filter_manager] check_untyped_defs = False [mypy-galaxy.web_stack.message] check_untyped_defs = False [mypy-galaxy.web.framework.middleware.translogger] check_untyped_defs = False [mypy-galaxy.web.framework.middleware.profile] check_untyped_defs = False [mypy-galaxy.web.framework.middleware.error] check_untyped_defs = False [mypy-galaxy.util.topsort] check_untyped_defs = False [mypy-galaxy.util.submodules] check_untyped_defs = False [mypy-galaxy.util.monitors] check_untyped_defs = False [mypy-galaxy.tools.util.maf_utilities] check_untyped_defs = False [mypy-galaxy.tools.parameters.wrapped_json] check_untyped_defs = False [mypy-galaxy.tools.bundled.stats.grouping] check_untyped_defs = False [mypy-galaxy.tools.bundled.ngs_simulation.ngs_simulation] check_untyped_defs = False [mypy-galaxy.tools.bundled.metag_tools.shrimp_wrapper] check_untyped_defs = False [mypy-galaxy.tools.bundled.filters.random_lines_two_pass] check_untyped_defs = False [mypy-galaxy.tool_util.deps.brew_exts] check_untyped_defs = False [mypy-galaxy.model.item_attrs] check_untyped_defs = False [mypy-galaxy.model.base] check_untyped_defs = False [mypy-galaxy.model.dataset_collections.structure] check_untyped_defs = False [mypy-galaxy.managers.annotatable] check_untyped_defs = False [mypy-galaxy.jobs.runners.util.cli] check_untyped_defs = False [mypy-galaxy.datatypes.util.maf_utilities] check_untyped_defs = False [mypy-galaxy.datatypes.dataproviders.decorators] check_untyped_defs = False [mypy-galaxy.tools.bundled.maf.maf_to_interval] check_untyped_defs = False [mypy-galaxy.tools.bundled.maf.maf_stats] check_untyped_defs = False [mypy-galaxy.tools.bundled.maf.interval_maf_to_merged_fasta] check_untyped_defs = False [mypy-galaxy.tools.bundled.maf.interval2maf] check_untyped_defs = False [mypy-galaxy.util.yaml_util] check_untyped_defs = False [mypy-galaxy.util.rules_dsl] check_untyped_defs = False [mypy-test.functional.webhooks.phdcomics] check_untyped_defs = False [mypy-galaxy.util.path] check_untyped_defs = False [mypy-galaxy.util] check_untyped_defs = False no_implicit_reexport = False [mypy-galaxy.tools.bundled.maf.vcf_to_maf_customtrack] check_untyped_defs = False [mypy-tool_shed.webapp.security] check_untyped_defs = False [mypy-tool_shed.util.hgweb_config] check_untyped_defs = False [mypy-galaxy.workflow.resources] check_untyped_defs = False [mypy-galaxy.webapps.reports.config] check_untyped_defs = False [mypy-galaxy.web.framework.base] check_untyped_defs = False [mypy-galaxy.web.framework.middleware.remoteuser] check_untyped_defs = False [mypy-galaxy.util.task] check_untyped_defs = False [mypy-galaxy.util.sanitize_html] check_untyped_defs = False [mypy-galaxy.util.commands] check_untyped_defs = False [mypy-galaxy.util.tool_shed.xml_util] check_untyped_defs = False [mypy-galaxy.tools.parameters.sanitize] check_untyped_defs = False [mypy-galaxy.tools.bundled.filters.join] check_untyped_defs = False [mypy-galaxy.tool_util.output_checker] check_untyped_defs = False [mypy-galaxy.tool_util.verify.test_data] check_untyped_defs = False [mypy-galaxy.tool_util.toolbox.parser] check_untyped_defs = False [mypy-galaxy.tool_util.toolbox.filters] check_untyped_defs = False [mypy-galaxy.tool_util.parser.output_collection_def] check_untyped_defs = False [mypy-galaxy.tool_util.deps.mulled.get_tests] check_untyped_defs = False [mypy-galaxy.security.object_wrapper] check_untyped_defs = False [mypy-galaxy.quota] check_untyped_defs = False [mypy-galaxy.model.custom_types] check_untyped_defs = False [mypy-galaxy.datatypes.util.gff_util] check_untyped_defs = False [mypy-galaxy.datatypes.dataproviders.line] check_untyped_defs = False [mypy-galaxy.datatypes.dataproviders.external] check_untyped_defs = False [mypy-galaxy.datatypes.dataproviders.dataset] check_untyped_defs = False [mypy-galaxy.tools.bundled.filters.gff.gff_filter_by_feature_count] check_untyped_defs = False [mypy-galaxy.tools.bundled.extract.extract_genomic_dna] check_untyped_defs = False [mypy-galaxy.tool_util.deps.resolvers] check_untyped_defs = False [mypy-galaxy.tool_util.deps.mulled.mulled_update_singularity_containers] check_untyped_defs = False [mypy-galaxy.tool_util.deps.mulled.mulled_search] check_untyped_defs = False [mypy-galaxy.tool_shed.util.hg_util] check_untyped_defs = False [mypy-galaxy.jobs.runners.util.cli.shell.rsh] check_untyped_defs = False [mypy-galaxy.datatypes.display_applications.parameters] check_untyped_defs = False [mypy-tool_shed.webapp.search.tool_search] check_untyped_defs = False [mypy-tool_shed.webapp.search.repo_search] check_untyped_defs = False [mypy-tool_shed.util.hg_util] check_untyped_defs = False [mypy-tool_shed.repository_types.util] check_untyped_defs = False [mypy-tool_shed.managers.repositories] check_untyped_defs = False [mypy-tool_shed.managers.groups] check_untyped_defs = False [mypy-galaxy.workflow.trs_proxy] check_untyped_defs = False [mypy-galaxy.web_stack.handlers] check_untyped_defs = False [mypy-galaxy.web.proxy] check_untyped_defs = False [mypy-galaxy.web.framework.decorators] check_untyped_defs = False [mypy-galaxy.util.tool_shed.common_util] check_untyped_defs = False [mypy-galaxy.tools.expressions.evaluation] check_untyped_defs = False [mypy-galaxy.tool_util.verify] check_untyped_defs = False [mypy-galaxy.tool_util.toolbox.watcher] check_untyped_defs = False [mypy-galaxy.tool_util.toolbox.integrated_panel] check_untyped_defs = False [mypy-galaxy.tool_util.deps.resolvers.resolver_mixins] check_untyped_defs = False [mypy-galaxy.tool_util.deps.resolvers.modules] check_untyped_defs = False [mypy-galaxy.tool_util.cwl.representation] check_untyped_defs = False [mypy-galaxy.tool_shed.util.tool_dependency_util] check_untyped_defs = False [mypy-galaxy.tool_shed.tools.data_table_manager] check_untyped_defs = False [mypy-galaxy.objectstore.s3_multipart_upload] check_untyped_defs = False [mypy-galaxy.model.database_utils] check_untyped_defs = False [mypy-galaxy.model.dataset_collections.matching] check_untyped_defs = False [mypy-galaxy.datatypes.sniff] check_untyped_defs = False [mypy-galaxy.config.schema] check_untyped_defs = False [mypy-galaxy.auth.providers.ldap_ad] check_untyped_defs = False [mypy-galaxy.auth.util] check_untyped_defs = False [mypy-tool_shed.webapp.model.migrate.check] check_untyped_defs = False [mypy-galaxy.web_stack] check_untyped_defs = False [mypy-galaxy.web.legacy_framework.grids] check_untyped_defs = False [mypy-galaxy.web.framework.helpers.grids] check_untyped_defs = False [mypy-galaxy.util.script] check_untyped_defs = False [mypy-galaxy.tools.repositories] check_untyped_defs = False [mypy-galaxy.objectstore.s3] check_untyped_defs = False [mypy-galaxy.objectstore.pithos] check_untyped_defs = False [mypy-galaxy.objectstore.irods] check_untyped_defs = False [mypy-galaxy.objectstore.azure_blob] check_untyped_defs = False [mypy-galaxy.objectstore.cloud] check_untyped_defs = False [mypy-galaxy.model.tool_shed_install] check_untyped_defs = False [mypy-galaxy.model.metadata] check_untyped_defs = False [mypy-galaxy.model] check_untyped_defs = False [mypy-tool_shed.util.admin_util] check_untyped_defs = False [mypy-tool_shed.repository_types.registry] check_untyped_defs = False [mypy-galaxy.tools.errors] check_untyped_defs = False [mypy-galaxy.tools.parameters.validation] check_untyped_defs = False [mypy-galaxy.tools.parameters.dataset_matcher] check_untyped_defs = False [mypy-galaxy.tool_util.verify.script] check_untyped_defs = False [mypy-galaxy.tool_util.deps.resolvers.unlinked_tool_shed_packages] check_untyped_defs = False [mypy-galaxy.tool_shed.util.repository_util] check_untyped_defs = False [mypy-galaxy.model.security] check_untyped_defs = False [mypy-galaxy.model.search] check_untyped_defs = False [mypy-galaxy.model.database_heartbeat] check_untyped_defs = False [mypy-galaxy.model.tool_shed_install.mapping] check_untyped_defs = False [mypy-galaxy.model.store] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.model.orm.scripts] check_untyped_defs = False [mypy-galaxy.managers.taggable] check_untyped_defs = False [mypy-galaxy.jobs.splitters.multi] check_untyped_defs = False [mypy-galaxy.datatypes.display_applications.application] check_untyped_defs = False [mypy-tool_shed.util.metadata_util] check_untyped_defs = False [mypy-galaxy.tools.error_reports.plugins.base_git] check_untyped_defs = False [mypy-galaxy.tool_util.deps.mulled.mulled_build] check_untyped_defs = False [mypy-galaxy.tool_util.deps] check_untyped_defs = False [mypy-galaxy.model.dataset_collections.registry] check_untyped_defs = False [mypy-galaxy.metadata] check_untyped_defs = False [mypy-galaxy.jobs.runners.util.cli.job.lsf] check_untyped_defs = False [mypy-galaxy.authnz.managers] check_untyped_defs = False [mypy-tool_shed.util.repository_util] check_untyped_defs = False [mypy-tool_shed.grids.util] check_untyped_defs = False [mypy-galaxy.tools.error_reports.plugins.gitlab] check_untyped_defs = False [mypy-galaxy.tools.error_reports.plugins.github] check_untyped_defs = False [mypy-galaxy.tool_util.parser.yaml] check_untyped_defs = False [mypy-galaxy.tool_util.parser.xml] check_untyped_defs = False [mypy-galaxy.tool_util.deps.views] check_untyped_defs = False [mypy-galaxy.tool_util.deps.container_classes] check_untyped_defs = False [mypy-galaxy.tool_util.deps.resolvers.conda] check_untyped_defs = False [mypy-galaxy.jobs.rule_helper] check_untyped_defs = False [mypy-galaxy.datatypes.isa] check_untyped_defs = False [mypy-galaxy.datatypes.binary] check_untyped_defs = False [mypy-tool_shed.utility_containers.utility_container_manager] check_untyped_defs = False [mypy-tool_shed.dependencies.attribute_handlers] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.tool_util.parser.cwl] check_untyped_defs = False [mypy-galaxy.tool_shed.galaxy_install.repository_dependencies.repository_dependency_manager] check_untyped_defs = False [mypy-galaxy.datatypes.triples] check_untyped_defs = False [mypy-galaxy.datatypes.tabular] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.datatypes.sequence] check_untyped_defs = False [mypy-test.manual.workflows_scaling] check_untyped_defs = False [mypy-test.manual.gen_history_export_test_artifacts] check_untyped_defs = False [mypy-tool_shed.dependencies.repository.relation_builder] check_untyped_defs = False [mypy-galaxy.visualization.data_providers.basic] check_untyped_defs = False [mypy-galaxy.datatypes.proteomics] check_untyped_defs = False [mypy-galaxy.datatypes.molecules] check_untyped_defs = False [mypy-galaxy.datatypes.interval] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.datatypes.genetics] warn_unreachable = False check_untyped_defs = False [mypy-tool_shed.webapp.model] check_untyped_defs = False [mypy-tool_shed.utility_containers] check_untyped_defs = False [mypy-galaxy.datatypes.registry] check_untyped_defs = False [mypy-galaxy.tools.parameters.dynamic_options] check_untyped_defs = False [mypy-galaxy.tools.parameters] check_untyped_defs = False [mypy-galaxy.tools.bundled.data_source.upload] check_untyped_defs = False [mypy-galaxy.tool_util.lint] check_untyped_defs = False [mypy-test.functional.tools.upload] check_untyped_defs = False [mypy-tool_shed.util.shed_index] check_untyped_defs = False [mypy-tool_shed.grids.repository_review_grids] check_untyped_defs = False [mypy-tool_shed.grids.admin_grids] check_untyped_defs = False [mypy-tool_shed.galaxy_install.dependency_display] check_untyped_defs = False [mypy-galaxy.tools.parameters.meta] check_untyped_defs = False [mypy-galaxy.tools.data_manager.manager] check_untyped_defs = False [mypy-galaxy.tool_util.deps.container_resolvers.mulled] check_untyped_defs = False [mypy-galaxy.managers.context] check_untyped_defs = False [mypy-galaxy.managers.citations] check_untyped_defs = False [mypy-galaxy.config.script] check_untyped_defs = False [mypy-galaxy.tool_shed.cache] check_untyped_defs = False [mypy-galaxy.tool_util.deps.containers] check_untyped_defs = False [mypy-galaxy.managers.users] check_untyped_defs = False [mypy-galaxy.managers.ratable] check_untyped_defs = False [mypy-galaxy.tools.actions.upload] check_untyped_defs = False [mypy-galaxy.tools.actions.model_operations] check_untyped_defs = False [mypy-galaxy.job_execution.output_collect] check_untyped_defs = False [mypy-galaxy.actions.library] check_untyped_defs = False [mypy-galaxy.tool_util.cwl.runtime_actions] check_untyped_defs = False [mypy-galaxy.metadata.set_metadata] check_untyped_defs = False [mypy-galaxy.tools.error_reports] check_untyped_defs = False [mypy-galaxy.jobs.dynamic_tool_destination] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.jobs.runners.state_handler_factory] check_untyped_defs = False [mypy-galaxy.queue_worker] check_untyped_defs = False [mypy-galaxy.jobs.mapper] check_untyped_defs = False [mypy-galaxy.jobs.runners] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.jobs] check_untyped_defs = False [mypy-galaxy.jobs.handler] check_untyped_defs = False [mypy-galaxy.workflow.scheduling_manager] check_untyped_defs = False [mypy-galaxy.jobs.manager] check_untyped_defs = False [mypy-galaxy.tool_shed.galaxy_install.installed_repository_manager] check_untyped_defs = False [mypy-tool_shed.webapp.config] check_untyped_defs = False [mypy-galaxy.main] check_untyped_defs = False [mypy-galaxy.web.framework.middleware.sqldebug] check_untyped_defs = False [mypy-galaxy.tools.recommendations] check_untyped_defs = False [mypy-galaxy.tool_shed.galaxy_install.install_manager] check_untyped_defs = False [mypy-galaxy.jobs.runners.pulsar] check_untyped_defs = False [mypy-galaxy.jobs.runners.pbs] check_untyped_defs = False [mypy-galaxy.jobs.runners.local] check_untyped_defs = False [mypy-galaxy.jobs.runners.kubernetes] check_untyped_defs = False [mypy-galaxy.jobs.runners.godocker] check_untyped_defs = False [mypy-galaxy.jobs.runners.drmaa] check_untyped_defs = False [mypy-galaxy.jobs.runners.condor] check_untyped_defs = False [mypy-galaxy.jobs.runners.cli] check_untyped_defs = False [mypy-galaxy.jobs.runners.chronos] check_untyped_defs = False [mypy-galaxy.jobs.runners.state_handlers.resubmit] check_untyped_defs = False [mypy-integration.objectstore_selection_rules.rules] check_untyped_defs = False [mypy-galaxy.webapps.base.controller] check_untyped_defs = False [mypy-galaxy.jobs.runners.univa] check_untyped_defs = False [mypy-integration.resubmission_runners] check_untyped_defs = False [mypy-tool_shed.webapp.controllers.upload] check_untyped_defs = False [mypy-tool_shed.webapp.controllers.repository_review] check_untyped_defs = False [mypy-tool_shed.webapp.api.repository_revisions] check_untyped_defs = False [mypy-tool_shed.webapp.api.repositories] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.workflow] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.forms] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.data_manager] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.async] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.admin] check_untyped_defs = False [mypy-galaxy.webapps.base.webapp] warn_unreachable = False check_untyped_defs = False [mypy-starlette_context.*] no_implicit_reexport = False [mypy-tool_shed.webapp.buildapp] check_untyped_defs = False [mypy-galaxy.webapps.reports.buildapp] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.buildapp] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.visualization] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.user] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.page] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.history] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.dataset] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.controllers.admin_toolshed] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.users] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.tool_shed_repositories] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.provenance] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.plugins] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.library_contents] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.jobs] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.item_tags] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.history_contents] warn_unreachable = False check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.genomes] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.datasets] check_untyped_defs = False [mypy-galaxy.webapps.galaxy.api.authenticate] check_untyped_defs = False [mypy-tool_shed.webapp.controllers.user] check_untyped_defs = False [mypy-test.functional.test_toolbox] check_untyped_defs = False [mypy-tool_shed.test.base.test_db_util] check_untyped_defs = False [mypy-integration.test_save_job_id_on_datasets] check_untyped_defs = False [mypy-integration.test_config_defaults] check_untyped_defs = False [mypy-test.functional.test_toolbox_pytest] check_untyped_defs = False [mypy-galaxy_test.selenium.test_histories_published] check_untyped_defs = False [mypy-galaxy_test.selenium.test_histories_list] check_untyped_defs = False [mypy-galaxy_test.api.test_history_contents] check_untyped_defs = False [mypy-galaxy_test.api.test_histories] check_untyped_defs = False [mypy-galaxy_test.api.test_folder_contents] check_untyped_defs = False [mypy-integration.test_workflow_sync] check_untyped_defs = False [mypy-integration.test_tool_data_delete] check_untyped_defs = False [mypy-integration.test_shed_tool_tests] check_untyped_defs = False [mypy-integration.test_scripts] check_untyped_defs = False [mypy-integration.test_pulsar_embedded_mq] check_untyped_defs = False [mypy-integration.test_local_job_cancellation] check_untyped_defs = False [mypy-integration.test_job_environments] check_untyped_defs = False [mypy-integration.test_handler_assignment_methods] check_untyped_defs = False [mypy-integration.test_default_permissions] check_untyped_defs = False [mypy-integration.test_data_manager_refgenie] check_untyped_defs = False [mypy-integration.test_data_manager] check_untyped_defs = False [mypy-integration.objectstore.test_swift_objectstore] check_untyped_defs = False [mypy-integration.objectstore._base] check_untyped_defs = False [mypy-test.functional.conftest] check_untyped_defs = False [mypy-integration.test_containerized_jobs] check_untyped_defs = False [mypy-integration.test_chained_dynamic_destinations] check_untyped_defs = False [mypy-integration.test_pulsar_embedded_containers] check_untyped_defs = False [mypy-integration.test_kubernetes_runner] check_untyped_defs = False [mypy-integration.objectstore.test_objectstore_datatype_upload] check_untyped_defs = False [mypy-integration.test_coexecution] check_untyped_defs = False [mypy-integration.test_interactivetools_api] check_untyped_defs = False [mypy-functional.*] check_untyped_defs = False [mypy-manual.*] check_untyped_defs = False # unit tests with prickly typing issues still that need to be worked through carefully [mypy-*.tools.test_tool_dependency_description] check_untyped_defs = False [mypy-*.tools.test_dataset_matcher] check_untyped_defs = False [mypy-*.datatypes.test_vcf] check_untyped_defs = False [mypy-*.datatypes.test_qiime2] check_untyped_defs = False [mypy-*.test_galaxy_mapping] check_untyped_defs = False [mypy-*.workflows.workflow_support] check_untyped_defs = False [mypy-*.workflows.test_modules] check_untyped_defs = False [mypy-*.managers.test_HDAManager] check_untyped_defs = False [mypy-*.managers.test_DatasetManager] check_untyped_defs = False [mypy-*.config.test_path_resolves_to] check_untyped_defs = False [mypy-*.config.test_config_values] check_untyped_defs = False [mypy-*.datatypes.test_storm] check_untyped_defs = False [mypy-*.datatypes.test_bcsl] check_untyped_defs = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/pyproject.toml0000644000175100017510000000202215234436334021110 0ustar00runnerrunner[build-system] build-backend = "setuptools.build_meta" requires = ["setuptools", "setuptools-scm>=8"] [project] dynamic = [ "authors", "dependencies", "description", "license", "optional-dependencies", "readme", "requires-python", "version", ] name = "galaxy-tool-util-models" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Testing", ] keywords = ["Galaxy"] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1785871590.953648 galaxy_tool_util_models-26.1.1/scripts/0000755000175100017510000000000015234436347017673 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/scripts/commit_version.py0000644000175100017510000000266615234436334023310 0ustar00runnerrunner#!/usr/bin/env python # Modify version... import datetime import os import re import subprocess import sys def main(argv): DEV_RELEASE = os.environ.get("DEV_RELEASE", None) == "1" PROJECT_DIRECTORY = os.getcwd() PROJECT_DIRECTORY_NAME = os.path.basename(os.path.abspath(PROJECT_DIRECTORY)) PROJECT_NAME = PROJECT_DIRECTORY_NAME.replace("_", "-") version = argv[1] setup_cfg_path = os.path.join(PROJECT_DIRECTORY, "setup.cfg") if not DEV_RELEASE: history_path = os.path.join(PROJECT_DIRECTORY, "HISTORY.rst") with open(history_path) as f: history = f.read() today = datetime.datetime.today() today_str = today.strftime("%Y-%m-%d") history = history.replace(".dev0", f" ({today_str})") with open(history_path, "w") as f: f.write(history) with open(setup_cfg_path) as f: setup_cfg = f.read() setup_cfg = re.sub( r"^version = [\d\.]+\.dev\d+", f"version = {version}", setup_cfg, count=1, flags=re.MULTILINE ) with open(setup_cfg_path, "w") as f: f.write(setup_cfg) tag = f"galaxy-{PROJECT_NAME}-{version}" shell(["git", "commit", "-m", f"Version {version} of {PROJECT_NAME} (tag {tag}).", "HISTORY.rst", setup_cfg_path]) shell(["git", "tag", tag]) def shell(cmds, **kwds): p = subprocess.Popen(cmds, **kwds) return p.wait() if __name__ == "__main__": main(sys.argv) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/scripts/new_version.py0000644000175100017510000000433515234436334022604 0ustar00runnerrunner#!/usr/bin/env python # Modify version... import os import re import subprocess import sys from packaging.version import Version DEV_RELEASE = os.environ.get("DEV_RELEASE", None) == "1" PROJECT_DIRECTORY = os.getcwd() PROJECT_DIRECTORY_NAME = os.path.basename(os.path.abspath(PROJECT_DIRECTORY)) PROJECT_NAME = PROJECT_DIRECTORY_NAME.replace("_", "-") def main(argv): version = argv[1] version_obj = Version(version) if not DEV_RELEASE: # Discard epoch, release numbers after micro, pre-, post-, dev-release segments and local version new_version_tuple = list(version_obj.release[:3]) if len(new_version_tuple) < 3: new_version_tuple.append(0) else: new_version_tuple[2] += 1 new_version = ".".join(map(str, new_version_tuple)) else: dev_number = version_obj.dev assert dev_number is not None new_version = f"{version_obj.base_version}.dev{dev_number + 1}" history_path = os.path.join(PROJECT_DIRECTORY, "HISTORY.rst") if not DEV_RELEASE: with open(history_path) as f: history = f.read() def extend(from_str, line): from_str += "\n" return history.replace(from_str, from_str + line + "\n") history = extend( ".. to_doc", f""" --------------------- {new_version}.dev0 --------------------- """, ) with open(history_path, "w") as f: f.write(history) setup_cfg_path = os.path.join(PROJECT_DIRECTORY, "setup.cfg") with open(setup_cfg_path) as f: setup_cfg = f.read() if not DEV_RELEASE: setup_cfg = re.sub(r"^version = .*$", f"version = {new_version}.dev0", setup_cfg, count=1, flags=re.MULTILINE) else: setup_cfg = re.sub( r"^version = .*$", rf"version = {new_version}", setup_cfg, count=1, flags=re.MULTILINE, ) with open(setup_cfg_path, "w") as f: f.write(setup_cfg) shell(["git", "commit", "-m", f"Starting work on {PROJECT_NAME} {new_version}", "HISTORY.rst", setup_cfg_path]) def shell(cmds, **kwds): p = subprocess.Popen(cmds, **kwds) return p.wait() if __name__ == "__main__": main(sys.argv) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/scripts/print_version_for_release.py0000644000175100017510000000125415234436334025512 0ustar00runnerrunnerimport os import re from packaging.version import Version _version_re = re.compile(r"^version\s+=\s+(.*)", flags=re.MULTILINE) def main(): DEV_RELEASE = os.environ.get("DEV_RELEASE", None) == "1" PROJECT_DIRECTORY = os.getcwd() setup_cfg_path = os.path.join(PROJECT_DIRECTORY, "setup.cfg") with open(setup_cfg_path) as f: setup_cfg = f.read() version_match = _version_re.search(setup_cfg) assert version_match version = version_match.group(1) if not DEV_RELEASE: version_obj = Version(version) # Strip .devN print(version_obj.base_version) else: print(version) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1785871590.959022 galaxy_tool_util_models-26.1.1/setup.cfg0000644000175100017510000000125615234436347020031 0ustar00runnerrunner[metadata] author = Galaxy Project and Community author_email = galaxy-committers@lists.galaxyproject.org description = Pydantic models for Galaxy tools license = MIT license_files = LICENSE long_description = file: README.rst, HISTORY.rst long_description_content_type = text/x-rst name = galaxy-tool-util-models url = https://github.com/galaxyproject/galaxy version = 26.1.1 [options] include_package_data = True install_requires = pydantic>=2.7.4 pydantic-extra-types typing-extensions package_dir = =src packages = find_namespace: python_requires = >=3.8 [options.extras_require] test = pytest [options.packages.find] where = src [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1785871590.9510221 galaxy_tool_util_models-26.1.1/src/0000755000175100017510000000000015234436347016773 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1785871590.9510221 galaxy_tool_util_models-26.1.1/src/galaxy/0000755000175100017510000000000015234436347020260 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1785871590.956049 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/0000755000175100017510000000000015234436347023635 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/__init__.py0000644000175100017510000010275615234436334025755 0ustar00runnerrunner"""Define the ParsedTool model representing metadata extracted from a tool's source. This is abstraction exported by newer tool shed APIS (circa 2024) and should be sufficient for reasoning about tool state externally from Galaxy. """ import re from typing import ( Any, ClassVar, Dict, List, Optional, Set, Tuple, Union, ) from pydantic import ( AnyUrl, BaseModel, ConfigDict, Discriminator, Field, field_validator, model_serializer, model_validator, RootModel, SerializerFunctionWrapHandler, Tag, ValidationError, ) from pydantic_core import PydanticCustomError from typing_extensions import ( Annotated, Literal, NotRequired, TypedDict, ) from ._base import ( CollectionType, StrictModel, ToolSourceBaseModel, ) from .assertions import assertions from .parameters import ToolParameterT from .test_job import Job from .tool_outputs import ( IncomingToolOutput, IncomingToolOutputCollection, IncomingToolOutputDataset, ToolOutput, ) from .tool_source import ( Citation, Container, ContainerRequirement, HelpContent, JavascriptRequirement, OutputCompareType, PackageRequirement, ResourceRequirement, SetEnvironmentRequirement, Stdio, XrefDict, YamlTemplateConfigFile, ) from .yaml_parameters import YamlGalaxyToolParameter def normalize_dict(values, keys: List[str]): for key in keys: items = values.get(key) if isinstance(items, dict): # dict-of-dicts format # Transform dict-of-dicts to list-of-dicts values[key] = [{"name": k, **v} for k, v in items.items()] # Tool ID: lowercase, leading letter, letters/digits/'_'/'-'. _TOOL_ID_RE = re.compile(r"^[a-z][a-z0-9_-]*$") TOOL_ID_PATTERN = r"^[a-z][a-z0-9_-]*$" # Templated ecmascript inside `shell_command` / `configfiles[*].content`. # Pull every $() and extract the *leading* 'inputs.' identifier. # Only the top-level name is checked, so nested references like # 'inputs.cond.test_parameter' or 'inputs.repeat[0].x' resolve against # the conditional / repeat / section's own top-level name -- no false # positives for nested structures. Computed/aliased references (e.g. # 'var x = inputs; x.foo') are intentionally not parsed; the goal is # catching obvious typos cheaply, not modelling ecmascript scope. _TEMPLATE_BLOCK_RE = re.compile(r"\$\((.*?)\)", re.DOTALL) _INPUTS_REF_RE = re.compile(r"\binputs\.([A-Za-z_][A-Za-z0-9_]*)") def _command_input_refs(text: Optional[str]) -> Set[str]: refs: Set[str] = set() if not text: return refs for block in _TEMPLATE_BLOCK_RE.findall(text): for match in _INPUTS_REF_RE.findall(block): refs.add(match) return refs def format_validation_errors(exc: ValidationError) -> List[str]: """Distill a pydantic ValidationError into a human-readable list. Each entry is `: `, or just `` for model-level errors with no location. Suitable for surfacing directly to a user (in the agent's bullet list, or as an API 4xx body). """ lines: List[str] = [] for err in exc.errors(): loc_parts = [str(p) for p in err.get("loc", ()) if p not in ("__root__",)] loc = ".".join(loc_parts) msg = err.get("msg", "validation error") lines.append(f"{loc}: {msg}" if loc else msg) return lines class _DynamicToolSourceBase(ToolSourceBaseModel): # extra="forbid" rejects unknown top-level keys (e.g. a stray `argument:` at # the tool level), matching the strict-narrow stance on `inputs`. model_config = ConfigDict( extra="forbid", field_title_generator=lambda field_name, field_info: field_name.lower(), ) id: Annotated[ Optional[str], Field( description=( "Unique identifier for the tool. Lowercase, must start with a letter, " "may contain letters, digits, '_' and '-'." ), examples=["my-cool-tool"], min_length=3, max_length=255, pattern=TOOL_ID_PATTERN, ), ] = None version: Annotated[Optional[str], Field(description="Version for the tool.", examples=["0.1.0"])] = None name: Annotated[ str, Field( description="The name of the tool, displayed in the tool menu. This is not the same as the tool id, which is a unique identifier for the tool.", min_length=5, ), ] description: Annotated[ Optional[str], Field( description="The description is displayed in the tool menu immediately following the hyperlink for the tool." ), ] = None configfiles: Annotated[ Optional[List[YamlTemplateConfigFile]], Field(description="A list of config files for this tool.") ] = None requirements: Annotated[ Optional[List[Union[JavascriptRequirement, ResourceRequirement, ContainerRequirement]]], Field( description="A list of requirements needed to execute this tool. These can be javascript expressions, resource requirements or container images." ), ] = [] shell_command: Annotated[ str, Field( title="shell_command", description="A string that contains the command to be executed. Parameters can be referenced inside $().", examples=["head -n '$(inputs.num_lines)' '$(inputs.input_file.path)' > output.txt"], ), ] inputs: List[YamlGalaxyToolParameter] = [] outputs: List[IncomingToolOutput] = [] citations: Optional[List[Citation]] = None license: Annotated[ Optional[str], Field( description="A full URI or a a short [SPDX](https://spdx.org/licenses/) identifier for a license for this tool wrapper. The tool wrapper license can be independent of the underlying tool license. This license covers the tool yaml and associated scripts shipped with the tool.", examples=["MIT"], ), ] = None edam_operations: Optional[List[str]] = None edam_topics: Optional[List[str]] = None xrefs: Optional[List[XrefDict]] = None profile: Optional[float] = None help: Annotated[Optional[HelpContent], Field(description="Help text shown below the tool interface.")] = None # NOTE: `tests` is intentionally NOT declared here. It lives on the concrete # subclasses (`UserToolSource`, `YamlToolSource`) so that the slim # `UserToolSourceAuthoringView` can inherit everything *except* the test # field. The test-assertion DSL pulled in by `tests` is ~70% of the JSON # schema; keeping it off the authoring view is what shrinks the # structured-output schema handed to LLM tool-generation agents. @model_validator(mode="before") @classmethod def normalize_items(cls, values): if isinstance(values, dict): normalize_dict(values, ["inputs", "outputs"]) return values @field_validator("name", "version", mode="after") @classmethod def _reject_blank_strings(cls, v: Optional[str]) -> Optional[str]: if v is not None and not v.strip(): raise PydanticCustomError( "dynamic_tool.blank_string", "must not be empty or whitespace", ) return v @model_validator(mode="after") def _check_input_refs(self) -> "_DynamicToolSourceBase": declared_inputs: Set[str] = {param.root.name for param in self.inputs} referenced: Set[str] = _command_input_refs(self.shell_command) for configfile in self.configfiles or []: referenced |= _command_input_refs(configfile.content) undeclared = sorted(referenced - declared_inputs) if undeclared: joined = "; ".join( f"references inputs.{name} but no input named '{name}' is declared" for name in undeclared ) raise PydanticCustomError( "dynamic_tool.undeclared_input_ref", joined, ) return self @model_validator(mode="after") def _check_output_claims(self) -> "_DynamicToolSourceBase": errors: List[str] = [] for output in self.outputs: if isinstance(output, IncomingToolOutputDataset): if not output.from_work_dir and not output.discover_datasets: errors.append( f"output '{output.name}' must set 'from_work_dir' or 'discover_datasets' " "(otherwise its bytes will never be claimed from the working directory)" ) elif isinstance(output, IncomingToolOutputCollection): if not output.discover_datasets: errors.append( f"output collection '{output.name}' must set 'discover_datasets' " "(otherwise no elements will be claimed from the working directory)" ) if errors: raise PydanticCustomError( "dynamic_tool.output_unclaimed", "; ".join(errors), ) return self class CondaPackage(BaseModel): """A conda package a tool wraps -- a name plus an optional pinned version. Inferred by tool-authoring agents (from a tool's command/configfiles) so a verified ``quay.io/biocontainers`` image can be resolved deterministically rather than guessed. ``UserToolSource`` persists only a single ``container`` string and has no package-requirement field, so this is never stored on a tool -- it is transient input to container resolution. """ name: str version: Optional[str] = None # Schema-narrowed view of ``UserToolSource`` for LLM tool authoring. # # This is the *parent* of ``UserToolSource`` and carries every field and # validator except ``tests``. Authoring agents (see ``galaxy.agents.custom_tool``) # point their structured-output ``output_type`` at this view so the JSON schema # the model must satisfy omits the test-assertion DSL — ~70% of # ``UserToolSource``'s ~150 KB schema and never written in the first generation # pass. Shrinking the schema cuts per-call token cost and sidesteps the # nested-``$defs`` grammar failures some local inference backends hit (see # ``CustomToolAgent._handle_model_http_error``). # # A produced view is a strict subset of ``UserToolSource``; convert with # ``UserToolSource.model_validate(view.model_dump(by_alias=True))`` before storing # or linting so downstream types stay honest. # # NOTE: the class docstring below is published verbatim as the structured-output # tool *description* sent to the model on every call — keep it model-facing and # concise; put implementation rationale in comments like this one, not the # docstring. class UserToolSourceAuthoringView(_DynamicToolSourceBase): """A Galaxy user-defined tool: a containerized shell command wrapped with typed inputs and outputs. Provide the tool's identity (``id``, ``name``, ``version``), a ``container`` image to run in, and a ``shell_command`` that references inputs as ``$(inputs.NAME)`` for scalar values or ``$(inputs.NAME.path)`` for files. Declare every referenced input under ``inputs`` and every produced file under ``outputs`` (each output must set ``from_work_dir`` or ``discover_datasets``). """ class_: Annotated[Literal["GalaxyUserTool"], Field(alias="class")] container: Annotated[ str, Field(description="Container image to use for this tool.", examples=["quay.io/biocontainers/python:3.13"]) ] # Required here (it's optional on the base for stored/legacy rows). Galaxy's # linter rejects a versionless tool, so forcing it into the structured-output # ``required`` set stops the model dropping it -- notably on a retry, where the # model would otherwise regenerate without it and trip ``ToolVersionMissing``. version: Annotated[str, Field(description="Version for the tool.", examples=["0.1.0"])] # Required here (optional on the base / canonical model). Under grammar-constrained # structured output, an optional list is one the model is free to never emit -- and # weak models do exactly that, stopping after ``container`` and dropping ``inputs``/ # ``outputs`` entirely (then ``shell_command`` references an input that was never # declared and validation fails). Forcing both into the ``required`` set compels the # model to emit them. ``UserToolSource`` restores the lenient defaults so stored / # API-authored tools may still omit them. An empty list is allowed -- this forces the # key to be present, not non-empty. inputs: List[YamlGalaxyToolParameter] outputs: List[IncomingToolOutput] # Field declaration order puts subclass fields (class_, container) after # parent ones, which serializes them at the end. Re-order on dump so the # YAML the tool editor renders leads with identity + runtime. _CANONICAL_FIELD_ORDER: ClassVar[Tuple[str, ...]] = ( "class_", "id", "name", "version", "description", "container", "requirements", "shell_command", "configfiles", "inputs", "outputs", "citations", "license", "profile", "edam_operations", "edam_topics", "xrefs", "help", "tests", ) @field_validator("container", mode="after") @classmethod def _reject_blank_container(cls, value: str) -> str: if not value or not value.strip(): raise PydanticCustomError( "dynamic_tool.blank_container", "container must not be empty", ) return value @model_serializer(mode="wrap") def _canonical_order(self, handler: SerializerFunctionWrapHandler, info: Any): # Runs for both direct ``model_dump`` calls and nested serialization # (e.g. inside ``UnprivilegedToolResponse``), where pydantic_core # bypasses ``model_dump`` on the child. # No return annotation so pydantic derives the output JSON schema from # the model fields instead of treating the result as opaque dict. data = handler(self) if not isinstance(data, dict): return data by_alias = bool(getattr(info, "by_alias", False)) fields = type(self).model_fields ordered: Dict[str, Any] = {} for field_name in self._CANONICAL_FIELD_ORDER: field_info = fields.get(field_name) key = (field_info.alias or field_name) if by_alias and field_info and field_info.alias else field_name if key in data: ordered[key] = data.pop(key) # Preserve any unexpected keys (forward compat) at the end. ordered.update(data) return ordered class UserToolSource(UserToolSourceAuthoringView): """Full unprivileged tool source, including the optional ``tests`` block. This is the model persisted and validated on the API surface (``DynamicUnprivilegedToolCreatePayload.representation``). LLM authoring uses the slimmer ``UserToolSourceAuthoringView`` parent; ``tests`` is added back here so direct authors and stored rows can still carry tests. """ # ``version`` is required (inherited from UserToolSourceAuthoringView). A stored # row that predates the requirement won't validate; ``lift_user_tool_source`` # returns it as the raw dict with status "invalid" so its author still sees it. tests: Optional[List["YamlToolTest"]] = None # Restore lenient defaults: the required-ness of ``inputs``/``outputs`` on the # authoring view is a structured-output nudge for the LLM only. The canonical # model (API surface, stored rows) must still accept tools that omit them. inputs: List[YamlGalaxyToolParameter] = [] outputs: List[IncomingToolOutput] = [] class YamlToolSource(_DynamicToolSourceBase): class_: Annotated[Literal["GalaxyTool"], Field(alias="class")] container: Annotated[ Optional[str], Field( description="Container image to use for this tool.", examples=["quay.io/biocontainers/python:3.13"], ), ] = None tests: Optional[List["YamlToolTest"]] = None DynamicToolSources = Annotated[Union[UserToolSource, YamlToolSource], Field(discriminator="class_")] # --------------------------------------------------------------------------- # Schema-drift "lift" for stored DynamicTool.value rows. # # The strict `UserToolSource` schema is the single source of truth for both # input and output. Stored rows from older Galaxy versions may carry fields # that the current schema rejects (e.g. legacy internal-model fields after the # YAML narrowing) or values that fail tightened constraints (e.g. a future # `container` regex). `lift_user_tool_source` validates against the strict # schema and: # - on success: returns ("ok", parsed_model, []). # - on `extra_forbidden`-only failure: strips the offending paths and # re-validates. Returns ("lifted", parsed_model, dropped_paths). # - on any other failure: returns ("invalid", original_dict, error_summary). # The endpoint exposes this so legacy/broken rows don't crash the API -- e.g. # a stored row that predates the required `version` comes back as the raw dict # with status "invalid", so its author can still see and fix what they defined. # --------------------------------------------------------------------------- LiftStatus = Literal["ok", "lifted", "invalid"] def _navigable_path(value: Any, loc: tuple) -> Tuple[Optional[Any], List[Any]]: """Walk `loc` against the structure of `value`, skipping steps that don't correspond to a real key/index (pydantic inserts discriminator literals like `"data"` into the loc for tagged unions). Returns the parent container of the leaf and the cleaned path components, or (None, []) if the path can't be resolved.""" cur: Any = value if not loc: return cur, [] *prefix, leaf = loc cleaned: List[Any] = [] for step in prefix: if isinstance(cur, list) and isinstance(step, int) and 0 <= step < len(cur): cur = cur[step] cleaned.append(step) elif isinstance(cur, dict) and step in cur: cur = cur[step] cleaned.append(step) # else: skip — discriminator tag or stale path cleaned.append(leaf) return cur, cleaned def _format_loc(value: Any, loc: tuple) -> str: _parent, cleaned = _navigable_path(value, loc) return ".".join(str(p) for p in cleaned if p != "representation") def _strip_path(value: dict, loc: tuple) -> bool: """Remove the field at the given pydantic error `loc` from `value`. Returns True if a key was actually removed.""" parent, cleaned = _navigable_path(value, loc) if not cleaned or not isinstance(parent, dict): return False leaf = cleaned[-1] if leaf in parent: del parent[leaf] return True return False def lift_user_tool_source( value: dict, ) -> Tuple[LiftStatus, Union["UserToolSource", Dict[str, Any]], List[str]]: """Validate `value` against the strict UserToolSource, lifting drift where safe. See module docstring above for the contract. """ import copy try: return ("ok", UserToolSource.model_validate(value), []) except ValidationError as e: errors = e.errors() extra_forbidden = [err for err in errors if err.get("type") == "extra_forbidden"] other = [err for err in errors if err.get("type") != "extra_forbidden"] if extra_forbidden and not other: stripped = copy.deepcopy(value) dropped: List[str] = [] for err in extra_forbidden: loc = tuple(err["loc"]) if _strip_path(stripped, loc): dropped.append(_format_loc(value, loc)) try: return ("lifted", UserToolSource.model_validate(stripped), dropped) except ValidationError as e2: errors = e2.errors() summary = [f"{_format_loc(value, tuple(err['loc']))}: {err.get('msg', err.get('type', ''))}" for err in errors] return ("invalid", value, summary) class ParsedTool(ToolSourceBaseModel): id: str version: Optional[str] name: str description: Optional[str] requirements: List[ Union[PackageRequirement, SetEnvironmentRequirement, ResourceRequirement, JavascriptRequirement] ] = Field(default_factory=list) containers: List[Container] = Field(default_factory=list) stdio: Stdio = Field(default_factory=Stdio) inputs: List[ToolParameterT] outputs: List[ToolOutput] citations: List[Citation] license: Optional[str] profile: Optional[str] edam_operations: List[str] edam_topics: List[str] xrefs: List[XrefDict] help: Optional[HelpContent] class BaseTestOutputModel(StrictModel): model_config = ConfigDict(extra="forbid", title="BaseTestOutputModel") file: Annotated[ Optional[str], Field( title="File", description=( "Name of the output file stored in the target `test-data` directory that will be used to " "compare against the results of executing the tool via the functional test framework." ), ), ] = None path: Annotated[ Optional[str], Field(title="Path", description="Filesystem path to a local output file used for comparison."), ] = None location: Annotated[ Optional[AnyUrl], Field( title="Location", description=( "URL that points to a remote output file that will be downloaded and used for output " "comparison. Use only when the file cannot be included in the `test-data` folder. May be " "combined with `file` (downloads when missing on disk) or used alone (filename inferred " "from the URL). A `checksum` is also used to verify the download when provided." ), ), ] = None ftype: Annotated[ Optional[str], Field( title="File Type", description=( "If specified, this value is checked against the corresponding output's data type. " "If these do not match, the test will fail." ), ), ] = None sort: Annotated[ Optional[bool], Field( title="Sort", description=( "Applies only if `compare` is `diff`, `re_match` or `re_match_multiline`. Sorts the lines " "of the history data set before comparison; for `diff` and `re_match` the local file is " "also sorted. Useful for non-deterministic output." ), ), ] = None compare: Annotated[ Optional[OutputCompareType], Field( title="Compare", description="Comparison mode used when matching the output against the reference file.", ), ] = None checksum: Annotated[ Optional[str], Field( title="Checksum", description=( "The target output's checksum should match the value specified here, in the form " "`hash_type$hash_value` (e.g. `sha1$8156d7ca0f46ed7abac98f82e36cfaddb2aca041`). Useful " "for large static files where uploading the whole file is inconvenient." ), ), ] = None metadata: Annotated[ Optional[Dict[str, Any]], Field( title="Metadata", description="Mapping of metadata keys to expected values for this output.", ), ] = None asserts: Annotated[ Optional[assertions], Field(title="Asserts", description="Assertions about the content of the output."), ] = None delta: Annotated[ Optional[int], Field( title="Delta", description=( "If `compare` is set to `sim_size`, the maximum allowed absolute size difference (in " "bytes) between the generated data set and the reference file in `test-data/`. Default " "is 10000 bytes. Can be combined with `delta_frac`." ), ), ] = None delta_frac: Annotated[ Optional[float], Field( title="Delta Frac", description=( "If `compare` is set to `sim_size`, the maximum allowed relative size difference between " "the generated data set and the reference file in `test-data/`. 0.1 means the generated " "file can differ by at most 10%. Default is not to check for relative size difference. " "Can be combined with `delta`." ), ), ] = None lines_diff: Annotated[ Optional[int], Field( title="Lines Diff", description=( "Applies when `compare` is set to `diff`, `re_match`, or `contains`. For `diff`, the " "number of lines of difference to allow (a modified line counts as two: one added, one " "removed)." ), ), ] = None decompress: Annotated[ Optional[bool], Field( title="Decompress", description=( "If true, decompress files before comparison. Applies to assertions expressed with " "`assert_contents` or `compare` set to anything but `sim_size`. Useful for testing " "compressed outputs that are non-deterministic despite having deterministic decompressed " "contents. By default, only files compressed with bz2, gzip and zip are automatically " "decompressed." ), ), ] = None class TestDataOutputAssertions(BaseTestOutputModel): model_config = ConfigDict(extra="forbid", title="TestDataOutputAssertions") class_: Optional[Literal["File"]] = Field("File", alias="class", title="Class") class TestCollectionCollectionElementAssertions(StrictModel): model_config = ConfigDict(extra="forbid", title="TestCollectionCollectionElementAssertions") class_: Optional[Literal["Collection"]] = Field("Collection", alias="class", title="Class") elements: Annotated[ Optional[Dict[str, "TestCollectionElementAssertion"]], Field(title="Elements"), ] = None element_tests: Annotated[ Optional[Dict[str, "TestCollectionElementAssertion"]], Field(title="Element Tests"), ] = None class TestCollectionDatasetElementAssertions(BaseTestOutputModel): model_config = ConfigDict(extra="forbid", title="TestCollectionDatasetElementAssertions") class_: Optional[Literal["File"]] = Field("File", alias="class", title="Class") def _discriminate_collection_element(v): if isinstance(v, dict): if v.get("class") == "Collection": return "Collection" return "File" if isinstance(v, TestCollectionCollectionElementAssertions): return "Collection" if isinstance(v, TestCollectionDatasetElementAssertions): return "File" return None TestCollectionElementAssertion = Annotated[ Union[ Annotated[TestCollectionDatasetElementAssertions, Tag("File")], Annotated[TestCollectionCollectionElementAssertions, Tag("Collection")], ], Discriminator(_discriminate_collection_element), ] TestCollectionCollectionElementAssertions.model_rebuild() class CollectionAttributes(StrictModel): model_config = ConfigDict(extra="forbid", title="CollectionAttributes") collection_type: Annotated[CollectionType, Field(title="Collection Type")] = None class TestCollectionOutputAssertions(StrictModel): model_config = ConfigDict(extra="forbid", title="TestCollectionOutputAssertions") class_: Optional[Literal["Collection"]] = Field("Collection", alias="class", title="Class") elements: Annotated[ Optional[Dict[str, TestCollectionElementAssertion]], Field(title="Elements"), ] = None element_tests: Annotated[ Optional[Dict[str, "TestCollectionElementAssertion"]], Field(title="Element Tests"), ] = None element_count: Annotated[Optional[int], Field(title="Element Count")] = None attributes: Annotated[Optional[CollectionAttributes], Field(title="Attributes")] = None collection_type: Annotated[CollectionType, Field(title="Collection Type")] = None TestOutputLiteral = Union[bool, int, float, str] def _discriminate_output(v): if isinstance(v, dict): if v.get("class") == "Collection": return "Collection" return "File" if isinstance(v, TestCollectionOutputAssertions): return "Collection" if isinstance(v, TestDataOutputAssertions): return "File" if isinstance(v, (bool, int, float, str)): return "scalar" return None TestOutputAssertions = Annotated[ Union[ Annotated[TestCollectionOutputAssertions, Tag("Collection")], Annotated[TestDataOutputAssertions, Tag("File")], Annotated[TestOutputLiteral, Tag("scalar")], ], Discriminator(_discriminate_output), ] TestInputValue = Union[bool, int, float, str, List[Any], Dict[str, Any]] class YamlTestCredentialValue(StrictModel): model_config = ConfigDict(extra="forbid", title="YamlTestCredentialValue") name: Annotated[str, Field(title="Name", description="Name of the credential variable or secret.")] value: Annotated[str, Field(title="Value", description="Value of the credential variable or secret.")] class YamlTestCredential(StrictModel): model_config = ConfigDict(extra="forbid", title="YamlTestCredential") name: Annotated[str, Field(title="Name", description="Name of the credentials group.")] variables: Annotated[ List[YamlTestCredentialValue], Field(title="Variables", description="Variables exposed to the tool environment."), ] = [] secrets: Annotated[ List[YamlTestCredentialValue], Field(title="Secrets", description="Secrets exposed to the tool environment."), ] = [] version: Annotated[ Optional[str], Field(title="Version", description="Version of the credential definition."), ] = None class YamlToolTest(BaseModel): """In-tool test case as authored in YAML tool fixtures.""" model_config = ConfigDict(extra="forbid") doc: Annotated[Optional[str], Field(description="Human-readable description of this test case.")] = None inputs: Annotated[ Optional[Dict[str, TestInputValue]], Field(description="Mapping of input parameter names to test values."), ] = None outputs: Annotated[ Dict[str, TestOutputAssertions], Field(description="Mapping of output names to expected values or assertions."), ] = {} assert_stdout: Annotated[ Optional[assertions], Field(description="Assertions to apply against the tool's standard output."), ] = None assert_stderr: Annotated[ Optional[assertions], Field(description="Assertions to apply against the tool's standard error."), ] = None command: Annotated[ Optional[assertions], Field(description="Assertions to apply against the executed command line."), ] = None expect_exit_code: Annotated[ Optional[int], Field(description="Expected process exit code."), ] = None expect_failure: Annotated[ Optional[bool], Field(description="If true, the tool is expected to produce an error."), ] = None expect_test_failure: Annotated[ Optional[bool], Field(description="If true, the test itself is expected to fail."), ] = None credentials: Annotated[ Optional[List[YamlTestCredential]], Field(description="Credentials to inject for this test case."), ] = None UserToolSource.model_rebuild() YamlToolSource.model_rebuild() # Loose alias retained for TestJobDict / TypedDict consumers where helpers # still tolerate Dict[str, Any]. The strict, validated shape is `Job` (see # galaxy.tool_util_models.test_job). JobDict = Dict[str, Any] class TestJob(StrictModel): model_config = ConfigDict(extra="forbid", title="TestJob") doc: Annotated[ Optional[str], Field(title="Doc", description="Describes the purpose of the test."), ] = None job: Annotated[ Job, Field( title="Job", description=( "Defines the job to execute. Can be a path to a file or an inline dictionary describing " "the job inputs." ), ), ] outputs: Annotated[ Dict[str, TestOutputAssertions], Field( title="Outputs", description=( "Defines assertions about outputs (datasets, collections or parameters). Each key " "corresponds to a labeled output; values are dictionaries describing the expected output." ), ), ] expect_failure: Annotated[ Optional[bool], Field( title="Expect Failure", description="If true, the workflow is expected to produce an error.", ), ] = False class Tests(RootModel[List[TestJob]]): model_config = ConfigDict( title="GalaxyWorkflowTests", json_schema_extra={ "$schema": "https://json-schema.org/draft/2020-12/schema", "description": ( "Galaxy workflow tests file — a YAML list of test entries asserting the expected " "inputs and outputs of a workflow run." ), }, ) # TODO: typed dict versions of all thee above for verify code - make this Dict[str, Any] here more # specific. OutputChecks = Union[TestOutputLiteral, Dict[str, Any]] OutputsDict = Dict[str, OutputChecks] class JobTestDict(TypedDict): doc: NotRequired[str] job: NotRequired[JobDict] expect_failure: NotRequired[bool] outputs: OutputsDict TestDicts = List[JobTestDict] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/_base.py0000644000175100017510000000135115234436334025254 0ustar00runnerrunner"""Base model classes for tool utilities.""" from typing import Optional from pydantic import ( AfterValidator, BaseModel, ConfigDict, ) from typing_extensions import Annotated class ToolSourceBaseModel(BaseModel): pass class StrictModel(BaseModel): model_config = ConfigDict(extra="forbid") def _check_collection_type(v: str) -> str: if len(v) == 0: raise ValueError("Invalid empty collection_type specified.") for level in v.split(":"): if level not in ("list", "paired", "paired_or_unpaired", "record", "sample_sheet"): raise ValueError(f"Invalid collection_type specified [{v}]") return v CollectionType = Annotated[Optional[str], AfterValidator(_check_collection_type)] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/_types.py0000644000175100017510000000376415234436334025520 0ustar00runnerrunner"""Type utilities for building pydantic models for tool parameters. Lots of mypy exceptions in here - this code is all well tested and the exceptions are fine otherwise because we're using the typing system to interact with pydantic and build runtime models not to use mypy to type check static code. """ from typing import ( Any, cast, Dict, List, Optional, Type, Union, ) # https://stackoverflow.com/questions/56832881/check-if-a-field-is-typing-optional from typing_extensions import ( Annotated, get_args, get_origin, ) def optional(type: Type) -> Type: return_type: Type = Optional[type] # type: ignore[assignment] return return_type def optional_if_needed(type: Type, is_optional: bool) -> Type: return_type: Type = type if is_optional: return_type = optional(type) return return_type def union_type(args: List[Type]) -> Type: return Union[tuple(args)] # type: ignore[return-value] def list_type(arg: Type) -> Type: return List[arg] # type: ignore[valid-type] def dict_type(key: Type, val: Type) -> Type: return Dict[key, val] # type: ignore[valid-type] def cast_as_type(arg) -> Type: return cast(Type, arg) def is_optional(field) -> bool: f = _strip_annotation(field) if f == type(None): # noqa: E721 return True origin = get_origin(f) if origin is Union: return any(is_optional(f) for f in get_args(f)) return False def _strip_annotation(field): is_annotation = get_origin(field) is Annotated if is_annotation: args = get_args(field) return args[0] else: return field def expand_annotation(field: Type, new_annotations: List[Any]) -> Type: is_annotation = get_origin(field) is Annotated if is_annotation: args = get_args(field) # noqa: F841 return Annotated[(args[0], *args[1:], *new_annotations)] # type: ignore[return-value] else: return Annotated[(field, *new_annotations)] # type: ignore[return-value] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/assertions.py0000644000175100017510000043304415234436334026405 0ustar00runnerrunner"""This module is auto-generated, please do not modify.""" import re import typing from pydantic import ( BaseModel, BeforeValidator, ConfigDict, Field, model_validator, RootModel, StrictFloat, StrictInt, ) from typing_extensions import ( Annotated, Literal, ) BYTES_PATTERN = re.compile(r"^(0|[1-9][0-9]*)([kKMGTPE]i?)?$") class AssertionModel(BaseModel): model_config = ConfigDict( extra="forbid", ) def check_bytes(v: typing.Any) -> typing.Any: if isinstance(v, str): assert BYTES_PATTERN.match(v), "Not valid bytes string" return v def check_center_of_mass(v: typing.Any): assert isinstance(v, str) split_parts = v.split(",") assert len(split_parts) == 2 for part in split_parts: assert float(part.strip()) return v def check_regex(v: typing.Any): assert isinstance(v, str) try: re.compile(v) except re.error: raise AssertionError(f"Invalid regular expression {v}") return v def check_non_negative_if_set(v: typing.Any): if v is not None: try: assert float(v) >= 0 except TypeError: raise AssertionError(f"Invalid type found {v}") return v def check_non_negative_if_int(v: typing.Any): if v is not None and isinstance(v, int): assert v >= 0 return v has_line_line_description = """The full line of text to search for in the output.""" has_line_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_line_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_line_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_line_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_line_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_line_model(AssertionModel): """base model for has_line describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_line_model") line: str = Field( ..., title="Line", description=has_line_line_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_line_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_line_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_line_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_line_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_line_negate_description, ) class base_has_line_model_relaxed(AssertionModel): """base model for has_line describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_line_model_relaxed") line: str = Field( ..., title="Line", description=has_line_line_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_line_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_line_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_line_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_line_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_line_negate_description, ) class has_line_model(base_has_line_model): r"""Asserts the specified output contains the line specified by the argument line. The exact number of occurrences can be optionally specified by the argument n""" model_config = ConfigDict(extra="forbid", title="Assert Has Line") that: Literal["has_line"] = Field("has_line", title="That") class has_line_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Line (Nested)") has_line: base_has_line_model = Field(..., title="Assert Has Line") class has_line_model_relaxed(base_has_line_model_relaxed): r"""Asserts the specified output contains the line specified by the argument line. The exact number of occurrences can be optionally specified by the argument n""" model_config = ConfigDict(extra="forbid", title="Assert Has Line (Relaxed)") that: Literal["has_line"] = Field("has_line", title="That") has_line_matching_expression_description = """The regular expressions to attempt match in the output.""" has_line_matching_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_line_matching_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_line_matching_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_line_matching_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_line_matching_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_line_matching_model(AssertionModel): """base model for has_line_matching describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_line_matching_model") expression: str = Field( ..., title="Expression", description=has_line_matching_expression_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_line_matching_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_line_matching_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_line_matching_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_line_matching_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_line_matching_negate_description, ) class base_has_line_matching_model_relaxed(AssertionModel): """base model for has_line_matching describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_line_matching_model_relaxed") expression: str = Field( ..., title="Expression", description=has_line_matching_expression_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_line_matching_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_line_matching_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_line_matching_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_line_matching_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_line_matching_negate_description, ) class has_line_matching_model(base_has_line_matching_model): r"""Asserts the specified output contains a line matching the regular expression specified by the argument expression. If n is given the assertion checks for exactly n occurrences.""" model_config = ConfigDict(extra="forbid", title="Assert Has Line Matching") that: Literal["has_line_matching"] = Field("has_line_matching", title="That") class has_line_matching_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Line Matching (Nested)") has_line_matching: base_has_line_matching_model = Field(..., title="Assert Has Line Matching") class has_line_matching_model_relaxed(base_has_line_matching_model_relaxed): r"""Asserts the specified output contains a line matching the regular expression specified by the argument expression. If n is given the assertion checks for exactly n occurrences.""" model_config = ConfigDict(extra="forbid", title="Assert Has Line Matching (Relaxed)") that: Literal["has_line_matching"] = Field("has_line_matching", title="That") has_n_lines_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_lines_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_n_lines_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_lines_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_lines_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_n_lines_model(AssertionModel): """base model for has_n_lines describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_n_lines_model") n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_n_lines_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_n_lines_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_n_lines_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_n_lines_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_n_lines_negate_description, ) class base_has_n_lines_model_relaxed(AssertionModel): """base model for has_n_lines describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_n_lines_model_relaxed") n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_n_lines_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_n_lines_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_n_lines_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_n_lines_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_n_lines_negate_description, ) class has_n_lines_model(base_has_n_lines_model): r"""Asserts the specified output contains ``n`` lines allowing for a difference in the number of lines (delta) or relative differebce in the number of lines""" model_config = ConfigDict(extra="forbid", title="Assert Has N Lines") that: Literal["has_n_lines"] = Field("has_n_lines", title="That") class has_n_lines_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Lines (Nested)") has_n_lines: base_has_n_lines_model = Field(..., title="Assert Has N Lines") class has_n_lines_model_relaxed(base_has_n_lines_model_relaxed): r"""Asserts the specified output contains ``n`` lines allowing for a difference in the number of lines (delta) or relative differebce in the number of lines""" model_config = ConfigDict(extra="forbid", title="Assert Has N Lines (Relaxed)") that: Literal["has_n_lines"] = Field("has_n_lines", title="That") has_text_text_description = """The text to search for in the output.""" has_text_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_text_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_text_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_text_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_text_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_text_model(AssertionModel): """base model for has_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_text_model") text: str = Field( ..., title="Text", description=has_text_text_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_text_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_text_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_text_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_text_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_text_negate_description, ) class base_has_text_model_relaxed(AssertionModel): """base model for has_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_text_model_relaxed") text: str = Field( ..., title="Text", description=has_text_text_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_text_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_text_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_text_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_text_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_text_negate_description, ) class has_text_model(base_has_text_model): r"""Asserts specified output contains the substring specified by the argument text. The exact number of occurrences can be optionally specified by the argument n""" model_config = ConfigDict(extra="forbid", title="Assert Has Text") that: Literal["has_text"] = Field("has_text", title="That") class has_text_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Text (Nested)") has_text: base_has_text_model = Field(..., title="Assert Has Text") class has_text_model_relaxed(base_has_text_model_relaxed): r"""Asserts specified output contains the substring specified by the argument text. The exact number of occurrences can be optionally specified by the argument n""" model_config = ConfigDict(extra="forbid", title="Assert Has Text (Relaxed)") that: Literal["has_text"] = Field("has_text", title="That") has_text_matching_expression_description = """The regular expressions to attempt match in the output.""" has_text_matching_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_text_matching_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_text_matching_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_text_matching_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_text_matching_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_text_matching_model(AssertionModel): """base model for has_text_matching describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_text_matching_model") expression: str = Field( ..., title="Expression", description=has_text_matching_expression_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_text_matching_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_text_matching_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_text_matching_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_text_matching_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_text_matching_negate_description, ) class base_has_text_matching_model_relaxed(AssertionModel): """base model for has_text_matching describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_text_matching_model_relaxed") expression: str = Field( ..., title="Expression", description=has_text_matching_expression_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_text_matching_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_text_matching_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_text_matching_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_text_matching_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_text_matching_negate_description, ) class has_text_matching_model(base_has_text_matching_model): r"""Asserts the specified output contains text matching the regular expression specified by the argument expression. If n is given the assertion checks for exactly n (nonoverlapping) occurrences.""" model_config = ConfigDict(extra="forbid", title="Assert Has Text Matching") that: Literal["has_text_matching"] = Field("has_text_matching", title="That") class has_text_matching_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Text Matching (Nested)") has_text_matching: base_has_text_matching_model = Field(..., title="Assert Has Text Matching") class has_text_matching_model_relaxed(base_has_text_matching_model_relaxed): r"""Asserts the specified output contains text matching the regular expression specified by the argument expression. If n is given the assertion checks for exactly n (nonoverlapping) occurrences.""" model_config = ConfigDict(extra="forbid", title="Assert Has Text Matching (Relaxed)") that: Literal["has_text_matching"] = Field("has_text_matching", title="That") not_has_text_text_description = """The text to search for in the output.""" class base_not_has_text_model(AssertionModel): """base model for not_has_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_not_has_text_model") text: str = Field( ..., title="Text", description=not_has_text_text_description, ) class base_not_has_text_model_relaxed(AssertionModel): """base model for not_has_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_not_has_text_model_relaxed") text: str = Field( ..., title="Text", description=not_has_text_text_description, ) class not_has_text_model(base_not_has_text_model): r"""Asserts specified output does not contain the substring specified by the argument text""" model_config = ConfigDict(extra="forbid", title="Assert Not Has Text") that: Literal["not_has_text"] = Field("not_has_text", title="That") class not_has_text_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Not Has Text (Nested)") not_has_text: base_not_has_text_model = Field(..., title="Assert Not Has Text") class not_has_text_model_relaxed(base_not_has_text_model_relaxed): r"""Asserts specified output does not contain the substring specified by the argument text""" model_config = ConfigDict(extra="forbid", title="Assert Not Has Text (Relaxed)") that: Literal["not_has_text"] = Field("not_has_text", title="That") has_n_columns_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_columns_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_n_columns_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_columns_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_columns_sep_description = """Separator defining columns, default: tab""" has_n_columns_comment_description = ( """Comment character(s) used to skip comment lines (which should not be used for counting columns)""" ) has_n_columns_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_n_columns_model(AssertionModel): """base model for has_n_columns describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_n_columns_model") n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_n_columns_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_n_columns_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_n_columns_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_n_columns_max_description, ) sep: str = Field( " ", title="Sep", description=has_n_columns_sep_description, ) comment: str = Field( "", title="Comment", description=has_n_columns_comment_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_n_columns_negate_description, ) class base_has_n_columns_model_relaxed(AssertionModel): """base model for has_n_columns describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_n_columns_model_relaxed") n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_n_columns_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_n_columns_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_n_columns_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_n_columns_max_description, ) sep: str = Field( " ", title="Sep", description=has_n_columns_sep_description, ) comment: str = Field( "", title="Comment", description=has_n_columns_comment_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_n_columns_negate_description, ) class has_n_columns_model(base_has_n_columns_model): r"""Asserts tabular output contains the specified number (``n``) of columns. For instance, ````. The assertion tests only the first line. Number of columns can optionally also be specified with ``delta``. Alternatively the range of expected occurrences can be specified by ``min`` and/or ``max``. Optionally a column separator (``sep``, default is `` ``) `and comment character(s) can be specified (``comment``, default is empty string). The first non-comment line is used for determining the number of columns.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Columns") that: Literal["has_n_columns"] = Field("has_n_columns", title="That") class has_n_columns_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Columns (Nested)") has_n_columns: base_has_n_columns_model = Field(..., title="Assert Has N Columns") class has_n_columns_model_relaxed(base_has_n_columns_model_relaxed): r"""Asserts tabular output contains the specified number (``n``) of columns. For instance, ````. The assertion tests only the first line. Number of columns can optionally also be specified with ``delta``. Alternatively the range of expected occurrences can be specified by ``min`` and/or ``max``. Optionally a column separator (``sep``, default is `` ``) `and comment character(s) can be specified (``comment``, default is empty string). The first non-comment line is used for determining the number of columns.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Columns (Relaxed)") that: Literal["has_n_columns"] = Field("has_n_columns", title="That") attribute_is_path_description = """The Python xpath-like expression to find the target element.""" attribute_is_attribute_description = """The XML attribute name to test against from the target XML element.""" attribute_is_text_description = """The expected attribute value to test against on the target XML element""" attribute_is_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_attribute_is_model(AssertionModel): """base model for attribute_is describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_attribute_is_model") path: str = Field( ..., title="Path", description=attribute_is_path_description, ) attribute: str = Field( ..., title="Attribute", description=attribute_is_attribute_description, ) text: str = Field( ..., title="Text", description=attribute_is_text_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=attribute_is_negate_description, ) class base_attribute_is_model_relaxed(AssertionModel): """base model for attribute_is describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_attribute_is_model_relaxed") path: str = Field( ..., title="Path", description=attribute_is_path_description, ) attribute: str = Field( ..., title="Attribute", description=attribute_is_attribute_description, ) text: str = Field( ..., title="Text", description=attribute_is_text_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=attribute_is_negate_description, ) class attribute_is_model(base_attribute_is_model): r"""Asserts the XML ``attribute`` for the element (or tag) with the specified XPath-like ``path`` is the specified ``text``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the equality) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Attribute Is") that: Literal["attribute_is"] = Field("attribute_is", title="That") class attribute_is_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Attribute Is (Nested)") attribute_is: base_attribute_is_model = Field(..., title="Assert Attribute Is") class attribute_is_model_relaxed(base_attribute_is_model_relaxed): r"""Asserts the XML ``attribute`` for the element (or tag) with the specified XPath-like ``path`` is the specified ``text``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the equality) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Attribute Is (Relaxed)") that: Literal["attribute_is"] = Field("attribute_is", title="That") attribute_matches_path_description = """The Python xpath-like expression to find the target element.""" attribute_matches_attribute_description = """The XML attribute name to test against from the target XML element.""" attribute_matches_expression_description = ( """The regular expressions to apply against the named attribute on the target XML element.""" ) attribute_matches_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_attribute_matches_model(AssertionModel): """base model for attribute_matches describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_attribute_matches_model") path: str = Field( ..., title="Path", description=attribute_matches_path_description, ) attribute: str = Field( ..., title="Attribute", description=attribute_matches_attribute_description, ) expression: Annotated[str, BeforeValidator(check_regex)] = Field( ..., title="Expression", description=attribute_matches_expression_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=attribute_matches_negate_description, ) class base_attribute_matches_model_relaxed(AssertionModel): """base model for attribute_matches describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_attribute_matches_model_relaxed") path: str = Field( ..., title="Path", description=attribute_matches_path_description, ) attribute: str = Field( ..., title="Attribute", description=attribute_matches_attribute_description, ) expression: Annotated[str, BeforeValidator(check_regex)] = Field( ..., title="Expression", description=attribute_matches_expression_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=attribute_matches_negate_description, ) class attribute_matches_model(base_attribute_matches_model): r"""Asserts the XML ``attribute`` for the element (or tag) with the specified XPath-like ``path`` matches the regular expression specified by ``expression``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the matching) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Attribute Matches") that: Literal["attribute_matches"] = Field("attribute_matches", title="That") class attribute_matches_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Attribute Matches (Nested)") attribute_matches: base_attribute_matches_model = Field(..., title="Assert Attribute Matches") class attribute_matches_model_relaxed(base_attribute_matches_model_relaxed): r"""Asserts the XML ``attribute`` for the element (or tag) with the specified XPath-like ``path`` matches the regular expression specified by ``expression``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the matching) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Attribute Matches (Relaxed)") that: Literal["attribute_matches"] = Field("attribute_matches", title="That") element_text_path_description = """The Python xpath-like expression to find the target element.""" element_text_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_element_text_model(AssertionModel): """base model for element_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_element_text_model") path: str = Field( ..., title="Path", description=element_text_path_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=element_text_negate_description, ) children: typing.Optional["assertion_list"] = Field(None, title="Children") asserts: typing.Optional["assertion_list"] = Field(None, title="Asserts") @model_validator(mode="before") @classmethod def validate_children(self, data: typing.Any): if isinstance(data, dict) and "children" not in data and "asserts" not in data: raise ValueError("At least one of 'children' or 'asserts' must be specified for this assertion type.") return data class base_element_text_model_relaxed(AssertionModel): """base model for element_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_element_text_model_relaxed") path: str = Field( ..., title="Path", description=element_text_path_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=element_text_negate_description, ) children: typing.Optional["assertion_list"] = Field(None, title="Children") asserts: typing.Optional["assertion_list"] = Field(None, title="Asserts") @model_validator(mode="before") @classmethod def validate_children(self, data: typing.Any): if isinstance(data, dict) and "children" not in data and "asserts" not in data: raise ValueError("At least one of 'children' or 'asserts' must be specified for this assertion type.") return data class element_text_model(base_element_text_model): r"""This tag allows the developer to recurisively specify additional assertions as child elements about just the text contained in the element specified by the XPath-like ``path``, e.g. ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the implicit assertions can be inverted. The sub-assertions, which have their own ``negate`` attribute, are not affected by ``negate``.""" model_config = ConfigDict(extra="forbid", title="Assert Element Text") that: Literal["element_text"] = Field("element_text", title="That") class element_text_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Element Text (Nested)") element_text: base_element_text_model = Field(..., title="Assert Element Text") class element_text_model_relaxed(base_element_text_model_relaxed): r"""This tag allows the developer to recurisively specify additional assertions as child elements about just the text contained in the element specified by the XPath-like ``path``, e.g. ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the implicit assertions can be inverted. The sub-assertions, which have their own ``negate`` attribute, are not affected by ``negate``.""" model_config = ConfigDict(extra="forbid", title="Assert Element Text (Relaxed)") that: Literal["element_text"] = Field("element_text", title="That") element_text_is_path_description = """The Python xpath-like expression to find the target element.""" element_text_is_text_description = ( """The expected element text (body of the XML tag) to test against on the target XML element""" ) element_text_is_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_element_text_is_model(AssertionModel): """base model for element_text_is describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_element_text_is_model") path: str = Field( ..., title="Path", description=element_text_is_path_description, ) text: str = Field( ..., title="Text", description=element_text_is_text_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=element_text_is_negate_description, ) class base_element_text_is_model_relaxed(AssertionModel): """base model for element_text_is describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_element_text_is_model_relaxed") path: str = Field( ..., title="Path", description=element_text_is_path_description, ) text: str = Field( ..., title="Text", description=element_text_is_text_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=element_text_is_negate_description, ) class element_text_is_model(base_element_text_is_model): r"""Asserts the text of the XML element with the specified XPath-like ``path`` is the specified ``text``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the equality) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Element Text Is") that: Literal["element_text_is"] = Field("element_text_is", title="That") class element_text_is_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Element Text Is (Nested)") element_text_is: base_element_text_is_model = Field(..., title="Assert Element Text Is") class element_text_is_model_relaxed(base_element_text_is_model_relaxed): r"""Asserts the text of the XML element with the specified XPath-like ``path`` is the specified ``text``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the equality) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Element Text Is (Relaxed)") that: Literal["element_text_is"] = Field("element_text_is", title="That") element_text_matches_path_description = """The Python xpath-like expression to find the target element.""" element_text_matches_expression_description = """The regular expressions to apply against the target element.""" element_text_matches_negate_description = ( """A boolean that can be set to true to negate the outcome of the assertion.""" ) class base_element_text_matches_model(AssertionModel): """base model for element_text_matches describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_element_text_matches_model") path: str = Field( ..., title="Path", description=element_text_matches_path_description, ) expression: Annotated[str, BeforeValidator(check_regex)] = Field( ..., title="Expression", description=element_text_matches_expression_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=element_text_matches_negate_description, ) class base_element_text_matches_model_relaxed(AssertionModel): """base model for element_text_matches describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_element_text_matches_model_relaxed") path: str = Field( ..., title="Path", description=element_text_matches_path_description, ) expression: Annotated[str, BeforeValidator(check_regex)] = Field( ..., title="Expression", description=element_text_matches_expression_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=element_text_matches_negate_description, ) class element_text_matches_model(base_element_text_matches_model): r"""Asserts the text of the XML element with the specified XPath-like ``path`` matches the regular expression defined by ``expression``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the matching) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Element Text Matches") that: Literal["element_text_matches"] = Field("element_text_matches", title="That") class element_text_matches_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Element Text Matches (Nested)") element_text_matches: base_element_text_matches_model = Field(..., title="Assert Element Text Matches") class element_text_matches_model_relaxed(base_element_text_matches_model_relaxed): r"""Asserts the text of the XML element with the specified XPath-like ``path`` matches the regular expression defined by ``expression``. For example: ```xml ``` The assertion implicitly also asserts that an element matching ``path`` exists. With ``negate`` the result of the assertion (on the matching) can be inverted (the implicit assertion on the existence of the path is not affected).""" model_config = ConfigDict(extra="forbid", title="Assert Element Text Matches (Relaxed)") that: Literal["element_text_matches"] = Field("element_text_matches", title="That") has_element_with_path_path_description = """The Python xpath-like expression to find the target element.""" has_element_with_path_negate_description = ( """A boolean that can be set to true to negate the outcome of the assertion.""" ) class base_has_element_with_path_model(AssertionModel): """base model for has_element_with_path describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_element_with_path_model") path: str = Field( ..., title="Path", description=has_element_with_path_path_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_element_with_path_negate_description, ) class base_has_element_with_path_model_relaxed(AssertionModel): """base model for has_element_with_path describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_element_with_path_model_relaxed") path: str = Field( ..., title="Path", description=has_element_with_path_path_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_element_with_path_negate_description, ) class has_element_with_path_model(base_has_element_with_path_model): r"""Asserts the XML output contains at least one element (or tag) with the specified XPath-like ``path``, e.g. ```xml ``` With ``negate`` the result of the assertion can be inverted.""" model_config = ConfigDict(extra="forbid", title="Assert Has Element With Path") that: Literal["has_element_with_path"] = Field("has_element_with_path", title="That") class has_element_with_path_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Element With Path (Nested)") has_element_with_path: base_has_element_with_path_model = Field(..., title="Assert Has Element With Path") class has_element_with_path_model_relaxed(base_has_element_with_path_model_relaxed): r"""Asserts the XML output contains at least one element (or tag) with the specified XPath-like ``path``, e.g. ```xml ``` With ``negate`` the result of the assertion can be inverted.""" model_config = ConfigDict(extra="forbid", title="Assert Has Element With Path (Relaxed)") that: Literal["has_element_with_path"] = Field("has_element_with_path", title="That") has_n_elements_with_path_path_description = """The Python xpath-like expression to find the target element.""" has_n_elements_with_path_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_n_elements_with_path_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_n_elements_with_path_min_description = ( """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_n_elements_with_path_max_description = ( """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_n_elements_with_path_negate_description = ( """A boolean that can be set to true to negate the outcome of the assertion.""" ) class base_has_n_elements_with_path_model(AssertionModel): """base model for has_n_elements_with_path describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_n_elements_with_path_model") path: str = Field( ..., title="Path", description=has_n_elements_with_path_path_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_n_elements_with_path_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_n_elements_with_path_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_n_elements_with_path_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_n_elements_with_path_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_n_elements_with_path_negate_description, ) class base_has_n_elements_with_path_model_relaxed(AssertionModel): """base model for has_n_elements_with_path describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_n_elements_with_path_model_relaxed") path: str = Field( ..., title="Path", description=has_n_elements_with_path_path_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_n_elements_with_path_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_n_elements_with_path_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_n_elements_with_path_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_n_elements_with_path_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_n_elements_with_path_negate_description, ) class has_n_elements_with_path_model(base_has_n_elements_with_path_model): r"""Asserts the XML output contains the specified number (``n``, optionally with ``delta``) of elements (or tags) with the specified XPath-like ``path``. For example: ```xml ``` Alternatively to ``n`` and ``delta`` also the ``min`` and ``max`` attributes can be used to specify the range of the expected number of occurrences. With ``negate`` the result of the assertion can be inverted.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Elements With Path") that: Literal["has_n_elements_with_path"] = Field("has_n_elements_with_path", title="That") class has_n_elements_with_path_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Elements With Path (Nested)") has_n_elements_with_path: base_has_n_elements_with_path_model = Field(..., title="Assert Has N Elements With Path") class has_n_elements_with_path_model_relaxed(base_has_n_elements_with_path_model_relaxed): r"""Asserts the XML output contains the specified number (``n``, optionally with ``delta``) of elements (or tags) with the specified XPath-like ``path``. For example: ```xml ``` Alternatively to ``n`` and ``delta`` also the ``min`` and ``max`` attributes can be used to specify the range of the expected number of occurrences. With ``negate`` the result of the assertion can be inverted.""" model_config = ConfigDict(extra="forbid", title="Assert Has N Elements With Path (Relaxed)") that: Literal["has_n_elements_with_path"] = Field("has_n_elements_with_path", title="That") class base_is_valid_xml_model(AssertionModel): """base model for is_valid_xml describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_is_valid_xml_model") class base_is_valid_xml_model_relaxed(AssertionModel): """base model for is_valid_xml describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_is_valid_xml_model_relaxed") class is_valid_xml_model(base_is_valid_xml_model): r"""Asserts the output is a valid XML file (e.g. ````).""" model_config = ConfigDict(extra="forbid", title="Assert Is Valid Xml") that: Literal["is_valid_xml"] = Field("is_valid_xml", title="That") class is_valid_xml_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Is Valid Xml (Nested)") is_valid_xml: base_is_valid_xml_model = Field(..., title="Assert Is Valid Xml") class is_valid_xml_model_relaxed(base_is_valid_xml_model_relaxed): r"""Asserts the output is a valid XML file (e.g. ````).""" model_config = ConfigDict(extra="forbid", title="Assert Is Valid Xml (Relaxed)") that: Literal["is_valid_xml"] = Field("is_valid_xml", title="That") xml_element_path_description = """The Python xpath-like expression to find the target element.""" xml_element_attribute_description = """The XML attribute name to test against from the target XML element.""" xml_element_all_description = ( """Check the sub-assertions for all paths matching the path. Default: false, i.e. only the first """ ) xml_element_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" xml_element_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) xml_element_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" xml_element_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" xml_element_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_xml_element_model(AssertionModel): """base model for xml_element describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_xml_element_model") path: str = Field( ..., title="Path", description=xml_element_path_description, ) attribute: typing.Optional[typing.Union[str]] = Field( None, title="Attribute", description=xml_element_attribute_description, ) all: typing.Union[bool, str] = Field( False, title="All", description=xml_element_all_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=xml_element_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=xml_element_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=xml_element_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=xml_element_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=xml_element_negate_description, ) children: typing.Optional["assertion_list"] = Field(None, title="Children") asserts: typing.Optional["assertion_list"] = Field(None, title="Asserts") class base_xml_element_model_relaxed(AssertionModel): """base model for xml_element describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_xml_element_model_relaxed") path: str = Field( ..., title="Path", description=xml_element_path_description, ) attribute: typing.Optional[typing.Union[str]] = Field( None, title="Attribute", description=xml_element_attribute_description, ) all: typing.Union[bool, str] = Field( False, title="All", description=xml_element_all_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=xml_element_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=xml_element_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=xml_element_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=xml_element_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=xml_element_negate_description, ) children: typing.Optional["assertion_list"] = Field(None, title="Children") asserts: typing.Optional["assertion_list"] = Field(None, title="Asserts") class xml_element_model(base_xml_element_model): r"""Assert if the XML file contains element(s) or tag(s) with the specified [XPath-like ``path``](https://lxml.de/xpathxslt.html). If ``n`` and ``delta`` or ``min`` and ``max`` are given also the number of occurrences is checked. ```xml ``` With ``negate="true"`` the outcome of the assertions wrt the presence and number of ``path`` can be negated. If there are any sub assertions then check them against - the content of the attribute ``attribute`` - the element's text if no attribute is given ```xml ``` Sub-assertions are not subject to the ``negate`` attribute of ``xml_element``. If ``all`` is ``true`` then the sub assertions are checked for all occurrences. Note that all other XML assertions can be expressed by this assertion (Galaxy also implements the other assertions by calling this one).""" model_config = ConfigDict(extra="forbid", title="Assert Xml Element") that: Literal["xml_element"] = Field("xml_element", title="That") class xml_element_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Xml Element (Nested)") xml_element: base_xml_element_model = Field(..., title="Assert Xml Element") class xml_element_model_relaxed(base_xml_element_model_relaxed): r"""Assert if the XML file contains element(s) or tag(s) with the specified [XPath-like ``path``](https://lxml.de/xpathxslt.html). If ``n`` and ``delta`` or ``min`` and ``max`` are given also the number of occurrences is checked. ```xml ``` With ``negate="true"`` the outcome of the assertions wrt the presence and number of ``path`` can be negated. If there are any sub assertions then check them against - the content of the attribute ``attribute`` - the element's text if no attribute is given ```xml ``` Sub-assertions are not subject to the ``negate`` attribute of ``xml_element``. If ``all`` is ``true`` then the sub assertions are checked for all occurrences. Note that all other XML assertions can be expressed by this assertion (Galaxy also implements the other assertions by calling this one).""" model_config = ConfigDict(extra="forbid", title="Assert Xml Element (Relaxed)") that: Literal["xml_element"] = Field("xml_element", title="That") has_json_property_with_text_property_description = """The property name to search the JSON document for.""" has_json_property_with_text_text_description = """The expected text value of the target JSON attribute.""" class base_has_json_property_with_text_model(AssertionModel): """base model for has_json_property_with_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_json_property_with_text_model") property: str = Field( ..., title="Property", description=has_json_property_with_text_property_description, ) text: str = Field( ..., title="Text", description=has_json_property_with_text_text_description, ) class base_has_json_property_with_text_model_relaxed(AssertionModel): """base model for has_json_property_with_text describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_json_property_with_text_model_relaxed") property: str = Field( ..., title="Property", description=has_json_property_with_text_property_description, ) text: str = Field( ..., title="Text", description=has_json_property_with_text_text_description, ) class has_json_property_with_text_model(base_has_json_property_with_text_model): r"""Asserts the JSON document contains a property or key with the specified text (i.e. string) value. ```xml ```""" model_config = ConfigDict(extra="forbid", title="Assert Has Json Property With Text") that: Literal["has_json_property_with_text"] = Field("has_json_property_with_text", title="That") class has_json_property_with_text_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Json Property With Text (Nested)") has_json_property_with_text: base_has_json_property_with_text_model = Field( ..., title="Assert Has Json Property With Text" ) class has_json_property_with_text_model_relaxed(base_has_json_property_with_text_model_relaxed): r"""Asserts the JSON document contains a property or key with the specified text (i.e. string) value. ```xml ```""" model_config = ConfigDict(extra="forbid", title="Assert Has Json Property With Text (Relaxed)") that: Literal["has_json_property_with_text"] = Field("has_json_property_with_text", title="That") has_json_property_with_value_property_description = """The property name to search the JSON document for.""" has_json_property_with_value_value_description = ( """The expected JSON value of the target JSON attribute (as a JSON encoded string).""" ) class base_has_json_property_with_value_model(AssertionModel): """base model for has_json_property_with_value describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_json_property_with_value_model") property: str = Field( ..., title="Property", description=has_json_property_with_value_property_description, ) value: str = Field( ..., title="Value", description=has_json_property_with_value_value_description, ) class base_has_json_property_with_value_model_relaxed(AssertionModel): """base model for has_json_property_with_value describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_json_property_with_value_model_relaxed") property: str = Field( ..., title="Property", description=has_json_property_with_value_property_description, ) value: str = Field( ..., title="Value", description=has_json_property_with_value_value_description, ) class has_json_property_with_value_model(base_has_json_property_with_value_model): r"""Asserts the JSON document contains a property or key with the specified JSON value. ```xml ```""" model_config = ConfigDict(extra="forbid", title="Assert Has Json Property With Value") that: Literal["has_json_property_with_value"] = Field("has_json_property_with_value", title="That") class has_json_property_with_value_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Json Property With Value (Nested)") has_json_property_with_value: base_has_json_property_with_value_model = Field( ..., title="Assert Has Json Property With Value" ) class has_json_property_with_value_model_relaxed(base_has_json_property_with_value_model_relaxed): r"""Asserts the JSON document contains a property or key with the specified JSON value. ```xml ```""" model_config = ConfigDict(extra="forbid", title="Assert Has Json Property With Value (Relaxed)") that: Literal["has_json_property_with_value"] = Field("has_json_property_with_value", title="That") has_h5_attribute_key_description = """HDF5 attribute to check value of.""" has_h5_attribute_value_description = """Expected value of HDF5 attribute to check.""" class base_has_h5_attribute_model(AssertionModel): """base model for has_h5_attribute describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_h5_attribute_model") key: str = Field( ..., title="Key", description=has_h5_attribute_key_description, ) value: str = Field( ..., title="Value", description=has_h5_attribute_value_description, ) class base_has_h5_attribute_model_relaxed(AssertionModel): """base model for has_h5_attribute describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_h5_attribute_model_relaxed") key: str = Field( ..., title="Key", description=has_h5_attribute_key_description, ) value: str = Field( ..., title="Value", description=has_h5_attribute_value_description, ) class has_h5_attribute_model(base_has_h5_attribute_model): r"""Asserts HDF5 output contains the specified ``value`` for an attribute (``key``), e.g. ```xml ```""" model_config = ConfigDict(extra="forbid", title="Assert Has H5 Attribute") that: Literal["has_h5_attribute"] = Field("has_h5_attribute", title="That") class has_h5_attribute_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has H5 Attribute (Nested)") has_h5_attribute: base_has_h5_attribute_model = Field(..., title="Assert Has H5 Attribute") class has_h5_attribute_model_relaxed(base_has_h5_attribute_model_relaxed): r"""Asserts HDF5 output contains the specified ``value`` for an attribute (``key``), e.g. ```xml ```""" model_config = ConfigDict(extra="forbid", title="Assert Has H5 Attribute (Relaxed)") that: Literal["has_h5_attribute"] = Field("has_h5_attribute", title="That") has_h5_keys_keys_description = """HDF5 attributes to check value of as a comma-separated string.""" class base_has_h5_keys_model(AssertionModel): """base model for has_h5_keys describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_h5_keys_model") keys: str = Field( ..., title="Keys", description=has_h5_keys_keys_description, ) class base_has_h5_keys_model_relaxed(AssertionModel): """base model for has_h5_keys describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_h5_keys_model_relaxed") keys: str = Field( ..., title="Keys", description=has_h5_keys_keys_description, ) class has_h5_keys_model(base_has_h5_keys_model): r"""Asserts the specified HDF5 output has the given keys.""" model_config = ConfigDict(extra="forbid", title="Assert Has H5 Keys") that: Literal["has_h5_keys"] = Field("has_h5_keys", title="That") class has_h5_keys_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has H5 Keys (Nested)") has_h5_keys: base_has_h5_keys_model = Field(..., title="Assert Has H5 Keys") class has_h5_keys_model_relaxed(base_has_h5_keys_model_relaxed): r"""Asserts the specified HDF5 output has the given keys.""" model_config = ConfigDict(extra="forbid", title="Assert Has H5 Keys (Relaxed)") that: Literal["has_h5_keys"] = Field("has_h5_keys", title="That") has_archive_member_path_description = """The regular expression specifying the archive member.""" has_archive_member_all_description = ( """Check the sub-assertions for all paths matching the path. Default: false, i.e. only the first""" ) has_archive_member_n_description = """Desired number, can be suffixed by ``(k|M|G|T|P|E)i?``""" has_archive_member_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_archive_member_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_archive_member_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_archive_member_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_archive_member_model(AssertionModel): """base model for has_archive_member describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_archive_member_model") path: str = Field( ..., title="Path", description=has_archive_member_path_description, ) all: typing.Union[bool, str] = Field( False, title="All", description=has_archive_member_all_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_archive_member_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_archive_member_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_archive_member_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_archive_member_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_archive_member_negate_description, ) children: typing.Optional["assertion_list"] = Field(None, title="Children") asserts: typing.Optional["assertion_list"] = Field(None, title="Asserts") class base_has_archive_member_model_relaxed(AssertionModel): """base model for has_archive_member describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_archive_member_model_relaxed") path: str = Field( ..., title="Path", description=has_archive_member_path_description, ) all: typing.Union[bool, str] = Field( False, title="All", description=has_archive_member_all_description, ) n: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="N", description=has_archive_member_n_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_archive_member_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_archive_member_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_archive_member_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_archive_member_negate_description, ) children: typing.Optional["assertion_list"] = Field(None, title="Children") asserts: typing.Optional["assertion_list"] = Field(None, title="Asserts") class has_archive_member_model(base_has_archive_member_model): r"""This tag allows to check if ``path`` is contained in a compressed file. The path is a regular expression that is matched against the full paths of the objects in the compressed file (remember that "matching" means it is checked if a prefix of the full path of an archive member is described by the regular expression). Valid archive formats include ``.zip``, ``.tar``, and ``.tar.gz``. Note that depending on the archive creation method: - full paths of the members may be prefixed with ``./`` - directories may be treated as empty files ```xml ``` With ``n`` and ``delta`` (or ``min`` and ``max``) assertions on the number of archive members matching ``path`` can be expressed. The following could be used, e.g., to assert an archive containing n±1 elements out of which at least 4 need to have a ``txt`` extension. ```xml ``` In addition the tag can contain additional assertions as child elements about the first member in the archive matching the regular expression ``path``. For instance ```xml ``` If the ``all`` attribute is set to ``true`` then all archive members are subject to the assertions. Note that, archive members matching the ``path`` are sorted alphabetically. The ``negate`` attribute of the ``has_archive_member`` assertion only affects the asserts on the presence and number of matching archive members, but not any sub-assertions (which can offer the ``negate`` attribute on their own). The check if the file is an archive at all, which is also done by the function, is not affected.""" model_config = ConfigDict(extra="forbid", title="Assert Has Archive Member") that: Literal["has_archive_member"] = Field("has_archive_member", title="That") class has_archive_member_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Archive Member (Nested)") has_archive_member: base_has_archive_member_model = Field(..., title="Assert Has Archive Member") class has_archive_member_model_relaxed(base_has_archive_member_model_relaxed): r"""This tag allows to check if ``path`` is contained in a compressed file. The path is a regular expression that is matched against the full paths of the objects in the compressed file (remember that "matching" means it is checked if a prefix of the full path of an archive member is described by the regular expression). Valid archive formats include ``.zip``, ``.tar``, and ``.tar.gz``. Note that depending on the archive creation method: - full paths of the members may be prefixed with ``./`` - directories may be treated as empty files ```xml ``` With ``n`` and ``delta`` (or ``min`` and ``max``) assertions on the number of archive members matching ``path`` can be expressed. The following could be used, e.g., to assert an archive containing n±1 elements out of which at least 4 need to have a ``txt`` extension. ```xml ``` In addition the tag can contain additional assertions as child elements about the first member in the archive matching the regular expression ``path``. For instance ```xml ``` If the ``all`` attribute is set to ``true`` then all archive members are subject to the assertions. Note that, archive members matching the ``path`` are sorted alphabetically. The ``negate`` attribute of the ``has_archive_member`` assertion only affects the asserts on the presence and number of matching archive members, but not any sub-assertions (which can offer the ``negate`` attribute on their own). The check if the file is an archive at all, which is also done by the function, is not affected.""" model_config = ConfigDict(extra="forbid", title="Assert Has Archive Member (Relaxed)") that: Literal["has_archive_member"] = Field("has_archive_member", title="That") has_size_value_description = """Deprecated alias for `size`""" has_size_size_description = """Desired size of the output (in bytes), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_size_delta_description = ( """Allowed difference with respect to n (default: 0), can be suffixed by ``(k|M|G|T|P|E)i?``""" ) has_size_min_description = """Minimum number (default: -infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_size_max_description = """Maximum number (default: infinity), can be suffixed by ``(k|M|G|T|P|E)i?``""" has_size_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_size_model(AssertionModel): """base model for has_size describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_size_model") value: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Value", description=has_size_value_description, ) size: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Size", description=has_size_size_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_size_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_size_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_size_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_size_negate_description, ) class base_has_size_model_relaxed(AssertionModel): """base model for has_size describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_size_model_relaxed") value: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Value", description=has_size_value_description, ) size: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Size", description=has_size_size_description, ) delta: Annotated[ typing.Union[int, str], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int) ] = Field( 0, title="Delta", description=has_size_delta_description, ) min: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Min", description=has_size_min_description, ) max: Annotated[ typing.Optional[typing.Union[str, int]], BeforeValidator(check_bytes), BeforeValidator(check_non_negative_if_int), ] = Field( None, title="Max", description=has_size_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_size_negate_description, ) class has_size_model(base_has_size_model): r"""Asserts the specified output has a size of the specified value Attributes size and value or synonyms though value is considered deprecated. The size optionally allows for absolute (``delta``) difference.""" model_config = ConfigDict(extra="forbid", title="Assert Has Size") that: Literal["has_size"] = Field("has_size", title="That") class has_size_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Size (Nested)") has_size: base_has_size_model = Field(..., title="Assert Has Size") class has_size_model_relaxed(base_has_size_model_relaxed): r"""Asserts the specified output has a size of the specified value Attributes size and value or synonyms though value is considered deprecated. The size optionally allows for absolute (``delta``) difference.""" model_config = ConfigDict(extra="forbid", title="Assert Has Size (Relaxed)") that: Literal["has_size"] = Field("has_size", title="That") has_image_center_of_mass_center_of_mass_description = """The required center of mass of the image intensities (horizontal and vertical coordinate, separated by a comma).""" has_image_center_of_mass_channel_description = """Restricts the assertion to a specific channel of the image (where ``0`` corresponds to the first image channel).""" has_image_center_of_mass_slice_description = ( """Restricts the assertion to a specific slice of the image (where ``0`` corresponds to the first image slice).""" ) has_image_center_of_mass_frame_description = """Restricts the assertion to a specific frame of the image sequence (where ``0`` corresponds to the first image frame).""" has_image_center_of_mass_eps_description = ( """The maximum allowed Euclidean distance to the required center of mass (defaults to ``0.01``).""" ) class base_has_image_center_of_mass_model(AssertionModel): """base model for has_image_center_of_mass describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_center_of_mass_model") center_of_mass: Annotated[str, BeforeValidator(check_center_of_mass)] = Field( ..., title="Center Of Mass", description=has_image_center_of_mass_center_of_mass_description, ) channel: typing.Optional[StrictInt] = Field( None, title="Channel", description=has_image_center_of_mass_channel_description, ) slice: typing.Optional[StrictInt] = Field( None, title="Slice", description=has_image_center_of_mass_slice_description, ) frame: typing.Optional[StrictInt] = Field( None, title="Frame", description=has_image_center_of_mass_frame_description, ) eps: Annotated[typing.Union[StrictInt, StrictFloat], BeforeValidator(check_non_negative_if_set)] = Field( 0.01, title="Eps", description=has_image_center_of_mass_eps_description, ) class base_has_image_center_of_mass_model_relaxed(AssertionModel): """base model for has_image_center_of_mass describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_center_of_mass_model_relaxed") center_of_mass: Annotated[str, BeforeValidator(check_center_of_mass)] = Field( ..., title="Center Of Mass", description=has_image_center_of_mass_center_of_mass_description, ) channel: typing.Optional[typing.Union[str, int]] = Field( None, title="Channel", description=has_image_center_of_mass_channel_description, ) slice: typing.Optional[typing.Union[str, int]] = Field( None, title="Slice", description=has_image_center_of_mass_slice_description, ) frame: typing.Optional[typing.Union[str, int]] = Field( None, title="Frame", description=has_image_center_of_mass_frame_description, ) eps: Annotated[typing.Union[float, str], BeforeValidator(check_non_negative_if_set)] = Field( 0.01, title="Eps", description=has_image_center_of_mass_eps_description, ) class has_image_center_of_mass_model(base_has_image_center_of_mass_model): r"""Asserts the specified output is an image and has the specified center of mass. Asserts the output is an image and has a specific center of mass, or has an Euclidean distance of ``eps`` or less to that point (e.g., ````).""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Center Of Mass") that: Literal["has_image_center_of_mass"] = Field("has_image_center_of_mass", title="That") class has_image_center_of_mass_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Center Of Mass (Nested)") has_image_center_of_mass: base_has_image_center_of_mass_model = Field(..., title="Assert Has Image Center Of Mass") class has_image_center_of_mass_model_relaxed(base_has_image_center_of_mass_model_relaxed): r"""Asserts the specified output is an image and has the specified center of mass. Asserts the output is an image and has a specific center of mass, or has an Euclidean distance of ``eps`` or less to that point (e.g., ````).""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Center Of Mass (Relaxed)") that: Literal["has_image_center_of_mass"] = Field("has_image_center_of_mass", title="That") has_image_channels_channels_description = """Expected number of channels of the image.""" has_image_channels_delta_description = """Maximum allowed difference of the number of channels (default is 0). The observed number of channels has to be in the range ``value +- delta``.""" has_image_channels_min_description = """Minimum allowed number of channels.""" has_image_channels_max_description = """Maximum allowed number of channels.""" has_image_channels_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_image_channels_model(AssertionModel): """base model for has_image_channels describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_channels_model") channels: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Channels", description=has_image_channels_channels_description, ) delta: Annotated[StrictInt, BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_channels_delta_description, ) min: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_channels_min_description, ) max: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_channels_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_channels_negate_description, ) class base_has_image_channels_model_relaxed(AssertionModel): """base model for has_image_channels describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_channels_model_relaxed") channels: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Channels", description=has_image_channels_channels_description, ) delta: Annotated[typing.Union[int, str], BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_channels_delta_description, ) min: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_channels_min_description, ) max: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_channels_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_channels_negate_description, ) class has_image_channels_model(base_has_image_channels_model): r"""Asserts the output is an image and has a specific number of channels. The number of channels is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected number of channels can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Channels") that: Literal["has_image_channels"] = Field("has_image_channels", title="That") class has_image_channels_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Channels (Nested)") has_image_channels: base_has_image_channels_model = Field(..., title="Assert Has Image Channels") class has_image_channels_model_relaxed(base_has_image_channels_model_relaxed): r"""Asserts the output is an image and has a specific number of channels. The number of channels is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected number of channels can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Channels (Relaxed)") that: Literal["has_image_channels"] = Field("has_image_channels", title="That") has_image_depth_depth_description = """Expected depth of the image (number of slices).""" has_image_depth_delta_description = """Maximum allowed difference of the image depth (number of slices, default is 0). The observed depth has to be in the range ``value +- delta``.""" has_image_depth_min_description = """Minimum allowed depth of the image (number of slices).""" has_image_depth_max_description = """Maximum allowed depth of the image (number of slices).""" has_image_depth_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_image_depth_model(AssertionModel): """base model for has_image_depth describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_depth_model") depth: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Depth", description=has_image_depth_depth_description, ) delta: Annotated[StrictInt, BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_depth_delta_description, ) min: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_depth_min_description, ) max: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_depth_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_depth_negate_description, ) class base_has_image_depth_model_relaxed(AssertionModel): """base model for has_image_depth describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_depth_model_relaxed") depth: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Depth", description=has_image_depth_depth_description, ) delta: Annotated[typing.Union[int, str], BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_depth_delta_description, ) min: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_depth_min_description, ) max: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_depth_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_depth_negate_description, ) class has_image_depth_model(base_has_image_depth_model): r"""Asserts the output is an image and has a specific depth (number of slices). The depth is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected depth can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Depth") that: Literal["has_image_depth"] = Field("has_image_depth", title="That") class has_image_depth_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Depth (Nested)") has_image_depth: base_has_image_depth_model = Field(..., title="Assert Has Image Depth") class has_image_depth_model_relaxed(base_has_image_depth_model_relaxed): r"""Asserts the output is an image and has a specific depth (number of slices). The depth is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected depth can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Depth (Relaxed)") that: Literal["has_image_depth"] = Field("has_image_depth", title="That") has_image_frames_frames_description = """Expected number of frames in the image sequence (number of time steps).""" has_image_frames_delta_description = """Maximum allowed difference of the number of frames in the image sequence (number of time steps, default is 0). The observed number of frames has to be in the range ``value +- delta``.""" has_image_frames_min_description = """Minimum allowed number of frames in the image sequence (number of time steps).""" has_image_frames_max_description = """Maximum allowed number of frames in the image sequence (number of time steps).""" has_image_frames_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_image_frames_model(AssertionModel): """base model for has_image_frames describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_frames_model") frames: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Frames", description=has_image_frames_frames_description, ) delta: Annotated[StrictInt, BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_frames_delta_description, ) min: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_frames_min_description, ) max: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_frames_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_frames_negate_description, ) class base_has_image_frames_model_relaxed(AssertionModel): """base model for has_image_frames describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_frames_model_relaxed") frames: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Frames", description=has_image_frames_frames_description, ) delta: Annotated[typing.Union[int, str], BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_frames_delta_description, ) min: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_frames_min_description, ) max: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_frames_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_frames_negate_description, ) class has_image_frames_model(base_has_image_frames_model): r"""Asserts the output is an image and has a specific number of frames (number of time steps). The number of frames is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected number of frames can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Frames") that: Literal["has_image_frames"] = Field("has_image_frames", title="That") class has_image_frames_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Frames (Nested)") has_image_frames: base_has_image_frames_model = Field(..., title="Assert Has Image Frames") class has_image_frames_model_relaxed(base_has_image_frames_model_relaxed): r"""Asserts the output is an image and has a specific number of frames (number of time steps). The number of frames is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected number of frames can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Frames (Relaxed)") that: Literal["has_image_frames"] = Field("has_image_frames", title="That") has_image_height_height_description = """Expected height of the image (in pixels).""" has_image_height_delta_description = """Maximum allowed difference of the image height (in pixels, default is 0). The observed height has to be in the range ``value +- delta``.""" has_image_height_min_description = """Minimum allowed height of the image (in pixels).""" has_image_height_max_description = """Maximum allowed height of the image (in pixels).""" has_image_height_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_image_height_model(AssertionModel): """base model for has_image_height describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_height_model") height: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Height", description=has_image_height_height_description, ) delta: Annotated[StrictInt, BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_height_delta_description, ) min: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_height_min_description, ) max: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_height_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_height_negate_description, ) class base_has_image_height_model_relaxed(AssertionModel): """base model for has_image_height describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_height_model_relaxed") height: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Height", description=has_image_height_height_description, ) delta: Annotated[typing.Union[int, str], BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_height_delta_description, ) min: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_height_min_description, ) max: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_height_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_height_negate_description, ) class has_image_height_model(base_has_image_height_model): r"""Asserts the output is an image and has a specific height (in pixels). The height is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected height can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Height") that: Literal["has_image_height"] = Field("has_image_height", title="That") class has_image_height_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Height (Nested)") has_image_height: base_has_image_height_model = Field(..., title="Assert Has Image Height") class has_image_height_model_relaxed(base_has_image_height_model_relaxed): r"""Asserts the output is an image and has a specific height (in pixels). The height is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected height can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Height (Relaxed)") that: Literal["has_image_height"] = Field("has_image_height", title="That") has_image_mean_intensity_channel_description = """Restricts the assertion to a specific channel of the image (where ``0`` corresponds to the first image channel).""" has_image_mean_intensity_slice_description = ( """Restricts the assertion to a specific slice of the image (where ``0`` corresponds to the first image slice).""" ) has_image_mean_intensity_frame_description = """Restricts the assertion to a specific frame of the image sequence (where ``0`` corresponds to the first image frame).""" has_image_mean_intensity_mean_intensity_description = """The required mean value of the image intensities.""" has_image_mean_intensity_eps_description = """The absolute tolerance to be used for ``value`` (defaults to ``0.01``). The observed mean value of the image intensities has to be in the range ``value +- eps``.""" has_image_mean_intensity_min_description = """A lower bound of the required mean value of the image intensities.""" has_image_mean_intensity_max_description = """An upper bound of the required mean value of the image intensities.""" class base_has_image_mean_intensity_model(AssertionModel): """base model for has_image_mean_intensity describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_mean_intensity_model") channel: typing.Optional[StrictInt] = Field( None, title="Channel", description=has_image_mean_intensity_channel_description, ) slice: typing.Optional[StrictInt] = Field( None, title="Slice", description=has_image_mean_intensity_slice_description, ) frame: typing.Optional[StrictInt] = Field( None, title="Frame", description=has_image_mean_intensity_frame_description, ) mean_intensity: typing.Optional[typing.Union[StrictInt, StrictFloat]] = Field( None, title="Mean Intensity", description=has_image_mean_intensity_mean_intensity_description, ) eps: Annotated[typing.Union[StrictInt, StrictFloat], BeforeValidator(check_non_negative_if_set)] = Field( 0.01, title="Eps", description=has_image_mean_intensity_eps_description, ) min: typing.Optional[typing.Union[StrictInt, StrictFloat]] = Field( None, title="Min", description=has_image_mean_intensity_min_description, ) max: typing.Optional[typing.Union[StrictInt, StrictFloat]] = Field( None, title="Max", description=has_image_mean_intensity_max_description, ) class base_has_image_mean_intensity_model_relaxed(AssertionModel): """base model for has_image_mean_intensity describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_mean_intensity_model_relaxed") channel: typing.Optional[typing.Union[str, int]] = Field( None, title="Channel", description=has_image_mean_intensity_channel_description, ) slice: typing.Optional[typing.Union[str, int]] = Field( None, title="Slice", description=has_image_mean_intensity_slice_description, ) frame: typing.Optional[typing.Union[str, int]] = Field( None, title="Frame", description=has_image_mean_intensity_frame_description, ) mean_intensity: typing.Optional[typing.Union[float, str]] = Field( None, title="Mean Intensity", description=has_image_mean_intensity_mean_intensity_description, ) eps: Annotated[typing.Union[float, str], BeforeValidator(check_non_negative_if_set)] = Field( 0.01, title="Eps", description=has_image_mean_intensity_eps_description, ) min: typing.Optional[typing.Union[float, str]] = Field( None, title="Min", description=has_image_mean_intensity_min_description, ) max: typing.Optional[typing.Union[float, str]] = Field( None, title="Max", description=has_image_mean_intensity_max_description, ) class has_image_mean_intensity_model(base_has_image_mean_intensity_model): r"""Asserts the output is an image and has a specific mean intensity value. The mean intensity value is plus/minus ``eps`` (e.g., ````). Alternatively the range of the expected mean intensity value can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Mean Intensity") that: Literal["has_image_mean_intensity"] = Field("has_image_mean_intensity", title="That") class has_image_mean_intensity_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Mean Intensity (Nested)") has_image_mean_intensity: base_has_image_mean_intensity_model = Field(..., title="Assert Has Image Mean Intensity") class has_image_mean_intensity_model_relaxed(base_has_image_mean_intensity_model_relaxed): r"""Asserts the output is an image and has a specific mean intensity value. The mean intensity value is plus/minus ``eps`` (e.g., ````). Alternatively the range of the expected mean intensity value can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Mean Intensity (Relaxed)") that: Literal["has_image_mean_intensity"] = Field("has_image_mean_intensity", title="That") has_image_mean_object_size_channel_description = """Restricts the assertion to a specific channel of the image (where ``0`` corresponds to the first image channel).""" has_image_mean_object_size_slice_description = ( """Restricts the assertion to a specific slice of the image (where ``0`` corresponds to the first image slice).""" ) has_image_mean_object_size_frame_description = """Restricts the assertion to a specific frame of the image sequence (where ``0`` corresponds to the first image frame).""" has_image_mean_object_size_labels_description = """List of labels, separated by a comma. Labels *not* on this list will be excluded from consideration. Cannot be used in combination with ``exclude_labels``.""" has_image_mean_object_size_exclude_labels_description = """List of labels to be excluded from consideration, separated by a comma. The primary usage of this attribute is to exclude the background of a label image. Cannot be used in combination with ``labels``.""" has_image_mean_object_size_mean_object_size_description = """The required mean size of the uniquely labeled objects.""" has_image_mean_object_size_eps_description = """The absolute tolerance to be used for ``value`` (defaults to ``0.01``). The observed mean size of the uniquely labeled objects has to be in the range ``value +- eps``.""" has_image_mean_object_size_min_description = ( """A lower bound of the required mean size of the uniquely labeled objects.""" ) has_image_mean_object_size_max_description = ( """An upper bound of the required mean size of the uniquely labeled objects.""" ) class base_has_image_mean_object_size_model(AssertionModel): """base model for has_image_mean_object_size describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_mean_object_size_model") channel: typing.Optional[StrictInt] = Field( None, title="Channel", description=has_image_mean_object_size_channel_description, ) slice: typing.Optional[StrictInt] = Field( None, title="Slice", description=has_image_mean_object_size_slice_description, ) frame: typing.Optional[StrictInt] = Field( None, title="Frame", description=has_image_mean_object_size_frame_description, ) labels: typing.Optional[typing.List[typing.Union[StrictInt, StrictFloat]]] = Field( None, title="Labels", description=has_image_mean_object_size_labels_description, ) exclude_labels: typing.Optional[typing.List[typing.Union[StrictInt, StrictFloat]]] = Field( None, title="Exclude Labels", description=has_image_mean_object_size_exclude_labels_description, ) mean_object_size: Annotated[ typing.Optional[typing.Union[StrictInt, StrictFloat]], BeforeValidator(check_non_negative_if_set) ] = Field( None, title="Mean Object Size", description=has_image_mean_object_size_mean_object_size_description, ) eps: Annotated[typing.Union[StrictInt, StrictFloat], BeforeValidator(check_non_negative_if_set)] = Field( 0.01, title="Eps", description=has_image_mean_object_size_eps_description, ) min: Annotated[ typing.Optional[typing.Union[StrictInt, StrictFloat]], BeforeValidator(check_non_negative_if_set) ] = Field( None, title="Min", description=has_image_mean_object_size_min_description, ) max: Annotated[ typing.Optional[typing.Union[StrictInt, StrictFloat]], BeforeValidator(check_non_negative_if_set) ] = Field( None, title="Max", description=has_image_mean_object_size_max_description, ) class base_has_image_mean_object_size_model_relaxed(AssertionModel): """base model for has_image_mean_object_size describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_mean_object_size_model_relaxed") channel: typing.Optional[typing.Union[str, int]] = Field( None, title="Channel", description=has_image_mean_object_size_channel_description, ) slice: typing.Optional[typing.Union[str, int]] = Field( None, title="Slice", description=has_image_mean_object_size_slice_description, ) frame: typing.Optional[typing.Union[str, int]] = Field( None, title="Frame", description=has_image_mean_object_size_frame_description, ) labels: typing.Optional[typing.Union[str, typing.List[typing.Union[float, int]]]] = Field( None, title="Labels", description=has_image_mean_object_size_labels_description, ) exclude_labels: typing.Optional[typing.Union[str, typing.List[typing.Union[float, int]]]] = Field( None, title="Exclude Labels", description=has_image_mean_object_size_exclude_labels_description, ) mean_object_size: Annotated[ typing.Optional[typing.Union[float, str]], BeforeValidator(check_non_negative_if_set) ] = Field( None, title="Mean Object Size", description=has_image_mean_object_size_mean_object_size_description, ) eps: Annotated[typing.Union[float, str], BeforeValidator(check_non_negative_if_set)] = Field( 0.01, title="Eps", description=has_image_mean_object_size_eps_description, ) min: Annotated[typing.Optional[typing.Union[float, str]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_mean_object_size_min_description, ) max: Annotated[typing.Optional[typing.Union[float, str]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_mean_object_size_max_description, ) class has_image_mean_object_size_model(base_has_image_mean_object_size_model): r"""Asserts the output is an image with labeled objects which have the specified mean size (number of pixels), The mean size is plus/minus ``eps`` (e.g., ````). The labels must be unique.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Mean Object Size") that: Literal["has_image_mean_object_size"] = Field("has_image_mean_object_size", title="That") class has_image_mean_object_size_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Mean Object Size (Nested)") has_image_mean_object_size: base_has_image_mean_object_size_model = Field( ..., title="Assert Has Image Mean Object Size" ) class has_image_mean_object_size_model_relaxed(base_has_image_mean_object_size_model_relaxed): r"""Asserts the output is an image with labeled objects which have the specified mean size (number of pixels), The mean size is plus/minus ``eps`` (e.g., ````). The labels must be unique.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Mean Object Size (Relaxed)") that: Literal["has_image_mean_object_size"] = Field("has_image_mean_object_size", title="That") has_image_n_labels_channel_description = """Restricts the assertion to a specific channel of the image (where ``0`` corresponds to the first image channel).""" has_image_n_labels_slice_description = ( """Restricts the assertion to a specific slice of the image (where ``0`` corresponds to the first image slice).""" ) has_image_n_labels_frame_description = """Restricts the assertion to a specific frame of the image sequence (where ``0`` corresponds to the first image frame).""" has_image_n_labels_labels_description = """List of labels, separated by a comma. Labels *not* on this list will be excluded from consideration. Cannot be used in combination with ``exclude_labels``.""" has_image_n_labels_exclude_labels_description = """List of labels to be excluded from consideration, separated by a comma. The primary usage of this attribute is to exclude the background of a label image. Cannot be used in combination with ``labels``.""" has_image_n_labels_n_description = """Expected number of labels.""" has_image_n_labels_delta_description = """Maximum allowed difference of the number of labels (default is 0). The observed number of labels has to be in the range ``value +- delta``.""" has_image_n_labels_min_description = """Minimum allowed number of labels.""" has_image_n_labels_max_description = """Maximum allowed number of labels.""" has_image_n_labels_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_image_n_labels_model(AssertionModel): """base model for has_image_n_labels describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_n_labels_model") channel: typing.Optional[StrictInt] = Field( None, title="Channel", description=has_image_n_labels_channel_description, ) slice: typing.Optional[StrictInt] = Field( None, title="Slice", description=has_image_n_labels_slice_description, ) frame: typing.Optional[StrictInt] = Field( None, title="Frame", description=has_image_n_labels_frame_description, ) labels: typing.Optional[typing.List[typing.Union[StrictInt, StrictFloat]]] = Field( None, title="Labels", description=has_image_n_labels_labels_description, ) exclude_labels: typing.Optional[typing.List[typing.Union[StrictInt, StrictFloat]]] = Field( None, title="Exclude Labels", description=has_image_n_labels_exclude_labels_description, ) n: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="N", description=has_image_n_labels_n_description, ) delta: Annotated[StrictInt, BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_n_labels_delta_description, ) min: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_n_labels_min_description, ) max: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_n_labels_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_n_labels_negate_description, ) class base_has_image_n_labels_model_relaxed(AssertionModel): """base model for has_image_n_labels describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_n_labels_model_relaxed") channel: typing.Optional[typing.Union[str, int]] = Field( None, title="Channel", description=has_image_n_labels_channel_description, ) slice: typing.Optional[typing.Union[str, int]] = Field( None, title="Slice", description=has_image_n_labels_slice_description, ) frame: typing.Optional[typing.Union[str, int]] = Field( None, title="Frame", description=has_image_n_labels_frame_description, ) labels: typing.Optional[typing.Union[str, typing.List[typing.Union[float, int]]]] = Field( None, title="Labels", description=has_image_n_labels_labels_description, ) exclude_labels: typing.Optional[typing.Union[str, typing.List[typing.Union[float, int]]]] = Field( None, title="Exclude Labels", description=has_image_n_labels_exclude_labels_description, ) n: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="N", description=has_image_n_labels_n_description, ) delta: Annotated[typing.Union[int, str], BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_n_labels_delta_description, ) min: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_n_labels_min_description, ) max: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_n_labels_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_n_labels_negate_description, ) class has_image_n_labels_model(base_has_image_n_labels_model): r"""Asserts the output is an image and has the specified labels. Labels can be a number of labels or unique values (e.g., ````). The primary usage of this assertion is to verify the number of objects in images with uniquely labeled objects.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image N Labels") that: Literal["has_image_n_labels"] = Field("has_image_n_labels", title="That") class has_image_n_labels_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image N Labels (Nested)") has_image_n_labels: base_has_image_n_labels_model = Field(..., title="Assert Has Image N Labels") class has_image_n_labels_model_relaxed(base_has_image_n_labels_model_relaxed): r"""Asserts the output is an image and has the specified labels. Labels can be a number of labels or unique values (e.g., ````). The primary usage of this assertion is to verify the number of objects in images with uniquely labeled objects.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image N Labels (Relaxed)") that: Literal["has_image_n_labels"] = Field("has_image_n_labels", title="That") has_image_width_width_description = """Expected width of the image (in pixels).""" has_image_width_delta_description = """Maximum allowed difference of the image width (in pixels, default is 0). The observed width has to be in the range ``value +- delta``.""" has_image_width_min_description = """Minimum allowed width of the image (in pixels).""" has_image_width_max_description = """Maximum allowed width of the image (in pixels).""" has_image_width_negate_description = """A boolean that can be set to true to negate the outcome of the assertion.""" class base_has_image_width_model(AssertionModel): """base model for has_image_width describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_width_model") width: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Width", description=has_image_width_width_description, ) delta: Annotated[StrictInt, BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_width_delta_description, ) min: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_width_min_description, ) max: Annotated[typing.Optional[StrictInt], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_width_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_width_negate_description, ) class base_has_image_width_model_relaxed(AssertionModel): """base model for has_image_width describing attributes.""" model_config = ConfigDict(extra="forbid", title="base_has_image_width_model_relaxed") width: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Width", description=has_image_width_width_description, ) delta: Annotated[typing.Union[int, str], BeforeValidator(check_non_negative_if_set)] = Field( 0, title="Delta", description=has_image_width_delta_description, ) min: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Min", description=has_image_width_min_description, ) max: Annotated[typing.Optional[typing.Union[str, int]], BeforeValidator(check_non_negative_if_set)] = Field( None, title="Max", description=has_image_width_max_description, ) negate: typing.Union[bool, str] = Field( False, title="Negate", description=has_image_width_negate_description, ) class has_image_width_model(base_has_image_width_model): r"""Asserts the output is an image and has a specific width (in pixels). The width is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected width can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Width") that: Literal["has_image_width"] = Field("has_image_width", title="That") class has_image_width_model_nested(AssertionModel): r"""Nested version of this assertion model.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Width (Nested)") has_image_width: base_has_image_width_model = Field(..., title="Assert Has Image Width") class has_image_width_model_relaxed(base_has_image_width_model_relaxed): r"""Asserts the output is an image and has a specific width (in pixels). The width is plus/minus ``delta`` (e.g., ````). Alternatively the range of the expected width can be specified by ``min`` and/or ``max``.""" model_config = ConfigDict(extra="forbid", title="Assert Has Image Width (Relaxed)") that: Literal["has_image_width"] = Field("has_image_width", title="That") any_assertion_model_flat = Annotated[ typing.Union[ has_line_model, has_line_matching_model, has_n_lines_model, has_text_model, has_text_matching_model, not_has_text_model, has_n_columns_model, attribute_is_model, attribute_matches_model, element_text_model, element_text_is_model, element_text_matches_model, has_element_with_path_model, has_n_elements_with_path_model, is_valid_xml_model, xml_element_model, has_json_property_with_text_model, has_json_property_with_value_model, has_h5_attribute_model, has_h5_keys_model, has_archive_member_model, has_size_model, has_image_center_of_mass_model, has_image_channels_model, has_image_depth_model, has_image_frames_model, has_image_height_model, has_image_mean_intensity_model, has_image_mean_object_size_model, has_image_n_labels_model, has_image_width_model, ], Field(discriminator="that"), ] any_assertion_model_nested = typing.Union[ has_line_model_nested, has_line_matching_model_nested, has_n_lines_model_nested, has_text_model_nested, has_text_matching_model_nested, not_has_text_model_nested, has_n_columns_model_nested, attribute_is_model_nested, attribute_matches_model_nested, element_text_model_nested, element_text_is_model_nested, element_text_matches_model_nested, has_element_with_path_model_nested, has_n_elements_with_path_model_nested, is_valid_xml_model_nested, xml_element_model_nested, has_json_property_with_text_model_nested, has_json_property_with_value_model_nested, has_h5_attribute_model_nested, has_h5_keys_model_nested, has_archive_member_model_nested, has_size_model_nested, has_image_center_of_mass_model_nested, has_image_channels_model_nested, has_image_depth_model_nested, has_image_frames_model_nested, has_image_height_model_nested, has_image_mean_intensity_model_nested, has_image_mean_object_size_model_nested, has_image_n_labels_model_nested, has_image_width_model_nested, ] any_assertion_model_flat_relaxed = Annotated[ typing.Union[ has_line_model_relaxed, has_line_matching_model_relaxed, has_n_lines_model_relaxed, has_text_model_relaxed, has_text_matching_model_relaxed, not_has_text_model_relaxed, has_n_columns_model_relaxed, attribute_is_model_relaxed, attribute_matches_model_relaxed, element_text_model_relaxed, element_text_is_model_relaxed, element_text_matches_model_relaxed, has_element_with_path_model_relaxed, has_n_elements_with_path_model_relaxed, is_valid_xml_model_relaxed, xml_element_model_relaxed, has_json_property_with_text_model_relaxed, has_json_property_with_value_model_relaxed, has_h5_attribute_model_relaxed, has_h5_keys_model_relaxed, has_archive_member_model_relaxed, has_size_model_relaxed, has_image_center_of_mass_model_relaxed, has_image_channels_model_relaxed, has_image_depth_model_relaxed, has_image_frames_model_relaxed, has_image_height_model_relaxed, has_image_mean_intensity_model_relaxed, has_image_mean_object_size_model_relaxed, has_image_n_labels_model_relaxed, has_image_width_model_relaxed, ], Field(discriminator="that"), ] class assertion_list(RootModel[typing.List[typing.Union[any_assertion_model_flat, any_assertion_model_nested]]]): model_config = ConfigDict(title="assertion_list") # used to model what the XML conversion should look like - not meant to be consumed outside of # of Galaxy internals / linting. class relaxed_assertion_list(RootModel[typing.List[any_assertion_model_flat_relaxed]]): model_config = ConfigDict(title="relaxed_assertion_list") class assertion_dict(AssertionModel): model_config = ConfigDict(extra="forbid", title="assertion_dict") has_line: typing.Optional[base_has_line_model] = Field(None, title="Assert Has Line") has_line_matching: typing.Optional[base_has_line_matching_model] = Field(None, title="Assert Has Line Matching") has_n_lines: typing.Optional[base_has_n_lines_model] = Field(None, title="Assert Has N Lines") has_text: typing.Optional[base_has_text_model] = Field(None, title="Assert Has Text") has_text_matching: typing.Optional[base_has_text_matching_model] = Field(None, title="Assert Has Text Matching") not_has_text: typing.Optional[base_not_has_text_model] = Field(None, title="Assert Not Has Text") has_n_columns: typing.Optional[base_has_n_columns_model] = Field(None, title="Assert Has N Columns") attribute_is: typing.Optional[base_attribute_is_model] = Field(None, title="Assert Attribute Is") attribute_matches: typing.Optional[base_attribute_matches_model] = Field(None, title="Assert Attribute Matches") element_text: typing.Optional[base_element_text_model] = Field(None, title="Assert Element Text") element_text_is: typing.Optional[base_element_text_is_model] = Field(None, title="Assert Element Text Is") element_text_matches: typing.Optional[base_element_text_matches_model] = Field( None, title="Assert Element Text Matches" ) has_element_with_path: typing.Optional[base_has_element_with_path_model] = Field( None, title="Assert Has Element With Path" ) has_n_elements_with_path: typing.Optional[base_has_n_elements_with_path_model] = Field( None, title="Assert Has N Elements With Path" ) is_valid_xml: typing.Optional[base_is_valid_xml_model] = Field(None, title="Assert Is Valid Xml") xml_element: typing.Optional[base_xml_element_model] = Field(None, title="Assert Xml Element") has_json_property_with_text: typing.Optional[base_has_json_property_with_text_model] = Field( None, title="Assert Has Json Property With Text" ) has_json_property_with_value: typing.Optional[base_has_json_property_with_value_model] = Field( None, title="Assert Has Json Property With Value" ) has_h5_attribute: typing.Optional[base_has_h5_attribute_model] = Field(None, title="Assert Has H5 Attribute") has_h5_keys: typing.Optional[base_has_h5_keys_model] = Field(None, title="Assert Has H5 Keys") has_archive_member: typing.Optional[base_has_archive_member_model] = Field(None, title="Assert Has Archive Member") has_size: typing.Optional[base_has_size_model] = Field(None, title="Assert Has Size") has_image_center_of_mass: typing.Optional[base_has_image_center_of_mass_model] = Field( None, title="Assert Has Image Center Of Mass" ) has_image_channels: typing.Optional[base_has_image_channels_model] = Field(None, title="Assert Has Image Channels") has_image_depth: typing.Optional[base_has_image_depth_model] = Field(None, title="Assert Has Image Depth") has_image_frames: typing.Optional[base_has_image_frames_model] = Field(None, title="Assert Has Image Frames") has_image_height: typing.Optional[base_has_image_height_model] = Field(None, title="Assert Has Image Height") has_image_mean_intensity: typing.Optional[base_has_image_mean_intensity_model] = Field( None, title="Assert Has Image Mean Intensity" ) has_image_mean_object_size: typing.Optional[base_has_image_mean_object_size_model] = Field( None, title="Assert Has Image Mean Object Size" ) has_image_n_labels: typing.Optional[base_has_image_n_labels_model] = Field(None, title="Assert Has Image N Labels") has_image_width: typing.Optional[base_has_image_width_model] = Field(None, title="Assert Has Image Width") assertions = typing.Union[assertion_list, assertion_dict] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/dynamic_tool_models.py0000644000175100017510000000160515234436334030231 0ustar00runnerrunnerfrom typing import ( Optional, Union, ) from pydantic import BaseModel from typing_extensions import Literal from galaxy.tool_util_models import ( DynamicToolSources, UserToolSource, ) class BaseDynamicToolCreatePayload(BaseModel): active: Optional[bool] = None hidden: Optional[bool] = None class DynamicToolCreatePayload(BaseDynamicToolCreatePayload): src: Literal["representation"] = "representation" representation: DynamicToolSources active: Optional[bool] = True hidden: Optional[bool] = False class DynamicUnprivilegedToolCreatePayload(DynamicToolCreatePayload): representation: UserToolSource class PathBasedDynamicToolCreatePayload(BaseDynamicToolCreatePayload): src: Literal["from_path"] path: str tool_directory: Optional[str] = None DynamicToolPayload = Union[DynamicToolCreatePayload, PathBasedDynamicToolCreatePayload] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/parameter_validators.py0000644000175100017510000004410615234436334030420 0ustar00runnerrunnerfrom typing import ( Any, List, Optional, Union, ) from pydantic import ( BaseModel, ConfigDict, Field, model_validator, PrivateAttr, TypeAdapter, ) from typing_extensions import ( Annotated, Literal, Protocol, Self, ) try: import regex except ImportError: import re as regex class ValidationArgument: doc: Optional[str] xml_body: bool xml_allow_json_load: bool def __init__( self, doc: Optional[str], xml_body: bool = False, xml_allow_json_load: bool = False, ): self.doc = doc self.xml_body = xml_body self.xml_allow_json_load = xml_allow_json_load Negate = Annotated[ bool, ValidationArgument("Negates the result of the validator."), ] NEGATE_DEFAULT = False SPLIT_DEFAULT = "\t" DEFAULT_VALIDATOR_MESSAGE = "Parameter validation error." ValidatorType = Literal[ "expression", "regex", "in_range", "length", "metadata", "dataset_metadata_equal", "unspecified_build", "no_options", "empty_field", "empty_dataset", "empty_extra_files_path", "dataset_metadata_in_data_table", "dataset_metadata_not_in_data_table", "dataset_metadata_in_range", "value_in_data_table", "value_not_in_data_table", "dataset_ok_validator", "dataset_metadata_in_file", ] class ValidatorDescription(Protocol): @property def negate(self) -> bool: ... @property def message(self) -> Optional[str]: ... class StrictModel(BaseModel): model_config = ConfigDict(extra="forbid") class ParameterValidatorModel(StrictModel): type: ValidatorType message: Annotated[ Optional[str], ValidationArgument( """The error message displayed on the tool form if validation fails. A placeholder string ``%s`` will be repaced by the ``value``""" ), ] = None # track validators setup by other input parameters and not validation explicitly implicit: bool = False _static: bool = PrivateAttr(False) _deprecated: bool = PrivateAttr(False) # validators must be explicitly set as 'safe' to operate as user-defined workflow parameters or to be used # within future user-defined tool parameters _safe: bool = PrivateAttr(False) @model_validator(mode="after") def set_default_message(self) -> Self: if self.message is None: self.message = self.default_message return self @property def default_message(self) -> str: return DEFAULT_VALIDATOR_MESSAGE class StaticValidatorModel(ParameterValidatorModel): _static: bool = PrivateAttr(True) def statically_validate(self, v: Any) -> None: ... class ExpressionParameterValidatorModel(StaticValidatorModel): """Check if a one line python expression given expression evaluates to True. The expression is given is the content of the validator tag.""" type: Literal["expression"] = "expression" negate: Negate = NEGATE_DEFAULT expression: Annotated[str, ValidationArgument("Python expression to validate.", xml_body=True)] def statically_validate(self, value: Any) -> None: ExpressionParameterValidatorModel.expression_validation(self.expression, value, self) @staticmethod def ensure_compiled(expression: Union[str, Any]) -> Any: if isinstance(expression, str): return compile(expression, "", "eval") else: return expression @staticmethod def expression_validation( expression: str, value: Any, validator: "ValidatorDescription", compiled_expression: Optional[Any] = None ): if compiled_expression is None: compiled_expression = ExpressionParameterValidatorModel.ensure_compiled(expression) message = None try: evalresult = eval(compiled_expression, dict(value=value)) except Exception: message = f"Validator '{expression}' could not be evaluated on '{value}'" evalresult = False raise_error_if_validation_fails(bool(evalresult), validator, message=message, value_to_show=value) @property def default_message(self) -> str: return f"Value '%s' does not evaluate to {'True' if not self.negate else 'False'} for '{self.expression}'" class RegexParameterValidatorModel(StaticValidatorModel): """Check if a regular expression **matches** the value, i.e. appears at the beginning of the value. To enforce a match of the complete value use ``$`` at the end of the expression. The expression is given is the content of the validator tag. Note that for ``selects`` each option is checked separately.""" type: Literal["regex"] = "regex" negate: Negate = NEGATE_DEFAULT expression: Annotated[str, ValidationArgument("Regular expression to validate against.", xml_body=True)] _safe: bool = PrivateAttr(True) @property def default_message(self) -> str: return f"Value '%s' does {'not ' if not self.negate else ''}match regular expression '{self.expression.replace('%', '%%')}'" def statically_validate(self, value: Any) -> None: if value and not isinstance(value, str): raise ValueError(f"Wrong type found value {value}") RegexParameterValidatorModel.regex_validation(self.expression, value, self) @staticmethod def regex_validation(expression: str, value: Any, validator: "ValidatorDescription"): if not isinstance(value, list): value = [value] for val in value: match = regex.match(expression, val or "") raise_error_if_validation_fails(match is not None, validator, value_to_show=val) class InRangeParameterValidatorModel(StaticValidatorModel): type: Literal["in_range"] = "in_range" min: Optional[Union[float, int]] = None max: Optional[Union[float, int]] = None exclude_min: bool = False exclude_max: bool = False negate: Negate = NEGATE_DEFAULT _safe: bool = PrivateAttr(True) def statically_validate(self, value: Any): if isinstance(value, (int, float)): validates = True if self.min is not None and value == self.min and self.exclude_min: validates = False elif self.min is not None and value < self.min: validates = False elif self.max is not None and value == self.max and self.exclude_max: validates = False if self.max is not None and value > self.max: validates = False raise_error_if_validation_fails(validates, self) @property def default_message(self) -> str: op1 = "<=" op2 = "<=" if self.exclude_min: op1 = "<" if self.exclude_max: op2 = "<" min_str = str(self.min) if self.min is not None else "-infinity" max_str = str(self.max) if self.max is not None else "+infinity" range_description_str = f"({min_str} {op1} value {op2} {max_str})" return f"Value ('%s') must {'not ' if self.negate else ''}fulfill {range_description_str}" class LengthParameterValidatorModel(StaticValidatorModel): type: Literal["length"] = "length" min: Optional[int] = None max: Optional[int] = None negate: Negate = NEGATE_DEFAULT _safe: bool = PrivateAttr(True) def statically_validate(self, value: Any): if isinstance(value, str): length = len(value) validates = True if self.min is not None and length < self.min: validates = False if self.max is not None and length > self.max: validates = False raise_error_if_validation_fails(validates, self) @property def default_message(self) -> str: return f"Must {'not ' if self.negate else ''}have length of at least {self.min} and at most {self.max}" class MetadataParameterValidatorModel(ParameterValidatorModel): type: Literal["metadata"] = "metadata" check: Optional[List[str]] = None skip: Optional[List[str]] = None negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: check = self.check skip = self.skip message = DEFAULT_VALIDATOR_MESSAGE if not self.negate: message = "Metadata '%s' missing, click the pencil icon in the history item to edit / save the metadata attributes" else: if check: message = f"""At least one of the checked metadata '{",".join(check)}' is set, click the pencil icon in the history item to edit / save the metadata attributes""" elif skip: message = f"""At least one of the non skipped metadata '{",".join(skip)}' is set, click the pencil icon in the history item to edit / save the metadata attributes""" return message class DatasetMetadataEqualParameterValidatorModel(ParameterValidatorModel): type: Literal["dataset_metadata_equal"] = "dataset_metadata_equal" metadata_name: str value: Annotated[Any, ValidationArgument("Value to test against", xml_allow_json_load=True)] negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: if not self.negate: message = f"Metadata value for '{self.metadata_name}' must be '{self.value}', but it is '%s'." else: message = f"Metadata value for '{self.metadata_name}' must not be '{self.value}' but it is." return message class UnspecifiedBuildParameterValidatorModel(ParameterValidatorModel): type: Literal["unspecified_build"] = "unspecified_build" negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: return f"{'Unspecified' if not self.negate else 'Specified'} genome build, click the pencil icon in the history item to {'set' if not self.negate else 'remove'} the genome build" class NoOptionsParameterValidatorModel(StaticValidatorModel): type: Literal["no_options"] = "no_options" negate: Negate = NEGATE_DEFAULT @staticmethod def no_options_validate(value: Any, validator: "ValidatorDescription"): raise_error_if_validation_fails(value is not None, validator) def statically_validate(self, value: Any) -> None: NoOptionsParameterValidatorModel.no_options_validate(value, self) @property def default_message(self) -> str: return f"{'No options' if not self.negate else 'Options'} available for selection" class EmptyFieldParameterValidatorModel(StaticValidatorModel): type: Literal["empty_field"] = "empty_field" negate: Negate = NEGATE_DEFAULT @staticmethod def empty_validate(value: Any, validator: "ValidatorDescription"): raise_error_if_validation_fails((value not in ("", None)), validator) def statically_validate(self, value: Any) -> None: EmptyFieldParameterValidatorModel.empty_validate(value, self) @property def default_message(self) -> str: if not self.negate: message = "Field requires a value" else: message = "Field must not set a value" return message class EmptyDatasetParameterValidatorModel(ParameterValidatorModel): type: Literal["empty_dataset"] = "empty_dataset" negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: return f"The selected dataset is {'non-' if self.negate else ''}empty, this tool expects {'non-' if not self.negate else ''}empty files." class EmptyExtraFilesPathParameterValidatorModel(ParameterValidatorModel): type: Literal["empty_extra_files_path"] = "empty_extra_files_path" negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: negate = self.negate return f"The selected dataset's extra_files_path directory is {'non-' if negate else ''}empty or does {'not ' if not negate else ''}exist, this tool expects {'non-' if not negate else ''}empty extra_files_path directories associated with the selected input." class DatasetMetadataInDataTableParameterValidatorModel(ParameterValidatorModel): type: Literal["dataset_metadata_in_data_table"] = "dataset_metadata_in_data_table" table_name: str metadata_name: str metadata_column: Union[int, str] negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: return f"Value for metadata {self.metadata_name} was not found in {self.table_name}." class DatasetMetadataNotInDataTableParameterValidatorModel(ParameterValidatorModel): type: Literal["dataset_metadata_not_in_data_table"] = "dataset_metadata_not_in_data_table" table_name: str metadata_name: str metadata_column: Union[int, str] negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: return f"Value for metadata {self.metadata_name} was not found in {self.table_name}." class DatasetMetadataInRangeParameterValidatorModel(ParameterValidatorModel): type: Literal["dataset_metadata_in_range"] = "dataset_metadata_in_range" metadata_name: str min: Optional[Union[float, int]] = None max: Optional[Union[float, int]] = None exclude_min: bool = False exclude_max: bool = False negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: op1 = "<=" op2 = "<=" if self.exclude_min: op1 = "<" if self.exclude_max: op2 = "<" range_description_str = f"({self.min} {op1} value {op2} {self.max})" return f"Value ('%s') must {'not ' if self.negate else ''}fulfill {range_description_str}" class ValueInDataTableParameterValidatorModel(ParameterValidatorModel): type: Literal["value_in_data_table"] = "value_in_data_table" table_name: str metadata_column: Union[int, str] negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: return "Value for metadata not found." class ValueNotInDataTableParameterValidatorModel(ParameterValidatorModel): type: Literal["value_not_in_data_table"] = "value_not_in_data_table" table_name: str metadata_column: Union[int, str] negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: return f"Value was not found in {self.table_name}." class DatasetOkValidatorParameterValidatorModel(ParameterValidatorModel): type: Literal["dataset_ok_validator"] = "dataset_ok_validator" negate: Negate = NEGATE_DEFAULT @property def default_message(self) -> str: if not self.negate: message = ( "The selected dataset is still being generated, select another dataset or wait until it is completed" ) else: message = "The selected dataset must not be in state OK" return message class DatasetMetadataInFileParameterValidatorModel(ParameterValidatorModel): type: Literal["dataset_metadata_in_file"] = "dataset_metadata_in_file" filename: str metadata_name: str metadata_column: Union[int, str] line_startswith: Optional[str] = None split: str = SPLIT_DEFAULT negate: Negate = NEGATE_DEFAULT _deprecated: bool = PrivateAttr(True) @property def default_message(self) -> str: return f"Value for metadata {self.metadata_name} was not found in {self.filename}." AnyValidatorModel = Annotated[ Union[ ExpressionParameterValidatorModel, RegexParameterValidatorModel, InRangeParameterValidatorModel, LengthParameterValidatorModel, MetadataParameterValidatorModel, DatasetMetadataEqualParameterValidatorModel, UnspecifiedBuildParameterValidatorModel, NoOptionsParameterValidatorModel, EmptyFieldParameterValidatorModel, EmptyDatasetParameterValidatorModel, EmptyExtraFilesPathParameterValidatorModel, DatasetMetadataInDataTableParameterValidatorModel, DatasetMetadataNotInDataTableParameterValidatorModel, DatasetMetadataInRangeParameterValidatorModel, ValueInDataTableParameterValidatorModel, ValueNotInDataTableParameterValidatorModel, DatasetOkValidatorParameterValidatorModel, DatasetMetadataInFileParameterValidatorModel, ], Field(discriminator="type"), ] AnySafeValidatorModel = Annotated[ Union[ RegexParameterValidatorModel, InRangeParameterValidatorModel, LengthParameterValidatorModel, ], Field(discriminator="type"), ] DiscriminatedAnyValidatorModel = TypeAdapter(AnyValidatorModel) # type: ignore[var-annotated] DiscriminatedAnySafeValidatorModel = TypeAdapter(AnySafeValidatorModel) # type: ignore[var-annotated] def raise_error_if_validation_fails( value: bool, validator: ValidatorDescription, message: Optional[str] = None, value_to_show: Optional[str] = None ): if not isinstance(value, bool): raise AssertionError("Validator logic problem - computed validation value must be boolean") if message is None: message = validator.message if not message: message = DEFAULT_VALIDATOR_MESSAGE assert message is not None if value_to_show and "%s" in message: message = message % value_to_show negate = validator.negate if (not negate and value) or (negate and not value): return else: raise ValueError(message) __all__ = ( "AnyValidatorModel", "DiscriminatedAnyValidatorModel", "ValidatorType", "ParameterValidatorModel", "SPLIT_DEFAULT", "StaticValidatorModel", "ExpressionParameterValidatorModel", "RegexParameterValidatorModel", "InRangeParameterValidatorModel", "LengthParameterValidatorModel", "MetadataParameterValidatorModel", "DatasetMetadataEqualParameterValidatorModel", "UnspecifiedBuildParameterValidatorModel", "NoOptionsParameterValidatorModel", "EmptyFieldParameterValidatorModel", "EmptyDatasetParameterValidatorModel", "EmptyExtraFilesPathParameterValidatorModel", "DatasetMetadataInDataTableParameterValidatorModel", "DatasetMetadataNotInDataTableParameterValidatorModel", "DatasetMetadataInRangeParameterValidatorModel", "ValueInDataTableParameterValidatorModel", "ValueNotInDataTableParameterValidatorModel", "DatasetOkValidatorParameterValidatorModel", "DatasetMetadataInFileParameterValidatorModel", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/parameters.py0000644000175100017510000027330015234436334026353 0ustar00runnerrunner# attempt to model requires_value... # conditional can descend... from abc import abstractmethod from functools import lru_cache from typing import ( Any, Callable, cast, Dict, get_args, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Sequence, Type, TypeVar, Union, ) import annotated_types from pydantic import ( AfterValidator, AliasChoices, AnyUrl, BaseModel, ConfigDict, create_model, Discriminator, Field, field_validator, HttpUrl, model_validator, RootModel, StrictBool, StrictFloat, StrictInt, StrictStr, Tag, TypeAdapter, ) from pydantic.json_schema import SkipJsonSchema from pydantic_extra_types.color import Color from typing_extensions import ( Annotated, Literal, Protocol, ) from ._base import ToolSourceBaseModel from ._types import ( cast_as_type, dict_type, expand_annotation, is_optional, list_type, optional, optional_if_needed, union_type, ) from .parameter_validators import ( EmptyFieldParameterValidatorModel, ExpressionParameterValidatorModel, InRangeParameterValidatorModel, LengthParameterValidatorModel, NoOptionsParameterValidatorModel, RegexParameterValidatorModel, StaticValidatorModel, ) from .sample_sheet import ( SampleSheetColumnDefinitions, SampleSheetRow, ) from .tool_source import ( DrillDownOptionsDict, JsonTestCollectionDefDict, JsonTestDatasetDefDict, ) # TODO: # - implement data_ref on rules and implement some cross model validation # + request: Return info needed to build request pydantic model at runtime. # + request_internal: This is a pydantic model to validate what Galaxy expects to find in the database, # in particular dataset and collection references should be decoded integers. StateRepresentationT = Literal[ "relaxed_request", "request", "request_internal", "request_internal_dereferenced", "landing_request", "landing_request_internal", "job_runtime", "job_internal", "test_case_xml", "test_case_json", "workflow_step", "workflow_step_linked", ] DEFAULT_MODEL_NAME = "DynamicModelForTool" RawStateDict = Dict[str, Any] # could be made more specific - validators need to be classmethod ValidatorDictT = Dict[str, Callable] class DynamicModelInformation(NamedTuple): name: str definition: tuple validators: ValidatorDictT class StrictModel(BaseModel): model_config = ConfigDict(extra="forbid") class ConnectedValue(BaseModel): discriminator: Literal["ConnectedValue"] = Field(alias="__class__") def allow_connected_value(type: Type): return union_type([type, ConnectedValue]) def allow_batching(job_template: DynamicModelInformation, batch_type: Optional[Type] = None) -> DynamicModelInformation: job_py_type: Type = job_template.definition[0] default_value = job_template.definition[1] batch_type = batch_type or job_py_type class BatchRequest(StrictModel): meta_class: Literal["Batch"] = Field(..., alias="__class__") values: List[batch_type] # type: ignore[valid-type] linked: Optional[bool] = None # maybe True instead? request_type = union_type([job_py_type, BatchRequest]) return DynamicModelInformation( job_template.name, (request_type, default_value), {}, # should we modify these somehow? ) class Validators: def validate_not_none(cls, v): assert v is not None, "null is an invalid value for attribute" return v class ParamModel(Protocol): @property def name(self) -> str: ... @property def request_requires_value(self) -> bool: # if this is a non-optional type and no default is defined - an # input value MUST be specified. ... def field_kwargs(self) -> Dict[str, Any]: """Return kwargs for pydantic Field() including json_schema_extra metadata.""" ... def safe_field_name(name: str) -> str: if name.startswith("_"): return f"X{name}" return name def _label_value_dicts(options: List[Any]) -> List[Dict[str, Any]]: return [{"label": o.label, "value": o.value, "selected": o.selected} for o in options] _UNSET: Any = object() def dynamic_model_information_from_py_type( param_model: ParamModel, py_type: Type, requires_value: Optional[bool] = None, validators: Optional[Dict[str, Any]] = None, extra_json_schema: Optional[Dict[str, Any]] = None, default: Any = _UNSET, ) -> DynamicModelInformation: name = safe_field_name(param_model.name) if default is not _UNSET: initialize = default requires_value = False else: if requires_value is None: requires_value = param_model.request_requires_value initialize = ... if requires_value else None py_type_is_optional = is_optional(py_type) validators = validators or {} if not py_type_is_optional and not requires_value: validators["not_null"] = field_validator(name)(Validators.validate_not_none) field_kwargs = param_model.field_kwargs() if extra_json_schema: field_kwargs.setdefault("json_schema_extra", {}).update(extra_json_schema) return DynamicModelInformation( name, (py_type, Field(initialize, alias=param_model.name if param_model.name != name else None, **field_kwargs)), validators, ) # We probably need incoming (parameter def) and outgoing (parameter value as transmitted) models, # where value in the incoming model means "default value" and value in the outgoing model is the actual # value a user has set. (incoming/outgoing from the client perspective). class BaseToolParameterModelDefinition(ToolSourceBaseModel): name: Annotated[ str, Field(description="Parameter name. Used when referencing parameter in workflows or inside command templating."), ] parameter_type: str @abstractmethod def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: """Return info needed to build Pydantic model at runtime for validation.""" def field_kwargs(self) -> Dict[str, Any]: """Return kwargs for pydantic Field() including json_schema_extra metadata.""" return {"json_schema_extra": {"gx_type": self.parameter_type}} class BaseGalaxyToolParameterModelDefinition(BaseToolParameterModelDefinition): hidden: bool = False label: Annotated[ Optional[str], Field(description="Will be displayed on the tool page as the label of the parameter.") ] = None help: Annotated[ Optional[str], Field( description="Short bit of text, rendered on the tool form just below the associated field to provide information about the field." ), ] = None argument: Annotated[ Optional[str], Field( description="""If the parameter reflects just one command line argument of a certain tool, this tag should be set to that particular argument. It is rendered in parenthesis after the help section, and it will create the name attribute (if not given explicitly) from the argument attribute by stripping leading dashes and replacing all remaining dashes by underscores (e.g. if argument="--long-parameter" then name="long_parameter" is implicit).""" ), ] = None is_dynamic: bool = False optional: Annotated[bool, Field(description="If `false`, parameter must have a value.")] = False def field_kwargs(self) -> Dict[str, Any]: kwargs: Dict[str, Any] = {} if self.label: kwargs["title"] = self.label description_parts = [] if self.help: description_parts.append(self.help) if self.argument: description_parts.append(f"({self.argument})") if description_parts: kwargs["description"] = " ".join(description_parts) kwargs["json_schema_extra"] = {"gx_type": self.parameter_type} return kwargs class LabelValue(BaseModel): label: str value: str selected: bool TextCompatiableValidators = Union[ LengthParameterValidatorModel, RegexParameterValidatorModel, ExpressionParameterValidatorModel, EmptyFieldParameterValidatorModel, ] def pydantic_to_galaxy_type(value: Any) -> Any: """We use advanced Pydantic types like URL but the Galaxy validators only expect strings for these.""" if isinstance(value, AnyUrl): return str(value) return value VT = TypeVar("VT", bound=StaticValidatorModel) def _json_schema_annotations_for(static_validator_models: Sequence[VT]) -> List[Any]: """Extract JSON Schema-representable constraint annotations from validators. Non-negated in_range and length validators have direct annotated_types equivalents that Pydantic emits as JSON Schema keywords. Regex is handled separately via json_schema_extra since StringConstraints is incompatible with non-string types (e.g. AnyUrl). """ annotations: List[Any] = [] for v in static_validator_models: if isinstance(v, InRangeParameterValidatorModel) and not v.negate: if v.min is not None: annotations.append(annotated_types.Gt(v.min) if v.exclude_min else annotated_types.Ge(v.min)) if v.max is not None: annotations.append(annotated_types.Lt(v.max) if v.exclude_max else annotated_types.Le(v.max)) elif isinstance(v, LengthParameterValidatorModel) and not v.negate: if v.min is not None: annotations.append(annotated_types.MinLen(v.min)) if v.max is not None: annotations.append(annotated_types.MaxLen(v.max)) return annotations def _json_schema_extra_for_validators(validators: Sequence[VT]) -> Dict[str, Any]: """Extract JSON Schema keywords for validators best handled via json_schema_extra. Regex pattern is emitted here rather than as a type annotation because StringConstraints is incompatible with non-string types like AnyUrl. Negated length uses ``not: {minLength, maxLength}`` since the non-negated form is handled by annotated_types. """ extra: Dict[str, Any] = {} for v in validators: if isinstance(v, RegexParameterValidatorModel) and not v.negate: pattern = v.expression # Python re.match anchors at start; JSON Schema pattern does not if not pattern.startswith("^"): pattern = "^" + pattern extra["pattern"] = pattern break for v in validators: if isinstance(v, LengthParameterValidatorModel) and v.negate: not_constraint: Dict[str, Any] = {} if v.min is not None: not_constraint["minLength"] = v.min if v.max is not None: not_constraint["maxLength"] = v.max if not_constraint: extra["not"] = not_constraint break return extra def decorate_type_with_validators_if_needed( py_type: Type, static_validator_models: Sequence[VT], optional: bool = False ) -> Type: pydantic_validator = pydantic_validator_for(static_validator_models, optional=optional) json_schema_annotations = _json_schema_annotations_for(static_validator_models) all_annotations = json_schema_annotations[:] if pydantic_validator: all_annotations.append(pydantic_validator) if all_annotations: return expand_annotation(py_type, all_annotations) return py_type # Looks like Annotated only work with one PlainValidator so condensing all static validators # into a single PlainValidator for pydantic. def pydantic_validator_for(static_validator_models: Sequence[VT], optional: bool = False) -> Optional[AfterValidator]: if static_validator_models: def validator(v: Any) -> Any: if optional and (v is None or v == ""): return v gx_val = pydantic_to_galaxy_type(v) for static_validator_model in static_validator_models: static_validator_model.statically_validate(gx_val) return v return AfterValidator(validator) else: return None class TextParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_text"] = "gx_text" type: Literal["text"] area: bool = False default_value: Optional[str] = Field(default=None, alias="value") default_options: List[LabelValue] = [] validators: List[TextCompatiableValidators] = [] def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] extra["gx_area"] = self.area if self.default_options: extra["gx_default_options"] = _label_value_dicts(self.default_options) return kwargs @property def py_type(self) -> Type: return optional_if_needed(StrictStr, self.optional) @property def py_type_relaxed_request(self) -> Type: # such a hack but explicit nulls are always allowed in the API even for non-optional # parameters - it becomes "" in the internal state. return optional(StrictStr) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type if state_representation == "relaxed_request": py_type = self.py_type_relaxed_request py_type = decorate_type_with_validators_if_needed(py_type, self.validators, optional=self.optional) if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type( self, py_type, requires_value=requires_value, extra_json_schema=_json_schema_extra_for_validators(self.validators), ) @property def request_requires_value(self) -> bool: return False NumberCompatiableValidators = Union[InRangeParameterValidatorModel,] class IntegerParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_integer"] = "gx_integer" type: Literal["integer"] optional: bool = False value: Optional[int] = None min: Optional[int] = None max: Optional[int] = None validators: List[NumberCompatiableValidators] = [] def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] if self.min is not None: extra["gx_min"] = self.min if self.max is not None: extra["gx_max"] = self.max return kwargs @property def py_type(self) -> Type: return optional_if_needed(StrictInt, self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type validators = self.validators[:] if self.min is not None or self.max is not None: validators.append(InRangeParameterValidatorModel(min=self.min, max=self.max, implicit=True)) py_type = decorate_type_with_validators_if_needed(py_type, validators) if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True elif _is_landing_request(state_representation): requires_value = False return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: return not self.optional and self.value is None _INFINITY_SENTINEL = "__Infinity__" _NEG_INFINITY_SENTINEL = "__-Infinity__" def _convert_infinity_sentinel(v: Any) -> Any: """Convert Galaxy JSON sentinel strings for infinity back to Python floats. Galaxy's custom JSON encoder (galaxy.util.json.safe_dumps) serializes float('inf') as '__Infinity__' and float('-inf') as '__-Infinity__' to produce valid JSON. When these sentinel values appear in deserialized parameter dicts (e.g. from GET /api/tools/{id}/test_data) Pydantic must accept them as valid float input. """ if v == _INFINITY_SENTINEL: return float("inf") elif v == _NEG_INFINITY_SENTINEL: return float("-inf") return v class FloatParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_float"] = "gx_float" type: Literal["float"] value: Optional[float] = None min: Optional[float] = None max: Optional[float] = None validators: List[NumberCompatiableValidators] = [] @field_validator("value", "min", "max", mode="before") @classmethod def convert_infinity_sentinels(cls, v: Any) -> Any: return _convert_infinity_sentinel(v) def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] if self.min is not None: extra["gx_min"] = self.min if self.max is not None: extra["gx_max"] = self.max return kwargs @property def py_type(self) -> Type: return optional_if_needed(union_type([StrictInt, StrictFloat]), self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True elif _is_landing_request(state_representation): requires_value = False validators = self.validators[:] if self.min is not None or self.max is not None: validators.append(InRangeParameterValidatorModel(min=self.min, max=self.max, implicit=True)) py_type = decorate_type_with_validators_if_needed(py_type, validators) # Convert Galaxy JSON sentinel strings ("__Infinity__", "__-Infinity__") to Python floats # before Pydantic validates the field. These sentinels appear when float('inf') values are # round-tripped through Galaxy's safe_dumps/json.loads path (e.g. GET /api/tools/{id}/test_data). dynamic_validators: Dict[str, Any] = { "infinity_sentinel": field_validator(safe_field_name(self.name), mode="before")(_convert_infinity_sentinel) } return dynamic_model_information_from_py_type( self, py_type, requires_value=requires_value, validators=dynamic_validators ) @property def request_requires_value(self) -> bool: return False # External collection source type. ``dce`` is accepted because a job that maps # over a nested collection (e.g. subcollection mapping over a ``list:paired``) # records its input as a ``DatasetCollectionElement``; rerunning such a job # resubmits that ``dce`` reference through the request model. CollectionSrcT = Literal["hdca", "dce"] # Internal collection source type - includes dce for subcollection mapping CollectionInternalSrcT = Literal["hdca", "dce"] class LegacyRequestModelAttributes(StrictModel): # Here for bioblend's sake, should be stripped map_over_type: SkipJsonSchema[Optional[str]] = Field(None, exclude=True) hid: SkipJsonSchema[Optional[int]] = Field(None, exclude=True) workflow_step_id: SkipJsonSchema[Optional[str]] = Field(None, exclude=True) label: SkipJsonSchema[Optional[str]] = Field(None, exclude=True) class DataRequestHda(LegacyRequestModelAttributes): src: Literal["hda"] = "hda" id: StrictStr class DataRequestLdda(LegacyRequestModelAttributes): src: Literal["ldda"] = "ldda" id: StrictStr class DataRequestLd(LegacyRequestModelAttributes): src: Literal["ld"] = Field(deprecated=True) id: StrictStr class DataRequestHdca(LegacyRequestModelAttributes): src: Literal["hdca"] = "hdca" id: StrictStr class DataRequestDce(LegacyRequestModelAttributes): src: Literal["dce"] = "dce" id: StrictStr class FileHash(StrictModel): hash_function: Literal["MD5", "SHA-1", "SHA-256", "SHA-512"] hash_value: StrictStr class BaseDataRequest(StrictModel): url: StrictStr = Field(..., alias="location", validation_alias=AliasChoices("url", "location")) name: Optional[StrictStr] = None ext: StrictStr dbkey: StrictStr = "?" deferred: StrictBool = False created_from_basename: Optional[StrictStr] = None info: Optional[StrictStr] = None tags: Optional[List[str]] = None hashes: Optional[List[FileHash]] = None space_to_tab: bool = False to_posix_lines: bool = False # to implement: # tags model_config = ConfigDict(extra="forbid", populate_by_name=True) @model_validator(mode="before") @classmethod def allow_filetype_and_extension(cls, data: Any): if isinstance(data, dict): extension = data.get("filetype") if extension: data = data.copy() data.pop("filetype") data["ext"] = extension extension = data.get("extension") if extension: data = data.copy() data.pop("extension") data["ext"] = extension return data class DataRequestUri(BaseDataRequest): # calling it url instead of uri to match data fetch schema... src: Literal["url"] = "url" class FileRequestUri(BaseDataRequest): class_: Literal["File"] = Field(..., alias="class") src: None = Field(None, exclude=True) class CollectionElementDataRequestUri(FileRequestUri): class_: Literal["File"] = Field(..., alias="class") identifier: StrictStr = Field( ..., description="A unique identifier for this element within the collection.", validation_alias=AliasChoices("identifier", "name"), ) class CollectionElementCollectionRequestUri(StrictModel): class_: Literal["Collection"] = Field(..., alias="class") identifier: StrictStr = Field( ..., description="A unique identifier for this element within the collection.", validation_alias=AliasChoices("identifier", "name"), ) collection_type: StrictStr elements: List["CollectionRequestUriElement"] @model_validator(mode="before") @classmethod def allow_collection_type_by_type(cls, data: Any): if isinstance(data, dict): collection_type = data.get("type") if collection_type: data = data.copy() data.pop("type") data["collection_type"] = collection_type return data def _collection_element_discriminator(value: Any) -> Optional[str]: if isinstance(value, dict): return value.get("class") or value.get("class_") return getattr(value, "class_", None) # A callable Discriminator avoids the PydanticJsonSchemaWarning emitted for # the recursive Field(discriminator="class_") on this self-referential union; # json_schema_extra restores the OpenAPI discriminator metadata. CollectionRequestUriElement = Annotated[ Union[ Annotated[CollectionElementCollectionRequestUri, Tag("Collection")], Annotated[CollectionElementDataRequestUri, Tag("File")], ], Discriminator(_collection_element_discriminator), Field( json_schema_extra={ "discriminator": { "propertyName": "class", "mapping": { "Collection": "#/components/schemas/CollectionElementCollectionRequestUri", "File": "#/components/schemas/CollectionElementDataRequestUri", }, } } ), ] class DataRequestCollectionUri(StrictModel): class_: Literal["Collection"] = Field(..., alias="class") collection_type: str elements: List[CollectionRequestUriElement] deferred: StrictBool = False name: Optional[StrictStr] = None src: None = Field(None, exclude=True) # Sample sheet metadata column_definitions: Optional[SampleSheetColumnDefinitions] = None rows: Optional[Dict[str, SampleSheetRow]] = None _DataRequest = Annotated[ Union[DataRequestHda, DataRequestLdda, DataRequestLd, DataRequestDce, DataRequestUri], Field(discriminator="src") ] DataRequest: Type = cast(Type, _DataRequest) DataOrCollectionRequest = Union[_DataRequest, FileRequestUri, DataRequestCollectionUri, DataRequestHdca] FileOrCollectionRequest = Annotated[Union[FileRequestUri, DataRequestCollectionUri], Field(discriminator="class_")] DataRequestHda.model_rebuild() DataRequestLd.model_rebuild() DataRequestLdda.model_rebuild() DataRequestDce.model_rebuild() DataRequestUri.model_rebuild() DataRequestHdca.model_rebuild() CollectionElementCollectionRequestUri.model_rebuild() DataRequestCollectionUri.model_rebuild() DataOrCollectionRequestAdapter: TypeAdapter[DataOrCollectionRequest] = TypeAdapter(DataOrCollectionRequest) class BatchDataHdcaInstance(StrictModel): src: Literal["hdca"] id: StrictStr map_over_type: Optional[str] = None class BatchDataDceInstance(StrictModel): src: Literal["dce"] id: StrictStr map_over_type: Optional[str] = None class BatchDataNonCollectionInstance(StrictModel): src: Literal["hda", "ldda"] id: StrictStr BatchDataInstance: Type = cast( Type, Annotated[ Union[BatchDataHdcaInstance, BatchDataDceInstance, BatchDataNonCollectionInstance], Field(discriminator="src") ], ) def multi_data_discriminator(v: Any) -> str: if isinstance(v, dict): src = v.get("src", None) clazz = v.get("class", None) if clazz == "Collection": return "data_request_collection_uri" elif src == "hda": return "data_request_hda" elif src == "ldda": return "data_request_ldda" elif src == "hdca": return "data_request_hdca" elif src == "dce": return "data_request_dce" elif src == "url": return "data_request_uri" return "" def tag(field: Type, tag: str) -> Type: return Annotated[field, Tag(tag)] # type: ignore[return-value] MultiDataInstanceDiscriminator = Discriminator(multi_data_discriminator) MultiDataInstance: Type = cast( Type, Annotated[ union_type( [ tag(DataRequestHda, "data_request_hda"), tag(DataRequestLdda, "data_request_ldda"), tag(DataRequestHdca, "data_request_hdca"), tag(DataRequestDce, "data_request_dce"), tag(DataRequestUri, "data_request_uri"), tag(DataRequestCollectionUri, "data_request_collection_uri"), ] ), Field(discriminator=MultiDataInstanceDiscriminator), ], ) MultiDataRequest: Type = union_type([MultiDataInstance, list_type(MultiDataInstance)]) class DataRequestInternalHda(StrictModel): src: Literal["hda"] id: StrictInt class DataRequestInternalLdda(StrictModel): src: Literal["ldda"] id: StrictInt class DataRequestInternalHdca(StrictModel): src: Literal["hdca"] id: StrictInt class DataRequestInternalDce(StrictModel): src: Literal["dce"] id: StrictInt class DataInternalJson(StrictModel): class_: Annotated[Literal["File"], Field(alias="class")] basename: Annotated[ str, Field( description="The base name of the file, that is, the name of the file without any leading directory path" ), ] location: str path: Annotated[str, Field(description="The absolute path to the file on disk.")] listing: Optional[List[str]] = None # Should be recursive nameroot: Annotated[Optional[str], Field(description="The basename root such that nameroot + nameext == basename")] nameext: Annotated[ Optional[str], Field(description="The basename extension such that nameroot + nameext == basename") ] format: Annotated[str, Field(description="The datatype extension of the file, e.g. 'txt', 'bam', 'fastq.gz'.")] # "secondaryFiles": List[Any], checksum: Optional[str] = None size: int # When a gx_data param receives a DCE (subcollection mapping), preserve element_identifier # for output naming and collection traceability element_identifier: Optional[str] = None class DataCollectionElementInternalJson(DataInternalJson): """A file within a collection element - adds collection-specific metadata.""" element_identifier: str columns: Optional[List[Any]] = None # for sample_sheet elements # Collection runtime models with metadata class DataCollectionInternalJsonBase(StrictModel): """Base model for collection runtime representations with metadata.""" class_: Annotated[Literal["Collection"], Field(alias="class")] name: Optional[str] # None for raw DatasetCollection inputs collection_type: str tags: List[str] = [] # Special metadata fields (optional, type-dependent) column_definitions: Optional[List[Dict[str, Any]]] = None # for sample_sheet fields: Optional[List[Dict[str, Any]]] = None # for record has_single_item: Optional[bool] = None # for paired_or_unpaired columns: Optional[List[Any]] = None # for sample_sheet elements model_config = ConfigDict(populate_by_name=True) class DataCollectionPairedElements(StrictModel): forward: DataCollectionElementInternalJson reverse: DataCollectionElementInternalJson class DataCollectionPairedRuntime(DataCollectionInternalJsonBase): """Paired collection runtime representation.""" collection_type: Literal["paired"] elements: DataCollectionPairedElements class DataCollectionListRuntime(DataCollectionInternalJsonBase): """List collection runtime representation.""" collection_type: Literal["list"] elements: List[DataCollectionElementInternalJson] class DataCollectionSampleSheetRuntime(DataCollectionInternalJsonBase): """Sample sheet collection runtime representation.""" collection_type: Literal["sample_sheet"] elements: List[DataCollectionElementInternalJson] class DataCollectionRecordRuntime(DataCollectionInternalJsonBase): """Record collection runtime representation.""" collection_type: Literal["record"] elements: Dict[ str, Union[ DataCollectionElementInternalJson, "DataCollectionNestedListRuntime", "DataCollectionNestedRecordRuntime" ], ] class DataCollectionPairedOrUnpairedRuntime(DataCollectionInternalJsonBase): """Paired or Unpaired collection runtime representation.""" collection_type: Literal["paired_or_unpaired"] elements: Dict[str, DataCollectionElementInternalJson] class DataCollectionNestedListRuntime(DataCollectionInternalJsonBase): """Nested collection with list-like outer structure (list:*, sample_sheet:*).""" collection_type: str @field_validator("collection_type") @classmethod def must_be_nested_list_like(cls, v: str) -> str: if ":" not in v: raise ValueError(f'Nested collection_type must contain ":", got "{v}"') first_segment = v.split(":")[0] if first_segment not in ("list", "sample_sheet"): raise ValueError(f'Outer type must be list-like (list, sample_sheet), got "{first_segment}"') return v elements: List[ Union[ "DataCollectionListRuntime", "DataCollectionSampleSheetRuntime", "DataCollectionPairedRuntime", "DataCollectionRecordRuntime", "DataCollectionPairedOrUnpairedRuntime", "DataCollectionNestedListRuntime", "DataCollectionNestedRecordRuntime", ] ] class DataCollectionNestedRecordRuntime(DataCollectionInternalJsonBase): """Nested collection with record-like outer structure (paired:*, record:*).""" collection_type: str @field_validator("collection_type") @classmethod def must_be_nested_record_like(cls, v: str) -> str: if ":" not in v: raise ValueError(f'Nested collection_type must contain ":", got "{v}"') first_segment = v.split(":")[0] if first_segment in ("list", "sample_sheet"): raise ValueError(f'Outer type must be record-like, got list-like "{first_segment}"') return v elements: Dict[ str, Union[ DataCollectionElementInternalJson, "DataCollectionListRuntime", "DataCollectionSampleSheetRuntime", "DataCollectionPairedRuntime", "DataCollectionRecordRuntime", "DataCollectionPairedOrUnpairedRuntime", "DataCollectionNestedListRuntime", "DataCollectionNestedRecordRuntime", ], ] DataCollectionNestedListRuntime.model_rebuild() DataCollectionNestedRecordRuntime.model_rebuild() _LEAF_COLLECTION_MODELS: Dict[str, Type] = { "list": DataCollectionListRuntime, "paired": DataCollectionPairedRuntime, "record": DataCollectionRecordRuntime, "paired_or_unpaired": DataCollectionPairedOrUnpairedRuntime, "sample_sheet": DataCollectionSampleSheetRuntime, } @lru_cache(maxsize=128) def build_collection_model_for_type(collection_type: str) -> Optional[Type]: """Dynamically generate a Pydantic model for a specific collection_type. Simple types -> existing static model. Nested types -> model with Literal[collection_type] and elements narrowed to exact inner model. Unknown single-segment types -> None (caller decides fallback). """ if collection_type in _LEAF_COLLECTION_MODELS: return _LEAF_COLLECTION_MODELS[collection_type] if ":" not in collection_type: return None outer_segment, inner_type = collection_type.split(":", 1) inner_model = build_collection_model_for_type(inner_type) if inner_model is None: return None is_list_like = outer_segment in ("list", "sample_sheet") if is_list_like: elements_type = list_type(inner_model) else: elements_type = dict_type(str, inner_model) safe_name = f"DynamicCollection_{'_'.join(collection_type.split(':'))}" model = create_model( safe_name, __base__=DataCollectionInternalJsonBase, collection_type=(Literal[collection_type], ...), elements=(elements_type, ...), ) return model def _collection_type_discriminator(v: Any) -> str: """Return the full collection_type string for discriminated union routing. Used for subset unions (comma-separated types) where tags may be dynamic model collection_type strings like "list:paired". """ if isinstance(v, dict): return v.get("collection_type", "") return getattr(v, "collection_type", "") def collection_runtime_discriminator(v: Any) -> str: """Discriminator function for collection runtime unions. Routes validation to the correct model based on collection_type pattern. """ if isinstance(v, dict): ct = v.get("collection_type", "") else: ct = getattr(v, "collection_type", "") # Simple types - exact match if ct == "list": return "list" elif ct == "paired": return "paired" elif ct == "record": return "record" elif ct == "paired_or_unpaired": return "paired_or_unpaired" elif ct == "sample_sheet": return "sample_sheet" elif ":" in ct: # Nested types - route by outer structure first_segment = ct.split(":")[0] if first_segment in ("list", "sample_sheet"): return "nested_list" else: return "nested_record" elif not ct: # Missing collection_type — data isn't a runtime collection dict. # Route to list so Pydantic fails validation with a clear schema error # rather than a discriminator error. return "list" else: raise ValueError(f"Unknown collection_type for runtime discrimination: '{ct}'") CollectionRuntimeDiscriminated: Type = cast( Type, Annotated[ Union[ Annotated[DataCollectionListRuntime, Tag("list")], Annotated[DataCollectionSampleSheetRuntime, Tag("sample_sheet")], Annotated[DataCollectionPairedRuntime, Tag("paired")], Annotated[DataCollectionRecordRuntime, Tag("record")], Annotated[DataCollectionPairedOrUnpairedRuntime, Tag("paired_or_unpaired")], Annotated[DataCollectionNestedListRuntime, Tag("nested_list")], Annotated[DataCollectionNestedRecordRuntime, Tag("nested_record")], ], Discriminator(collection_runtime_discriminator), ], ) DataRequestInternal: Type = cast( Type, Annotated[ union_type( [ tag(DataRequestInternalHda, "data_request_hda"), tag(DataRequestInternalLdda, "data_request_ldda"), tag(DataRequestInternalHdca, "data_request_hdca"), tag(DataRequestInternalDce, "data_request_dce"), tag(DataRequestUri, "data_request_uri"), tag(DataRequestCollectionUri, "data_request_collection_uri"), ] ), Field(discriminator=MultiDataInstanceDiscriminator), ], ) class DatasetCollectionElementReference(StrictModel): src: Literal["dce"] id: StrictInt map_over_type: Optional[str] = None DataRequestInternalDereferencedT = Union[ DataRequestInternalHda, DataRequestInternalLdda, DatasetCollectionElementReference ] DataRequestInternalDereferenced: Type = cast( Type, Annotated[DataRequestInternalDereferencedT, Field(discriminator="src")], ) DataJobInternalT = Union[DataRequestInternalHda, DataRequestInternalLdda, DatasetCollectionElementReference] DataJobInternal: Type = cast( Type, Annotated[DataJobInternalT, Field(discriminator="src")], ) class BatchDataHdcaInstanceInternal(StrictModel): src: Literal["hdca"] id: StrictInt map_over_type: Optional[str] = None class BatchDataDceInstanceInternal(StrictModel): src: Literal["dce"] id: StrictInt map_over_type: Optional[str] = None class BatchDataNonCollectionInstanceInternal(StrictModel): src: Literal["hda", "ldda"] id: StrictInt BatchDataInstanceInternal: Type = cast( Type, Annotated[ Union[BatchDataHdcaInstanceInternal, BatchDataDceInstanceInternal, BatchDataNonCollectionInstanceInternal], Field(discriminator="src"), ], ) MultiDataInstanceInternal: Type = cast( Type, Annotated[ Union[ DataRequestInternalHda, DataRequestInternalLdda, DataRequestInternalHdca, DataRequestInternalDce, DataRequestUri, ], Field(discriminator="src"), ], ) MultiDataInstanceInternalDereferenced: Type = cast( Type, Annotated[ Union[DataRequestInternalHda, DataRequestInternalLdda, DataRequestInternalHdca, DataRequestInternalDce], Field(discriminator="src"), ], ) MultiDataRequestInternal: Type = union_type([MultiDataInstanceInternal, list_type(MultiDataInstanceInternal)]) MultiDataRequestInternalDereferenced: Type = union_type( [MultiDataInstanceInternalDereferenced, list_type(MultiDataInstanceInternalDereferenced)] ) class DataParameterModel(BaseGalaxyToolParameterModelDefinition): model_config = ConfigDict(populate_by_name=True) parameter_type: Literal["gx_data"] = "gx_data" type: Literal["data"] extensions: Annotated[ List[str], Field( validation_alias=AliasChoices("extensions", "format"), description="Limit inputs to datasets with these extensions. Use 'data' to allow all input datasets.", examples=["txt", "tabular", "tiff"], ), ] = ["data"] multiple: Annotated[bool, Field(description="Allow multiple values to be selected.")] = False min: Optional[int] = None max: Optional[int] = None url_default: Optional[str] = None @model_validator(mode="before") @classmethod def _reject_extensions_and_format(cls, data): if isinstance(data, dict) and "extensions" in data and "format" in data: raise ValueError("Specify either 'extensions' or 'format', not both") return data def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] extra["gx_extensions"] = self.extensions extra["gx_multiple"] = self.multiple if self.min is not None: extra["gx_min"] = self.min if self.max is not None: extra["gx_max"] = self.max return kwargs @property def py_type(self) -> Type: base_model: Type if self.multiple: base_model = MultiDataRequest else: base_model = DataRequest return optional_if_needed(base_model, self.optional) @property def py_type_internal_json(self) -> Type: base_model: Type if self.multiple: base_model = list_type(DataInternalJson) else: base_model = DataInternalJson return optional_if_needed(base_model, self.optional) @property def py_type_internal(self) -> Type: base_model: Type if self.multiple: base_model = MultiDataRequestInternal else: base_model = DataRequestInternal return optional_if_needed(base_model, self.optional) @property def py_type_internal_dereferenced(self) -> Type: base_model: Type if self.multiple: base_model = MultiDataRequestInternalDereferenced else: base_model = DataRequestInternalDereferenced return optional_if_needed(base_model, self.optional) @property def py_type_job_internal(self) -> Type: base_model: Type if self.multiple: base_model = MultiDataRequestInternalDereferenced else: base_model = DataJobInternal return optional_if_needed(base_model, self.optional) @property def py_type_test_case(self) -> Type: base_model: Type if self.multiple: base_model = list_type(JsonTestDatasetDefDict) else: base_model = JsonTestDatasetDefDict return optional_if_needed(base_model, self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: if state_representation in ["request", "relaxed_request"]: requires_value = None if not self.url_default else False return allow_batching( dynamic_model_information_from_py_type(self, self.py_type, requires_value=requires_value), BatchDataInstance, ) elif state_representation == "landing_request": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type, requires_value=False), BatchDataInstance ) elif state_representation == "request_internal": requires_value = None if not self.url_default else False return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=requires_value), BatchDataInstanceInternal, ) elif state_representation == "landing_request_internal": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=False), BatchDataInstanceInternal, ) elif state_representation == "request_internal_dereferenced": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal_dereferenced), BatchDataInstanceInternal, ) elif state_representation == "job_internal": return dynamic_model_information_from_py_type(self, self.py_type_job_internal, requires_value=True) elif state_representation == "job_runtime": return dynamic_model_information_from_py_type(self, self.py_type_internal_json, requires_value=True) elif state_representation in ("test_case_xml", "test_case_json"): if self.url_default: return dynamic_model_information_from_py_type( self, self.py_type_test_case, default={"class": "File", "location": self.url_default}, ) return dynamic_model_information_from_py_type(self, self.py_type_test_case) elif state_representation == "workflow_step": return dynamic_model_information_from_py_type(self, type(None), requires_value=False) elif state_representation == "workflow_step_linked": return dynamic_model_information_from_py_type(self, ConnectedValue) else: raise NotImplementedError( f"Have not implemented data collection parameter models for state representation {state_representation}" ) @property def request_requires_value(self) -> bool: return not self.optional and self.url_default is None class DataCollectionRequest(StrictModel): src: CollectionSrcT id: StrictStr class BatchCollectionInstance(StrictModel): src: CollectionSrcT id: StrictStr map_over_type: Optional[str] = None class BatchCollectionInstanceInternal(StrictModel): src: CollectionInternalSrcT id: StrictInt map_over_type: Optional[str] = None DataCollectionRequestOrCollectionUri: Type = union_type([DataCollectionRequest, DataRequestCollectionUri]) class DataCollectionRequestInternal(StrictModel): """Internal request for a collection - tracks source type. src can be: - "hdca": Direct collection input (HistoryDatasetCollectionAssociation) - "dce": Subcollection mapping (DatasetCollectionElement) """ src: CollectionInternalSrcT id: StrictInt DataCollectionRequestInternalOrCollectionUri: Type = union_type( [DataCollectionRequestInternal, DataRequestCollectionUri] ) CollectionAdapterSrcT = Literal["CollectionAdapter"] class AdaptedDataCollectionRequestBase(StrictModel): src: CollectionAdapterSrcT class AdaptedDataCollectionPromoteDatasetToCollectionRequest(AdaptedDataCollectionRequestBase): adapter_type: Literal["PromoteDatasetToCollection"] collection_type: Literal["list", "paired_or_unpaired"] adapting: DataRequestHda # calling this name and element_identifier to align with fetch API, etc... class AdapterElementRequest(DataRequestHda): name: str # element_identifier class AdaptedDataCollectionPromoteDatasetsToCollectionRequest(AdaptedDataCollectionRequestBase): adapter_type: Literal["PromoteDatasetsToCollection"] # could allow list in here without changing much else I think but I'm trying to keep these tight in scope collection_type: Literal["paired", "paired_or_unpaired"] adapting: List[AdapterElementRequest] AdaptedDataCollectionRequest = Annotated[ Union[ AdaptedDataCollectionPromoteDatasetToCollectionRequest, AdaptedDataCollectionPromoteDatasetsToCollectionRequest ], Field(discriminator="adapter_type"), ] AdaptedDataCollectionRequestTypeAdapter = TypeAdapter(AdaptedDataCollectionRequest) # type: ignore[var-annotated] class AdaptedDataCollectionPromoteCollectionElementToCollectionRequestInternal(AdaptedDataCollectionRequestBase): adapter_type: Literal["PromoteCollectionElementToCollection"] adapting: DatasetCollectionElementReference class AdaptedDataCollectionPromoteDatasetToCollectionRequestInternal(AdaptedDataCollectionRequestBase): adapter_type: Literal["PromoteDatasetToCollection"] collection_type: Literal["list", "paired_or_unpaired"] adapting: DataRequestInternalHda class AdapterElementRequestInternal(DataRequestInternalHda): name: str # element_identifier class AdaptedDataCollectionPromoteDatasetsToCollectionRequestInternal(AdaptedDataCollectionRequestBase): adapter_type: Literal["PromoteDatasetsToCollection"] # could allow list in here without changing much else I think but I'm trying to keep these tight in scope collection_type: Literal["paired", "paired_or_unpaired"] adapting: List[AdapterElementRequestInternal] AdaptedDataCollectionRequestInternal = Annotated[ Union[ AdaptedDataCollectionPromoteCollectionElementToCollectionRequestInternal, AdaptedDataCollectionPromoteDatasetToCollectionRequestInternal, AdaptedDataCollectionPromoteDatasetsToCollectionRequestInternal, ], Field(discriminator="adapter_type"), ] AdaptedDataCollectionRequestInternalTypeAdapter = TypeAdapter( AdaptedDataCollectionRequestInternal ) # type: ignore[var-annotated] DataCollectionJobInternal: Type = Union[DataCollectionRequestInternal, AdaptedDataCollectionRequestInternal] # type: ignore[assignment] class DataCollectionParameterModel(BaseGalaxyToolParameterModelDefinition): model_config = ConfigDict(populate_by_name=True) parameter_type: Literal["gx_data_collection"] = "gx_data_collection" type: Literal["data_collection"] collection_type: Optional[str] = None extensions: Annotated[ List[str], Field(validation_alias=AliasChoices("extensions", "format")), ] = ["data"] value: Optional[Dict[str, Any]] @model_validator(mode="before") @classmethod def _reject_extensions_and_format(cls, data): if isinstance(data, dict) and "extensions" in data and "format" in data: raise ValueError("Specify either 'extensions' or 'format', not both") return data def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() kwargs["json_schema_extra"]["gx_extensions"] = self.extensions return kwargs @property def py_type(self) -> Type: return optional_if_needed(DataCollectionRequestOrCollectionUri, self.optional) @property def py_type_internal(self) -> Type: return optional_if_needed(DataCollectionRequestInternalOrCollectionUri, self.optional) @property def py_type_internal_dereferenced(self) -> Type: return optional_if_needed(DataCollectionRequestInternal, self.optional) def _runtime_model_for_collection_type(self, ct: str) -> tuple: """Map a single collection type to its runtime model and tag. Returns tuple of (model, tag) for use in discriminated unions. Uses build_collection_model_for_type which handles both leaf and nested types via _LEAF_COLLECTION_MODELS lookup + recursive dynamic model generation. """ model = build_collection_model_for_type(ct) if model is not None: return (model, ct) return (None, None) @property def py_type_internal_json(self) -> Type: # Return normalized collection runtime models with metadata if not self.collection_type: # Unknown collection_type - use full discriminated union return optional_if_needed(CollectionRuntimeDiscriminated, self.optional) # Handle comma-separated collection types (e.g., "list,paired") if "," in self.collection_type: types = [t.strip() for t in self.collection_type.split(",")] tagged_types = [] tags_seen: set = set() for t in types: model, tag_str = self._runtime_model_for_collection_type(t) if model and tag_str not in tags_seen: tags_seen.add(tag_str) tagged_types.append(Annotated[model, Tag(tag_str)]) if tagged_types: if len(tagged_types) == 1: # Single type - no union needed, unwrap Annotated to get base model base_type: Type = get_args(tagged_types[0])[0] else: # Multiple types - build discriminated union # Use _collection_type_discriminator which returns full collection_type, # matching both simple tags ("list") and dynamic tags ("list:paired") base_type = cast( Type, Annotated[Union[tuple(tagged_types)], Discriminator(_collection_type_discriminator)] ) return optional_if_needed(base_type, self.optional) # Fall through to full union if no models matched # Single collection type model, _tag = self._runtime_model_for_collection_type(self.collection_type) if model: return optional_if_needed(model, self.optional) raise ValueError(f"Unknown collection_type for runtime model: '{self.collection_type}'") def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: if state_representation in ["request", "relaxed_request"]: return allow_batching(dynamic_model_information_from_py_type(self, self.py_type), BatchCollectionInstance) elif state_representation == "landing_request": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type, requires_value=False), BatchCollectionInstance, ) elif state_representation == "landing_request_internal": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=False), BatchCollectionInstanceInternal, ) elif state_representation == "request_internal": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal), BatchCollectionInstanceInternal, ) elif state_representation == "request_internal_dereferenced": return allow_batching( dynamic_model_information_from_py_type(self, self.py_type_internal_dereferenced), BatchCollectionInstanceInternal, ) elif state_representation == "job_internal": return dynamic_model_information_from_py_type( self, optional_if_needed(DataCollectionJobInternal, self.optional), requires_value=True ) elif state_representation == "job_runtime": return dynamic_model_information_from_py_type(self, self.py_type_internal_json, requires_value=True) elif state_representation == "workflow_step": return dynamic_model_information_from_py_type(self, type(None), requires_value=False) elif state_representation == "workflow_step_linked": return dynamic_model_information_from_py_type(self, ConnectedValue) elif state_representation == "test_case_xml": return dynamic_model_information_from_py_type(self, JsonTestCollectionDefDict) elif state_representation == "test_case_json": return dynamic_model_information_from_py_type(self, JsonTestCollectionDefDict) else: raise NotImplementedError( f"Have not implemented data collection parameter models for state representation {state_representation}" ) @property def request_requires_value(self) -> bool: return not self.optional and self.value is None class HiddenParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_hidden"] = "gx_hidden" type: Literal["hidden"] value: Optional[str] validators: List[TextCompatiableValidators] = [] @property def py_type(self) -> Type: return optional_if_needed(StrictStr, self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type requires_value = not self.optional and self.value is None py_type = decorate_type_with_validators_if_needed(py_type, self.validators) if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) elif state_representation == "workflow_step" and not self.optional: # allow it to be linked in so force allow optional... py_type = optional(py_type) requires_value = False if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: return not self.optional and self.value is None def ensure_color_valid(value: Optional[Any]): if value is None: return if not isinstance(value, str): raise ValueError(f"Invalid color value type {value.__class__} encountered.") try: Color(value) except Exception as e: raise ValueError(f"Invalid color value {value!r}: {e}") class ColorParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_color"] = "gx_color" type: Literal["color"] value: Optional[str] = None def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() kwargs["json_schema_extra"]["format"] = "color" return kwargs @property def py_type(self) -> Type: return optional_if_needed(StrictStr, self.optional) @staticmethod def validate_color_str(value) -> str: ensure_color_valid(value) return value @staticmethod def validate_color_str_if_value(value) -> str: if value: ensure_color_valid(value) return value @staticmethod def validate_color_str_or_connected_value(value) -> str: if not isinstance(value, ConnectedValue): ensure_color_valid(value) return value def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type requires_value = self.request_requires_value initialize = ... if requires_value else None if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) validators = { "color_format": field_validator(self.name)(ColorParameterModel.validate_color_str_or_connected_value) } elif state_representation == "workflow_step": validators = {"color_format": field_validator(self.name)(ColorParameterModel.validate_color_str_if_value)} else: validators = {"color_format": field_validator(self.name)(ColorParameterModel.validate_color_str)} field_kwargs = self.field_kwargs() return DynamicModelInformation( self.name, (py_type, Field(initialize, **field_kwargs)), validators, ) @property def request_requires_value(self) -> bool: return False class BooleanParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_boolean"] = "gx_boolean" type: Literal["boolean"] value: Optional[bool] = False truevalue: Optional[str] = None falsevalue: Optional[str] = None @property def py_type(self) -> Type: return optional_if_needed(StrictBool, self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: # these parameters always have an implicit default - either None if # if it is optional or 'checked' if not (itself defaulting to False). return False class DirectoryUriParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_directory_uri"] = "gx_directory_uri" type: Literal["directory"] validators: List[TextCompatiableValidators] = [] @property def py_type(self) -> Type: return AnyUrl def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type py_type = decorate_type_with_validators_if_needed(py_type, self.validators) if state_representation == "workflow_step_linked": py_type = allow_connected_value(py_type) requires_value = self.request_requires_value if _is_landing_request(state_representation): requires_value = False return dynamic_model_information_from_py_type( self, py_type, requires_value=requires_value, extra_json_schema=_json_schema_extra_for_validators(self.validators), ) @property def request_requires_value(self) -> bool: return True class RulesMapping(StrictModel): type: str columns: List[StrictInt] class RulesModel(StrictModel): rules: List[Dict[str, Any]] mapping: List[RulesMapping] class RulesParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_rules"] = "gx_rules" type: Literal["rules"] @property def py_type(self) -> Type: return RulesModel def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return dynamic_model_information_from_py_type(self, self.py_type) @property def request_requires_value(self) -> bool: return True SelectCompatiableValidators = Union[NoOptionsParameterValidatorModel,] class SelectParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_select"] = "gx_select" type: Literal["select"] options: Optional[List[LabelValue]] = None multiple: bool = False validators: List[SelectCompatiableValidators] = [] def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] if self.options is not None and self.options: extra["gx_options"] = _label_value_dicts(self.options) extra["gx_multiple"] = self.multiple return kwargs @staticmethod def split_str(cls, data: Any) -> Any: if isinstance(data, str): return [x.strip() for x in data.split(",")] return data def py_type_if_required(self, allow_connections: bool = False) -> Type: if self.options is not None: if len(self.options) > 0: literal_options: List[Type] = [cast_as_type(Literal[o.value]) for o in self.options] py_type = union_type(literal_options) else: py_type = type(None) else: py_type = StrictStr if self.multiple: if allow_connections: py_type = list_type(allow_connected_value(py_type)) else: py_type = list_type(py_type) elif allow_connections: py_type = allow_connected_value(py_type) return py_type @property def py_type(self) -> Type: return optional_if_needed(self.py_type_if_required(), self.optional or self.multiple) @property def py_type_workflow_step(self) -> Type: # this is always optional in this context return optional(self.py_type_if_required()) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: validators = {} requires_value = self.request_requires_value py_type = None if state_representation == "workflow_step": py_type = self.py_type_workflow_step elif state_representation == "workflow_step_linked": py_type = self.py_type_if_required(allow_connections=True) py_type = optional_if_needed(py_type, self.optional or self.multiple) elif state_representation == "test_case_xml": # in a YAML test case representation this can be string, in XML we are still expecting a comma separated string py_type = self.py_type_if_required(allow_connections=False) if self.multiple: validators = {"from_string": field_validator(self.name, mode="before")(SelectParameterModel.split_str)} py_type = union_type([StrictStr, py_type]) py_type = optional_if_needed(py_type, self.optional) elif state_representation == "test_case_json": # in JSON test case representation, lists are already validated as lists (no string splitting) py_type = self.py_type_if_required(allow_connections=False) py_type = optional_if_needed(py_type, self.optional) elif state_representation in ("job_internal", "job_runtime"): requires_value = True py_type = self.py_type else: py_type = self.py_type py_type = decorate_type_with_validators_if_needed(py_type, self.validators) return dynamic_model_information_from_py_type( self, py_type, validators=validators, requires_value=requires_value ) @property def has_selected_static_option(self): return self.options is not None and any(o.selected for o in self.options) @property def default_value(self) -> Optional[str]: assert not self.multiple if self.options: for option in self.options: if option.selected: return option.value # single value pick up first value if not self.optional: return self.options[0].value return None @property def default_values(self) -> Optional[List[str]]: assert self.multiple if self.options: return [option.value for option in self.options if option.selected] return None @property def request_requires_value(self) -> bool: # API will allow an empty value and just grab the first static option # see API Tests -> test_tools.py -> test_select_first_by_default # If it is multiple - it will also always just allow null regardless of # optional - see test_select_multiple_null_handling return False @property def dynamic_options(self) -> bool: return self.options is None class GenomeBuildParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_genomebuild"] = "gx_genomebuild" type: Literal["genomebuild"] multiple: bool def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() kwargs["json_schema_extra"]["gx_multiple"] = self.multiple return kwargs @property def py_type(self) -> Type: py_type: Type = StrictStr if self.multiple: py_type = list_type(py_type) return optional_if_needed(py_type, self.optional or self.multiple) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type(self, self.py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: # it seems to always just pick values currently - an empty multiple or optional comes through as null # and empty single non-optional input comes through as "?"". See gx_genomebuild*.xml tools. return False DrillDownHierarchyT = Literal["recurse", "exact"] def drill_down_possible_values( options: List[DrillDownOptionsDict], multiple: bool, hierarchy: DrillDownHierarchyT ) -> List[str]: possible_values = [] def add_value(option: str, is_leaf: bool): if not multiple and not is_leaf and hierarchy == "recurse": return possible_values.append(option) def walk_selection(option: DrillDownOptionsDict): child_options = option["options"] is_leaf = not child_options add_value(option["value"], is_leaf) if not is_leaf: for child_option in child_options: walk_selection(child_option) for option in options: walk_selection(option) return possible_values class DrillDownParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_drill_down"] = "gx_drill_down" type: Literal["drill_down"] options: Optional[List[DrillDownOptionsDict]] = None multiple: bool hierarchy: DrillDownHierarchyT def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() kwargs["json_schema_extra"]["gx_multiple"] = self.multiple return kwargs @property def py_type(self) -> Type: if self.options is not None: literal_options: List[Type] = [ cast_as_type(Literal[o]) for o in drill_down_possible_values(self.options, self.multiple, self.hierarchy) ] py_type = union_type(literal_options) else: py_type = StrictStr if self.multiple: py_type = list_type(py_type) return py_type @property def py_type_test_case_xml(self) -> Type: base_model = str return optional_if_needed(base_model, not self.request_requires_value) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: py_type = self.py_type_test_case_xml if state_representation == "test_case_xml" else self.py_type if state_representation == "test_case_json": # JSON test cases use the normal type (not string-based) py_type = self.py_type requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: options = self.options if options: # if any of these are selected, they seem to serve as defaults - check out test_tools -> test_drill_down_first_by_default return not any_drill_down_options_selected(options) else: # I'm not sure how to handle dynamic options... they might or might not be required? # do we need to default to assuming they're not required? return False @property def default_option(self) -> Optional[str]: options = self.options if options: selected_options = selected_drill_down_options(options) if len(selected_options) > 0: return selected_options[0] return None @property def default_options(self) -> Optional[List[str]]: options = self.options if options: selected_options = selected_drill_down_options(options) return selected_options return None def any_drill_down_options_selected(options: List[DrillDownOptionsDict]) -> bool: for option in options: selected = option.get("selected") if selected: return True child_options = option.get("options", []) if any_drill_down_options_selected(child_options): return True return False def selected_drill_down_options(options: List[DrillDownOptionsDict]) -> List[str]: selected_options: List[str] = [] for option in options: selected = option.get("selected") value = option.get("value") if selected and value: selected_options.append(value) child_options = option.get("options", []) selected_options.extend(selected_drill_down_options(child_options)) return selected_options class DataColumnParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_data_column"] = "gx_data_column" type: Literal["data_column"] multiple: bool value: Optional[Union[int, List[int]]] = None def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() kwargs["json_schema_extra"]["gx_multiple"] = self.multiple return kwargs @staticmethod def split_str(cls, data: Any) -> Any: if isinstance(data, str): return [int(x.strip()) for x in data.split(",")] elif isinstance(data, int): return [data] return data @property def py_type(self) -> Type: py_type: Type = StrictInt if self.multiple: py_type = list_type(py_type) return optional_if_needed(py_type, self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: if state_representation == "test_case_xml": if self.multiple: validators = { "from_string": field_validator(self.name, mode="before")(DataColumnParameterModel.split_str) } py_type = union_type([StrictStr, self.py_type]) else: validators = {} py_type = self.py_type requires_value = self.request_requires_value return dynamic_model_information_from_py_type( self, py_type, validators=validators, requires_value=requires_value ) elif state_representation == "test_case_json": # JSON test cases accept lists directly (no string splitting) requires_value = self.request_requires_value return dynamic_model_information_from_py_type( self, self.py_type, validators={}, requires_value=requires_value ) else: requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type(self, self.py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: return self.multiple and not (self.optional or self.value) class GroupTagParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_group_tag"] = "gx_group_tag" type: Literal["group_tag"] multiple: bool def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() kwargs["json_schema_extra"]["gx_multiple"] = self.multiple return kwargs @property def py_type(self) -> Type: py_type: Type = StrictStr if self.multiple: py_type = list_type(py_type) return optional_if_needed(py_type, self.optional) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True return dynamic_model_information_from_py_type(self, self.py_type, requires_value=requires_value) @property def request_requires_value(self) -> bool: return not self.optional class BaseUrlParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_baseurl"] = "gx_baseurl" type: Literal["baseurl"] @property def py_type(self) -> Type: return HttpUrl def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return dynamic_model_information_from_py_type(self, self.py_type) @property def request_requires_value(self) -> bool: return True DiscriminatorType = Union[bool, str] def cond_test_parameter_default_value( test_parameter: Union[BooleanParameterModel, "SelectParameterModel"], ) -> Optional[DiscriminatorType]: default_value: Optional[DiscriminatorType] = None if isinstance(test_parameter, BooleanParameterModel): default_value = test_parameter.value elif isinstance(test_parameter, SelectParameterModel): select_default_value = test_parameter.default_value if select_default_value is not None: default_value = select_default_value return default_value class ConditionalWhen(StrictModel): discriminator: DiscriminatorType parameters: List["ToolParameterT"] is_default_when: bool class ConditionalParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_conditional"] = "gx_conditional" type: Literal["conditional"] test_parameter: Union[BooleanParameterModel, SelectParameterModel] whens: List[ConditionalWhen] def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] test_param = self.test_parameter if isinstance(test_param, SelectParameterModel) and test_param.options: extra["gx_options"] = _label_value_dicts(test_param.options) if test_param.label: extra["gx_test_label"] = test_param.label if test_param.help: extra["gx_test_help"] = test_param.help return kwargs def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: is_boolean = isinstance(self.test_parameter, BooleanParameterModel) test_param_name = self.test_parameter.name test_info = self.test_parameter.pydantic_template(state_representation) extra_validators = test_info.validators if state_representation in ("job_internal", "job_runtime"): test_parameter_requires_value = True else: test_parameter_requires_value = self.test_parameter.request_requires_value when_types: List[Type[BaseModel]] = [] default_type = None for when in self.whens: discriminator = when.discriminator parameters = when.parameters if test_parameter_requires_value: initialize_test = ... else: initialize_test = None tag = str(discriminator) if not is_boolean else str(discriminator).lower() extra_kwd = {test_param_name: (Literal[when.discriminator], initialize_test)} when_types.append( cast( Type[BaseModel], Annotated[ create_field_model( parameters, f"When_{test_param_name}_{discriminator}", state_representation, extra_kwd=extra_kwd, extra_validators=extra_validators, ), Tag(tag), ], ) ) # job_internal requires parameters are filled in - so don't allow the absent branch # here that most other state representations allow if state_representation not in ("job_internal", "job_runtime"): if when.is_default_when: extra_kwd = {} default_type = create_field_model( parameters, f"When_{test_param_name}___absent", state_representation, extra_kwd=extra_kwd, extra_validators={}, ) when_types.append(cast(Type[BaseModel], Annotated[default_type, Tag("__absent__")])) def model_x_discriminator(v: Any) -> Optional[str]: # returning None causes a validation error, this is what we would want if # if the conditional state is not a dictionary. if not isinstance(v, dict): return None if test_param_name not in v: return "__absent__" else: test_param_val = v[test_param_name] if test_param_val is True: return "true" elif test_param_val is False: return "false" else: return str(test_param_val) py_type: Type if len(when_types) > 1: cond_type = union_type(when_types) class ConditionalType(RootModel): root: cond_type = Field(..., discriminator=Discriminator(model_x_discriminator)) # type: ignore[valid-type] if default_type is not None: initialize_cond = None else: initialize_cond = ... py_type = ConditionalType else: py_type = when_types[0] # a better check here would be if any of the parameters below this have a required value, # in the case of job_internal though this is correct if state_representation in ("job_internal", "job_runtime"): initialize_cond = ... else: initialize_cond = None field_kwargs = self.field_kwargs() return DynamicModelInformation( self.name, (py_type, Field(initialize_cond, **field_kwargs)), {}, ) @property def request_requires_value(self) -> bool: return False # TODO class RepeatParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_repeat"] = "gx_repeat" type: Literal["repeat"] parameters: List["ToolParameterT"] min: Optional[int] = None max: Optional[int] = None def field_kwargs(self) -> Dict[str, Any]: kwargs = super().field_kwargs() extra = kwargs["json_schema_extra"] if self.min is not None: extra["gx_min"] = self.min if self.max is not None: extra["gx_max"] = self.max return kwargs def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: # Maybe validators for min and max... instance_class: Type[BaseModel] = create_field_model( self.parameters, f"Repeat_{self.name}", state_representation ) min_length = self.min max_length = self.max requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True elif _is_landing_request(state_representation): requires_value = False min_length = 0 # in a landing request - parameters can be partially filled initialize_repeat: Any if requires_value: initialize_repeat = ... else: initialize_repeat = None class RepeatType(RootModel): root: List[instance_class] = Field(initialize_repeat, min_length=min_length, max_length=max_length) # type: ignore[valid-type] field_kwargs = self.field_kwargs() return DynamicModelInformation( self.name, (RepeatType, Field(initialize_repeat, **field_kwargs)), {}, ) @property def request_requires_value(self) -> bool: if self.min is None or self.min == 0: return False # so we know we need at least one value, but maybe none of the parameters in the list # are required for parameter in self.parameters: if parameter.request_requires_value: return True return False class SectionParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["gx_section"] = "gx_section" type: Literal["section"] parameters: List["ToolParameterT"] def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: instance_class: Type[BaseModel] = create_field_model( self.parameters, f"Section_{self.name}", state_representation ) requires_value = self.request_requires_value if state_representation in ("job_internal", "job_runtime"): requires_value = True if requires_value: initialize_section = ... else: initialize_section = None field_kwargs = self.field_kwargs() return DynamicModelInformation( self.name, (instance_class, Field(initialize_section, **field_kwargs)), {}, ) @property def request_requires_value(self) -> bool: any_request_parameters_required = False for parameter in self.parameters: if parameter.request_requires_value: any_request_parameters_required = True break return any_request_parameters_required LiteralNone: Type = Literal[None] # type: ignore[assignment] class CwlNullParameterModel(BaseToolParameterModelDefinition): parameter_type: Literal["cwl_null"] = "cwl_null" @property def py_type(self) -> Type: return LiteralNone def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return DynamicModelInformation( self.name, (self.py_type, ...), {}, ) @property def request_requires_value(self) -> bool: return False class CwlStringParameterModel(BaseToolParameterModelDefinition): parameter_type: Literal["cwl_string"] = "cwl_string" @property def py_type(self) -> Type: return StrictStr def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return DynamicModelInformation( self.name, (self.py_type, ...), {}, ) @property def request_requires_value(self) -> bool: return True class CwlIntegerParameterModel(BaseToolParameterModelDefinition): parameter_type: Literal["cwl_integer"] = "cwl_integer" @property def py_type(self) -> Type: return StrictInt def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return DynamicModelInformation( self.name, (self.py_type, ...), {}, ) @property def request_requires_value(self) -> bool: return True class CwlFloatParameterModel(BaseToolParameterModelDefinition): parameter_type: Literal["cwl_float"] = "cwl_float" @property def py_type(self) -> Type: return union_type([StrictFloat, StrictInt]) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return DynamicModelInformation( self.name, (self.py_type, ...), {}, ) @property def request_requires_value(self) -> bool: return True class CwlBooleanParameterModel(BaseToolParameterModelDefinition): parameter_type: Literal["cwl_boolean"] = "cwl_boolean" @property def py_type(self) -> Type: return StrictBool def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return DynamicModelInformation( self.name, (self.py_type, ...), {}, ) @property def request_requires_value(self) -> bool: return True class CwlUnionParameterModel(BaseToolParameterModelDefinition): parameter_type: Literal["cwl_union"] = "cwl_union" parameters: List["CwlParameterT"] @property def py_type(self) -> Type: union_of_cwl_types: List[Type] = [] for parameter in self.parameters: union_of_cwl_types.append(parameter.py_type) return union_type(union_of_cwl_types) def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return DynamicModelInformation( self.name, (self.py_type, ...), {}, ) @property def request_requires_value(self) -> bool: return False # TODO: class CwlFileParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["cwl_file"] = "cwl_file" @property def py_type(self) -> Type: return DataRequest def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return dynamic_model_information_from_py_type(self, self.py_type) @property def request_requires_value(self) -> bool: return True class CwlDirectoryParameterModel(BaseGalaxyToolParameterModelDefinition): parameter_type: Literal["cwl_directory"] = "cwl_directory" @property def py_type(self) -> Type: return DataRequest def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation: return dynamic_model_information_from_py_type(self, self.py_type) @property def request_requires_value(self) -> bool: return True CwlParameterT = Union[ CwlIntegerParameterModel, CwlFloatParameterModel, CwlStringParameterModel, CwlBooleanParameterModel, CwlNullParameterModel, CwlFileParameterModel, CwlDirectoryParameterModel, CwlUnionParameterModel, ] GalaxyParameterT = Union[ TextParameterModel, IntegerParameterModel, FloatParameterModel, BooleanParameterModel, HiddenParameterModel, SelectParameterModel, DataParameterModel, DataCollectionParameterModel, DataColumnParameterModel, DirectoryUriParameterModel, RulesParameterModel, DrillDownParameterModel, GroupTagParameterModel, BaseUrlParameterModel, GenomeBuildParameterModel, ColorParameterModel, ConditionalParameterModel, RepeatParameterModel, SectionParameterModel, ] ToolParameterT = Union[ CwlParameterT, GalaxyParameterT, ] class ToolParameterModel(RootModel): root: ToolParameterT = Field(..., discriminator="parameter_type") class GalaxyToolParameterModel(RootModel): root: GalaxyParameterT = Field(..., discriminator="type") ConditionalWhen.model_rebuild() ConditionalParameterModel.model_rebuild() RepeatParameterModel.model_rebuild() CwlUnionParameterModel.model_rebuild() class MaybeToolParameterBundle(Protocol): """An object that may or may not be a ToolParameterModel, but if it is a model, it has a root that is a ToolParameterT""" parameters: Optional[List[ToolParameterT]] class ToolParameterBundle(Protocol): """An object having a dictionary of input models (i.e. a 'Tool')""" parameters: List[ToolParameterT] class ToolParameterBundleModel(BaseModel): parameters: List[ToolParameterT] def to_simple_model(input_parameter: Union[ToolParameterModel, ToolParameterT]) -> ToolParameterT: if input_parameter.__class__ == ToolParameterModel: assert isinstance(input_parameter, ToolParameterModel) return input_parameter.root else: return cast(ToolParameterT, input_parameter) def simple_input_models( parameters: Union[List[ToolParameterModel], List[ToolParameterT]], ) -> Iterable[ToolParameterT]: return [to_simple_model(m) for m in parameters] def iter_parameter_models(parameters: Iterable[ToolParameterT]) -> Iterator[ToolParameterT]: """Yield every parameter in a parameter tree, depth-first. Descends into repeats, sections and *all* branches of every conditional - the purely structural view of the tree, independent of any state. A conditional yields the conditional itself, then its discriminator ``test_parameter``, then every parameter across all of its whens; a repeat/section yields the group node then its children. Use this for structure-only queries (collecting names, validating types). When the walk needs to follow values - and so only the active when of each conditional - use ``visit_input_values`` instead. """ for parameter in parameters: yield parameter if isinstance(parameter, ConditionalParameterModel): yield parameter.test_parameter for when in parameter.whens: yield from iter_parameter_models(when.parameters) elif isinstance(parameter, (RepeatParameterModel, SectionParameterModel)): yield from iter_parameter_models(parameter.parameters) def create_model_strict(*args, **kwd) -> Type[BaseModel]: # protected_namespaces here prevents tool with model_ parameter names from issuing warnings model_config = ConfigDict(extra="forbid", protected_namespaces=()) return create_model(*args, __config__=model_config, **kwd) def create_model_factory(state_representation: StateRepresentationT): def create_method(tool: ToolParameterBundle, name: Optional[str] = None) -> Type[BaseModel]: return create_field_model(tool.parameters, name or DEFAULT_MODEL_NAME, state_representation) return create_method create_relaxed_request_model = create_model_factory("relaxed_request") create_request_model = create_model_factory("request") create_request_internal_model = create_model_factory("request_internal") create_request_internal_dereferenced_model = create_model_factory("request_internal_dereferenced") create_landing_request_model = create_model_factory("landing_request") create_landing_request_internal_model = create_model_factory("landing_request_internal") create_job_internal_model = create_model_factory("job_internal") create_job_runtime_model = create_model_factory("job_runtime") create_test_case_model = create_model_factory("test_case_xml") create_test_case_json_model = create_model_factory("test_case_json") create_workflow_step_model = create_model_factory("workflow_step") create_workflow_step_linked_model = create_model_factory("workflow_step_linked") def create_field_model( tool_parameter_models: Union[List[ToolParameterModel], List[ToolParameterT]], name: str, state_representation: StateRepresentationT, extra_kwd: Optional[Mapping[str, tuple]] = None, extra_validators: Optional[ValidatorDictT] = None, ) -> Type[BaseModel]: kwd: Dict[str, tuple] = {} if extra_kwd: kwd.update(extra_kwd) model_validators = (extra_validators or {}).copy() for input_model in tool_parameter_models: input_model = to_simple_model(input_model) pydantic_request_template = input_model.pydantic_template(state_representation) input_name = pydantic_request_template.name kwd[input_name] = pydantic_request_template.definition input_validators = pydantic_request_template.validators for validator_name, validator_callable in input_validators.items(): model_validators[f"{input_name}_{validator_name}"] = validator_callable pydantic_model = create_model_strict(name, __validators__=model_validators, **kwd) return pydantic_model def _is_landing_request(state_representation: StateRepresentationT): return state_representation in ["landing_request", "landing_request_internal"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/py.typed0000644000175100017510000000000015234436334025316 0ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/sample_sheet.py0000644000175100017510000000360615234436334026661 0ustar00runnerrunner"""Sample sheet type definitions for Galaxy. This module contains type definitions for sample sheets, extracted to avoid circular imports. These types are used across the codebase for sample sheet metadata in collections. """ from typing import ( Any, Dict, List, Optional, Union, ) from pydantic import ( ConfigDict, with_config, ) from typing_extensions import ( Literal, NotRequired, TypedDict, ) # Named in compatibility with CWL - trying to keep CWL fields in mind with # this implementation. https://www.commonwl.org/user_guide/topics/inputs.html#inputs # element_identifier is not like CWL - it is used to specify the value in the row should # be the element_identifier for another element if present. It is a way to specify relationships # between elements in the collection - specifically implemented for the "control" use case. SampleSheetColumnType = Literal[ "string", "int", "float", "boolean", "element_identifier" ] # excluding "long" and "double" and composite types from CWL for now - we don't think at this level of abstraction in Galaxy generally NoneType = type(None) SampleSheetColumnValueT = Union[int, float, bool, str, NoneType] # type ignore because mypy can't handle closed TypedDicts yet @with_config(ConfigDict(extra="forbid")) class SampleSheetColumnDefinition(TypedDict, closed=True): # type: ignore[call-arg] name: str description: NotRequired[Optional[str]] type: SampleSheetColumnType optional: bool default_value: NotRequired[Optional[SampleSheetColumnValueT]] validators: NotRequired[Optional[List[Dict[str, Any]]]] restrictions: NotRequired[Optional[List[SampleSheetColumnValueT]]] suggestions: NotRequired[Optional[List[SampleSheetColumnValueT]]] SampleSheetColumnDefinitions = List[SampleSheetColumnDefinition] SampleSheetRow = List[SampleSheetColumnValueT] SampleSheetRows = Dict[str, SampleSheetRow] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/test_job.py0000644000175100017510000001576215234436334026027 0ustar00runnerrunner"""Pydantic model for the ``job:`` block of Planemo / ``*.gxwf-tests.yml`` tests. Defines the canonical CWL-style workflow-test input syntax — the shape the schema blesses, not every shape the helpers in ``galaxy_test.base.populators`` happen to tolerate. Legacy ``type: File | Directory | raw`` / ``value`` forms are intentionally not modeled; those are left to helper-layer tolerances and should not leak into test fixtures. Follow-up to galaxyproject/galaxy#18884, which modeled ``TestJob.outputs`` but left ``TestJob.job`` as ``Dict[str, Any]``. """ from typing import ( Dict, List, Optional, Union, ) from pydantic import ( ConfigDict, Discriminator, Field, RootModel, Tag, ) from typing_extensions import ( Annotated, Literal, ) from ._base import ( CollectionType, StrictModel, ) # Mirrored from galaxy.util.hash_util.HashFunctionNames — duplicated rather than # imported so this package stays free of a galaxy-util runtime dep. Kept in sync # by test/unit/tool_util_models/test_hash_function_names_sync.py, which runs in # the monorepo where galaxy.util is available. HashFunctionNames = Literal["MD5", "SHA-1", "SHA-256", "SHA-512"] class _StrictJobModel(StrictModel): model_config = ConfigDict( extra="forbid", populate_by_name=True, ) class HashEntry(_StrictJobModel): model_config = ConfigDict(extra="forbid", populate_by_name=True, title="HashEntry") hash_function: Annotated[HashFunctionNames, Field(title="Hash Function")] hash_value: Annotated[str, Field(title="Hash Value")] class BaseFile(_StrictJobModel): """Fields common to every ``class: File`` variant.""" model_config = ConfigDict(extra="forbid", populate_by_name=True, title="BaseFile") class_: Literal["File"] = Field(alias="class", title="Class") filetype: Annotated[Optional[str], Field(title="File Type")] = None dbkey: Annotated[Optional[str], Field(title="Dbkey")] = None decompress: Annotated[Optional[bool], Field(title="Decompress")] = None to_posix_lines: Annotated[Optional[bool], Field(title="To POSIX Lines")] = None space_to_tab: Annotated[Optional[bool], Field(title="Space To Tab")] = None deferred: Annotated[Optional[bool], Field(title="Deferred")] = None name: Annotated[Optional[str], Field(title="Name")] = None info: Annotated[Optional[str], Field(title="Info")] = None tags: Annotated[Optional[List[str]], Field(title="Tags")] = None hashes: Annotated[Optional[List[HashEntry]], Field(title="Hashes")] = None identifier: Annotated[Optional[str], Field(title="Identifier")] = None class LocationFile(BaseFile): model_config = ConfigDict(extra="forbid", populate_by_name=True, title="LocationFile") location: Annotated[str, Field(title="Location")] path: Annotated[Optional[str], Field(title="Path")] = None contents: Annotated[Optional[str], Field(title="Contents")] = None composite_data: Annotated[Optional[List[str]], Field(title="Composite Data")] = None class PathFile(BaseFile): model_config = ConfigDict(extra="forbid", populate_by_name=True, title="PathFile") path: Annotated[str, Field(title="Path")] location: Annotated[Optional[str], Field(title="Location")] = None contents: Annotated[Optional[str], Field(title="Contents")] = None composite_data: Annotated[Optional[List[str]], Field(title="Composite Data")] = None class ContentsFile(BaseFile): """CWL File literal — content inlined as a string.""" model_config = ConfigDict(extra="forbid", populate_by_name=True, title="ContentsFile") contents: Annotated[str, Field(title="Contents")] path: Annotated[Optional[str], Field(title="Path")] = None location: Annotated[Optional[str], Field(title="Location")] = None composite_data: Annotated[Optional[List[str]], Field(title="Composite Data")] = None class CompositeDataFile(BaseFile): model_config = ConfigDict(extra="forbid", populate_by_name=True, title="CompositeDataFile") composite_data: Annotated[List[str], Field(title="Composite Data")] path: Annotated[Optional[str], Field(title="Path")] = None location: Annotated[Optional[str], Field(title="Location")] = None contents: Annotated[Optional[str], Field(title="Contents")] = None def _discriminate_file(v): if isinstance(v, dict): if "location" in v: return "location" if "path" in v: return "path" if "contents" in v: return "contents" if "composite_data" in v: return "composite_data" return None if isinstance(v, LocationFile): return "location" if isinstance(v, PathFile): return "path" if isinstance(v, ContentsFile): return "contents" if isinstance(v, CompositeDataFile): return "composite_data" return None File = Annotated[ Union[ Annotated[LocationFile, Tag("location")], Annotated[PathFile, Tag("path")], Annotated[ContentsFile, Tag("contents")], Annotated[CompositeDataFile, Tag("composite_data")], ], Discriminator(_discriminate_file), ] class Collection(_StrictJobModel): model_config = ConfigDict(extra="forbid", populate_by_name=True, title="Collection") class_: Literal["Collection"] = Field(alias="class", title="Class") collection_type: Annotated[CollectionType, Field(title="Collection Type")] = None name: Annotated[Optional[str], Field(title="Name")] = None identifier: Annotated[Optional[str], Field(title="Identifier")] = None elements: Annotated[Optional[List["CollectionElement"]], Field(title="Elements")] = None rows: Annotated[Optional[Dict[str, list]], Field(title="Rows")] = None CollectionElement = Annotated[ Union[File, Collection], Field(discriminator="class_"), ] Collection.model_rebuild() class Directory(_StrictJobModel): """CWL-style directory input. Supported by stage_inputs for directory-typed datasets (e.g. bwa_mem2_index test fixtures). Rare in workflow tests; IWC does not use it. """ model_config = ConfigDict(extra="forbid", populate_by_name=True, title="Directory") class_: Literal["Directory"] = Field(alias="class", title="Class") path: Annotated[Optional[str], Field(title="Path")] = None location: Annotated[Optional[str], Field(title="Location")] = None filetype: Annotated[Optional[str], Field(title="File Type")] = None name: Annotated[Optional[str], Field(title="Name")] = None # JobParamValue is non-recursive at the list axis: a job-param list may contain # files or scalars but not further nested lists. Collection nesting (lists of # collections) is recursive via ``CollectionElement``. No observed workflow # test value needs a list-of-lists at the job-input level; widen explicitly if # that changes rather than defaulting to Any. JobParamValue = Union[ File, Collection, Directory, str, int, float, bool, None, List[Union[File, str, int, float, bool, None]], ] class Job(RootModel[Dict[str, JobParamValue]]): model_config = ConfigDict(title="Job") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/testing_types.py0000644000175100017510000000167015234436334027110 0ustar00runnerrunner"""Shared TypedDicts for tool test definitions and assertion structures. These live in ``tool_util_models`` so both ``tool_util_models`` and ``tool_util.parser`` can depend on them without creating a circular import. """ from typing import ( Any, Dict, List, Optional, ) from typing_extensions import TypedDict class AssertionDict(TypedDict): tag: str attributes: Dict[str, Any] children: "AssertionList" AssertionList = Optional[List[AssertionDict]] class DirectCredentialValue(TypedDict): """Represents a credential value (variable or secret) provided directly.""" name: str value: str class _DirectCredentialRequired(TypedDict): name: str variables: List[DirectCredentialValue] secrets: List[DirectCredentialValue] class DirectCredential(_DirectCredentialRequired, total=False): """Represents a credential group with variables and secrets provided directly.""" version: str ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/tool_outputs.py0000644000175100017510000002260215234436334026765 0ustar00runnerrunner"""Modern pydantic based descriptions of Galaxy tool output objects. output_objects.py is still used for internals and contain references to the actual tool object but the goal here is to switch to using these overtime at least for external APIs and in library code where actual tool objects aren't created. """ from typing import ( Any, Dict, Generic, List, Optional, Union, ) from pydantic import ( ConfigDict, Field, model_validator, ) from typing_extensions import ( Annotated, Literal, TypeVar, ) from ._base import ToolSourceBaseModel AnyT = TypeVar("AnyT") NotRequired = Optional[AnyT] IncomingNotRequiredBoolT = TypeVar("IncomingNotRequiredBoolT") IncomingNotRequiredStringT = TypeVar("IncomingNotRequiredStringT") # Use IncomingNotRequired when concrete key: Optional[str] = None would be incorrect class GenericToolOutputBaseModel(ToolSourceBaseModel, Generic[IncomingNotRequiredBoolT, IncomingNotRequiredStringT]): name: Annotated[ IncomingNotRequiredStringT, Field(description="Parameter name. Used when referencing parameter in workflows.") ] label: Annotated[Optional[str], Field(description="Output label. Will be used as dataset name in history.")] = None hidden: Annotated[ IncomingNotRequiredBoolT, Field(description="If true, the output will not be shown in the history.") ] DiscoverViaT = Literal["tool_provided_metadata", "pattern"] SortKeyT = Literal["filename", "name", "designation", "dbkey"] SortCompT = Literal["lexical", "numeric"] # Defaults below mirror the XML parser (galaxy.tool_util.parser.output_collection_def): # every non-essential discovery attribute has a sensible default there, so an author # (or an LLM) only needs to supply ``pattern``. Requiring all of them in the model is # the friction that makes ``discover_datasets`` nearly impossible to author by hand. class DatasetCollectionDescription(ToolSourceBaseModel): # extra="forbid" so a typo'd key (e.g. ``patern``) is rejected rather than silently # absorbed by the metadata arm below (which would otherwise collect nothing). model_config = ConfigDict(extra="forbid") discover_via: DiscoverViaT format: Optional[str] = None visible: bool = False assign_primary_output: bool = False directory: Optional[str] = None recurse: bool = False match_relative_path: bool = False class ToolProvidedMetadataDatasetCollection(DatasetCollectionDescription): # ``discover_via`` is required (no default) so an under-specified descriptor # ({} / {format: ...} / a typo'd pattern) does NOT silently resolve to # tool_provided_metadata -- it must say so explicitly. Pattern discovery (the # default in the XML parser) is opt-out only via the ``FilePattern`` arm, which # still requires a real ``pattern``. discover_via: Literal["tool_provided_metadata"] class FilePatternDatasetCollectionDescription(DatasetCollectionDescription): discover_via: Literal["pattern"] = "pattern" sort_key: SortKeyT = "filename" sort_comp: SortCompT = "lexical" sort_reverse: bool = False pattern: str DatasetCollectionDescriptionT = Union[FilePatternDatasetCollectionDescription, ToolProvidedMetadataDatasetCollection] class GenericToolOutputDataset( GenericToolOutputBaseModel[IncomingNotRequiredBoolT, IncomingNotRequiredStringT], Generic[IncomingNotRequiredBoolT, IncomingNotRequiredStringT], ): type: Literal["data"] format: Annotated[IncomingNotRequiredStringT, Field(description="The short name for the output datatype.")] format_source: Annotated[ Optional[str], Field( description="This sets the data type of the output dataset(s) to be the same format as that of the specified tool input." ), ] = None metadata_source: Annotated[ Optional[str], Field( description="This copies the metadata information from the tool’s input dataset to serve as default for information that cannot be detected from the output. One prominent use case is interval data with a non-standard column order that cannot be deduced from a header line, but which is known to be identical in the input and output datasets." ), ] = None discover_datasets: Optional[List[DatasetCollectionDescriptionT]] = None from_work_dir: Annotated[ Optional[str], Field( title="from_work_dir", description="Relative path to a file produced by the tool in its working directory. Output’s contents are set to this file’s contents.", ), ] = None precreate_directory: Optional[bool] = False class ToolOutputDataset(GenericToolOutputDataset[bool, str]): ... class IncomingToolOutputDataset( GenericToolOutputDataset[ NotRequired[bool], NotRequired[str], ] ): name: Annotated[ Optional[str], Field(description="Parameter name. Used when referencing parameter in workflows.") ] = None hidden: Annotated[Optional[bool], Field(description="If true, the output will not be shown in the history.")] = None format: Annotated[Optional[str], Field(description="The short name for the output datatype.")] = None def lift_legacy_collection_structure(output_dict: Dict[str, Any]) -> Dict[str, Any]: # Older DynamicTool.value rows nest collection fields under ``structure:``; # the current model expects them flat on the output. Inline them so the # parser and pydantic model both see the same flat form. Top-level keys # win, but only when they carry a value — an explicit top-level ``None`` # mustn't shadow a real nested value, or a partial-merge writer could # silently drop fields. Returns input untouched when there's no wrapper. structure = output_dict.get("structure") if not isinstance(structure, dict): return output_dict lifted = {k: v for k, v in output_dict.items() if k != "structure"} for key, value in structure.items(): if lifted.get(key) is None: lifted[key] = value return lifted class GenericToolOutputCollection( GenericToolOutputBaseModel[IncomingNotRequiredBoolT, IncomingNotRequiredStringT], Generic[IncomingNotRequiredBoolT, IncomingNotRequiredStringT], ): type: Literal["collection"] collection_type: Optional[str] = None collection_type_source: Optional[str] = None collection_type_from_rules: Optional[str] = None structured_like: Optional[str] = None discover_datasets: Optional[List[DatasetCollectionDescriptionT]] = None @model_validator(mode="before") @classmethod def _lift_legacy_structure(cls, values): if isinstance(values, dict): return lift_legacy_collection_structure(values) return values class ToolOutputCollection(GenericToolOutputCollection[bool, str]): ... class IncomingToolOutputCollection(GenericToolOutputCollection[NotRequired[bool], NotRequired[str]]): name: Annotated[ Optional[str], Field(description="Parameter name. Used when referencing parameter in workflows.") ] = None hidden: Annotated[Optional[bool], Field(description="If true, the output will not be shown in the history.")] = None class GenericToolOutputSimple( GenericToolOutputBaseModel[IncomingNotRequiredBoolT, IncomingNotRequiredStringT], Generic[IncomingNotRequiredBoolT, IncomingNotRequiredStringT], ): pass # Internal / parser-facing variants: ``name`` and ``hidden`` are required, matching # how ``ToolOutput*Model`` instances are constructed in the output parser (which # always supplies both). Binding the type vars to ``[bool, str]`` mirrors # ``ToolOutputDataset`` / ``ToolOutputCollection``. class ToolOutputText(GenericToolOutputSimple[bool, str]): type: Literal["text"] class ToolOutputInteger(GenericToolOutputSimple[bool, str]): type: Literal["integer"] class ToolOutputFloat(GenericToolOutputSimple[bool, str]): type: Literal["float"] class ToolOutputBoolean(GenericToolOutputSimple[bool, str]): type: Literal["boolean"] # Incoming / authoring variants: ``hidden`` is optional (it has a sensible # default), but ``name`` stays *required* -- unlike datasets/collections, a simple # value output has no discovery step to supply a name, so an unnamed one can never # be referenced. Previously these reused the strict types above, whose unbound type # vars also forced ``hidden`` to be required -- a bug that made the published schema # demand a ``hidden`` flag on every simple output. class IncomingToolOutputSimple(GenericToolOutputSimple[NotRequired[bool], str]): hidden: Annotated[Optional[bool], Field(description="If true, the output will not be shown in the history.")] = None class IncomingToolOutputText(IncomingToolOutputSimple): type: Literal["text"] class IncomingToolOutputInteger(IncomingToolOutputSimple): type: Literal["integer"] class IncomingToolOutputFloat(IncomingToolOutputSimple): type: Literal["float"] class IncomingToolOutputBoolean(IncomingToolOutputSimple): type: Literal["boolean"] IncomingToolOutputT = Union[ IncomingToolOutputDataset, IncomingToolOutputCollection, IncomingToolOutputText, IncomingToolOutputInteger, IncomingToolOutputFloat, IncomingToolOutputBoolean, ] IncomingToolOutput = Annotated[IncomingToolOutputT, Field(discriminator="type")] ToolOutputT = Union[ ToolOutputDataset, ToolOutputCollection, ToolOutputText, ToolOutputInteger, ToolOutputFloat, ToolOutputBoolean ] ToolOutput = Annotated[ToolOutputT, Field(discriminator="type")] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/tool_source.py0000644000175100017510000002522515234436334026546 0ustar00runnerrunnerimport re from enum import Enum from typing import ( List, Optional, Union, ) from pydantic import ( ConfigDict, Field, model_validator, with_config, ) from pydantic_core import PydanticCustomError from typing_extensions import ( Annotated, Literal, NotRequired, TypedDict, ) from ._base import ToolSourceBaseModel class Container(ToolSourceBaseModel): type: Literal["docker", "singularity"] container_id: str class Requirement(ToolSourceBaseModel): type: Literal["package", "set_environment"] class ContainerRequirement(ToolSourceBaseModel): type: Literal["container"] container: Container class PackageRequirement(Requirement): type: Literal["package"] name: str version: Optional[str] = None class SetEnvironmentRequirement(Requirement): type: Literal["set_environment"] environment: str cores_min_description = "Minimum reserved number of CPU cores." cores_max_description = "Maximum reserved number of CPU cores." cores_description = """May be a fractional value to indicate to a scheduling algorithm that one core can be allocated to multiple jobs. For example, a value of 0.25 indicates that up to 4 jobs may run in parallel on 1 core. A value of 1.25 means that up to 3 jobs can run on a 4 core system (4/1.25 ≈ 3). The reported number of CPU cores reserved for the process is a non-zero integer calculated by rounding up the cores request to the next whole number. """ ram_min_description = "Minimum reserved RAM in mebibytes (2**20)." ram_max_description = "Maximum reserved RAM in mebibytes (2**20)." ram_description = """May be a fractional value. If so, the actual RAM request is rounded up to the next whole number. The reported amount of RAM reserved for the process is a non-zero integer.""" ResourceRequirementValue = Union[int, float, str, None] class ResourceRequirement(ToolSourceBaseModel): type: Literal["resource"] cores_min: Annotated[ ResourceRequirementValue, Field(description=f"{cores_min_description}\n{cores_description}") ] = 1 cores_max: Annotated[ ResourceRequirementValue, Field(description=f"{cores_max_description}\n{cores_description}") ] = None ram_min: Annotated[ResourceRequirementValue, Field(description=f"{ram_min_description}\n{ram_description}")] = 256 ram_max: Annotated[ResourceRequirementValue, Field(description=f"{ram_max_description}\n{ram_description}")] = None tmpdir_min: ResourceRequirementValue = None tmpdir_max: ResourceRequirementValue = None cuda_version_min: ResourceRequirementValue = None cuda_compute_capability: ResourceRequirementValue = None gpu_memory_min: ResourceRequirementValue = None cuda_device_count_min: ResourceRequirementValue = None cuda_device_count_max: ResourceRequirementValue = None shm_size: ResourceRequirementValue = None timelimit: Annotated[ ResourceRequirementValue, Field(description="Maximum time in seconds the tool is allowed to run. Job will be terminated if exceeded."), ] = None class JavascriptRequirement(ToolSourceBaseModel): type: Literal["javascript"] expression_lib: Optional[ List[ Annotated[ str, Field( title="expression_lib", description="Provide Javascript/ECMAScript 5.1 code here that will be available for expressions inside the `shell_command` field.", examples=[r"""function pickValue() { if (inputs.conditional_parameter.test_parameter == "a") { return inputs.conditional_parameter.integer_parameter } else { return inputs.conditional_parameter.boolean_parameter } }"""], ), ] ] ] class XrefDict(TypedDict): value: str type: str class TemplateConfigFile(ToolSourceBaseModel): content: str name: Optional[str] = None filename: Optional[str] = None class InputConfigFileContent(ToolSourceBaseModel): format: Literal["json"] = "json" handle_files: Optional[Literal["paths", "staging_path_and_source_path"]] = None type: Literal["inputs"] = "inputs" class InputConfigFile(ToolSourceBaseModel): name: Optional[str] = None content: InputConfigFileContent filename: Optional[str] = None class FileSourceConfigFileContent(ToolSourceBaseModel): type: Literal["files"] = "files" class FileSourceConfigFile(ToolSourceBaseModel): name: Optional[str] filename: Optional[str] = None content: FileSourceConfigFileContent class XmlTemplateConfigFile(TemplateConfigFile): eval_engine: Literal["cheetah"] = "cheetah" class YamlTemplateConfigFile(TemplateConfigFile): eval_engine: Literal["ecmascript"] = "ecmascript" # DOI: '10./' per Crossref's published shape. DOI_RE = re.compile(r"^10\.\d{4,9}/.+$") # Legacy W3C/RFC form prefixes a bare DOI with 'doi:' (optionally with whitespace # after the colon). Tools written before 26.1 routinely used this form, so we # normalize it away to the bare DOI rather than rejecting it. DOI_PREFIX_RE = re.compile(r"^doi:\s*", re.IGNORECASE) # BibTeX entries open with '@{' -- e.g. '@article{', '@inproceedings{'. BIBTEX_RE = re.compile(r"^@[a-zA-Z]+\s*\{", re.MULTILINE) class Citation(ToolSourceBaseModel): type: str content: str @model_validator(mode="after") def _check_citation_shape(self) -> "Citation": content = (self.content or "").strip() if not content: raise PydanticCustomError( "dynamic_tool.citation_empty", "citation content must not be empty", ) # Normalize the legacy 'doi:' prefix to a bare DOI so older tools load and # so downstream DOI resolution (https://doi.org/) gets a clean value. normalized = DOI_PREFIX_RE.sub("", content, count=1) if normalized != content: content = normalized self.content = normalized citation_type = (self.type or "").strip().lower() if citation_type == "doi": if not DOI_RE.match(content): raise PydanticCustomError( "dynamic_tool.citation_doi_invalid", "declared as DOI but '{content}' does not match DOI shape (^10\\.\\d{{4,9}}/.+$)", {"content": content}, ) return self if citation_type == "bibtex": if not BIBTEX_RE.search(content): raise PydanticCustomError( "dynamic_tool.citation_bibtex_invalid", "declared as bibtex but content does not start with '@{{'", ) return self # Type wasn't explicitly doi/bibtex -- accept if the content shape is # one of the two known forms. Lets a slightly mis-typed entry through # instead of fighting models that emit type='reference' or similar. if DOI_RE.match(content) or BIBTEX_RE.search(content): return self raise PydanticCustomError( "dynamic_tool.citation_unrecognized", "citation (type={ctype}) is neither a recognizable DOI nor a BibTeX entry", {"ctype": repr(self.type)}, ) class HelpContent(ToolSourceBaseModel): format: Literal["restructuredtext", "plain_text", "markdown"] content: str StdioExitCodeRangeValue = Union[int, float, Literal["-inf", "inf"]] class StdioExitCode(ToolSourceBaseModel): range_start: StdioExitCodeRangeValue range_end: StdioExitCodeRangeValue error_level: Union[int, float] desc: Optional[str] = None class StdioRegex(ToolSourceBaseModel): match: str stdout_match: bool stderr_match: bool error_level: Union[int, float] desc: Optional[str] = None class Stdio(ToolSourceBaseModel): exit_codes: List[StdioExitCode] = Field(default_factory=list) regexes: List[StdioRegex] = Field(default_factory=list) class OutputCompareType(str, Enum): diff = "diff" re_match = "re_match" sim_size = "sim_size" re_match_multiline = "re_match_multiline" contains = "contains" image_diff = "image_diff" class DrillDownOptionsDict(TypedDict): name: Optional[str] value: str options: List["DrillDownOptionsDict"] selected: bool # For fields... just implementing a subset of CWL for Galaxy flavors of these objects # so far. CwlType = Literal["File", "null", "boolean", "int", "float", "string"] FieldType = Union[CwlType, List[CwlType]] # type ignore because mypy can't handle closed TypedDicts yet @with_config(ConfigDict(extra="forbid")) class FieldDict(TypedDict, closed=True): # type: ignore[call-arg] name: str type: FieldType format: NotRequired[Optional[str]] JsonTestDatasetDefDict = TypedDict( "JsonTestDatasetDefDict", { "class": Literal["File"], "path": NotRequired[Optional[str]], "location": NotRequired[Optional[str]], "name": NotRequired[Optional[str]], "dbkey": NotRequired[Optional[str]], "filetype": NotRequired[Optional[str]], "composite_data": NotRequired[Optional[List[str]]], "tags": NotRequired[Optional[List[str]]], }, ) JsonTestCollectionDefElementDict = Union[ "JsonTestCollectionDefDatasetElementDict", "JsonTestCollectionDefCollectionElementDict" ] JsonTestCollectionDefDatasetElementDict = TypedDict( "JsonTestCollectionDefDatasetElementDict", { "identifier": str, "class": Literal["File"], "path": NotRequired[Optional[str]], "location": NotRequired[Optional[str]], "name": NotRequired[Optional[str]], "dbkey": NotRequired[Optional[str]], "filetype": NotRequired[Optional[str]], "composite_data": NotRequired[Optional[List[str]]], "tags": NotRequired[Optional[List[str]]], }, ) BaseJsonTestCollectionDefCollectionElementDict = TypedDict( "BaseJsonTestCollectionDefCollectionElementDict", { "class": Literal["Collection"], "collection_type": Optional[str], "elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]], }, ) JsonTestCollectionDefCollectionElementDict = TypedDict( "JsonTestCollectionDefCollectionElementDict", { "identifier": str, "class": Literal["Collection"], "collection_type": Optional[str], "elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]], }, ) JsonTestCollectionDefDict = TypedDict( "JsonTestCollectionDefDict", { "class": Literal["Collection"], "collection_type": Optional[str], "elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]], "name": NotRequired[Optional[str]], "fields": NotRequired[Optional[List[FieldDict]]], }, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/src/galaxy/tool_util_models/yaml_parameters.py0000644000175100017510000002421715234436334027376 0ustar00runnerrunner"""Narrow YAML-facing tool parameter models. `UserToolSource` and `YamlToolSource` use these for their `inputs` field instead of the full internal Galaxy XML metamodel union. The YAML layer is purely an authoring/publication surface: it validates what users may write in YAML tools and rejects XML-only fields and unsupported parameter types via ``extra="forbid"``. Every model exposes ``to_internal()`` returning the matching internal ``GalaxyParameterT`` instance so callers that need the internal metamodel (e.g. round-trip tests) can construct it directly without re-parsing through ``YamlToolSource``. The primary production path still builds internal models via ``input_models_for_tool_source`` from the raw validated dict, so ``to_internal()`` is not load-bearing for execution today. """ from typing import ( List, Optional, Union, ) from pydantic import ( BaseModel, ConfigDict, Field, field_validator, RootModel, ) from typing_extensions import ( Annotated, Literal, ) from .parameter_validators import ( EmptyFieldParameterValidatorModel, InRangeParameterValidatorModel, LengthParameterValidatorModel, NoOptionsParameterValidatorModel, RegexParameterValidatorModel, ) from .parameters import ( BooleanParameterModel, ColorParameterModel, cond_test_parameter_default_value, ConditionalParameterModel, ConditionalWhen, DataCollectionParameterModel, DataParameterModel, FloatParameterModel, GalaxyParameterT, IntegerParameterModel, LabelValue, RepeatParameterModel, SectionParameterModel, SelectParameterModel, TextParameterModel, ) class YamlLabelValue(BaseModel): """YAML-friendly option model — ``selected`` defaults to ``False``.""" label: str value: str selected: bool = False def to_internal(self) -> LabelValue: return LabelValue(label=self.label, value=self.value, selected=self.selected) # Narrow validator unions — drops XML-only validators like Expression. YamlTextValidators = Union[ LengthParameterValidatorModel, RegexParameterValidatorModel, EmptyFieldParameterValidatorModel, ] YamlNumberValidators = Union[InRangeParameterValidatorModel,] YamlSelectValidators = Union[NoOptionsParameterValidatorModel,] class _YamlParamBase(BaseModel): model_config = ConfigDict(extra="forbid", populate_by_name=True) name: str label: Optional[str] = None help: Optional[str] = None optional: bool = False def _common_internal_kwargs(yaml_param: "_YamlParamBase") -> dict: kwargs: dict = {"name": yaml_param.name, "optional": yaml_param.optional} if yaml_param.label is not None: kwargs["label"] = yaml_param.label if yaml_param.help is not None: kwargs["help"] = yaml_param.help return kwargs class YamlBooleanParameter(_YamlParamBase): type: Literal["boolean"] value: Optional[bool] = False def to_internal(self) -> BooleanParameterModel: return BooleanParameterModel(type="boolean", value=self.value, **_common_internal_kwargs(self)) class YamlIntegerParameter(_YamlParamBase): type: Literal["integer"] value: Optional[int] = None min: Optional[int] = None max: Optional[int] = None validators: List[YamlNumberValidators] = [] def to_internal(self) -> IntegerParameterModel: return IntegerParameterModel( type="integer", value=self.value, min=self.min, max=self.max, validators=list(self.validators), **_common_internal_kwargs(self), ) class YamlFloatParameter(_YamlParamBase): type: Literal["float"] value: Optional[float] = None min: Optional[float] = None max: Optional[float] = None validators: List[YamlNumberValidators] = [] def to_internal(self) -> FloatParameterModel: return FloatParameterModel( type="float", value=self.value, min=self.min, max=self.max, validators=list(self.validators), **_common_internal_kwargs(self), ) class YamlTextParameter(_YamlParamBase): type: Literal["text"] value: Optional[str] = Field(default=None, alias="value") area: bool = False validators: List[YamlTextValidators] = [] def to_internal(self) -> TextParameterModel: return TextParameterModel( type="text", default_value=self.value, area=self.area, validators=list(self.validators), **_common_internal_kwargs(self), ) class YamlSelectParameter(_YamlParamBase): type: Literal["select"] options: Annotated[List[YamlLabelValue], Field(min_length=1)] multiple: bool = False validators: List[YamlSelectValidators] = [] def to_internal(self) -> SelectParameterModel: return SelectParameterModel( type="select", options=[o.to_internal() for o in self.options], multiple=self.multiple, validators=list(self.validators), **_common_internal_kwargs(self), ) class YamlColorParameter(_YamlParamBase): type: Literal["color"] value: Optional[str] = None def to_internal(self) -> ColorParameterModel: return ColorParameterModel(type="color", value=self.value, **_common_internal_kwargs(self)) def _split_format(v): # Accept the XML-style comma-separated string form (`format: "txt,tabular"`) # as well as a list. Internal `DataParameterModel.extensions` is always a list. if isinstance(v, str): return [ext.strip().lower() for ext in v.split(",") if ext.strip()] return v class YamlDataParameter(_YamlParamBase): type: Literal["data"] format: List[str] = ["data"] multiple: Annotated[ bool, Field(description="Set true to accept several datasets (a list) for this input instead of one."), ] = False # NOTE: `min`/`max` (the min/max number of selected datasets) are intentionally # NOT exposed here. They only have meaning for a `multiple` input and the runtime # rejects them on a single one; in practice authors (and LLMs) reach for `min: 1` # to mean "required" -- which a data input already is -- and then the tool fails # at build time. Omitting the fields means such a tool is rejected up front with a # clear `extra_forbidden` error instead. The internal model still supports them # for XML tools. @field_validator("format", mode="before") @classmethod def _coerce_format(cls, v): return _split_format(v) def to_internal(self) -> DataParameterModel: return DataParameterModel( type="data", extensions=list(self.format), multiple=self.multiple, **_common_internal_kwargs(self), ) class YamlDataCollectionParameter(_YamlParamBase): type: Literal["data_collection"] collection_type: Optional[str] = None format: List[str] = ["data"] @field_validator("format", mode="before") @classmethod def _coerce_format(cls, v): return _split_format(v) def to_internal(self) -> DataCollectionParameterModel: return DataCollectionParameterModel( type="data_collection", collection_type=self.collection_type, extensions=list(self.format), value=None, **_common_internal_kwargs(self), ) YamlConditionalTestParameter = Annotated[Union[YamlBooleanParameter, YamlSelectParameter], Field(discriminator="type")] class YamlConditionalWhen(BaseModel): model_config = ConfigDict(extra="forbid", populate_by_name=True) discriminator: Union[bool, str] parameters: List["YamlGalaxyToolParameter"] = [] class YamlConditionalParameter(_YamlParamBase): type: Literal["conditional"] test_parameter: YamlConditionalTestParameter whens: Annotated[List[YamlConditionalWhen], Field(min_length=1)] def to_internal(self) -> ConditionalParameterModel: internal_test = self.test_parameter.to_internal() default_value = cond_test_parameter_default_value(internal_test) internal_whens: List[ConditionalWhen] = [] for when in self.whens: internal_params = [p.root.to_internal() for p in when.parameters] internal_whens.append( ConditionalWhen( discriminator=when.discriminator, parameters=internal_params, is_default_when=when.discriminator == default_value, ) ) return ConditionalParameterModel( type="conditional", test_parameter=internal_test, whens=internal_whens, **_common_internal_kwargs(self), ) class YamlRepeatParameter(_YamlParamBase): type: Literal["repeat"] parameters: List["YamlGalaxyToolParameter"] = [] min: Optional[int] = None max: Optional[int] = None def to_internal(self) -> RepeatParameterModel: return RepeatParameterModel( type="repeat", parameters=[p.root.to_internal() for p in self.parameters], min=self.min, max=self.max, **_common_internal_kwargs(self), ) class YamlSectionParameter(_YamlParamBase): type: Literal["section"] parameters: List["YamlGalaxyToolParameter"] = [] def to_internal(self) -> SectionParameterModel: return SectionParameterModel( type="section", parameters=[p.root.to_internal() for p in self.parameters], **_common_internal_kwargs(self), ) YamlGalaxyParameterT = Union[ YamlBooleanParameter, YamlIntegerParameter, YamlFloatParameter, YamlTextParameter, YamlSelectParameter, YamlColorParameter, YamlDataParameter, YamlDataCollectionParameter, YamlConditionalParameter, YamlRepeatParameter, YamlSectionParameter, ] class YamlGalaxyToolParameter(RootModel): root: Annotated[YamlGalaxyParameterT, Field(discriminator="type")] def to_internal(self) -> GalaxyParameterT: return self.root.to_internal() YamlConditionalWhen.model_rebuild() YamlConditionalParameter.model_rebuild() YamlRepeatParameter.model_rebuild() YamlSectionParameter.model_rebuild() YamlGalaxyToolParameter.model_rebuild() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1785871590.9578714 galaxy_tool_util_models-26.1.1/src/galaxy_tool_util_models.egg-info/0000755000175100017510000000000015234436347025407 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871590.0 galaxy_tool_util_models-26.1.1/src/galaxy_tool_util_models.egg-info/PKG-INFO0000644000175100017510000002343715234436346026514 0ustar00runnerrunnerMetadata-Version: 2.4 Name: galaxy-tool-util-models Version: 26.1.1 Summary: Pydantic models for Galaxy tools Home-page: https://github.com/galaxyproject/galaxy Author: Galaxy Project and Community Author-email: galaxy-committers@lists.galaxyproject.org License: MIT Keywords: Galaxy Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Code Generators Classifier: Topic :: Software Development :: Testing Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: pydantic>=2.7.4 Requires-Dist: pydantic-extra-types Requires-Dist: typing-extensions Provides-Extra: test Requires-Dist: pytest; extra == "test" Dynamic: license-file .. image:: https://badge.fury.io/py/galaxy-tool-util-models.svg :target: https://pypi.org/project/galaxy-tool-util-models/ Overview -------- Pydantic models to support Galaxy_ tool utilities. * Code: https://github.com/galaxyproject/galaxy/tree/dev/packages/tool_util_models .. _Galaxy: http://galaxyproject.org/ History ------- .. to_doc ------------------- 26.1.1 (2026-08-04) ------------------- No recorded changes since last release ------------------- 26.1.0 (2026-08-02) ------------------- ========= Bug fixes ========= * Fix workflow implicit mapping of flat collections over paired_or_unpaired by `@jmchilton `_ in `#21933 `_ * [26.1 (spiritually)] Fix UDT collection outputs being silently dropped in YAML tool parser by `@jmchilton `_ in `#22761 `_ * Relax legacy doi:-prefixed citation handling (#22795) by `@mvdbeek `_ in `#22799 `_ * Add fixed color set from webcolors package by `@guerler `_ in `#22869 `_ * Always attempt to build tool request for new API endpoint by `@mvdbeek `_ in `#22894 `_ * Accept dce collection references in the tool request model by `@mvdbeek `_ in `#22926 `_ * Make the user-defined tool schema robust for LLM authoring (CustomToolAgent) by `@mvdbeek `_ in `#22968 `_ * Custom-tool agent: verified container resolution + authoring safeguards (with a reusable mulled-recommend CLI) by `@mvdbeek `_ in `#22981 `_ ============ Enhancements ============ * Various YAML Tool Hardening and Progress toward Tool State Goals by `@jmchilton `_ in `#21828 `_ * Migrate tool execution request from api/tools to api/jobs by `@guerler `_ in `#21842 `_ * Fix tool.parameters initialization by `@mvdbeek `_ in `#21914 `_ * Allow tools to specify time limits by `@jmchilton `_ in `#21927 `_ * Migrate Python packages to \`src\` layout and pure namespace packages by `@mr-c `_ in `#21977 `_ * Model subcollection mapping (map_over_type) and DCE in tool parameter schema by `@jmchilton `_ in `#21991 `_ * Enrich tool parameter JSON Schema with Galaxy metadata via model methods by `@mvdbeek `_ in `#22020 `_ * More Complete, Robust, and Tested JSON-Schema for Tool State Models by `@jmchilton `_ in `#22362 `_ * Clean up Pydantic v2 deprecation warnings by `@mvdbeek `_ in `#22463 `_ * Clean up various pytest warnings by `@mvdbeek `_ in `#22472 `_ * Narrow YAML schema by `@jmchilton `_ in `#22507 `_ * Tighten the workflow test schema by `@jmchilton `_ in `#22566 `_ * Push UserToolSource semantic validation onto the pydantic model by `@mvdbeek `_ in `#22615 `_ * Lift legacy UserToolSource representations on read by `@mvdbeek `_ in `#22624 `_ * Expose more metadata in ParsedTool abstraction by `@jmchilton `_ in `#22667 `_ ------------------- 26.0.1 (2026-06-04) ------------------- No recorded changes since last release ------------------- 26.0.0 (2026-04-08) ------------------- ========= Bug fixes ========= * Fix Pydantic UnsupportedFieldAttributeWarning for Field defaults in Annotated by `@mvdbeek `_ in `#21923 `_ * Backport paired or unpaired mapping fix by `@mvdbeek `_ in `#21936 `_ * Fix validation of certain classes of text validators in tools. by `@jmchilton `_ in `#22280 `_ ============ Enhancements ============ * Tool Request API by `@jmchilton `_ in `#20935 `_ * Update fastapi to 0.123.4 and ``get_openapi()`` fork by `@nsoranzo `_ in `#21384 `_ * Enable attaching sample sheet to landing requests by `@mvdbeek `_ in `#21489 `_ * Apply 2026 black style by `@galaxybot `_ in `#21618 `_ ============= Other changes ============= * Merge 25.1 into dev, fix openapi schema generation for TypedDict by `@mvdbeek `_ in `#21168 `_ * Fix tool request API for multiple select parameters with defaults. by `@jmchilton `_ in `#21416 `_ ------------------- 25.1.2 (2026-03-09) ------------------- No recorded changes since last release ------------------- 25.1.1 (2026-02-03) ------------------- No recorded changes since last release ------------------- 25.1.0 (2025-12-12) ------------------- ============ Enhancements ============ * Implement Sample Sheets by `@jmchilton `_ in `#19305 `_ * Prepare ``ToolBox.dynamic_tool_to_tool()`` for CWL formats by `@nsoranzo `_ in `#20364 `_ * Type annotation fixes for mypy 1.16.0 by `@nsoranzo `_ in `#20424 `_ * Add configfiles support and various enhancements for user defined tools by `@mvdbeek `_ in `#20761 `_ * Add support for picking ``from_work_dir`` directory by `@mvdbeek `_ in `#20916 `_ * Include format in internal json model by `@mvdbeek `_ in `#20917 `_ * Add resource docs and tweak tool source schema title generation by `@mvdbeek `_ in `#20951 `_ * Use workflow-style payload in data landing request by `@mvdbeek `_ in `#21107 `_ ------------------- 25.0.4 (2025-11-18) ------------------- No recorded changes since last release ------------------- 25.0.3 (2025-09-23) ------------------- ========= Bug fixes ========= * Fix collection element sorting in extended_metadata by `@mvdbeek `_ in `#20928 `_ ------------------- 25.0.2 (2025-08-13) ------------------- ========= Bug fixes ========= * Fix parameter models for optional color params. by `@jmchilton `_ in `#20705 `_ ------------------- 25.0.1 (2025-06-20) ------------------- No recorded changes since last release ------------------- 25.0.0 (2025-06-18) ------------------- First release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871590.0 galaxy_tool_util_models-26.1.1/src/galaxy_tool_util_models.egg-info/SOURCES.txt0000644000175100017510000000232715234436346027276 0ustar00runnerrunnerHISTORY.rst LICENSE Makefile README.rst dev-requirements.txt mypy.ini pyproject.toml setup.cfg scripts/commit_version.py scripts/new_version.py scripts/print_version_for_release.py src/galaxy/tool_util_models/__init__.py src/galaxy/tool_util_models/_base.py src/galaxy/tool_util_models/_types.py src/galaxy/tool_util_models/assertions.py src/galaxy/tool_util_models/dynamic_tool_models.py src/galaxy/tool_util_models/parameter_validators.py src/galaxy/tool_util_models/parameters.py src/galaxy/tool_util_models/py.typed src/galaxy/tool_util_models/sample_sheet.py src/galaxy/tool_util_models/test_job.py src/galaxy/tool_util_models/testing_types.py src/galaxy/tool_util_models/tool_outputs.py src/galaxy/tool_util_models/tool_source.py src/galaxy/tool_util_models/yaml_parameters.py src/galaxy_tool_util_models.egg-info/PKG-INFO src/galaxy_tool_util_models.egg-info/SOURCES.txt src/galaxy_tool_util_models.egg-info/dependency_links.txt src/galaxy_tool_util_models.egg-info/requires.txt src/galaxy_tool_util_models.egg-info/top_level.txt tests/__init__.py tests/tool_util_models/__init__.py tests/tool_util_models/test_output_assertions.py tests/tool_util_models/test_simple_parse.py tests/tool_util_models/test_user_tool_source_response.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871590.0 galaxy_tool_util_models-26.1.1/src/galaxy_tool_util_models.egg-info/dependency_links.txt0000644000175100017510000000000115234436346031454 0ustar00runnerrunner ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871590.0 galaxy_tool_util_models-26.1.1/src/galaxy_tool_util_models.egg-info/requires.txt0000644000175100017510000000010615234436346030003 0ustar00runnerrunnerpydantic>=2.7.4 pydantic-extra-types typing-extensions [test] pytest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871590.0 galaxy_tool_util_models-26.1.1/src/galaxy_tool_util_models.egg-info/top_level.txt0000644000175100017510000000000715234436346030135 0ustar00runnerrunnergalaxy ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1785871590.957053 galaxy_tool_util_models-26.1.1/tests/0000755000175100017510000000000015234436347017346 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/tests/__init__.py0000644000175100017510000000000015234436334021441 0ustar00runnerrunner././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1785871590.957709 galaxy_tool_util_models-26.1.1/tests/tool_util_models/0000755000175100017510000000000015234436347022723 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/tests/tool_util_models/__init__.py0000644000175100017510000000000015234436334025016 0ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/tests/tool_util_models/test_output_assertions.py0000644000175100017510000001372115234436334030146 0ustar00runnerrunner"""Tests for the discriminated `TestOutputAssertions` Union.""" import json import pytest from pydantic import ValidationError from galaxy.tool_util_models import ( TestCollectionCollectionElementAssertions, TestCollectionOutputAssertions, TestDataOutputAssertions, Tests, ) def _one_test(outputs): return [{"doc": "t", "job": {}, "outputs": outputs}] def test_implicit_file_output_validates_as_data(): tests = Tests.model_validate(_one_test({"out": {"asserts": [{"that": "has_text", "text": "x"}]}})) out = tests.root[0].outputs["out"] assert isinstance(out, TestDataOutputAssertions) def test_explicit_collection_output_validates_as_collection(): tests = Tests.model_validate(_one_test({"out": {"class": "Collection", "elements": {"a": {"asserts": []}}}})) out = tests.root[0].outputs["out"] assert isinstance(out, TestCollectionOutputAssertions) @pytest.mark.parametrize("value", [True, 42, 3.14, "hello"]) def test_scalar_literal_output_validates(value): tests = Tests.model_validate(_one_test({"out": value})) assert tests.root[0].outputs["out"] == value def test_nested_collection_element_with_class_collection_validates(): tests = Tests.model_validate( _one_test( { "out": { "class": "Collection", "elements": { "inner": { "class": "Collection", "elements": {"leaf": {"asserts": []}}, } }, } } ) ) out = tests.root[0].outputs["out"] assert isinstance(out, TestCollectionOutputAssertions) assert out.elements is not None inner = out.elements["inner"] assert isinstance(inner, TestCollectionCollectionElementAssertions) def test_unknown_field_on_file_output_yields_single_error(): with pytest.raises(ValidationError) as exc: Tests.model_validate(_one_test({"out": {"asserts": [], "garbage_key": 1}})) errs = exc.value.errors() assert len(errs) == 1 err = errs[0] assert err["loc"][:4] == (0, "outputs", "out", "File") assert err["loc"][-1] == "garbage_key" assert err["type"] == "extra_forbidden" def test_unknown_class_value_yields_single_class_literal_error(): with pytest.raises(ValidationError) as exc: Tests.model_validate(_one_test({"out": {"class": "Banana"}})) errs = exc.value.errors() assert len(errs) == 1 err = errs[0] assert err["loc"][:4] == (0, "outputs", "out", "File") assert err["type"] == "literal_error" def test_bad_asserts_error_scoped_to_file_branch(): with pytest.raises(ValidationError) as exc: Tests.model_validate(_one_test({"out": {"asserts": [{"that": "not_a_real_assert"}]}})) errs = exc.value.errors() for err in errs: assert "Collection" not in err["loc"] assert "scalar" not in err["loc"] assert err["loc"][:4] == (0, "outputs", "out", "File") def test_json_schema_emits_discriminator_for_outputs(): schema = Tests.model_json_schema() dumped = json.dumps(schema) assert "discriminator" in dumped or '"oneOf"' in dumped def test_doc_is_optional_on_test_job(): tests = Tests.model_validate([{"job": {}, "outputs": {}}]) assert tests.root[0].doc is None schema = Tests.model_json_schema() test_job = schema["$defs"]["TestJob"] assert "doc" not in test_job.get("required", []) def test_test_job_top_level_properties_have_descriptions(): schema = Tests.model_json_schema() props = schema["$defs"]["TestJob"]["properties"] for name in ("doc", "job", "outputs", "expect_failure"): assert props[name].get("description"), f"missing description on TestJob.{name}" def test_test_job_titles_are_human_readable_not_lowercase(): schema = Tests.model_json_schema() props = schema["$defs"]["TestJob"]["properties"] assert props["doc"]["title"] == "Doc" assert props["outputs"]["title"] == "Outputs" assert props["expect_failure"]["title"] == "Expect Failure" def test_assertion_model_titles_are_human_readable(): schema = Tests.model_json_schema() defs = schema["$defs"] assert defs["has_text_model"]["title"] == "Assert Has Text" assert defs["has_text_model_nested"]["title"] == "Assert Has Text (Nested)" # Every field in has_text_model has an explicit title. for name, prop in defs["has_text_model"]["properties"].items(): assert prop.get("title"), f"has_text_model.{name} missing title" def test_test_data_output_assertions_field_titles(): schema = Tests.model_json_schema() props = schema["$defs"]["TestDataOutputAssertions"]["properties"] assert props["lines_diff"]["title"] == "Lines Diff" assert props["class"]["title"] == "Class" assert props["ftype"]["title"] == "File Type" def test_top_level_schema_has_title_description_and_schema(): schema = Tests.model_json_schema() assert schema.get("title") == "GalaxyWorkflowTests" assert schema.get("description") assert schema.get("$schema") == "https://json-schema.org/draft/2020-12/schema" def test_root_model_wrappers_have_clean_defs_keys(): schema = Tests.model_json_schema() defs = schema["$defs"] assert "Job" in defs assert "assertion_list" in defs # None of the RootModel-auto-generated mangled names should appear. mangled = [k for k in defs if k.startswith("RootModel_")] assert not mangled, f"unexpected mangled RootModel defs: {mangled}" def test_test_data_output_assertions_properties_have_descriptions(): schema = Tests.model_json_schema() props = schema["$defs"]["TestDataOutputAssertions"]["properties"] for name in ( "asserts", "metadata", "file", "ftype", "sort", "checksum", "compare", "lines_diff", "decompress", "delta", "delta_frac", "location", ): assert props[name].get("description"), f"missing description on TestDataOutputAssertions.{name}" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/tests/tool_util_models/test_simple_parse.py0000644000175100017510000000105515234436334027014 0ustar00runnerrunnerfrom galaxy.tool_util_models import Tests simple_json_test = """ [{ "doc": "Test simple use of __ZIP_COLLECTION__ in a workflow.", "job": { "test_input_1": "samp1 10.0 samp2 20.0 ", "test_input_2": "samp1 20.0 samp2 40.0 " }, "outputs": { "out": { "asserts": [ {"that": "has_text", "text": "samp1 10.0 samp2 20.0 samp1 20.0 samp2 40.0" } ] } } }] """ def test_simple_validate(): Tests.model_validate_json(simple_json_test) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1785871580.0 galaxy_tool_util_models-26.1.1/tests/tool_util_models/test_user_tool_source_response.py0000644000175100017510000002100515234436334031637 0ustar00runnerrunner"""Regression tests for `lift_user_tool_source`. Stored DynamicTool.value rows on long-lived servers (e.g. test.galaxyproject.org) predate the YAML narrowing in commit ec5cfe6cce6 and contain wider internal-model fields. The lift helper validates against the strict schema, strips drift-only errors, and falls back to a raw-dict shape for genuinely broken stored data so the API doesn't 500. See Sentry GALAXY-TEST-588ZYT7JSX3V0. """ from typing import ( Any, Dict, ) from galaxy.tool_util_models import ( lift_user_tool_source, UserToolSource, ) from galaxy.tool_util_models.tool_outputs import ( FilePatternDatasetCollectionDescription, IncomingToolOutputCollection, ) LEGACY_DATA_INPUT: Dict[str, Any] = { "type": "data", "name": "input", "format": ["data"], "parameter_type": "gx_data", "argument": None, "hidden": False, "is_dynamic": False, "extensions": ["data"], } LEGACY_TEXT_INPUT: Dict[str, Any] = { "type": "text", "name": "msg", "value": "hello", "parameter_type": "gx_text", "argument": None, "hidden": False, "is_dynamic": False, "default_options": [], } BASE_TOOL: Dict[str, Any] = { "class": "GalaxyUserTool", "id": "legacy-tool", "name": "Legacy", "version": "1.0.0", "container": "busybox", "shell_command": "echo $(inputs.msg)", "outputs": [], } def _legacy_tool_value(): return {**BASE_TOOL, "inputs": [LEGACY_DATA_INPUT.copy(), LEGACY_TEXT_INPUT.copy()]} def test_lift_status_ok_for_clean_representation(): clean = { **BASE_TOOL, "inputs": [ {"type": "data", "name": "input", "format": ["data"]}, {"type": "text", "name": "msg", "value": "hi"}, ], } status, parsed, errors = lift_user_tool_source(clean) assert status == "ok" assert isinstance(parsed, UserToolSource) assert errors == [] def test_lift_status_lifted_for_drift_only(): status, parsed, errors = lift_user_tool_source(_legacy_tool_value()) assert status == "lifted" assert isinstance(parsed, UserToolSource) # Each legacy key is reported with its compact path; discriminator tags are # stripped from the user-facing path. assert "inputs.0.parameter_type" in errors assert "inputs.0.argument" in errors assert "inputs.1.default_options" in errors # Lifted model no longer carries the legacy fields. dumped = parsed.model_dump(by_alias=True) for inp in dumped["inputs"]: assert "parameter_type" not in inp assert "argument" not in inp def test_lift_status_invalid_for_missing_version_still_returns_tool(): """`version` is required, so a stored row that predates the requirement can't be auto-lifted. It is still returned to its author -- as the raw dict with an 'invalid' status -- so they can see and fix what they defined, rather than the endpoint 500-ing or hiding the tool.""" versionless = {k: v for k, v in BASE_TOOL.items() if k != "version"} status, parsed, errors = lift_user_tool_source(versionless) assert status == "invalid" assert parsed == versionless # returned verbatim assert any("version" in e for e in errors) def test_lift_handles_nested_conditional_drift(): value = { **BASE_TOOL, "shell_command": "echo hello", "inputs": [ { "type": "conditional", "name": "advanced", "test_parameter": {"type": "boolean", "name": "use", "value": False}, "whens": [ {"discriminator": True, "parameters": [LEGACY_DATA_INPUT.copy()]}, {"discriminator": False, "parameters": []}, ], }, ], } status, parsed, errors = lift_user_tool_source(value) assert status == "lifted" assert isinstance(parsed, UserToolSource) assert any("parameter_type" in e for e in errors) def test_lift_status_invalid_for_real_schema_error(): """A required-field violation can't be auto-lifted; helper returns the raw dict and a human-readable summary so the endpoint can still serve a response without 500-ing.""" bad = _legacy_tool_value() del bad["name"] # name is a required str status, parsed, errors = lift_user_tool_source(bad) assert status == "invalid" assert isinstance(parsed, dict) assert any("name" in e and "required" in e.lower() for e in errors) def test_lift_status_invalid_when_value_constraint_fails(): """Simulates a future tightening of the schema (e.g. a stricter constraint on a scalar field): even if drift exists, a real value-level error pushes the result to 'invalid' rather than 'lifted'.""" bad = _legacy_tool_value() bad["shell_command"] = 123 # must be a str status, parsed, errors = lift_user_tool_source(bad) assert status == "invalid" assert isinstance(parsed, dict) assert any("shell_command" in e for e in errors) def test_lift_does_not_mutate_input(): original = _legacy_tool_value() snapshot = {**original, "inputs": [dict(i) for i in original["inputs"]]} lift_user_tool_source(original) assert original["inputs"][0] == snapshot["inputs"][0] assert original["inputs"][1] == snapshot["inputs"][1] LEGACY_NESTED_COLLECTION_OUTPUT: Dict[str, Any] = { "type": "collection", "name": "outs", "label": None, "hidden": False, "structure": { "collection_type": "list", "collection_type_source": None, "collection_type_from_rules": None, "structured_like": None, "discover_datasets": [ { "discover_via": "pattern", "pattern": "__name_and_ext__", "directory": "outs", "format": None, "visible": False, "assign_primary_output": False, "recurse": False, "match_relative_path": False, "sort_key": "filename", "sort_comp": "lexical", "sort_reverse": False, } ], }, } def _collection_tool_value(output): return { **BASE_TOOL, "shell_command": "touch outs/a.txt", "inputs": [], "outputs": [output], } def test_legacy_structure_wrapper_lifted_silently(): value = _collection_tool_value(LEGACY_NESTED_COLLECTION_OUTPUT) status, parsed, errors = lift_user_tool_source(value) assert status == "ok", errors assert isinstance(parsed, UserToolSource) output = parsed.outputs[0] assert isinstance(output, IncomingToolOutputCollection) # Fields surface at the top level after the lift. assert output.collection_type == "list" assert output.discover_datasets discover = output.discover_datasets[0] assert isinstance(discover, FilePatternDatasetCollectionDescription) assert discover.pattern == "__name_and_ext__" # Dumped representation is flat — no leftover wrapper. dumped = parsed.model_dump(by_alias=True) assert "structure" not in dumped["outputs"][0] assert dumped["outputs"][0]["collection_type"] == "list" def test_legacy_structure_not_shadowed_by_explicit_top_level_none(): # Defensive: if a future writer ever produces a hybrid dict where the # top-level field is explicit ``None`` but the structure carries a real # value, the lift must not silently drop the structure value. hybrid_output = { "type": "collection", "name": "outs", "hidden": False, "collection_type": None, "structure": { "collection_type": "list", "discover_datasets": LEGACY_NESTED_COLLECTION_OUTPUT["structure"]["discover_datasets"], }, } value = _collection_tool_value(hybrid_output) status, parsed, errors = lift_user_tool_source(value) assert status == "ok", errors assert isinstance(parsed, UserToolSource) output = parsed.outputs[0] assert isinstance(output, IncomingToolOutputCollection) assert output.collection_type == "list" def test_flat_collection_output_validates_directly(): flat_output = { "type": "collection", "name": "outs", "hidden": False, "collection_type": "list", "discover_datasets": LEGACY_NESTED_COLLECTION_OUTPUT["structure"]["discover_datasets"], } value = _collection_tool_value(flat_output) status, parsed, errors = lift_user_tool_source(value) assert status == "ok", errors assert isinstance(parsed, UserToolSource) output = parsed.outputs[0] assert isinstance(output, IncomingToolOutputCollection) assert output.collection_type == "list"