ifc-commit/webapp/schema.py
2026-03-24 16:11:15 +01:00

196 lines
5.5 KiB
Python

"""
Pydantic models for ifccommit.yaml operation declarations and API payloads.
"""
from __future__ import annotations
from typing import Literal, Optional
from pydantic import BaseModel, model_validator
# ---------------------------------------------------------------------------
# ifccommit.yaml — operation declarations
# ---------------------------------------------------------------------------
class Operation(BaseModel):
name: str
command: Literal[
"list",
"info",
"extract",
"insert",
"replace",
"split",
"space",
"move",
"copy",
"diff",
"history",
"modify",
"merge",
"remove",
]
# Shared fields
input: Optional[str] = None
output: Optional[str] = None
# extract (bulk) / split
types: Optional[list[str]] = None
# extract (raw ifcopenshell selector query, used when types/id are insufficient)
query: Optional[str] = None
# extract (specific): type + id
type: Optional[str] = None # singular IFC type (e.g. IfcSpace)
id: Optional[str] = None # space name (e.g. A102)
# insert / replace / merge
base: Optional[str] = None
part: Optional[str] = None
# replace / space / merge
space: Optional[str] = None
# split
outdir: Optional[str] = None
# info
ifc_type: Optional[str] = None
# space
by: Literal["name", "longname"] = "name"
# move / copy / modify
element: Optional[str] = None # element name substring
entity_id: Optional[int] = None
entity_ids: Optional[list[int]] = None
tags: Optional[list[str]] = None
x: float = 0.0
y: float = 0.0
z: float = 0.0
# modify / merge
tag: Optional[str] = None
# diff
target: Optional[str] = None
verbose: bool = False
# history
yaml_src: Optional[str] = None # path to the yaml file (e.g. yaml/duplex.yaml)
write_psets: bool = False # write mode: stamp Pset_GitCommit on output elements
@model_validator(mode="after")
def check_required_fields(self) -> "Operation":
cmd = self.command
missing: list[str] = []
if cmd in (
"list",
"extract",
"split",
"space",
"move",
"copy",
"diff",
"modify",
):
if not self.input:
missing.append("input")
if cmd in (
"extract",
"insert",
"replace",
"space",
"move",
"copy",
"modify",
"merge",
):
if not self.output:
missing.append("output")
if cmd == "extract":
if not self.types and not self.query and not (self.type and self.id):
missing.append("types (bulk), query, or type+id (specific)")
if cmd in ("insert",):
if not self.base:
missing.append("base")
if not self.part:
missing.append("part")
if cmd in ("replace", "merge"):
if not self.base:
missing.append("base")
if not self.space:
missing.append("space")
if not self.part:
missing.append("part")
if cmd == "split":
if not self.outdir:
missing.append("outdir")
if cmd == "space":
if not self.space:
missing.append("space")
if cmd == "remove":
if not self.input:
missing.append("input")
if not self.type:
missing.append("type")
if cmd == "info":
if not self.input:
missing.append("input")
if not self.ifc_type:
missing.append("ifc_type")
if cmd == "history":
if self.input and not self.output:
missing.append("output (required when input is set)")
if cmd == "copy":
if not self.input:
missing.append("input")
if not self.output:
missing.append("output")
if not self.entity_ids and not self.tags:
missing.append("entity_ids or tags")
if missing:
raise ValueError(
f"Operation '{self.name}' (command={cmd}) is missing required fields: {missing}"
)
return self
class IfcCommitYaml(BaseModel):
src: Optional[str] = None
operations: list[Operation] = []
dest: Optional[str] = None
# ---------------------------------------------------------------------------
# API payloads
# ---------------------------------------------------------------------------
class RepoRequest(BaseModel):
repo: str # e.g. "orgname/reponame"
branch: str = "main"
token: str
yaml_file: str = "yaml/duplex.yaml" # repo-relative path to the pipeline yaml
overrides: dict[str, str] = {} # op name → ifc_type (for info ops)
id_overrides: dict[str, str] = {} # op name → id (for extract ops)
element_overrides: dict[str, str] = {} # op name → element (for modify ops)
class EntityRequest(BaseModel):
repo: str
branch: str = "main"
token: str
file: str # repo-relative path to the IFC file
ifc_type: str # IFC type to list (e.g. IfcSpace)
location: Optional[str] = None # if set, filter IfcElement by this space name
class HistoryRequest(BaseModel):
repo: str
branch: str = "main"
token: str
file: str = "ifc/history.json" # repo-relative path to the history JSON