mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-10 12:36:25 +03:00
refactor: discriminate appliance settings by template_type
This commit is contained in:
parent
9f9be983cd
commit
be52a0d0d4
@ -19,9 +19,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Annotated, List, Literal, Optional, Union
|
from typing import Annotated, List, Literal, Optional, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from pydantic import AnyUrl, BaseModel, Discriminator, EmailStr, Field, Tag
|
||||||
from pydantic import AnyUrl, BaseModel, Discriminator, EmailStr, Field, Tag, model_validator
|
|
||||||
|
|
||||||
from ..common import ExtraConfig
|
from ..common import ExtraConfig
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -599,45 +597,38 @@ class QemuPropertiesV8(BaseModel):
|
|||||||
process_priority: Optional[QemuProcessPriority] = Field(None, title="Process priority for QEMU")
|
process_priority: Optional[QemuProcessPriority] = Field(None, title="Process priority for QEMU")
|
||||||
|
|
||||||
|
|
||||||
_V8_PROPERTIES_MODELS = {
|
class TemplateSettingBase(BaseModel):
|
||||||
TemplateType.qemu: QemuPropertiesV8,
|
|
||||||
TemplateType.dynamips: DynamipsPropertiesV8,
|
|
||||||
TemplateType.iou: IouPropertiesV8,
|
|
||||||
TemplateType.docker: DockerPropertiesV8,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateSetting(BaseModel):
|
|
||||||
"""Emulator settings configuration (v8)"""
|
"""Emulator settings configuration (v8)"""
|
||||||
|
|
||||||
name: Optional[str] = Field(None, title="Name of the settings set")
|
name: Optional[str] = Field(None, title="Name of the settings set")
|
||||||
default: Optional[bool] = Field(None, title="Whether these are the default settings")
|
default: Optional[bool] = Field(None, title="Whether these are the default settings")
|
||||||
inherit_default_properties: Optional[bool] = Field(True, title="Whether the default properties should be used")
|
inherit_default_properties: Optional[bool] = Field(True, title="Whether the default properties should be used")
|
||||||
template_type: TemplateType = Field(..., title="Type of emulator properties")
|
|
||||||
template_properties: Union[QemuPropertiesV8, DynamipsPropertiesV8, IouPropertiesV8, DockerPropertiesV8] = Field(
|
|
||||||
..., title="Properties for the template"
|
|
||||||
)
|
|
||||||
|
|
||||||
@model_validator(mode="before")
|
|
||||||
@classmethod
|
|
||||||
def _validate_template_properties(cls, data):
|
|
||||||
"""
|
|
||||||
Validate template_properties against the model matching template_type.
|
|
||||||
The template_type discriminator lives at the settings level (not inside
|
|
||||||
template_properties), so the union cannot be discriminated by pydantic
|
|
||||||
alone and would misroute properties between the per-type models.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if isinstance(data, dict):
|
class QemuTemplateSetting(TemplateSettingBase):
|
||||||
# work on a copy: replacing template_properties with the validated
|
template_type: Literal[TemplateType.qemu] = Field(..., title="Type of emulator properties")
|
||||||
# model must not mutate the caller's data
|
template_properties: QemuPropertiesV8 = Field(..., title="Properties for the template")
|
||||||
data = data.copy()
|
|
||||||
template_type = data.get("template_type")
|
|
||||||
template_properties = data.get("template_properties")
|
class DynamipsTemplateSetting(TemplateSettingBase):
|
||||||
model = _V8_PROPERTIES_MODELS.get(template_type)
|
template_type: Literal[TemplateType.dynamips] = Field(..., title="Type of emulator properties")
|
||||||
if model is not None and isinstance(template_properties, dict):
|
template_properties: DynamipsPropertiesV8 = Field(..., title="Properties for the template")
|
||||||
data["template_properties"] = model.model_validate(template_properties)
|
|
||||||
return data
|
|
||||||
|
class IouTemplateSetting(TemplateSettingBase):
|
||||||
|
template_type: Literal[TemplateType.iou] = Field(..., title="Type of emulator properties")
|
||||||
|
template_properties: IouPropertiesV8 = Field(..., title="Properties for the template")
|
||||||
|
|
||||||
|
|
||||||
|
class DockerTemplateSetting(TemplateSettingBase):
|
||||||
|
template_type: Literal[TemplateType.docker] = Field(..., title="Type of emulator properties")
|
||||||
|
template_properties: DockerPropertiesV8 = Field(..., title="Properties for the template")
|
||||||
|
|
||||||
|
|
||||||
|
TemplateSetting = Annotated[
|
||||||
|
Union[QemuTemplateSetting, DynamipsTemplateSetting, IouTemplateSetting, DockerTemplateSetting],
|
||||||
|
Field(discriminator="template_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ApplianceVersionV8(BaseModel):
|
class ApplianceVersionV8(BaseModel):
|
||||||
|
|||||||
@ -166,6 +166,32 @@ def test_v8_template_properties_validated_against_template_type():
|
|||||||
ApplianceModel.model_validate(appliance)
|
ApplianceModel.model_validate(appliance)
|
||||||
|
|
||||||
|
|
||||||
|
def test_v8_unknown_template_type_is_discriminator_error():
|
||||||
|
appliance = dict(
|
||||||
|
XRD_V8,
|
||||||
|
settings=[{"name": "only", "default": True, "template_type": "vmware", "template_properties": {}}],
|
||||||
|
)
|
||||||
|
with pytest.raises(pydantic.ValidationError) as exc_info:
|
||||||
|
ApplianceModel.model_validate(appliance)
|
||||||
|
errors = exc_info.value.errors()
|
||||||
|
assert len(errors) == 1
|
||||||
|
assert errors[0]["type"] == "union_tag_invalid"
|
||||||
|
|
||||||
|
|
||||||
|
def test_v8_invalid_properties_report_only_matching_type():
|
||||||
|
appliance = dict(
|
||||||
|
XRD_V8,
|
||||||
|
settings=[
|
||||||
|
{"name": "only", "default": True, "template_type": "docker", "template_properties": {"adapters": 2}},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
with pytest.raises(pydantic.ValidationError) as exc_info:
|
||||||
|
ApplianceModel.model_validate(appliance)
|
||||||
|
locs = [error["loc"] for error in exc_info.value.errors()]
|
||||||
|
assert all("docker" in loc for loc in locs)
|
||||||
|
assert any(loc[-1] == "image" for loc in locs)
|
||||||
|
|
||||||
|
|
||||||
def test_v8_qemu_kvm_property_validates():
|
def test_v8_qemu_kvm_property_validates():
|
||||||
appliance = dict(
|
appliance = dict(
|
||||||
XRD_V8,
|
XRD_V8,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user