Skip to content

Introduce Shared View-Model Mixins for Common Fields #620

@bencap

Description

@bencap

We should consider de-duplicating repeated view-model structures by introducing shared Pydantic mixins/components for common fields (e.g., externalLinks, with URN). This would reduce duplication and drift between view models, centralize validation, aliasing, and serialization rules for shared fields and simplify future additions of linkouts/collections across resources.

Scope:
Within the new components/ directory introduced in #619, we should add a mixins.py with WithExternalLinks, WithOfficialCollections, WithUrn, etc. to compose shared fields. This would allow us to add new properties to view models easily while keeping validation logic with-in mix-ins, reducing duplicated code.

For example, we could construct a WithUrn mix-in and handle all URN validation there:

# src/mavedb/view_models/components/mixins.py
from typing import ClassVar, Iterable, Optional
import re
from pydantic import BaseModel, Field, field_validator

class WithUrn(BaseModel):
    urn: str = Field(..., alias="urn", description="Stable URN identifier")

    # Provide any number of acceptable patterns; subclasses override this.
    _urn_patterns: ClassVar[Iterable[re.Pattern[str]]] = ()

    @field_validator("urn")
    @classmethod
    def validate_urn(cls, v: str) -> str:
        v = v.strip()
        if not v:
            raise ValueError("urn cannot be empty")

        patterns = list(cls._urn_patterns)
        if not patterns:
            return v  # no constraints configured

        if any(p.match(v) for p in patterns):
            return v

        # Helpful error with pattern names (if available)
        expected = " or ".join(getattr(p, "name", p.pattern) for p in patterns)
        raise ValueError(f"invalid URN format; expected: {expected}")

You could then override the generic class with specific patterns we want to mix-in:

# src/mavedb/view_models/components/mixins.py (continued from above)
from mavedb.lib.validation.urn_re import SCORE_SET_URN_RE, EXPERIMENT_SET_URN_RE, TMP_URN_RE

class WithScoreSetOrTmpUrn(WithUrn):
     urn_patterns = (SCORE_SET_URN_RE, TMP_URN_RE)

class WithExperimentSetOrTmpUrn(WithUrn):
     urn_patterns = (EXPERIMENT_SET_URN_RE, TMP_URN_RE)

And finally inherit from these specific patterns in each class that has a URN

from mavedb.view_models.base.base import Base
from src.mavedb.view_models.components.mixins import WithScoreSetOrTmpUrn
class ScoreSetSaved(WithScoreSetOrTmpUrn, Base):
     title: str
     ... other properties ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    app: backendTask implementation touches the backendtype: maintenanceMaintaining this project

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions