This file is indexed.

/usr/share/pyshared/enthought/util/refresh.py is in python-enthoughtbase 3.1.0-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
#------------------------------------------------------------------------------
# Copyright (c) 2005, Enthought, Inc.
# All rights reserved.
# 
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license.  The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
# Thanks for using Enthought open source!
# 
# Author: Enthought, Inc.
# Description: <Enthought util package component>
#------------------------------------------------------------------------------
""" Live updating of objects in system from module reloads.

    The main function exposed by this module is::
    
      refresh(logger=None) 
    
    Check for edited python files for modules that are live in the 
    system.  If one or more are found, reload the module in such 
    a way that its new class and function definitions are used 
    by all pre-existing objects in the system.  This differs from
    a simple reload(module) which makes no attempt to provide the
    freshly loaded implementations of classes to objects created
    with a previous (outdated) version of the class.

    This feature allows you to make edits to a module
    while a program is running and not have to restart the
    program to get the new behavior in the system.  It is
    particularly handy when working on large GUI applications
    where you want to update the core code in the system 
    without a lengthy restart.

    Example::
        
        # foo.py
        class Foo:
            def method(self):
                print "hello"

        $ python
        >>> from foo import Foo
        >>> obj = Foo()
        >>> obj.method()
        hello
        #<edit file>                               
        # foo.py
        class Foo:
            def method(self):
                print "goodbye"
        #<end edit>        
        >>> import refresh
        >>> refresh.refresh()
        # obj will now have the behavior of the updated Foo classes.
        >>> obj.method()
        goodbye               
                              
    How It works
    ------------
    
    In python, classes and functions are mutable objects.  Importing
    a module instantiates an instance of all the classes and functions
    defined in the module.  
    
    Any objects instantiated from the classes have a obj.__class__ 
    attribute the points to the class they are based upon.  When 
    you call a method on the object in your code like so:: 
        
        obj.method()
        
    python actually calls the method othe __class__ object like so::
        
        obj.__class__.method(obj)
    
    with obj passed in as the "self" argument.  This indirection allows
    us to dynamically change the implementation of method().  
    
    When you edit a python module and then reload it in a python session,
    any classes within the module are reinstantiated within that module
    and any newly created objects created with a call to module.class()
    will have the behavior and methods of your freshly edited code.  But
    what about objects that are already "live" in the system that were
    created with a previous version of module.class?  Their __class__
    attribute is still pointing at the old version of the class, and so
    they will have the old behavior.  The quickest way to fix this is
    to find the old class object in the system and replace its attributes
    and methods with those of the freshly imported class.  This will 
    instantly give all old objects in the system the new behavior.
    
    Functions are updated in a similar way.  All old versions of 
    functions have their internals (func_code, etc.) replaced with
    those of the freshly loaded implementation. 
    
    Caveats
    -------
    
    Their are multiple issues with trying insert a new class definition
    back into classes.  Doing this clobbers and class scope variables that
    were set at runtime.  Also, this implementation cleans out old classes
    completely before inserting new method/attributes.  That means that
    any methods dynamically added to a class are also clobbered.  There
    are ways to potentially get around this, but it isn't clear that 
    it is worth the effort (at least for Enthought applications) as such
    tricks are not used very often in classes that are often edited.  When
    you do (or anytime you notice suspicious behavior), restart your 
    application.
    
    Enthought people: While refresh() can save you time for working with
    applications, ALWAYS do a fresh restart and check that everything is
    working properly before checking edited code in.
    
    Questions
    ---------
    * Check out why traits classes aren't cleaned up correctly.

    * Should we get rid of functions/classes that were in old version of module
      but aren't in new one?  This could prevent problems with stale functions
      left around that really weren't meant to be, but it might also delete
      functions that "hooked" into a module...
       
      fix me: I think we should to prevent refactor errors.

    :copyright: 2005, Enthought, Inc.
    :author:    Eric Jones    
    :license:   BSD

"""

# General Imports
import os
import sys
import gc
import logging
import compiler
from types import UnboundMethodType, FunctionType, ClassType
import linecache


#############################################################################
# Public functions
#############################################################################

def refresh(logger=None):
    """ Reload edited modules & update existing objects to new versions
    
        Check for edited python files for modules that are live in the 
        system.  If one or more are found, reload the module in such a way 
        that its new class and function definitions are used by all 
        pre-existing objects in the system.  This differs from a simple 
        reload(module) which makes no attempt to provide the freshly loaded 
        implementations of classes to objects created with a 
        previous (outdated) version of the class.

        This feature allows you to make edits to a module while a program is 
        running and not have to restart the program to get the new behavior in 
        the system.  It is particularly handy when working on large GUI 
        applications where you want to update the core code in the system 
        without a lengthy restart.

    """
    refresher = Refresher(logger)
    refresher.refresh()
    
