diff --git a/.gitmodules b/.gitmodules index 6367eae..47b0bad 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "submodules/cat_vrs"] path = submodules/cat_vrs url = https://github.com/ga4gh/cat-vrs - branch = 1.1 + branch = 1.1.1-ballot.2026-09 diff --git a/pyproject.toml b/pyproject.toml index 5d31cde..42c6773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ keywords = ["bioinformatics", "ga4gh", "genomics", "variation"] requires-python = ">=3.10" dynamic = ["version"] dependencies = [ - "ga4gh.vrs~=2.4.0-a4", + "ga4gh.vrs~=2.4.0-a5", "pydantic>=2.0,<3.0", ] diff --git a/src/ga4gh/cat_vrs/metadata.py b/src/ga4gh/cat_vrs/metadata.py index 7bbc397..d35a8c2 100644 --- a/src/ga4gh/cat_vrs/metadata.py +++ b/src/ga4gh/cat_vrs/metadata.py @@ -1,10 +1,10 @@ """Provide metadata mixins for Cat-VRS models.""" from ga4gh.cat_vrs.version import CATVRS_VERSION -from ga4gh.core.metadata import GKSMetadataMixin +from ga4gh.core.metadata import GKMMetadataMixin -class CatVRSMetadataMixin(GKSMetadataMixin): +class CatVRSMetadataMixin(GKMMetadataMixin): """Provide metadata for a concrete Cat-VRS model.""" _product_name = "cat-vrs" diff --git a/src/ga4gh/cat_vrs/models.py b/src/ga4gh/cat_vrs/models.py index 8d4ffc2..49f1d4c 100644 --- a/src/ga4gh/cat_vrs/models.py +++ b/src/ga4gh/cat_vrs/models.py @@ -196,6 +196,7 @@ class Constraint(CatVRSMetadataMixin, RootModel): """Constraints are used to construct an intensional semantics of categorical variant types.""" _maturity: ClassVar[Maturity] = Maturity.TRIAL_USE + _abstract: ClassVar[bool] = True root: ( DefiningAlleleConstraint diff --git a/src/ga4gh/cat_vrs/version.py b/src/ga4gh/cat_vrs/version.py index bf3961f..44a58b9 100644 --- a/src/ga4gh/cat_vrs/version.py +++ b/src/ga4gh/cat_vrs/version.py @@ -1,3 +1,3 @@ """Define Cat-VRS version""" -CATVRS_VERSION = "1.1.0" +CATVRS_VERSION = "1.1.1-ballot.2026-09.1" diff --git a/submodules/cat_vrs b/submodules/cat_vrs index fef0876..4083598 160000 --- a/submodules/cat_vrs +++ b/submodules/cat_vrs @@ -1 +1 @@ -Subproject commit fef0876ba9689f8983e1fba1f656f86f3d7b98e4 +Subproject commit 408359835d13077feeb303d914d148b4b7868ac4 diff --git a/tests/validation/test_cat_vrs_schema.py b/tests/validation/test_cat_vrs_schema.py index 1342ed3..b137b2e 100644 --- a/tests/validation/test_cat_vrs_schema.py +++ b/tests/validation/test_cat_vrs_schema.py @@ -12,14 +12,14 @@ class CatVrsSchema(str, Enum): - """Enum for Cat VRS schema""" + """Enum for Cat-VRS schemas.""" CAT_VRS = "cat_vrs" RECIPES = "recipes" class CatVrsSchemaMapping(BaseModel): - """Model for representing Cat-VRS Schema concrete classes, primitives, and schema""" + """Represent Cat-VRS JSON Schema classes, primitives, and definitions.""" base_classes: set = set() concrete_classes: set = set() @@ -41,7 +41,7 @@ def _update_cat_vrs_schema_mapping( spec_class = cls_def["title"] cat_vrs_schema_mapping.cat_vrs_schema[spec_class] = cls_def - if "properties" in cls_def: + if "properties" in cls_def and not cls_def.get("abstract"): cat_vrs_schema_mapping.concrete_classes.add(spec_class) elif cls_def.get("type") in {"array", "integer", "string"}: cat_vrs_schema_mapping.primitives.add(spec_class) diff --git a/tests/validation/test_model_metadata.py b/tests/validation/test_model_metadata.py index 0fac282..727531f 100644 --- a/tests/validation/test_model_metadata.py +++ b/tests/validation/test_model_metadata.py @@ -1,39 +1,47 @@ -"""Test model metadata against the Cat-VRS source and JSON schemas.""" +"""Test model metadata against the Cat-VRS JSON schemas.""" import json from pathlib import Path import pytest -import yaml +from pydantic import RootModel from ga4gh.cat_vrs import CATVRS_VERSION, models, recipes from ga4gh.core.metadata import Maturity SCHEMA_DIR = Path(__file__).parents[2] / "submodules" / "cat_vrs" / "schema" / "cat-vrs" -SCHEMAS = ( - (models, SCHEMA_DIR / "cat-vrs-source.yaml"), - (recipes, SCHEMA_DIR / "recipes-source.yaml"), -) +SCHEMAS = (models, recipes) JSON_DIR = SCHEMA_DIR / "json" -with (SCHEMA_DIR / "cat-vrs-source.yaml").open() as source_file: - CATVRS_SOURCE = yaml.safe_load(source_file) +with (JSON_DIR / "CategoricalVariant").open() as schema_file: + SPEC_VERSION = json.load(schema_file)["$id"].split("/")[-3] -SPEC_VERSION = CATVRS_SOURCE["$id"].split("/")[-2] +def _model_params(abstract: bool): + """Return model metadata discovered from JSON Schema files. -def _model_params(): - """Return model metadata discovered from both source YAML files.""" + :param abstract: Whether to return abstract models. + :returns: Pytest parameters for matching Cat-VRS models and schemas. + """ params = [] - for model_module, source_path in SCHEMAS: - with source_path.open() as source_file: - definitions = yaml.safe_load(source_file)["$defs"] - for name, definition in definitions.items(): - model = getattr(model_module, name) - with (JSON_DIR / name).open() as schema_file: - schema = json.load(schema_file) - params.append(pytest.param(model, definition, schema, id=name)) - assert params, "No concrete Cat-VRS models discovered" + for schema_path in sorted(JSON_DIR.iterdir()): + with schema_path.open() as schema_file: + schema = json.load(schema_file) + if schema.get("abstract", False) is not abstract: + continue + model = next( + ( + getattr(module, schema_path.name, None) + for module in SCHEMAS + if hasattr(module, schema_path.name) + ), + None, + ) + if model is not None: + params.append(pytest.param(model, schema, id=schema_path.name)) + assert ( + params + ), f"No {'abstract' if abstract else 'concrete'} Cat-VRS models discovered" return params @@ -42,17 +50,31 @@ def test_cat_vrs_version_matches_source_schema(): assert CATVRS_VERSION == SPEC_VERSION -@pytest.mark.parametrize(("model", "definition", "schema"), _model_params()) -def test_model_metadata(model, definition, schema): - """Model metadata matches its source and generated JSON Schemas.""" +@pytest.mark.parametrize(("model", "schema"), _model_params(abstract=False)) +def test_concrete_model_metadata(model, schema): + """Concrete model metadata matches its published JSON Schema.""" expected_schema_id = ( f"https://w3id.org/ga4gh/schema/cat-vrs/{SPEC_VERSION}/json/" f"{model.__name__}" ) assert model.schema_id() == expected_schema_id - assert model.maturity() == Maturity(definition["maturity"]) + assert model.maturity() == Maturity(schema["maturity"]) generated_schema = model.model_json_schema() assert generated_schema["$id"] == expected_schema_id assert generated_schema["maturity"] == schema["maturity"] assert "ga4gh" not in generated_schema + + +@pytest.mark.parametrize(("model", "schema"), _model_params(abstract=True)) +def test_abstract_model_metadata(model, schema): + """Abstract compatibility adapters expose published JSON Schema metadata.""" + assert model.maturity() == Maturity(schema["maturity"]) + + generated_schema = model.model_json_schema() + assert generated_schema["$id"] == schema["$id"] + assert generated_schema["maturity"] == schema["maturity"] + assert generated_schema["abstract"] is True + if issubclass(model, RootModel): + assert generated_schema["discriminator"]["propertyName"] == "type" + assert len(generated_schema["oneOf"]) == len(schema["oneOf"])