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

Source Code for Package data_store

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