Package data_store
[hide private]
[frames] | no frames]

Source Code for Package data_store

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2001-2004,2006-2015 Edward d'Auvergne                         # 
  4  # Copyright (C) 2006 Chris MacRaild                                           # 
  5  #                                                                             # 
  6  # This file is part of the program relax (http://www.nmr-relax.com).          # 
  7  #                                                                             # 
  8  # This program is free software: you can redistribute it and/or modify        # 
  9  # it under the terms of the GNU General Public License as published by        # 
 10  # the Free Software Foundation, either version 3 of the License, or           # 
 11  # (at your option) any later version.                                         # 
 12  #                                                                             # 
 13  # This program is distributed in the hope that it will be useful,             # 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
 16  # GNU General Public License for more details.                                # 
 17  #                                                                             # 
 18  # You should have received a copy of the GNU General Public License           # 
 19  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
 20  #                                                                             # 
 21  ############################################################################### 
 22   
 23  # Module docstring. 
 24  """Package containing the relax data storage object.""" 
 25   
 26   
 27  # Python module imports. 
 28  from re import search 
 29  from sys import stderr 
 30  from time import asctime 
 31  import xml.dom.minidom 
 32   
 33  # relax module imports. 
 34  from data_store.gui import Gui 
 35  from data_store.pipe_container import PipeContainer 
 36  from data_store.seq_align import Sequence_alignments 
 37  import pipe_control 
 38  from lib.compat import builtins 
 39  from lib.errors import RelaxError, RelaxPipeError, RelaxNoPipeError 
 40  from lib.xml import fill_object_contents, xml_to_object 
 41  from status import Status; status = Status() 
 42  import version 
 43   
 44   
 45  __all__ = [ 'align_tensor', 
 46              'data_classes', 
 47              'diff_tensor', 
 48              'exp_info', 
 49              'gui', 
 50              'interatomic', 
 51              'mol_res_spin', 
 52              'pipe_container', 
 53              'prototype', 
 54              'seq_align' 
 55  ] 
 56   
 57   
