""" 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