Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "submodules/cat_vrs"]
path = submodules/cat_vrs
url = https://github.com/ga4gh/cat-vrs
branch = 1.0
branch = 1.1.0-snapshot.2026-02
21 changes: 20 additions & 1 deletion src/ga4gh/cat_vrs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ class CopyCountConstraint(BaseModelForbidExtra):


class CopyChangeConstraint(BaseModelForbidExtra):
"""A representation of copy number change"""
"""The relative assessment of the change in copies that members of this categorical
variant satisfy.
"""

type: Literal["CopyChangeConstraint"] = Field(
default="CopyChangeConstraint", description="MUST be 'CopyChangeConstraint'"
Expand All @@ -101,6 +103,22 @@ class FeatureContextConstraint(BaseModelForbidExtra):
featureContext: MappableConcept = Field(..., description="A feature identifier.")


class FunctionConstraint(BaseModelForbidExtra):
"""A classification of the protein functional consequence that characterizes members of this categorical variant."""

type: Literal["FunctionConstraint"] = Field(
default="FunctionConstraint",
description='MUST be "FunctionConstraint"',
)
functionConsequence: MappableConcept = Field(
...,
description="The functional consequence of members of this categorical variant, as defined by an external ontology. We recommend using one of the defined terms from [The Sequence Ontology](http://www.sequenceontology.org). See Implementation Guidance for more details. ",
)
description: str | None = Field(
default=None, description="A free-text description of the function change."
)


class Constraint(RootModel):
"""Constraints are used to construct an intensional semantics of categorical variant types."""

Expand All @@ -110,6 +128,7 @@ class Constraint(RootModel):
| CopyCountConstraint
| CopyChangeConstraint
| FeatureContextConstraint
| FunctionConstraint
) = Field(..., discriminator="type")


Expand Down
61 changes: 60 additions & 1 deletion src/ga4gh/cat_vrs/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
CopyCountConstraint,
DefiningAlleleConstraint,
DefiningLocationConstraint,
FeatureContextConstraint,
FunctionConstraint,
Relation,
)

Expand Down Expand Up @@ -78,7 +80,7 @@ class CanonicalAllele(CategoricalVariant):
"""A canonical allele is defined by an
`Allele <https://vrs.ga4gh.org/en/2.x/concepts/MolecularVariation/Allele.html#>`_
that is representative of a collection of congruent Alleles, each of which depict
the same nucleic acid change on different underlying reference sequences. Congruent
the same nucleic acid on different underlying reference sequences. Congruent
representations of an Allele often exist across different genome assemblies and
associated cDNA transcript representations.
"""
Expand Down Expand Up @@ -210,3 +212,60 @@ def validate_constraints(cls, v: list[Constraint]) -> list[Constraint]:
raise ValueError(err_msg)

return v


class FunctionVariant(CategoricalVariant):
"""A representation of the constraints for matching knowledge about function
variants; e.g., gain-of-function or loss-of-function.
"""

constraints: list[Constraint] = Field(
...,
min_length=2,
description=(
"The constraints must contain at least two items: a FunctionConstraint and either a DefiningAlleleConstraint, DefiningLocationConstraint, or FeatureContextConstraint."
),
)

@field_validator("constraints")
@classmethod
def validate_constraints(cls, v: list[Constraint]) -> list[Constraint]:
"""Validate constraints property

``constraints`` must:
1. Contain at least one ``FunctionConstraint``
2. Contain at least one of:
- ``DefiningAlleleConstraint``
- ``DefiningLocationConstraint``
- ``FeatureContextConstraint``

:param v: Constraints property to validate
:raises ValueError: If constraints property does not satisfy requirements
:return: Constraints property
"""
has_function_constraint = False
has_context_constraint = False

for constraint in v:
root = constraint.root

if isinstance(root, FunctionConstraint):
has_function_constraint = True

if isinstance(
root,
DefiningAlleleConstraint
| DefiningLocationConstraint
| FeatureContextConstraint,
):
has_context_constraint = True

if not has_function_constraint:
msg = "Must contain at least one `FunctionConstraint`."
raise ValueError(msg)