58 -class Relax_data_store(dict):
59 """The relax data storage object.""" 60 61 # The current data pipe. 62 current_pipe = None 63 builtins.cdp = None 64 65 # Class variable for storing the class instance. 66 instance = None 67
68 - def __new__(self, *args, **kargs):
69 """Replacement function for implementing the singleton design pattern.""" 70 71 # First initialisation. 72 if self.instance is None: 73 # Create a new instance. 74 self.instance = dict.__new__(self, *args, **kargs) 75 76 # Add some initial structures. 77 self.instance.pipe_bundles = {} 78 self.instance.relax_gui = Gui() 79 80 # Already initialised, so return the instance. 81 return self.instance
82 83
84 - def __repr__(self):
85 """The string representation of the object. 86 87 Rather than using the standard Python conventions (either the string representation of the 88 value or the "<...desc...>" notation), a rich-formatted description of the object is given. 89 """ 90 91 # Intro text. 92 text = "The relax data storage object.\n" 93 94 # The data pipes. 95 text = text + "\n" 96 text = text + "Data pipes:\n" 97 pipes = sorted(self.instance.keys()) 98 if pipes: 99 for pipe in pipes: 100 text = text + " %s\n" % repr(pipe) 101 else: 102 text = text + " None\n" 103 104 # Data store objects. 105 text = text + "\n" 106 text = text + "Data store objects:\n" 107 names = sorted(self.__class__.__dict__.keys()) 108 for name in names: 109 # The object. 110 obj = getattr(self, name) 111 112 # The text. 113 if obj == None or isinstance(obj, str): 114 text = text + " %s %s: %s\n" % (name, type(obj), obj) 115 else: 116 text = text + " %s %s: %s\n" % (name, type(obj), obj.__doc__.split('\n')[0]) 117 118 # dict methods. 119 text = text + "\n" 120 text = text + "Inherited dictionary methods:\n" 121 for name in dir(dict): 122 # Skip special methods. 123 if search("^_", name): 124 continue 125 126 # Skip overwritten methods. 127 if name in self.__class__.__dict__: 128 continue 129 130 # The object. 131 obj = getattr(self, name) 132 133 # The text. 134 text = text + " %s %s: %s\n" % (name, type(obj), obj.__doc__.split('\n')[0]) 135 136 # All other objects. 137 text = text + "\n" 138 text = text + "All other objects:\n" 139 for name in dir(self): 140 # Skip special methods. 141 if search("^_", name): 142 continue 143 144 # Skip overwritten methods. 145 if name in self.__class__.__dict__: 146 continue 147 148 # Skip dictionary methods. 149 if name in dir(dict): 150 continue 151 152 # The object. 153 obj = getattr(self, name) 154 155 # The text. 156 text = text + " %s %s: %s\n" % (name, type(obj), obj) 157 158 # Return the text. 159 return text
160 161
162 - def __reset__(self):
163 """Delete all the data from the relax data storage object. 164 165 This method is to make the current single instance of the Data object identical to a newly 166 created instance of Data, hence resetting the relax program state. 167 """ 168 169 # Loop over the keys of self.__dict__ and delete the corresponding object. 170 keys = list(self.__dict__.keys()) 171 for key in keys: 172 # Delete the object. 173 del self.__dict__[key] 174 175 # Remove all items from the dictionary. 176 self.instance.clear() 177 178 # Reset the current data pipe. 179 builtins.cdp = None 180 181 # Recreate the pipe bundle object. 182 self.instance.pipe_bundles = {} 183 184 # Re-add the GUI object. 185 self.instance.relax_gui = Gui() 186 187 # Signal the change. 188 status.observers.reset.notify() 189 status.observers.pipe_alteration.notify()
190 191
192 - def _back_compat_hook(self, file_version=None, pipes=None):
193 """Method for converting the old data structures to the new ones. 194 195 @keyword file_version: The relax XML version of the XML file. 196 @type file_version: int 197 @keyword pipes: The list of new pipe names to update. 198 @type pipes: list of str 199 """ 200 201 # Loop over the new data pipes. 202 for pipe_name in pipes: 203 # The data pipe object. 204 dp = self[pipe_name] 205 206 # Convert the molecule-residue-spin data. 207 for mol in dp.mol: 208 # Loop over the residues. 209 for res in mol.res: 210 # Loop over the spins. 211 for spin in res.spin: 212 # The list of objects to remove at the end. 213 eliminate = [] 214 215 # The current spin ID. 216 spin_id = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin=spin) 217 218 # Rename the old peak intensity data structures. 219 if hasattr(spin, 'intensities'): 220 spin.peak_intensity = spin.intensities 221 eliminate.append('intensities') 222 if hasattr(spin, 'intensity_err'): 223 spin.peak_intensity_err = spin.intensity_err 224 eliminate.append('intensity_err') 225 if hasattr(spin, 'intensity_sim'): 226 spin.peak_intensity_sim = spin.intensity_sim 227 eliminate.append('intensity_sim') 228 if hasattr(spin, 'sim_intensity'): 229 spin.peak_intensity_sim = spin.sim_intensity 230 eliminate.append('sim_intensity') 231 if hasattr(spin, 'intensity_bc'): 232 spin.peak_intensity_bc = spin.intensity_bc 233 eliminate.append('intensity_bc') 234 235 # Convert proton spins (the 'heteronuc_type' variable indicates a pre-interatomic container design state). 236 if hasattr(spin, 'heteronuc_type') and hasattr(spin, 'element') and spin.element == 'H': 237 # Rename the nuclear isotope. 238 spin.isotope = spin.proton_type 239 240 # Append the old structures to be eliminated. 241 eliminate.append('proton_type') 242 243 # Convert heteronuclear spins (the 'heteronuc_type' variable indicates a pre-interatomic container design state). 244 elif hasattr(spin, 'heteronuc_type'): 245 # Rename the nuclear isotope. 246 spin.isotope = spin.heteronuc_type 247 248 # Name the spin if needed. 249 if spin.name == None: 250 if search('N', spin.isotope): 251 pipe_control.mol_res_spin.name_spin(spin_id=spin_id, name='N', pipe=pipe_name) 252 elif search('C', spin.isotope): 253 pipe_control.mol_res_spin.name_spin(spin_id=spin_id, name='C', pipe=pipe_name) 254 255 # An attached proton - convert into a spin container. 256 if (hasattr(spin, 'attached_proton') and spin.attached_proton != None) or (hasattr(spin, 'proton_type') and spin.proton_type != None): 257 # The proton name. 258 if hasattr(spin, 'attached_proton') and spin.attached_proton != None: 259 proton_name = spin.attached_proton 260 else: 261 proton_name = 'H' 262 263 # The two spin IDs (newly regenerated due to the above renaming). 264 spin_id1 = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin=spin) 265 spin_id2 = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin_name=proton_name) 266 267 # Fetch the proton spin if it exists. 268 h_spin = pipe_control.mol_res_spin.return_spin(spin_id=spin_id2, pipe=pipe_name) 269 if h_spin: 270 spin_id2 = pipe_control.mol_res_spin.generate_spin_id_unique(pipe_cont=dp, mol=mol, res=res, spin_name=proton_name, spin_num=h_spin.num) 271 272 # Create a new spin container for the proton if needed. 273 if not h_spin: 274 h_spin = pipe_control.mol_res_spin.create_spin(mol_name=mol.name, res_num=res.num, res_name=res.name, spin_name=proton_name, pipe=pipe_name)[0] 275 h_spin.select = False 276 277 # Set up a dipole interaction between the two spins if needed. 278 if not hasattr(h_spin, 'element'): 279 pipe_control.mol_res_spin.set_spin_element(spin_id=spin_id2, element='H', pipe=pipe_name) 280 if not hasattr(h_spin, 'isotope'): 281 pipe_control.mol_res_spin.set_spin_isotope(spin_id=spin_id2, isotope='1H', pipe=pipe_name) 282 pipe_control.interatomic.define_dipole_pair(spin_id1=spin_id1, spin_id2=spin_id2, spin1=spin, spin2=h_spin, verbose=False, pipe=pipe_name) 283 284 # Get the interatomic data container. 285 interatom = pipe_control.interatomic.return_interatom(spin_hash1=spin._hash, spin_hash2=h_spin._hash, pipe=pipe_name) 286 287 # Set the interatomic distance. 288 if hasattr(spin, 'r'): 289 interatom.r = spin.r 290 291 # Set the interatomic unit vectors. 292 if hasattr(spin, 'xh_vect'): 293 interatom.vector = spin.xh_vect 294 295 # Set the RDC values. 296 if hasattr(spin, 'rdc'): 297 interatom.rdc = spin.rdc 298 if hasattr(spin, 'rdc_err'): 299 interatom.rdc_err = spin.rdc_err 300 if hasattr(spin, 'rdc_sim'): 301 interatom.rdc_sim = spin.rdc_sim 302 if hasattr(spin, 'rdc_bc'): 303 interatom.rdc_bc = spin.rdc_bc 304 305 # Append the old structures to be eliminated. 306 eliminate += ['heteronuc_type', 'proton_type', 'attached_proton', 'r', 'r_err', 'r_sim', 'rdc', 'rdc_err', 'rdc_sim', 'rdc_bc', 'xh_vect'] 307 308 # Delete the old structures. 309 for name in eliminate: 310 if hasattr(spin, name): 311 delattr(spin, name) 312 313 # Conversions for the interatomic data containers. 314 if hasattr(dp, 'interatomic'): 315 for interatom in dp.interatomic: 316 # RDC data. 317 if hasattr(interatom, 'rdc') and not hasattr(interatom, 'rdc_data_types'): 318 # Initialise. 319 interatom.rdc_data_types = {} 320 321 # Add the data type, assumed to be 'D', for each alignment ID. 322 for id in dp.rdc_ids: 323 interatom.rdc_data_types[id] = 'D' 324 325 # Convert the alignment tensors. 326 if hasattr(dp, 'align_tensors'): 327 for i in range(len(dp.align_tensors)): 328 # Fix for the addition of the alignment ID structure as opposed to the tensor name or tag. 329 if not hasattr(dp.align_tensors[i], 'align_id'): 330 dp.align_tensors[i].set('align_id', dp.align_tensors[i].name) 331 332 # Convert spectrometer frequency information. 333 if hasattr(dp, 'frq'): 334 # Convert to the new structure. 335 dp.spectrometer_frq = dp.frq 336 del dp.frq 337 338 # Build the new frequency list structure. 339 dp.spectrometer_frq_list = [] 340 for frq in list(dp.spectrometer_frq.values()): 341 if frq not in dp.spectrometer_frq_list: 342 dp.spectrometer_frq_list.append(frq) 343 344 # And finally count the elements and sort the list. 345 dp.spectrometer_frq_count = len(dp.spectrometer_frq_list) 346 dp.spectrometer_frq_list.sort() 347 348 # Convert the Sobol' integration information. 349 if hasattr(dp, 'num_int_pts'): 350 # Convert to the new structure. 351 dp.sobol_max_points = dp.num_int_pts 352 del dp.num_int_pts 353 354 # Add the oversampling variable. 355 cdp.sobol_oversample = 1 356 357 # PCS Q factor conversions. 358 if hasattr(dp, 'q_factors_pcs'): 359 dp.q_factors_pcs_norm_squared_sum = dp.q_factors_pcs 360 del dp.q_factors_pcs 361 if hasattr(dp, 'q_pcs'): 362 dp.q_pcs_norm_squared_sum = dp.q_pcs 363 del dp.q_pcs 364 365 # RDC Q factor conversions. 366 if hasattr(dp, 'q_factors_rdc'): 367 dp.q_factors_rdc_norm_tensor_size = dp.q_factors_rdc 368 del dp.q_factors_rdc 369 if hasattr(dp, 'q_rdc'): 370 dp.q_rdc_norm_tensor_size = dp.q_rdc 371 del dp.q_rdc 372 if hasattr(dp, 'q_factors_rdc_norm2'): 373 dp.q_factors_rdc_norm_squared_sum = dp.q_factors_rdc_norm2 374 del dp.q_factors_rdc_norm2 375 if hasattr(dp, 'q_rdc_norm2'): 376 dp.q_rdc_norm_squared_sum = dp.q_rdc_norm2 377 del dp.q_rdc_norm2
378 379
380 - def add(self, pipe_name, pipe_type, bundle=None, switch=True):
381 """Method for adding a new data pipe container to the dictionary. 382 383 This method should be used rather than importing the PipeContainer class and using the statement 'D[pipe] = PipeContainer()', where D is the relax data storage object and pipe is the name of the data pipe. 384 385 @param pipe_name: The name of the new data pipe. 386 @type pipe_name: str 387 @param pipe_type: The data pipe type. 388 @type pipe_type: str 389 @keyword bundle: The optional data pipe bundle to associate the data pipe with. 390 @type bundle: str or None 391 @keyword switch: A flag which if True will cause the new data pipe to be set to the current data pipe. 392 @type switch: bool 393 """ 394 395 # Test if the pipe already exists. 396 if pipe_name in self.instance: 397 raise RelaxPipeError(pipe_name) 398 399 # Create a new container. 400 self[pipe_name] = PipeContainer() 401 402 # Add the data pipe type string to the container. 403 self[pipe_name].pipe_type = pipe_type 404 405 # The pipe bundle. 406 if bundle: 407 # A new bundle. 408 if bundle not in self.pipe_bundles: 409 self.pipe_bundles[bundle] = [] 410 411 # Add the pipe to the bundle. 412 self.pipe_bundles[bundle].append(pipe_name) 413 414 # Change the current data pipe. 415 if switch: 416 # Set the current data pipe. 417 self.instance.current_pipe = pipe_name 418 builtins.cdp = self[pipe_name] 419 420 # Signal the switch. 421 status.observers.pipe_alteration.notify()
422 423
424 - def is_empty(self, verbosity=False):
425 """Method for testing if the relax data store is empty. 426 427 @keyword verbosity: A flag which if True will cause messages to be printed to STDERR. 428 @type verbosity: bool 429 @return: True if the data store is empty, False otherwise. 430 @rtype: bool 431 """ 432 433 # No pipes should exist. 434 if len(self): 435 if verbosity: 436 stderr.write("The relax data store contains the data pipes %s.\n" % sorted(self.keys())) 437 return False 438 439 # Objects which should be in here. 440 blacklist = [ 441 'pipe_bundles', 442 'relax_gui' 443 ] 444 445 # An object has been added to the data store. 446 for name in dir(self): 447 # Skip the data store methods. 448 if name in self.__class__.__dict__: 449 continue 450 451 # Skip the dict methods. 452 if name in dict.__dict__: 453 continue 454 455 # Skip special objects. 456 if search("^__", name): 457 continue 458 459 # Blacklisted objects to skip. 460 if name in blacklist: 461 continue 462 463 # An object has been added. 464 if verbosity: 465 stderr.write("The relax data store contains the object %s.\n" % name) 466 return False 467 468 # The data store is empty. 469 return True
470 471
472 - def from_xml(self, file, dir=None, pipe_to=None, verbosity=1):
473 """Parse a XML document representation of a data pipe, and load it into the relax data store. 474 475 @param file: The open file object. 476 @type file: file 477 @keyword dir: The name of the directory containing the results file (needed for loading external files). 478 @type dir: str 479 @keyword pipe_to: The data pipe to load the XML data pipe into (the file must only contain one data pipe). 480 @type pipe_to: str 481 @keyword verbosity: A flag specifying the amount of information to print. The higher the value, the greater the verbosity. 482 @type verbosity: int 483 @raises RelaxError: If pipe_to is given and the file contains multiple pipe elements; or if the data pipes in the XML file already exist in the relax data store; or if the data pipe type is invalid; or if the target data pipe is not empty. 484 @raises RelaxNoPipeError: If pipe_to is given but the data pipe does not exist. 485 @raises RelaxError: If the data pipes in the XML file already exist in the relax data store, or if the data pipe type is invalid. 486 @raises RelaxPipeError: If the data pipes of the XML file are already present in the relax data store. 487 """ 488 489 # Create the XML document from the file. 490 doc = xml.dom.minidom.parse(file) 491 492 # Get the relax node. 493 relax_node = doc.childNodes[0] 494 495 # Get the relax version of the XML file. 496 file_version = relax_node.getAttribute('file_version') 497 if file_version == '': 498 file_version = 1 499 else: 500 file_version = int(file_version) 501 502 # Get the pipe nodes. 503 pipe_nodes = relax_node.getElementsByTagName('pipe') 504 505 # Structure for the names of the new pipes. 506 pipes = [] 507 508 # Target loading to a specific pipe (for pipe results reading). 509 if pipe_to: 510 # Check if there are multiple pipes in the XML file. 511 if len(pipe_nodes) > 1: 512 raise RelaxError("The pipe_to target pipe argument '%s' cannot be given as the file contains multiple pipe elements." % pipe_to) 513 514 # The pipe type. 515 pipe_type = pipe_nodes[0].getAttribute('type') 516 517 # Check that the pipe already exists. 518 if not pipe_to in self: 519 raise RelaxNoPipeError(pipe_to) 520 521 # Check if the pipe type matches. 522 if pipe_type != self[pipe_to].pipe_type: 523 raise RelaxError("The XML file pipe type '%s' does not match the pipe type '%s'" % (pipe_type, self[pipe_to].pipe_type)) 524 525 # Check if the pipe is empty. 526 if not self[pipe_to].is_empty(): 527 raise RelaxError("The data pipe '%s' is not empty." % pipe_to) 528 529 # Load the data. 530 self[pipe_to].from_xml(pipe_nodes[0], dir=dir, file_version=file_version) 531 532 # Store the pipe name. 533 pipes.append(pipe_to) 534 535 # Load the state. 536 else: 537 # Get the GUI nodes. 538 gui_nodes = relax_node.getElementsByTagName('relax_gui') 539 if gui_nodes: 540 self.relax_gui.from_xml(gui_nodes[0], file_version=file_version) 541 542 # Get the sequence alignment nodes. 543 seq_align_nodes = relax_node.getElementsByTagName('sequence_alignments') 544 if seq_align_nodes: 545 # Initialise the object. 546 self.sequence_alignments = Sequence_alignments() 547 548 # Populate it. 549 self.sequence_alignments.from_xml(seq_align_nodes[0], file_version=file_version) 550 551 # Recreate all the data store data structures. 552 xml_to_object(relax_node, self, file_version=file_version, blacklist=['pipe', 'relax_gui', 'sequence_alignments']) 553 554 # Checks. 555 for pipe_node in pipe_nodes: 556 # The pipe name and type. 557 pipe_name = str(pipe_node.getAttribute('name')) 558 pipe_type = pipe_node.getAttribute('type') 559 560 # Existence check. 561 if pipe_name in self: 562 raise RelaxPipeError(pipe_name) 563 564 # Valid type check. 565 if not pipe_type in pipe_control.pipes.VALID_TYPES: 566 raise RelaxError("The data pipe type '%s' is invalid and must be one of the strings in the list %s." % (pipe_type, pipe_control.pipes.VALID_TYPES)) 567 568 # Load the data pipes. 569 for pipe_node in pipe_nodes: 570 # The pipe name and type. 571 pipe_name = str(pipe_node.getAttribute('name')) 572 pipe_type = pipe_node.getAttribute('type') 573 574 # Add the data pipe. 575 switch = False 576 if self.current_pipe == None: 577 switch = True 578 self.add(pipe_name, pipe_type, switch=switch) 579 580 # Fill the pipe. 581 self[pipe_name].from_xml(pipe_node, file_version=file_version, dir=dir) 582 583 # Store the pipe name. 584 pipes.append(pipe_name) 585 586 # Set the current pipe. 587 if self.current_pipe in self: 588 builtins.cdp = self[self.current_pipe] 589 590 # Finally update the molecule, residue, and spin metadata for each data pipe. 591 for pipe in pipes: 592 pipe_control.mol_res_spin.metadata_update(pipe=pipe) 593 594 # Backwards compatibility transformations. 595 self._back_compat_hook(file_version, pipes=pipes)
596 597
598 - def to_xml(self, file, pipes=None):
599 """Create a XML document representation of the current data pipe. 600 601 This method creates the top level XML document including all the information needed 602 about relax, calls the PipeContainer.xml_write() method to fill in the document contents, 603 and writes the XML into the file object. 604 605 @param file: The open file object. 606 @type file: file 607 @param pipes: The name of the pipe, or list of pipes to place in the XML file. 608 @type pipes: str or list of str 609 """ 610 611 # The pipes to include in the XML file. 612 all = False 613 if not pipes: 614 all = True 615 pipes = list(self.keys()) 616 elif isinstance(pipes, str): 617 pipes = [pipes] 618 619 # Sort the pipes. 620 pipes.sort() 621 622 # Create the XML document object. 623 xmldoc = xml.dom.minidom.Document() 624 625 # Create the top level element, including the relax URL. 626 top_element = xmldoc.createElementNS('http://www.nmr-relax.com', 'relax') 627 top_element.setAttribute("xmlns", "http://www.nmr-relax.com") 628 629 # Append the element. 630 xmldoc.appendChild(top_element) 631 632 # Set the relax version number, and add a creation time. 633 top_element.setAttribute('version', version.version) 634 top_element.setAttribute('time', asctime()) 635 top_element.setAttribute('file_version', "2") 636 if version.repo_head: 637 top_element.setAttribute('head', version.repo_head) 638 if version.repo_url: 639 top_element.setAttribute('url', version.repo_url.replace('\n', '; ')) 640 641 # Add all objects in the data store base object to the XML element. 642 if all: 643 blacklist = list(self.__class__.__dict__.keys()) + list(dict.__dict__.keys()) 644 for name in dir(self): 645 # Skip blacklisted objects. 646 if name in blacklist: 647 continue 648 649 # Skip special objects. 650 if search('^_', name): 651 continue 652 653 # Execute any to_xml() methods, and add that object to the blacklist. 654 obj = getattr(self, name) 655 if hasattr(obj, 'to_xml'): 656 obj.to_xml(xmldoc, top_element) 657 blacklist = blacklist + [name] 658 659 # Remove the current data pipe from the blacklist! 660 blacklist.remove('current_pipe') 661 662 # Add all simple python objects within the store. 663 fill_object_contents(xmldoc, top_element, object=self, blacklist=blacklist) 664 665 # Loop over the pipes. 666 for pipe in pipes: 667 # Create the pipe XML element and add it to the top level XML element. 668 pipe_element = xmldoc.createElement('pipe') 669 top_element.appendChild(pipe_element) 670 671 # Set the data pipe attributes. 672 pipe_element.setAttribute('desc', 'The contents of a relax data pipe') 673 pipe_element.setAttribute('name', pipe) 674 pipe_element.setAttribute('type', self[pipe].pipe_type) 675 676 # Fill the data pipe XML element. 677 self[pipe].to_xml(xmldoc, pipe_element, pipe_type=self[pipe].pipe_type) 678 679 # Write out the XML file. 680 file.write(xmldoc.toprettyxml(indent=' '))
681