6565 "EnumerationVariable" ,
6666 "ClockVariable" ,
6767 "Variable" ,
68- "ModelVariables" ,
6968 "UnitDefinitions" ,
7069 "TypeDefinitions" ,
7170 "FmiModelDescription" ,
@@ -786,7 +785,7 @@ class Annotation(BaseModel):
786785
787786 model_config = ConfigDict (validate_by_name = True , validate_by_alias = True )
788787
789- tools : Annotated [list [Tool ], Field (... , alias = "Tool" )]
788+ tools : Annotated [list [Tool ] | None , Field (default = None , alias = "Tool" )] = None
790789
791790 def to_xml (self ) -> Element :
792791 """Convert Annotation to XML Element"""
@@ -802,11 +801,20 @@ class Dimension(BaseModel):
802801
803802 model_config = ConfigDict (validate_by_name = True , validate_by_alias = True )
804803
805- start : Annotated [int | None , Field (default = None , alias = "start" )]
804+ start : Annotated [int | None , Field (default = None , alias = "start" )] = None
806805 value_reference : Annotated [
807806 int | None , Field (default = None , alias = "valueReference" )
808807 ] = None
809808
809+ @model_validator (mode = "after" )
810+ def _validate_exclusive_start_or_value_reference (self ) -> "Dimension" :
811+ """Ensure exactly one of start or value_reference is provided."""
812+ if (self .start is None ) == (self .value_reference is None ):
813+ raise ValueError (
814+ "Dimension: exactly one of start or valueReference must be set"
815+ )
816+ return self
817+
810818 def to_xml (self ) -> Element :
811819 """Convert Dimension to XML Element"""
812820 element = Element ("Dimension" )
@@ -3113,7 +3121,7 @@ class Variable(BaseModel):
31133121 Field (default = None , alias = "Clock" , description = "Clock variable definition" ),
31143122 ] = None
31153123
3116- def get_variable_type (self ):
3124+ def get_variable_type (self ) -> str | None :
31173125 """Get the type of variable based on which field is set"""
31183126 if self .float32 is not None :
31193127 return "Float32"
@@ -3147,6 +3155,91 @@ def get_variable_type(self):
31473155 return "Clock"
31483156 return None
31493157
3158+ def _concrete (
3159+ self ,
3160+ ) -> (
3161+ Float32Variable
3162+ | Float64Variable
3163+ | Int8Variable
3164+ | Int16Variable
3165+ | Int32Variable
3166+ | Int64Variable
3167+ | UInt8Variable
3168+ | UInt16Variable
3169+ | UInt32Variable
3170+ | UInt64Variable
3171+ | BooleanVariable
3172+ | StringVariable
3173+ | BinaryVariable
3174+ | EnumerationVariable
3175+ | ClockVariable
3176+ ):
3177+ """Return the underlying concrete variable instance or raise if unset."""
3178+ for attr in (
3179+ "float32" ,
3180+ "float64" ,
3181+ "int8" ,
3182+ "int16" ,
3183+ "int32" ,
3184+ "int64" ,
3185+ "uint8" ,
3186+ "uint16" ,
3187+ "uint32" ,
3188+ "uint64" ,
3189+ "boolean" ,
3190+ "string" ,
3191+ "binary" ,
3192+ "enumeration" ,
3193+ "clock" ,
3194+ ):
3195+ value = getattr (self , attr )
3196+ if value is not None :
3197+ return value
3198+ raise ValueError ("No variable type is set" )
3199+
3200+ @property
3201+ def concrete (
3202+ self ,
3203+ ) -> (
3204+ Float32Variable
3205+ | Float64Variable
3206+ | Int8Variable
3207+ | Int16Variable
3208+ | Int32Variable
3209+ | Int64Variable
3210+ | UInt8Variable
3211+ | UInt16Variable
3212+ | UInt32Variable
3213+ | UInt64Variable
3214+ | BooleanVariable
3215+ | StringVariable
3216+ | BinaryVariable
3217+ | EnumerationVariable
3218+ | ClockVariable
3219+ ):
3220+ """Direct access to the concrete variable instance."""
3221+ return self ._concrete ()
3222+
3223+ @property
3224+ def name (self ) -> str :
3225+ return self .concrete .name
3226+
3227+ @property
3228+ def value_reference (self ) -> int :
3229+ return self .concrete .value_reference
3230+
3231+ @property
3232+ def causality (self ) -> CausalityEnum | None :
3233+ return getattr (self .concrete , "causality" , None )
3234+
3235+ @property
3236+ def variability (self ) -> VariabilityEnum | None :
3237+ return getattr (self .concrete , "variability" , None )
3238+
3239+ @property
3240+ def initial (self ) -> InitialEnum | None :
3241+ return getattr (self .concrete , "initial" , None )
3242+
31503243 def to_xml (self ) -> Element :
31513244 """Convert Variable to XML Element"""
31523245 # Add the appropriate variable type element
@@ -3184,22 +3277,6 @@ def to_xml(self) -> Element:
31843277 raise ValueError ("No variable type is set" )
31853278
31863279
3187- class ModelVariables (BaseModel ):
3188- """Model variables list for FMI 3.0"""
3189-
3190- model_config = ConfigDict (validate_by_name = True , validate_by_alias = True )
3191-
3192- variables : Annotated [list [Variable ], Field (..., alias = "Variable" )]
3193-
3194- def to_xml (self ) -> Element :
3195- """Convert ModelVariables to XML Element"""
3196- element = Element ("ModelVariables" )
3197- if self .variables is not None :
3198- for variable in self .variables :
3199- element .append (variable .to_xml ())
3200- return element
3201-
3202-
32033280class UnitDefinitions (BaseModel ):
32043281 """Unit definitions list for FMI 3.0"""
32053282
@@ -3391,7 +3468,7 @@ class FmiModelDescription(BaseModel):
33913468 ),
33923469 ] = None
33933470 model_variables : Annotated [
3394- ModelVariables ,
3471+ list [ Variable ] ,
33953472 Field (
33963473 ...,
33973474 alias = "ModelVariables" ,
@@ -3453,7 +3530,10 @@ def to_xml(self) -> Element:
34533530 if self .vendor_annotations is not None :
34543531 element .append (self .vendor_annotations .to_xml ())
34553532 if self .model_variables is not None :
3456- element .append (self .model_variables .to_xml ())
3533+ model_vars_elem = Element ("ModelVariables" )
3534+ for variable in self .model_variables :
3535+ model_vars_elem .append (variable .to_xml ())
3536+ element .append (model_vars_elem )
34573537 if self .model_structure is not None :
34583538 element .append (self .model_structure .to_xml ())
34593539
@@ -5182,8 +5262,8 @@ def _parse_dimensions(elem: Element) -> list[Dimension] | None:
51825262 return dims or None
51835263
51845264
5185- def _parse_model_variables (elem : Element ) -> ModelVariables :
5186- """Parse ModelVariables element"""
5265+ def _parse_model_variables (elem : Element ) -> list [ Variable ] :
5266+ """Parse ModelVariables element into a flat list """
51875267 variables = []
51885268 # Find all variable types in the model variables section
51895269 for var_type in [
@@ -5206,7 +5286,7 @@ def _parse_model_variables(elem: Element) -> ModelVariables:
52065286 for variable_elem in elem .findall (var_type ):
52075287 variable = _parse_variable (variable_elem , var_type )
52085288 variables .append (variable )
5209- return ModelVariables ( variables = variables )
5289+ return variables
52105290
52115291
52125292def _parse_variable (elem : Element , var_type : str ) -> Variable :
0 commit comments