vibe/vibe/core/config/schema.py
Clément Sirieix 04305bd77c
v2.8.0 (#616)
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-04-21 15:19:59 +02:00

85 lines
2.6 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from pydantic.fields import FieldInfo
from vibe.core.utils.merge import MergeStrategy
class DuplicateMergeMetadataError(TypeError):
"""Raised when a field declares more than one MergeFieldMetadata marker."""
@dataclass(frozen=True)
class MergeFieldMetadata:
"""Base Pydantic Annotated marker that declares how a config field merges across layers.
Usage::
active_model: Annotated[str, WithReplaceMerge()] = "devstral-2"
models: Annotated[list[M], WithUnionMerge(merge_key="alias")]
Use the concrete subclasses (``WithReplaceMerge``, ``WithConcatMerge``, etc.)
rather than instantiating this base class directly.
"""
merge_strategy: MergeStrategy
merge_key: str | None = None
@classmethod
def from_field(cls, field_info: FieldInfo) -> MergeFieldMetadata | None:
"""Extract MergeFieldMetadata from a FieldInfo's metadata, if present.
Raises DuplicateMergeMetadataError if more than one marker is found.
"""
result: MergeFieldMetadata | None = None
for item in field_info.metadata:
if isinstance(item, cls):
if result is not None:
raise DuplicateMergeMetadataError(
f"Field has multiple MergeFieldMetadata markers: "
f"{result} and {item}"
)
result = item
return result
@dataclass(frozen=True)
class WithReplaceMerge(MergeFieldMetadata):
"""Higher layer wins outright."""
merge_strategy: MergeStrategy = field(default=MergeStrategy.REPLACE, init=False)
@dataclass(frozen=True)
class WithConcatMerge(MergeFieldMetadata):
"""Lists appended in layer order."""
merge_strategy: MergeStrategy = field(default=MergeStrategy.CONCAT, init=False)
@dataclass(frozen=True)
class WithUnionMerge(MergeFieldMetadata):
"""Lists merged by key, higher layer wins per-key."""
merge_strategy: MergeStrategy = field(default=MergeStrategy.UNION, init=False)
merge_key: str = ""
def __post_init__(self) -> None:
if not self.merge_key:
raise TypeError("WithUnionMerge requires merge_key")
@dataclass(frozen=True)
class WithShallowMerge(MergeFieldMetadata):
"""Dicts shallow-merged, absent keys preserved."""
merge_strategy: MergeStrategy = field(default=MergeStrategy.MERGE, init=False)
@dataclass(frozen=True)
class WithConflictMerge(MergeFieldMetadata):
"""Raises error if more than one layer provides a value."""
merge_strategy: MergeStrategy = field(default=MergeStrategy.CONFLICT, init=False)