#############################################################################
# Public classes
#############################################################################

class Refresher(object):
    """ Implementation of refresh().  See referesher.refresh() function for 
        more information.
    """
    
    def __init__(self, logger=None):
        """ Create a Refesher class. If logger is None, logging.root is used.
        """
        if logger is None:
            self.logger = logging.root
        else:
            self.logger = logger
        
    
    #########################################################################
    # Public Refresh interface
    #########################################################################

    def refresh(self):
        """ Find all out-of-date modules and reload functions and classes.
        """
        
        # 1. Find outdated modules in sys.modules with out_of_date_modules()
        modules = out_of_date_modules()
                
       
        if not modules:
            # If there aren't any out of date modules, don't do anything...
            return
            
        # 2. find all the functions and clases in the system
        #    Note: Do this before you do any reloads!  Some of the
        #          sub-functions rely on reloaded functions not being
        #          in this list.
        objects = gc.get_objects()
        all_functions, all_classes = filter_functions_and_classes(objects)

        for module in modules:
            
            # 3. Find classes and functions that need to be updated.
            #    Note: This causes a reload of the module.
            updated_functions, updated_classes = \
                     new_functions_and_classes_for_module(module, self.logger)
            
            if updated_functions or updated_classes:
                # 4. now update the functions and classes affected by the refresh
                
                self.logger.info("Refresh module: %s" % module.__name__)
                self.logger.debug("Refresh file: %s" % module.__file__)
                
                self._refresh_functions(all_functions, updated_functions)
                self._refresh_classes(all_classes, updated_classes)
    
            # 5. Clear out the linecache used by some shells (pycrust, idle)
            #    because we just updated some modules, and they will report
            #    the wrong line numbers on exceptions.
            linecache.clearcache()
            
    #########################################################################
    # Private interface
    #########################################################################
    
    def _refresh_classes(self, all_classes, updated_classes):
        """ Replace classes from all_classes with implementation found in 
            updated_classes.
        """
        
        for new_class in updated_classes:
            # are their any classes with the same name and module?
            matched_classes = [klass for klass in all_classes if 
                                    klass.__name__ == new_class.__name__
                                and klass.__module__ == new_class.__module__]  
            
                       
            if len(matched_classes) == 0:
                # if no old versions are found, don't do anything...
                pass
                            
            elif len(matched_classes) >= 1:
                
                # plug reloaded methods/attrs from new class into old class
                for old_class in matched_classes:

                    self.logger.debug("    %s" % old_class)
                
                    # We delete everything previously defined in the class.
                    # While this could delete static class scope variables 
                    # set at runtime or methods dynamically attached to the 
                    # class at runtime, it also cleans out any
                    # methods/attributes from previous class definitions.
                    # This prevents the chance of external code erroneously
                    # calling a method from the old class definition that 
                    # isn't defined in the reloaded class.
                    #
                    # fixme: Revisit this, as there are ways to check the old
                    #        pyc files for methods that were defined in the
                    #        class and comparing that to the actual old class 
                    #        to identify dynamically attached methods that 
                    #        might need to remain attached.  

                    new_attrs = set(new_class.__dict__.keys())
                    old_attrs = set(old_class.__dict__.keys())
                    
                    # attributes in old_class that aren't in new class.
                    # and get rid of them.
                    clobber_attrs = old_attrs.difference(new_attrs)
                    for attr in clobber_attrs:
                        delattr(old_class, attr)
                                        
                    # some class __dict__ objects are dictproxy objects that 
                    # don't support update and del correctly.  The following 
                    # won't work. 
                    # old_class.__dict__.update(new_class.__dict__)
                        
                    for attr in new_attrs:
                        
                        if (isinstance(old_class, object) and 
                            attr in ["__dict__", "__doc__"]):
                            # new style classes return __dict__ as an attr
                            # and it can't be updated.
                            continue
                        
                        new_value = getattr(new_class, attr)
                        old_value = getattr(old_class, attr, None)
                        
                        if (isinstance(old_value, UnboundMethodType) and 
                            isinstance(new_value, UnboundMethodType)):

                            # ensure that old_value is a method defined on
                            # old_class and not one of its base classes before
                            # we change it.
                            if (old_value.im_class.__name__ == 
                                old_class.__name__ and
                                old_value.im_class.__module__ == 
                                old_class.__module__):
                                    
                                # when replacing a method with a new method, 
                                # update the old methods innards instead of 
                                # replacing it directly in the dict.  This
                                # helps lets trait properties (and probably
                                # other things) work correctly.
                                                                                
                                msg = "        %s method updated" % attr    
                                self.logger.debug(msg)    
                                                               
                                # update the functions innards.
                                self._update_function(old_value.im_func, 
                                                      new_value.im_func)
                                                                
                            else:
                                
                                # The old_class.attr method was on a base 
                                # class.  Don't replace its innards.  Instead
                                # overwrite it in the dict.                                                      
                                msg = "        %s updated" % attr                                                                                          
                                setattr(old_class, attr, new_value)
                                
                        else:
                            self.logger.debug("        %s updated" % attr)
                            setattr(old_class, attr, new_value)
                    
                    # The old class is now equivalent to the new class 
                    # definition.  Overwrite the new class definition with
                    # the old one so that the new one becomes unused and can
                    # be garbage collected.  While not entirely necessary, it
                    # does keep things tidier.
                    mod = sys.modules[new_class.__module__]
                    name = new_class.__name__
                    setattr(mod, name, old_class)
                
    def _refresh_functions(self, all_functions, updated_functions):
        """ Replace functions from all_functions with implementation found in 
            updated_functions.
        """
        
        for new_function in updated_functions:
            
            # Skip new_function if it doesn't have a name...
            #
            # note: added check for __name__ because a generator 
            # (enthought.plugins.text_editor.editor.text_editor._id_generator)
            # was sneaking through as new_function [ I believe because of the
            # reassignment of it].  Doesn't look like there is an intelligent
            # way to handle this...
            if not hasattr(new_function,'__name__'):
                continue

            # are their any functions with the same name and module?
            matched_functions = [func for func in all_functions if 
                                   func.__name__ == new_function.__name__ and
                                   func.__module__ == new_function.__module__]  
                
            for old_function in matched_functions:
                self.logger.debug("    %s" % old_function)
            
                # replace old function contents with new function contents
                self._update_function(old_function, new_function)
                                
                # The old function is now equivalent to the new function
                # definition.  Overwrite the new definition with the old
                # one so that the new one becomes unused and can
                # be garbage collected.  While not entirely necessary, it
                # does keep things tidier.
                mod = sys.modules[new_function.__module__]
                name = new_function.__name__
                setattr(mod, name, old_function)

    def _update_function(self, old_function, new_function):
        """ Update thbe old_function to have the same implementation
            code as new_function.  old_function is modified inplace !
        """
            
        # fix me: Does this handle closures correctly? Can we?
        #         readonly: func_closure, func_globals
        old_function.func_code = new_function.func_code
        old_function.func_defaults = new_function.func_defaults
        old_function.func_dict = new_function.func_dict
        old_function.func_doc = new_function.func_doc

        
#############################################################################
# Functions for finding class and function definitions within a module.
#############################################################################
    
def new_functions_and_classes_for_module(mod, logger=None):
    """ Return a list of the new class within a module
    
        Note that this reloads the module in the process.
    """
    # fix me: Is it really valuable to use this parsing technique
    #         instead of just searching the modules namespace for
    #         classes?  Perhaps so to prevent getting old classes...
    function_names, class_names = function_and_class_names_in_module(mod)
        
    # fixme: The try/except here was added because something strange
    #        is happening with Envisage *plugin_definition.py files.
    #        Python reports that they are not in sys.modules when you
    #        try to reload() them, but if you look in sys.modules, they
    #        are there.  I'm guessing that this has something to do
    #        with Martin's special import hook for loading modules, but 
    #        I am not sure.  I'll talk with Martin and try to fix this
    #        correctly instead of handling it with this hack.
        
    if logger is None:
        logger = logging.root

    ignore_as_plugin = False
    ignore_as_instance = False
    
    try:        
        import fnmatch, types 
        if fnmatch.fnmatch(mod.__name__, "*plugin_definition*"):
            # fixme: This is soo ugly I feel good blaming it on Martin...
            # fixme: this really should be logged.
            #print "ignoring plugin definition: ", mod.__name__    
            ignore_as_plugin = True

        elif (type(mod) is types.InstanceType):
            # this is added to get around scipy's delayed import.
            # fixme: this really should be logged.
            #print 'not reloading because it is instance type:', mod
            ignore_as_instance = True

        else:
            reload(mod)            
            
    except ImportError:
        # ignore plugin_definition failures.  Report all others, but
        # continue.
        import fnmatch
        if not fnmatch.fnmatch(mod.__name__, "*plugin_definition*"):
            #print "unexpected reload error.  Module: %s", mod.__name__
            pass
        else:
            ignore_as_plugin = True
                        
    # logging
    if not (ignore_as_plugin):
        logger.debug("Refresh ignoring plugin (ok): %s" % mod.__file__)
    if not (ignore_as_instance):
        logger.debug("Refresh ignoring Instance (ok): %s" % mod.__file__)

    if not (ignore_as_plugin or ignore_as_instance):
        function_list = [getattr(mod, name) for name in function_names]
        class_list = [getattr(mod, name) for name in class_names]
    else:
        function_list = []
        class_list = []

    return function_list, class_list


def function_and_class_names_in_module(mod):
    """ Parse .py file associated with module for class names (full path).
    
        The returned list contains class names without their module name 
        prefix.  For example if the foo module contains a Bar class, this
        method would return ['Bar']
        
        Nested classes are not currently supported.
    """
    classes = []
    functions = []
    
    file_name = source_file_for_module(mod)
    if file_name:
        functions, classes = function_and_class_names_in_file(file_name)
            
    return functions, classes
            
def function_and_class_names_in_file(file_name):
    """ Find the names of classes contained within a file.

        fix me: This currently only finds top level classes.  Nested 
        classes are ignored. ?? Does this really matter much ??
        
        Example::
            
            # foo.py
            class Foo:
                pass
            
            class Bar:
                pass
                
            >>> import enthought.util.refresh
            >>> refresh.function_and_class_names_in_file('foo.py')    
            [], ['Foo', 'Bar']
    """
    mod_ast = compiler.parseFile(file_name)

    class_names = []
    function_names = []
    for node in mod_ast.node.nodes:
        if node.__class__ is compiler.ast.Class:
            class_names.append(node.name)
        elif node.__class__ is compiler.ast.Function:
            function_names.append(node.name)

    return function_names, class_names

def source_file_for_module(module):
    """ Find the .py file that cooresponds to the module.
    """
    
    if hasattr(module,'__file__'):
        base,ext = os.path.splitext(module.__file__)
        file_name = base+'.py'
    else:
        file_name = None
        
    return file_name

#############################################################################
# Functions for filtering function and class objects out of a list.
#############################################################################

def filter_functions_and_classes(items):
    """ Filter items for all class and functions objects.
    
        Returns two lists: (functions, classes)

        This function is faster than calling filter_functions
        and filter_classes separately because it only traverses
        the entire list once to create a sub-list containing
        both functions and classes.  This (usually much shorter)
        sublist is then traversed again to divide it into functions
        and classes 
    """
    
    # fix me: inspect.isclass checks for __bases__.  Do we need
    #         to do this for python classes, or is this only
    #         needed for classes declared in C?  Adding bases    
    #         finds about 6000 classes compared to 2000 from
    #         the envisage interpeter.
    sub_items = [item for item in items if 
                    isinstance(item, (FunctionType, ClassType, type))
                    #or hasattr(item,'__bases__')
                ]

    functions = filter_functions(sub_items)                    
    classes = filter_classes(sub_items)                    
    
    return functions, classes

def filter_functions(items):
    """ Filter items for all function objects (not instances mind you)
    """
    return [item for item in items if isinstance(item, FunctionType)]

def filter_classes(items):
    """ Filter items for all class objects (not instances mind you)
    """
    return [item for item in items if isinstance(item, (ClassType, type))]

#############################################################################
# Functions for searching for modules that have been updated on disk.
#############################################################################

def out_of_date_modules():
    """ Find loaded modules that have been modified since they were loaded.
    
        Searches the modules in sys.modules looks for py files that have
        a newer timestamp than the associated pyc file.  Extension modules
        are ignored.
    """
    out_of_date = []
    
    for mod_name, mod in sys.modules.items():
        if mod_name == "__main__":
            continue
             
        if hasattr(mod,'__file__'):
            base,ext = os.path.splitext(mod.__file__)
        else:
            # fixme: why would this happen...
            ext = None

        # pyd, dll, and so files are all ignored
        if ext in ['.pyc','.py']:
            py_time = _timestamp(base+'.py')
            pyc_time = _timestamp(base+'.pyc')            
            
            if py_time is None:
                # strange case where someone has deleted the py file.
                pass 
            else:
                if pyc_time is None:
                    # case where pyc file has been deleted.
                    out_of_date.append(mod) 
                elif pyc_time <= py_time:
                    # module out of date
                    out_of_date.append(mod)

    return out_of_date                
    
def _timestamp(pathname):
    """Return the file modification time as a Long.
    """
    try:
        s = os.stat(pathname)
    except OSError:
        return None
    return long(s.st_mtime)