Module status
[hide private]
[frames] | no frames]

Source Code for Module status

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2010-2013,2019 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  """Module containing the status singleton object.""" 
 24   
 25  # Python module imports. 
 26  from os import F_OK, access, environ, getcwd 
 27  try: 
 28      from os import get_terminal_size    # Only in Python >= 3.3. 
 29  except ImportError: 
30 - def get_terminal_size(i): return None
31 from os.path import sep 32 import platform 33 from re import search 34 import sys 35 from time import time 36 from threading import Lock, RLock 37 38 # relax module imports. 39 from lib.compat import Queue 40 from lib.errors import RelaxError 41 42
43 -class Status(object):
44 """The relax status singleton class.""" 45 46 # Class variable for storing the class instance (for the singleton). 47 _instance = None 48
49 - def __new__(self, *args, **kargs):
50 """Replacement method for implementing the singleton design pattern.""" 51 52 # First instantiation. 53 if self._instance is None: 54 # Instantiate. 55 self._instance = object.__new__(self, *args, **kargs) 56 57 # Initialise some variables. 58 self._instance.debug = False 59 self._instance.traceback = False 60 self._instance.prompt = False 61 self._instance.test_mode = False 62 self._instance.uf_intro = False 63 self._instance.show_gui = False 64 self._instance.gui_uf_force_sync = False 65 self._instance.install_path = self._instance._det_install_path() 66 self._instance.skip_blacklisted_tests = True 67 68 # Set up the singleton. 69 self._instance._setup() 70 71 # Store the time to allow for elapsed time calculations. 72 self._instance.start_time = time() 73 74 # Already instantiated, so return the instance. 75 return self._instance
76 77
78 - def _det_install_path(self):
79 """Determine, with a bit of magic, the relax installation path. 80 81 @return: The relax installation path. With a Mac OS X app, this will be the path to the 'Resources'. 82 @rtype: str 83 """ 84 85 # The file to search for. 86 file_to_find = 'dep_check.py' 87 88 # Loop over the system paths, searching for the real path. 89 for path in sys.path: 90 # Found the file, so return the path. 91 if access(path + sep + file_to_find, F_OK): 92 return path 93 94 # Mac OS X application support. 95 for path in sys.path: 96 # Find the Resources folder, where the relax data files are located. 97 if search('Resources', path): 98 # Nasty hack for creating the Resources path. 99 bits = path.split('Resources') 100 mac_path = bits[0] + 'Resources' 101 102 # Return the Mac Resources folder path. 103 return mac_path 104 105 # Maybe the current directory? 106 if access(getcwd() + sep + file_to_find, F_OK): 107 return getcwd() 108 109 # Return the first entry of sys.path as a fallback. 110 return sys.path[0]
111 112
113 - def _setup(self):
114 """Initialise all the status data structures.""" 115 116 # Execution lock object. 117 self.exec_lock = Exec_lock() 118 119 # The data pipe lock object. 120 self.pipe_lock = Relax_lock(name='pipe_lock') 121 122 # The molecule, residue, spin structure lock object. 123 self.spin_lock = Relax_lock(name='spin_lock') 124 125 # The exception queue for handling exceptions in threads. 126 self.exception_queue = Queue() 127 128 # The auto-analysis status containers. 129 self.auto_analysis = {} 130 self.current_analysis = None 131 132 # GUI structures. 133 self.controller_max_entries = 100000 # Scroll back limit in the relax controller. 134 135 # A structure for skipped system and unit tests. 136 self.skipped_tests = [] 137 """The skipped tests list. Each element should be a list of the test case name, the missing Python module, and the name of the test suite category (i.e. 'system' or 'unit').""" 138 139 # Set up the observer objects. 140 self._setup_observers() 141 142 # Text wrapping widths on different operating systems. 143 self._set_text_width()
144 145
146 - def _set_text_width(self):
147 """Define the text width for text formatting throughout relax. 148 149 The width will be based on that reported by the terminal, bracketed by an upper value of 100 characters. If the value cannot be determined, on MS Windows it will be set to 79 characters to allow for the MS Windows cmd.exe prompt. 150 """ 151 152 # Determine the value from the terminal, checking all IO streams (for Python >= 3.3). 153 size = None 154 self.text_width = None 155 for i in range(3): 156 try: 157 size = get_terminal_size(i) 158 except OSError: 159 continue 160 if size: 161 self.text_width = min(size[0], 100) 162 163 # Default fallback values. 164 if not self.text_width: 165 self.text_width = 100 166 if platform.uname()[0] in ['Windows', 'Microsoft']: 167 self.text_width = 79 168 169 # Reset the COLUMNS environmental variable. 170 environ['COLUMNS'] = str(self.text_width)
171 172
173 - def _setup_observers(self):
174 """Set up all the observer objects.""" 175 176 # A container for all the observers. 177 self.observers = Observer_container() 178 179 # The observer object for status changes in the auto-analyses. 180 self.observers.auto_analyses = Observer('auto_analyses') 181 182 # The observer object for pipe switches. 183 self.observers.pipe_alteration = Observer('pipe_alteration') 184 185 # The observer object for GUI user function completion. 186 self.observers.gui_uf = Observer('gui_uf') 187 188 # The observer object for changes to the GUI analysis tabs. 189 self.observers.gui_analysis = Observer('gui_analysis') 190 191 # The observer object for relax resets. 192 self.observers.reset = Observer('reset') 193 194 # The observer object for the execution lock. 195 self.observers.exec_lock = Observer('exec_lock') 196 197 # The observer object for the creation of results files. 198 self.observers.result_file = Observer('result_file') 199 200 # The observer object for state loading. 201 self.observers.state_load = Observer('state_load') 202 203 # The observer object for current working directory change. 204 self.observers.system_cwd_path = Observer('system_cwd_path')
205 206
207 - def init_auto_analysis(self, name, type):
208 """Initialise a status container for an auto-analysis. 209 210 @param name: The unique name of the auto-analysis. This will act as a key. 211 @type name: str. 212 @param type: The type of auto-analysis. 213 @type type: str 214 """ 215 216 # Add a status container. 217 self.auto_analysis[name] = Auto_analysis(name, type)
218 219
220 - def reset(self):
221 """Reset the status object to its initial state.""" 222 223 # Simply call the setup again. 224 self._setup()
225 226 227
228 -class Auto_analysis:
229 """The auto-analysis status container.""" 230
231 - def __init__(self, name, type):
232 """Initialise the auto-analysis status object. 233 234 @param name: The unique name of the auto-analysis. This will act as a key. 235 @type name: str. 236 @param type: The type of auto-analysis. 237 @type type: str 238 """ 239 240 # The status container. 241 self._status = Status() 242 243 # Store the analysis type. 244 self.__dict__['type'] = type 245 246 # The completion flag. 247 self.__dict__['fin'] = False 248 249 # The Monte Carlo simulation status, if used. 250 self.__dict__['mc_number'] = None
251 252
253 - def __setattr__(self, name, value):
254 """Replacement __setattr__() method. 255 256 @param name: The name of the attribute. 257 @type name: str 258 @param value: The value of the attribute. 259 @type value: anything 260 """ 261 262 # First set the attribute. 263 self.__dict__[name] = value 264 265 # Then notify the observers. 266 self._status.observers.auto_analyses.notify()
267 268 269
270 -class Exec_lock:
271 """A type of locking object for locking execution of relax.""" 272
273 - def __init__(self, fake_lock=False):
274 """Set up the lock-like object. 275 276 @keyword fake_lock: A flag which is True will allow this object to be debugged as the locking mechanism is turned off. 277 @type fake_lock: bool 278 """ 279 280 # Store the arg. 281 self._fake_lock = fake_lock 282 283 # Init a threading.Lock object. 284 self._lock = Lock() 285 286 # The status container. 287 self._status = Status() 288 289 # The name and mode of the locker. 290 self._name = [] 291 self._mode = [] 292 293 # Script nesting level. 294 self._nest = 0 295 296 # Auto-analysis from script launch. 297 self._auto_from_script = False 298 299 # Debugging. 300 if self._fake_lock: 301 self.log = open('lock.log', 'w')
302 303
304 - def acquire(self, name, mode='script'):
305 """Simulate the Lock.acquire() mechanism. 306 307 @param name: The name of the locking code. 308 @type name: str 309 @keyword mode: The mode of the code trying to obtain the lock. This can be one of 'script' for the scripting interface or 'auto-analysis' for the auto-analyses. 310 @type mode: str 311 """ 312 313 # Debugging. 314 if self._status.debug: 315 sys.stdout.write("debug> Execution lock: Acquisition by '%s' ('%s' mode).\n" % (name, mode)) 316 317 # Store the new name and mode. 318 self._name.append(name) 319 self._mode.append(mode) 320 321 # Nested locking. 322 if self.locked(): 323 # Increment the nesting counter. 324 self._nest += 1 325 326 # Debugging. 327 if self._fake_lock: 328 self.log.write("Nested by %s (to level %s)\n" % (name, self._nest)) 329 self.log.flush() 330 331 # Return without doing anything. 332 return 333 334 # Debugging. 335 if self._fake_lock: 336 self.log.write("Acquired by %s\n" % self._name[-1]) 337 self.log.flush() 338 return 339 340 # Acquire the real lock. 341 lock = self._lock.acquire() 342 343 # Notify observers. 344 status = Status() 345 status.observers.exec_lock.notify() 346 347 # Return the real lock. 348 return lock
349 350
351 - def locked(self):
352 """Simulate the Lock.locked() mechanism.""" 353 354 # Debugging (pseudo-locking based on _name). 355 if self._fake_lock: 356 if len(self._name): 357 return True 358 else: 359 return False 360 361 # Call the real method. 362 return self._lock.locked()
363 364
365 - def release(self):
366 """Simulate the Lock.release() mechanism.""" 367 368 # Debugging. 369 if self._status.debug: 370 sys.stdout.write("debug> Execution lock: Release by '%s' ('%s' mode).\n" % (self._name[-1], self._mode[-1])) 371 372 # Pop the name and mode. 373 self._name.pop(-1) 374 self._mode.pop(-1) 375 376 # Nested locking. 377 if self._nest: 378 # Debugging. 379 if self._fake_lock: 380 self.log.write("Nested locking decrement (%s -> %s)\n" % (self._nest, self._nest-1)) 381 self.log.flush() 382 383 # Decrement. 384 self._nest -= 1 385 386 # Return without releasing the lock. 387 return 388 389 # Debugging. 390 if self._fake_lock: 391 # Main text. 392 text = 'Release' 393 394 # Test suite info. 395 if hasattr(self, 'test_name'): 396 text = text + 'd by %s' % self.test_name 397 398 # Write out, flush, and exit the method. 399 self.log.write("%s\n\n" % text) 400 self.log.flush() 401 return 402 403 # Release the real lock. 404 release = self._lock.release() 405 406 # Notify observers. 407 status = Status() 408 status.observers.exec_lock.notify() 409 410 # Return the status. 411 return release
412 413 414
415 -class Observer(object):
416 """The observer design pattern base class.""" 417
418 - def __init__(self, name='unknown'):
419 """Set up the object. 420 421 @keyword name: The special name for the observer object, used in debugging. 422 @type name: str 423 """ 424 425 # Store the args. 426 self._name = name 427 428 # The dictionary of callback methods (and their names). 429 self._callback = {} 430 self._method_names = {} 431 432 # The list of keys, for ordered execution. 433 self._keys = [] 434 435 # The status container. 436 self._status = Status()
437 438
439 - def notify(self):
440 """Notify all observers of the state change.""" 441 442 # Loop over the callback methods and execute them. 443 for key in self._keys: 444 # Debugging. 445 if self._status.debug: 446 if self._method_names[key]: 447 sys.stdout.write("debug> Observer: '%s' notifying the '%s' method %s().\n" % (self._name, key, self._method_names[key])) 448 else: 449 sys.stdout.write("debug> Observer: '%s' notifying '%s'.\n" % (self._name, key)) 450 451 # Call the method. 452 self._callback[key]()
453 454
455 - def register(self, key, method, method_name=None):
456 """Register a method to be called when the state changes. 457 458 @param key: The key to identify the observer's method. 459 @type key: str 460 @param method: The observer's method to be called after a state change. 461 @type method: method 462 @keyword method_name: The optional method name used in debugging printouts. 463 @type method_name: str or None 464 """ 465 466 # Already exists. 467 if key in self._keys: 468 raise RelaxError("The observer '%s' already exists." % key) 469 470 # Blank key. 471 if key == None: 472 raise RelaxError("The observer key must be supplied.") 473 474 # Debugging. 475 if self._status.debug: 476 if method_name: 477 sys.stdout.write("debug> Observer: '%s' registering the '%s' method %s().\n" % (self._name, key, method_name)) 478 else: 479 sys.stdout.write("debug> Observer: '%s' registering '%s'.\n" % (self._name, key)) 480 481 # Add the method to the dictionary of callbacks. 482 self._callback[key] = method 483 484 # Add the method name. 485 self._method_names[key] = method_name 486 487 # Add the key to the ordered list. 488 self._keys.append(key)
489 490
491 - def reset(self):
492 """Reset the object.""" 493 494 # Debugging. 495 if self._status.debug: 496 sys.stdout.write("debug> Resetting observer '%s'.\n" % self._name) 497 498 # Reinitialise the dictionary of callback methods. 499 self._callback = {} 500 501 # Reinitialise the key list. 502 self._keys = []
503 504
505 - def unregister(self, key):
506 """Unregister the method corresponding to the key. 507 508 @param key: The key to identify the observer's method. 509 @type key: str 510 """ 511 512 # Debugging. 513 if self._status.debug: 514 sys.stdout.write("debug> Observer: '%s' unregistering '%s'.\n" % (self._name, key)) 515 516 # Does not exist, so return (allow multiple code paths to unregister methods). 517 if key not in self._keys: 518 if self._status.debug: 519 sys.stdout.write("debug> The key '%s' does not exist.\n" % key) 520 return 521 522 # Remove the method from the dictionary of callbacks. 523 self._callback.pop(key) 524 525 # Remove the name. 526 self._method_names.pop(key) 527 528 # Remove the key for the ordered key list. 529 self._keys.remove(key)
530 531 532
533 -class Relax_lock:
534 """A type of locking object for relax.""" 535
536 - def __init__(self, name='unknown', fake_lock=False):
537 """Set up the lock-like object. 538 539 @keyword name: The special name for the lock, used in debugging. 540 @type name: str 541 @keyword fake_lock: A flag which is True will allow this object to be debugged as the locking mechanism is turned off. 542 @type fake_lock: bool 543 """ 544 545 # Store the args. 546 self.name = name 547 self._fake_lock = fake_lock 548 549 # Init a reentrant lock object. 550 self._lock = RLock() 551 552 # The status container. 553 self._status = Status() 554 555 # Fake lock. 556 if self._fake_lock: 557 # Track the number of acquires. 558 self._lock_level = 0
559 560
561 - def acquire(self, acquirer='unknown'):
562 """Simulate the RLock.acquire() mechanism. 563 564 @keyword acquirer: The optional name of the acquirer. 565 @type acquirer: str 566 """ 567 568 # Debugging. 569 if self._status.debug: 570 sys.stdout.write("debug> Lock '%s': Acquisition by '%s'.\n" % (self.name, acquirer)) 571 572 # Fake lock. 573 if self._fake_lock: 574 # Increment the lock level. 575 self._lock_level += 1 576 577 # Throw an error. 578 if self._lock_level > 1: 579 raise 580 581 # Return to prevent real locking. 582 return 583 584 # Acquire the real lock. 585 lock = self._lock.acquire() 586 587 # Return the real lock. 588 return lock
589 590
591 - def locked(self):
592 """Simulate the RLock.locked() mechanism.""" 593 594 # Call the real method. 595 return self._lock.locked()
596 597
598 - def release(self, acquirer='unknown'):
599 """Simulate the RLock.release() mechanism. 600 601 @keyword acquirer: The optional name of the acquirer. 602 @type acquirer: str 603 """ 604 605 # Debugging. 606 if self._status.debug: 607 sys.stdout.write("debug> Lock '%s': Release by '%s'.\n" % (self.name, acquirer)) 608 609 # Fake lock. 610 if self._fake_lock: 611 # Decrement the lock level. 612 self._lock_level -= 1 613 614 # Return to prevent real lock release. 615 return 616 617 # Release the real lock. 618 release = self._lock.release() 619 620 # Return the status. 621 return release
622 623 624
625 -class Observer_container:
626 """The container for holding all the observer objects.""" 627
628 - def info(self):
629 """Print out info about all the status objects.""" 630 631 # Blacklisted objects. 632 blacklist = list(self.__class__.__dict__.keys()) + list(dict.__dict__.keys()) 633 634 # Loop over all objects in this container. 635 for name in dir(self): 636 # Skip blacklisted objects. 637 if name in blacklist: 638 continue 639 640 # Get the object. 641 obj = getattr(self, name) 642 643 # An observer object. 644 print("Observer '%s' keys: %s" % (obj._name, obj._keys))
645