if not has_context_constraint:
msg = "Must contain at least one of: `DefiningAlleleConstraint`, `DefiningLocationConstraint`, or `FeatureContextConstraint`."
raise ValueError(msg)

return v
2 changes: 1 addition & 1 deletion submodules/cat_vrs
Submodule cat_vrs updated 69 files
+1 −1 .github/CODEOWNERS
+1 −1 .gitmodules
+2 −2 .readthedocs.yaml
+12 −5 .requirements.txt
+8 −0 docs/source/_static/theme_overrides.css
+41 −52 docs/source/appendices/design_decisions.rst
+0 −10 docs/source/appendices/faq.rst
+4 −5 docs/source/appendices/index.rst
+7 −7 docs/source/appendices/roadmap.rst
+91 −26 docs/source/concepts/catvrs_model.rst
+2 −0 docs/source/concepts/index.rst
+44 −50 docs/source/concepts/recipes.rst
+25 −22 docs/source/conf.py
+6 −4 docs/source/getting_involved.rst
+3 −1 docs/source/how_cat_vrs_works.rst
+6 −3 docs/source/impl-guide/index.rst
+0 −18 docs/source/index.rst
+3 −1 docs/source/introduction.rst
+9 −0 docs/source/releases/1.0.rst
+12 −5 docs/source/releases/index.rst
+0 −4 docs/source/requirements.txt
+50 −21 examples/README.md
+15 −15 examples/braf-v600.annotated.yaml
+15 −15 examples/braf-v600.yaml
+0 −43 examples/categoricalCnv-ex4.yaml
+69 −0 examples/functionVariant-ex1.yaml
+29 −0 examples/functionVariant-ex2.yaml
+109 −0 examples/functionVariant-ex3.yaml
+48 −8 examples/tp53-copy-loss.annotated.yaml
+80 −0 examples/tp53-copy-loss.yaml
+29 −4 schema/cat-vrs/cat-vrs-source.yaml
+1 −1 schema/cat-vrs/def/CanonicalAllele.rst
+1 −1 schema/cat-vrs/def/CopyChangeConstraint.rst
+38 −0 schema/cat-vrs/def/FunctionConstraint.rst
+22 −0 schema/cat-vrs/def/FunctionVariant.rst
+7 −0 schema/cat-vrs/def/example_functionVariant-ex1.rst
+7 −0 schema/cat-vrs/def/example_functionVariant-ex2.rst
+7 −0 schema/cat-vrs/def/example_functionVariant-ex3.rst
+0 −0 schema/cat-vrs/def/example_tp53-copy-loss.rst
+272 −19 schema/cat-vrs/examples-source.yaml
+3 −3 schema/cat-vrs/json/CanonicalAllele
+4 −4 schema/cat-vrs/json/CategoricalCnv
+13 −10 schema/cat-vrs/json/CategoricalVariant
+9 −6 schema/cat-vrs/json/Constraint
+2 −2 schema/cat-vrs/json/CopyChangeConstraint
+2 −2 schema/cat-vrs/json/CopyCountConstraint
+4 −4 schema/cat-vrs/json/DefiningAlleleConstraint
+5 −5 schema/cat-vrs/json/DefiningLocationConstraint
+2 −2 schema/cat-vrs/json/FeatureContextConstraint
+29 −0 schema/cat-vrs/json/FunctionConstraint
+50 −0 schema/cat-vrs/json/FunctionVariant
+2 −2 schema/cat-vrs/json/ProteinSequenceConsequence
+21 −19 schema/cat-vrs/json/example_braf-v600
+1 −1 schema/cat-vrs/json/example_canonicalAllele-ex1
+1 −1 schema/cat-vrs/json/example_canonicalAllele-ex2
+1 −1 schema/cat-vrs/json/example_categoricalCnv-ex1
+1 −1 schema/cat-vrs/json/example_categoricalCnv-ex2
+1 −1 schema/cat-vrs/json/example_categoricalCnv-ex3
+0 −75 schema/cat-vrs/json/example_categoricalCnv-ex4
+1 −1 schema/cat-vrs/json/example_describedVariant-ex1
+103 −0 schema/cat-vrs/json/example_functionVariant-ex1
+46 −0 schema/cat-vrs/json/example_functionVariant-ex2
+160 −0 schema/cat-vrs/json/example_functionVariant-ex3
+1 −1 schema/cat-vrs/json/example_proteinSequenceConsequence-ex1
+1 −1 schema/cat-vrs/json/example_proteinSequenceConsequence-ex2
+130 −0 schema/cat-vrs/json/example_tp53-copy-loss
+27 −3 schema/cat-vrs/recipes-source.yaml
+1 −1 submodules/vrs
+39 −2 tests/test_definitions.yaml
91 changes: 91 additions & 0 deletions tests/validation/test_cat_vrs_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ def defining_loc_constr():
)


@pytest.fixture(scope="module")
def feature_context_constr():
"""Create test fixture for feature context constraint"""
return models.FeatureContextConstraint(
featureContext=MappableConcept(
primaryCoding=Coding(
code=code("HGNC:1097"),
system="https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/",
)
)
)


@pytest.fixture(scope="module")
def function_constr():
"""Create test fixture for function constraint"""
return models.FunctionConstraint(
functionConsequence=MappableConcept(
primaryCoding=Coding(
code=code("SO:0002219"),
system="http://www.sequenceontology.org/browser/current_release/term/",
)
),
description="Function consequence described as functionally normal using Sequence Ontology.",
)


def test_copy_count_constraint():
"""Test the CopyCountConstraint validator"""
# Valid Copy Count Constraint
Expand Down Expand Up @@ -445,3 +472,67 @@ def test_categorical_cnv(
match="Must contain either a `CopyCountConstraint` or `CopyChangeConstraint`.",
):
recipes.CategoricalCnv(**invalid_params)


def test_function_variant(
members_and_name: dict,
function_constr: models.FunctionConstraint,
defining_loc_constr: models.DefiningLocationConstraint,
feature_context_constr: models.FeatureContextConstraint,
):
"""Test the FunctionVariant validator"""
# Valid FunctionVariant with DefiningAlleleConstraint
valid_params = deepcopy(members_and_name)
valid_params["constraints"] = [
models.Constraint(root=function_constr),
models.Constraint(root=def_allele_constr_empty_relations(is_empty_list=True)),
]
assert recipes.FunctionVariant(**valid_params)

# Valid FunctionVariant with DefiningLocationConstraint
valid_params = deepcopy(members_and_name)
valid_params["constraints"] = [
models.Constraint(root=function_constr),
models.Constraint(root=defining_loc_constr),
]
assert recipes.FunctionVariant(**valid_params)

# Valid FunctionVariant with FeatureContextConstraint
valid_params = deepcopy(members_and_name)
valid_params["constraints"] = [
models.Constraint(root=function_constr),
models.Constraint(root=feature_context_constr),
]
assert recipes.FunctionVariant(**valid_params)

# Invalid FunctionVariant: No FunctionConstraint
invalid_params = deepcopy(members_and_name)
invalid_params["constraints"] = [
models.Constraint(root=def_allele_constr_empty_relations(is_empty_list=True)),
models.Constraint(root=defining_loc_constr),
]
with pytest.raises(
ValueError,
match="Must contain at least one `FunctionConstraint`.",
):
recipes.FunctionVariant(**invalid_params)

# Invalid FunctionVariant: No contextual constraint
invalid_params = deepcopy(members_and_name)
invalid_params["constraints"] = [
models.Constraint(root=function_constr),
models.Constraint(root=function_constr.model_copy(deep=True)),
]
with pytest.raises(
ValueError,
match="Must contain at least one of:",
):
recipes.FunctionVariant(**invalid_params)

# Invalid FunctionVariant: Only one constraint provided
invalid_params = deepcopy(members_and_name)
invalid_params["constraints"] = [
models.Constraint(root=function_constr),
]
with pytest.raises(ValueError, match="List should have at least 2 items"):
recipes.FunctionVariant(**invalid_params)