Skip to content

Commit

Permalink
fix: forbid extra attributes when additionalProperties is set to False (
Browse files Browse the repository at this point in the history
#506)

close #505
  • Loading branch information
korikuzma authored Feb 12, 2025
1 parent e450542 commit 356d975
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 19 deletions.
14 changes: 10 additions & 4 deletions src/ga4gh/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
from ga4gh.core.identifiers import GA4GH_IR_REGEXP


class BaseModelForbidExtra(BaseModel):
"""Base Pydantic model class with extra attributes forbidden."""

model_config = ConfigDict(extra="forbid")


class Relation(str, Enum):
"""A mapping relation between concepts as defined by the Simple Knowledge
Organization System (SKOS).
Expand Down Expand Up @@ -129,7 +135,7 @@ class Element(BaseModel, ABC):
#########################################


class Coding(Element):
class Coding(Element, BaseModelForbidExtra):
"""A structured representation of a code for a defined concept in a terminology or
code system.
"""
Expand All @@ -149,7 +155,7 @@ class Coding(Element):
code: code # Cannot use Field due to PydanticUserError: field name and type annotation must not clash.


class ConceptMapping(Element):
class ConceptMapping(Element, BaseModelForbidExtra):
"""A mapping to a concept in a terminology or code system."""

model_config = ConfigDict(use_enum_values=True)
Expand All @@ -164,7 +170,7 @@ class ConceptMapping(Element):
)


class Extension(Element):
class Extension(Element, BaseModelForbidExtra):
"""The Extension class provides entities with a means to include additional
attributes that are outside of the specified standard but needed by a given content
provider or system implementer. These extensions are not expected to be natively
Expand All @@ -186,7 +192,7 @@ class Extension(Element):
)


class MappableConcept(Element):
class MappableConcept(Element, BaseModelForbidExtra):
"""A concept name that may be mapped to one or more `Codings`."""

conceptType: Optional[str] = Field( # noqa: N815
Expand Down
36 changes: 21 additions & 15 deletions src/ga4gh/vrs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@
PrevVrsVersion,
sha512t24u,
)
from ga4gh.core.models import Element, Entity, MappableConcept, iriReference
from ga4gh.core.models import (
BaseModelForbidExtra,
Element,
Entity,
MappableConcept,
iriReference,
)
from ga4gh.core.pydantic import get_pydantic_root, getattr_in


Expand Down Expand Up @@ -369,7 +375,7 @@ class ga4gh(_ValueObject.ga4gh): # noqa: N801
prefix: str


class Expression(Element):
class Expression(Element, BaseModelForbidExtra):
"""Representation of a variation by a specified nomenclature or syntax for a
Variation object. Common examples of expressions for the description of molecular
variation include the HGVS and ISCN nomenclatures.
Expand Down Expand Up @@ -465,7 +471,7 @@ class sequenceString(RootModel):
#########################################


class LengthExpression(_ValueObject):
class LengthExpression(_ValueObject, BaseModelForbidExtra):
"""A sequence expressed only by its length."""

type: Literal["LengthExpression"] = Field(
Expand All @@ -479,7 +485,7 @@ class ga4gh(_ValueObject.ga4gh):
inherent = ["length", "type"]


class ReferenceLengthExpression(_ValueObject):
class ReferenceLengthExpression(_ValueObject, BaseModelForbidExtra):
"""An expression of a length of a sequence from a repeating reference."""

type: Literal["ReferenceLengthExpression"] = Field(
Expand All @@ -501,7 +507,7 @@ class ga4gh(_ValueObject.ga4gh):
inherent = ["length", "repeatSubunitLength", "type"]


class LiteralSequenceExpression(_ValueObject):
class LiteralSequenceExpression(_ValueObject, BaseModelForbidExtra):
"""An explicit expression of a Sequence."""

type: Literal["LiteralSequenceExpression"] = Field(
Expand All @@ -519,7 +525,7 @@ class ga4gh(_ValueObject.ga4gh):
#########################################


class SequenceReference(_ValueObject):
class SequenceReference(_ValueObject, BaseModelForbidExtra):
"""A sequence of nucleic or amino acid character codes."""

model_config = ConfigDict(use_enum_values=True)
Expand Down Expand Up @@ -554,7 +560,7 @@ class ga4gh(_ValueObject.ga4gh):
inherent = ["refgetAccession", "type"]


class SequenceLocation(Ga4ghIdentifiableObject):
class SequenceLocation(Ga4ghIdentifiableObject, BaseModelForbidExtra):
"""A `Location` defined by an interval on a `Sequence`."""

type: Literal["SequenceLocation"] = Field(
Expand Down Expand Up @@ -660,7 +666,7 @@ class _VariationBase(Ga4ghIdentifiableObject, ABC):
#########################################


class Allele(_VariationBase):
class Allele(_VariationBase, BaseModelForbidExtra):
"""The state of a molecule at a `Location`."""

type: Literal["Allele"] = Field(
Expand Down Expand Up @@ -704,7 +710,7 @@ class ga4gh(Ga4ghIdentifiableObject.ga4gh): # noqa: N801
inherent = ["location", "state", "type"]


class CisPhasedBlock(_VariationBase):
class CisPhasedBlock(_VariationBase, BaseModelForbidExtra):
"""An ordered set of co-occurring `Variation` on the same molecule."""

type: Literal["CisPhasedBlock"] = Field(
Expand Down Expand Up @@ -736,7 +742,7 @@ class ga4gh(Ga4ghIdentifiableObject.ga4gh):
#########################################


class Adjacency(_VariationBase):
class Adjacency(_VariationBase, BaseModelForbidExtra):
"""The `Adjacency` class represents the adjoining of the end of a sequence with the
beginning of an adjacent sequence, potentially with an intervening linker sequence.
"""
Expand Down Expand Up @@ -779,7 +785,7 @@ class ga4gh(Ga4ghIdentifiableObject.ga4gh):
inherent = ["adjoinedSequences", "linker", "type"]


class Terminus(_VariationBase):
class Terminus(_VariationBase, BaseModelForbidExtra):
"""The `Terminus` data class provides a structure for describing the end
(terminus) of a sequence. Structurally similar to Adjacency but the linker sequence
is not allowed and it removes the unnecessary array structure.
Expand All @@ -797,7 +803,7 @@ class ga4gh(Ga4ghIdentifiableObject.ga4gh): # noqa: N815
inherent = ["location", "type"]


class TraversalBlock(_ValueObject):
class TraversalBlock(_ValueObject, BaseModelForbidExtra):
"""A component used to describe the orientation of applicable molecular variation
within a DerivativeMolecule.
"""
Expand All @@ -820,7 +826,7 @@ class ga4gh(_ValueObject.ga4gh):
inherent = ["component", "orientation", "type"]


class DerivativeMolecule(_VariationBase):
class DerivativeMolecule(_VariationBase, BaseModelForbidExtra):
"""The "Derivative Molecule" data class is a structure for describing a derivate
molecule composed from multiple sequence components.
"""
Expand Down Expand Up @@ -851,7 +857,7 @@ class ga4gh(Ga4ghIdentifiableObject.ga4gh): # noqa: N815
#########################################


class CopyNumberCount(_VariationBase):
class CopyNumberCount(_VariationBase, BaseModelForbidExtra):
"""The absolute count of discrete copies of a `Location`, within a system
(e.g. genome, cell, etc.).
"""
Expand All @@ -872,7 +878,7 @@ class ga4gh(Ga4ghIdentifiableObject.ga4gh): # noqa: N815
inherent = ["copies", "location", "type"]


class CopyNumberChange(_VariationBase):
class CopyNumberChange(_VariationBase, BaseModelForbidExtra):
"""An assessment of the copy number of a `Location` within a system
(e.g. genome, cell, etc.) relative to a baseline ploidy.
"""
Expand Down
5 changes: 5 additions & 0 deletions tests/validation/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ def test_schema_class_fields(gks_schema, pydantic_models):

required_schema_fields = set(mapping.schema[schema_model]["required"])

if mapping.schema[schema_model].get("additionalProperties") is False:
assert pydantic_model.model_config.get("extra") == "forbid", (
f"{pydantic_model} should forbid extra attributes"
)

for prop, property_def in schema_properties.items():
pydantic_model_field_info = pydantic_model.model_fields[prop]
pydantic_field_required = pydantic_model_field_info.is_required()
Expand Down

0 comments on commit 356d975

Please sign in to comment.