@@ -561,91 +561,150 @@ def _emit_constraint_decl(constraint, var_name: str) -> None:
561561
562562 # Generate object model code if provided
563563 if objectmodel :
564- f .write ("\n ################\n " )
565- f .write ("# OBJECT MODEL #\n " )
566- f .write ("################\n " )
567-
568- # Write object instances using fluent API
569- for obj in sorted (objectmodel .objects , key = lambda x : x .name_ ):
570- obj_var_name = f"{ obj .name_ .lower ()} _obj"
571- classifier_var_name = safe_class_name (obj .classifier .name )
572-
573- # Start the fluent API call using the proper syntax: Class("name")
574- f .write (f"{ obj_var_name } = { classifier_var_name } (\" { _escape_python_string (obj .name_ )} \" )" )
575-
576- # Add attributes if the object has slots
577- if obj .slots :
578- attributes_dict = {}
579- for slot in obj .slots :
580- attr_name = slot .attribute .name
581-
582- # Format the value based on type
583- if isinstance (slot .value .value , str ):
584- value_str = f'"{ _escape_python_string (slot .value .value )} "'
585- elif hasattr (slot .value .value , 'isoformat' ): # datetime objects
586- value_str = f'datetime.datetime.fromisoformat("{ slot .value .value .isoformat ()} ")'
587- elif hasattr (slot .value .value , 'owner' ) and hasattr (slot .value .value .owner , 'name' ):
588- # This is an enumeration literal - generate the proper reference
589- enum_name = slot .value .value .owner .name
590- literal_name = slot .value .value .name
591- value_str = f"{ enum_name } .{ literal_name } "
592- else :
593- value_str = str (slot .value .value )
564+ _write_object_model_section (f , objectmodel , object_model_var_name )
594565
595- attributes_dict [ attr_name ] = value_str
566+ print ( f"BUML model saved to { file_path } " )
596567
597- # Add attributes to the fluent API call
598- if attributes_dict :
599- attr_pairs = [f"{ k } ={ v } " for k , v in attributes_dict .items ()]
600- f .write (f".attributes({ ', ' .join (attr_pairs )} )" )
601568
602- # Complete the fluent API call
603- f . write ( ".build() \n " )
569+ def _write_object_model_section ( f , objectmodel : ObjectModel , object_model_var_name : str = "object_model" ):
570+ """Write the object-model portion of the builder output to an open file handle.
604571
605- f .write ("\n " )
572+ Emits the ``# OBJECT MODEL #`` banner, the object instances (via the fluent
573+ API), the links between objects, and the final ``ObjectModel(...)`` binding.
574+ This section assumes the referenced domain classes and enumerations are
575+ already defined earlier in the same (concatenated) file.
606576
607- # Add links after objects are created (avoiding forward reference issues)
608- if hasattr (objectmodel , 'links' ) and objectmodel .links :
609-
610- # Group links by (source_obj_var, end_name)
611- grouped_links = {}
612- for link in objectmodel .links :
613- if len (link .connections ) == 2 :
614- end1 , end2 = link .connections
615- obj1_var = f"{ end1 .object .name_ .lower ()} _obj"
616- obj2_var = f"{ end2 .object .name_ .lower ()} _obj"
617- end2_name = end2 .association_end .name
618-
619- key = (obj1_var , end2_name )
620- grouped_links .setdefault (key , set ()).add (obj2_var )
621-
622- # Write assignments for each group
623- for (obj_var , end_name ), targets in grouped_links .items ():
624- if len (targets ) == 1 :
625- [single_target ] = targets
626- f .write (f"{ obj_var } .{ end_name } = { single_target } \n " )
627- else :
628- target_str = ", " .join (sorted (targets )) # sorted for consistency
629- f .write (f"{ obj_var } .{ end_name } = {{{ target_str } }}\n " )
577+ Parameters:
578+ f: An already-open, writable file handle.
579+ objectmodel (ObjectModel): The B-UML object model to serialize.
580+ object_model_var_name (str): Name of the ObjectModel variable to bind.
581+ """
582+ object_model_var_name = object_model_var_name or "object_model"
630583
631- f .write ("\n " )
584+ f .write ("\n ################\n " )
585+ f .write ("# OBJECT MODEL #\n " )
586+ f .write ("################\n " )
587+
588+ # Write object instances using fluent API
589+ for obj in sorted (objectmodel .objects , key = lambda x : x .name_ ):
590+ obj_var_name = f"{ obj .name_ .lower ()} _obj"
591+ classifier_var_name = safe_class_name (obj .classifier .name )
592+
593+ # Start the fluent API call using the proper syntax: Class("name")
594+ f .write (f"{ obj_var_name } = { classifier_var_name } (\" { _escape_python_string (obj .name_ )} \" )" )
595+
596+ # Add attributes if the object has slots
597+ if obj .slots :
598+ attributes_dict = {}
599+ for slot in obj .slots :
600+ attr_name = slot .attribute .name
601+
602+ # Format the value based on type
603+ if isinstance (slot .value .value , str ):
604+ value_str = f'"{ _escape_python_string (slot .value .value )} "'
605+ elif hasattr (slot .value .value , 'isoformat' ): # datetime objects
606+ value_str = f'datetime.datetime.fromisoformat("{ slot .value .value .isoformat ()} ")'
607+ elif hasattr (slot .value .value , 'owner' ) and hasattr (slot .value .value .owner , 'name' ):
608+ # This is an enumeration literal - generate the proper reference
609+ enum_name = slot .value .value .owner .name
610+ literal_name = slot .value .value .name
611+ value_str = f"{ enum_name } .{ literal_name } "
612+ else :
613+ value_str = str (slot .value .value )
632614
633- # Create the object model instance
634- f .write ("# Object Model instance\n " )
635- objects_str = ", " .join ([f"{ obj .name_ .lower ()} _obj" for obj in sorted (objectmodel .objects , key = lambda x : x .name_ )])
636- f .write (f"{ object_model_var_name } : ObjectModel = ObjectModel(\n " )
637- f .write (f" name=\" { _escape_python_string (objectmodel .name )} \" ,\n " )
638- f .write (f" objects={{{ objects_str } }}" )
615+ attributes_dict [attr_name ] = value_str
639616
640- # Add metadata if it exists
641- if hasattr (objectmodel , 'metadata' ) and objectmodel .metadata :
642- if objectmodel .metadata .description :
643- f .write (",\n " )
644- f .write (f' metadata=Metadata(description="{ _escape_python_string (objectmodel .metadata .description )} ")\n ' )
645- else :
646- f .write ("\n " )
617+ # Add attributes to the fluent API call
618+ if attributes_dict :
619+ attr_pairs = [f"{ k } ={ v } " for k , v in attributes_dict .items ()]
620+ f .write (f".attributes({ ', ' .join (attr_pairs )} )" )
621+
622+ # Complete the fluent API call
623+ f .write (".build()\n " )
624+
625+ f .write ("\n " )
626+
627+ # Add links after objects are created (avoiding forward reference issues)
628+ if hasattr (objectmodel , 'links' ) and objectmodel .links :
629+
630+ # Group links by (source_obj_var, end_name)
631+ grouped_links = {}
632+ for link in objectmodel .links :
633+ if len (link .connections ) == 2 :
634+ end1 , end2 = link .connections
635+ obj1_var = f"{ end1 .object .name_ .lower ()} _obj"
636+ obj2_var = f"{ end2 .object .name_ .lower ()} _obj"
637+ end2_name = end2 .association_end .name
638+
639+ key = (obj1_var , end2_name )
640+ grouped_links .setdefault (key , set ()).add (obj2_var )
641+
642+ # Write assignments for each group
643+ for (obj_var , end_name ), targets in grouped_links .items ():
644+ if len (targets ) == 1 :
645+ [single_target ] = targets
646+ f .write (f"{ obj_var } .{ end_name } = { single_target } \n " )
647647 else :
648- f . write ( " \n " )
649- f .write (") \n " )
648+ target_str = ", " . join ( sorted ( targets )) # sorted for consistency
649+ f .write (f" { obj_var } . { end_name } = {{ { target_str } }} \n " )
650650
651- print (f"BUML model saved to { file_path } " )
651+ f .write ("\n " )
652+
653+ # Create the object model instance
654+ f .write ("# Object Model instance\n " )
655+ objects_str = ", " .join ([f"{ obj .name_ .lower ()} _obj" for obj in sorted (objectmodel .objects , key = lambda x : x .name_ )])
656+ f .write (f"{ object_model_var_name } : ObjectModel = ObjectModel(\n " )
657+ f .write (f" name=\" { _escape_python_string (objectmodel .name )} \" ,\n " )
658+ f .write (f" objects={{{ objects_str } }}" )
659+
660+ # Add metadata if it exists
661+ if hasattr (objectmodel , 'metadata' ) and objectmodel .metadata :
662+ if objectmodel .metadata .description :
663+ f .write (",\n " )
664+ f .write (f' metadata=Metadata(description="{ _escape_python_string (objectmodel .metadata .description )} ")\n ' )
665+ else :
666+ f .write ("\n " )
667+ else :
668+ f .write ("\n " )
669+ f .write (")\n " )
670+
671+
672+ def object_model_to_code (
673+ objectmodel : ObjectModel ,
674+ file_path : str ,
675+ object_model_var_name : str = "object_model" ,
676+ ):
677+ """Generate Python code for a standalone B-UML object model.
678+
679+ The generated file is self-contained apart from the domain classes and
680+ enumerations it references: it only imports ``ObjectModel`` and ``datetime``
681+ and then emits the object-model section. It is meant to be concatenated
682+ *after* the corresponding domain model (whose classes/enums it references)
683+ so the combined file executes cleanly.
684+
685+ Parameters:
686+ objectmodel (ObjectModel): The B-UML object model to serialize.
687+ file_path (str): The path where the generated code will be saved.
688+ object_model_var_name (str): Name of the ObjectModel variable in the
689+ generated code. Defaults to "object_model".
690+
691+ Outputs:
692+ - A Python file containing the object-model instances and their links.
693+ """
694+ output_dir = os .path .dirname (file_path )
695+ if output_dir and not os .path .exists (output_dir ):
696+ os .makedirs (output_dir )
697+ if not file_path .endswith ('.py' ):
698+ file_path += '.py'
699+
700+ object_model_var_name = object_model_var_name or "object_model"
701+
702+ with open (file_path , 'w' , encoding = 'utf-8' ) as f :
703+ # Object-model imports. The domain classes/enums are assumed to be
704+ # defined earlier in the concatenated output.
705+ f .write ("from besser.BUML.metamodel.object import ObjectModel\n " )
706+ f .write ("import datetime\n " )
707+
708+ _write_object_model_section (f , objectmodel , object_model_var_name )
709+
710+ print (f"BUML object model saved to { file_path } " )
0 commit comments