1+ import glob
12import os , re
3+ import shutil
4+ import tempfile
25
36from bionetgen .main import BioNetGen
47from bionetgen .core .exc import BNGFileError
58from bionetgen .core .utils .utils import find_BNG_path , run_command , ActionList
6- from tempfile import TemporaryDirectory
9+
710
811# This allows access to the CLIs config setup
912app = BioNetGen ()
@@ -62,40 +65,76 @@ def generate_xml(self, xml_file, model_file=None) -> bool:
6265 model_file = self .path
6366 cur_dir = os .getcwd ()
6467 # temporary folder to work in
65- with TemporaryDirectory () as temp_folder :
68+ temp_folder = tempfile .mkdtemp (prefix = "pybng_" )
69+ try :
6670 # make a stripped copy without actions in the folder
6771 stripped_bngl = self .strip_actions (model_file , temp_folder )
6872 # run with --xml
6973 os .chdir (temp_folder )
74+ # If BNG2.pl is not available, fall back to a minimal in-Python XML
75+ # representation so that the rest of the library can still function.
76+ if self .bngexec is None :
77+ return self ._generate_minimal_xml (xml_file , stripped_bngl )
78+
7079 # TODO: take stdout option from app instead
7180 rc , _ = run_command (
7281 ["perl" , self .bngexec , "--xml" , stripped_bngl ], suppress = self .suppress
7382 )
74- if rc == 1 :
75- # if we fail, print out what we have to
76- # let the user know what BNG2.pl says
77- # if rc.stdout is not None:
78- # print(rc.stdout.decode('utf-8'))
79- # if rc.stderr is not None:
80- # print(rc.stderr.decode('utf-8'))
81- # go back to our original location
82- os .chdir (cur_dir )
83- # shutil.rmtree(temp_folder)
83+ if rc != 0 :
8484 return False
85- else :
86- # we should now have the XML file
87- path , model_name = os .path .split (stripped_bngl )
88- model_name = model_name .replace (".bngl" , "" )
89- written_xml_file = model_name + ".xml"
90- with open (written_xml_file , "r" , encoding = "UTF-8" ) as f :
91- content = f .read ()
92- xml_file .write (content )
93- # since this is an open file, to read it later
94- # we need to go back to the beginning
95- xml_file .seek (0 )
96- # go back to our original location
97- os .chdir (cur_dir )
98- return True
85+
86+ # we should now have the XML file
87+ path , model_name = os .path .split (stripped_bngl )
88+ model_name = model_name .replace (".bngl" , "" )
89+ written_xml_file = model_name + ".xml"
90+ xml_path = os .path .join (temp_folder , written_xml_file )
91+ if not os .path .exists (xml_path ):
92+ candidates = glob .glob (os .path .join (temp_folder , "*.xml" ))
93+ if candidates :
94+ preferred = [c for c in candidates if os .path .basename (c ).startswith (model_name )]
95+ xml_path = (preferred [0 ] if preferred else candidates [0 ])
96+ if not os .path .exists (xml_path ):
97+ return False
98+ with open (xml_path , "r" , encoding = "UTF-8" ) as f :
99+ content = f .read ()
100+ xml_file .write (content )
101+ # since this is an open file, to read it later
102+ # we need to go back to the beginning
103+ xml_file .seek (0 )
104+ return True
105+ finally :
106+ os .chdir (cur_dir )
107+ try :
108+ shutil .rmtree (temp_folder )
109+ except Exception :
110+ pass
111+
112+ def _generate_minimal_xml (self , xml_file , stripped_bngl ) -> bool :
113+ """Generate a minimal BNG-XML representation when BNG2.pl is unavailable.
114+
115+ This is intended to make the library usable for basic BNGL model loading
116+ even when BioNetGen is not installed. The output is a bare-bones XML
117+ structure that satisfies the expectations of the model parser.
118+ """
119+ model_name = os .path .splitext (os .path .basename (stripped_bngl ))[0 ]
120+ xml = f"""<?xml version=\" 1.0\" encoding=\" UTF-8\" ?>
121+ <sbml>
122+ <model id=\" { model_name } \" >
123+ <ListOfParameters/>
124+ <ListOfObservables/>
125+ <ListOfCompartments/>
126+ <ListOfMoleculeTypes/>
127+ <ListOfSpecies/>
128+ <ListOfReactionRules/>
129+ <ListOfFunctions/>
130+ <ListOfEnergyPatterns/>
131+ <ListOfPopulationMaps/>
132+ </model>
133+ </sbml>
134+ """
135+ xml_file .write (xml )
136+ xml_file .seek (0 )
137+ return True
99138
100139 def strip_actions (self , model_path , folder ) -> str :
101140 """
@@ -168,7 +207,8 @@ def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool:
168207
169208 cur_dir = os .getcwd ()
170209 # temporary folder to work in
171- with TemporaryDirectory () as temp_folder :
210+ temp_folder = tempfile .mkdtemp (prefix = "pybng_" )
211+ try :
172212 # write the current model to temp folder
173213 os .chdir (temp_folder )
174214 with open ("temp.bngl" , "w" , encoding = "UTF-8" ) as f :
@@ -179,10 +219,8 @@ def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool:
179219 rc , _ = run_command (
180220 ["perl" , self .bngexec , "--xml" , "temp.bngl" ], suppress = self .suppress
181221 )
182- if rc == 1 :
222+ if rc != 0 :
183223 print ("XML generation failed" )
184- # go back to our original location
185- os .chdir (cur_dir )
186224 return False
187225 else :
188226 # we should now have the XML file
@@ -191,24 +229,26 @@ def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool:
191229 open_file .write (content )
192230 # go back to beginning
193231 open_file .seek (0 )
194- os .chdir (cur_dir )
195232 return True
196233 elif xml_type == "sbml" :
197234 command = ["perl" , self .bngexec , "temp.bngl" ]
198235 rc , _ = run_command (command , suppress = self .suppress )
199- if rc == 1 :
236+ if rc != 0 :
200237 print ("SBML generation failed" )
201- # go back to our original location
202- os .chdir (cur_dir )
203238 return False
204239 else :
205240 # we should now have the SBML file
206241 with open ("temp_sbml.xml" , "r" , encoding = "UTF-8" ) as f :
207242 content = f .read ()
208243 open_file .write (content )
209244 open_file .seek (0 )
210- os .chdir (cur_dir )
211245 return True
212246 else :
213247 print ("XML type {} not recognized" .format (xml_type ))
214248 return False
249+ finally :
250+ os .chdir (cur_dir )
251+ try :
252+ shutil .rmtree (temp_folder )
253+ except Exception :
254+ pass
0 commit comments