[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnue] r7392 - trunk/gnue-common/src/apps
From: |
johannes |
Subject: |
[gnue] r7392 - trunk/gnue-common/src/apps |
Date: |
Mon, 18 Apr 2005 04:34:24 -0500 (CDT) |
Author: johannes
Date: 2005-04-18 04:34:19 -0500 (Mon, 18 Apr 2005)
New Revision: 7392
Modified:
trunk/gnue-common/src/apps/GBaseApp.py
Log:
Added --debug-gc to GBaseApp. If this option is given a SIGUSR1 could be sent
to a descendant of GBaseApp to debug the current state of Python's garbage
collection (including detection of referece-cycles)
Modified: trunk/gnue-common/src/apps/GBaseApp.py
===================================================================
--- trunk/gnue-common/src/apps/GBaseApp.py 2005-04-18 09:20:40 UTC (rev
7391)
+++ trunk/gnue-common/src/apps/GBaseApp.py 2005-04-18 09:34:19 UTC (rev
7392)
@@ -1,6 +1,9 @@
+# GNU Enterprise Common Library - Application Services - Base Application Class
#
-# This file is part of GNU Enterprise.
+# Copyright 2001-2005 Free Software Foundation
#
+# This file is part of GNU Enterprise
+#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
@@ -16,21 +19,22 @@
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
-# Copyright 2000-2005 Free Software Foundation
-#
-# FILE:
-# GBaseApp.py
-#
-# DESCRIPTION:
+# $Id$
"""
Class that provides a basis for GNUe applications.
-Typically, this class will not be called; rather, a tool will
-be a GClientApp or GServerApp.
+Typically, this class will not be called; rather, a tool will be a GClientApp
+or GServerApp.
"""
-import sys, os, getopt, string, types
+import sys
+import os
+import getopt
+import string
+import types
import ConfigParser
+import gc
+import signal
from gnue import paths
@@ -38,9 +42,19 @@
from gnue.common.datasources import GConnections
from CommandOption import CommandOption
+
+# =============================================================================
+# Exceptions
+# =============================================================================
+
class StartupError (errors.UserError):
pass
+
+# =============================================================================
+# Base class for GNUe Applications
+# =============================================================================
+
class GBaseApp:
"""
The base class of the various GNUe application classes.
@@ -51,41 +65,47 @@
- An integrated profiler
- An integrated debugger
"""
+
# Attributes to be overwritten by subclasses
VERSION = "0.0.0"
- NAME = "GNUe Application"
- COMMAND_OPTIONS = [] # Should be in same format as _base_options below
+ NAME = "GNUe Application"
+ COMMAND_OPTIONS = [] # Should be in same format as _base_options below
SUMMARY = "A brief summary of the program goes here."
COMMAND = "app"
- USAGE = "[options]"
- USE_CONNECTIONS = 1 # Set to 1 if the program uses dbdrivers
+ USAGE = "[options]"
+ USE_CONNECTIONS = 1 # Set to 1 if the program uses dbdrivers
USE_DATABASE_OPTIONS = 0 # Also implies USE_CONNECTIONS = 1
- USE_RPC_OPTIONS = 0
+ USE_RPC_OPTIONS = 0
- # More options, but won't be changed unless
- # this is a non-GNUe app using GNUe-Common
- AUTHOR = "GNU Enterprise Project"
- EMAIL = "address@hidden"
+ # More options, but won't be changed unless this is a non-GNUe app using
+ # GNUe-Common
+ AUTHOR = "GNU Enterprise Project"
+ EMAIL = "address@hidden"
REPORT_BUGS_TO = "Please report any bugs to address@hidden"
- CONFIGFILE="gnue.conf"
+ CONFIGFILE = "gnue.conf"
- # Run the program
- # Should be overwritten by subclasses
- def run(self):
- pass
-
# Attributes that will be set by GClientApp after __init__ has run
- OPTIONS = {} # Will contain a hash containing command line options
- ARGUMENTS = [] # Will contain an array of command line arguments
+ OPTIONS = {} # Will contain a hash containing command line options
+ ARGUMENTS = [] # Will contain an array of command line arguments
connections = None # Will contain a GConnection object
- def __init__(self, connections=None, application='common', defaults=None):
+ # ---------------------------------------------------------------------------
+ # Create a new GNUe Application
+ # ---------------------------------------------------------------------------
- self.configDefaults = defaults
+ def __init__ (self, connections = None, application = 'common',
+ defaults = None):
+ """
+ @param connections:
+ @param application:
+ @param defaults:
+ """
+
+ self.configDefaults = defaults
- sys.excepthook = self.excepthook
+ sys.excepthook = self.excepthook
# Basic options
self._base_options = [
@@ -100,17 +120,16 @@
help = _('Enables debugging messages. Argument specifies the '
'level of messages to display (e.g., "--debug-level 5" '
'displays all debugging messages at level 5 or below.)')),
+
CommandOption ('debug-file', category = "base", argument = _("filename"),
help = _('Sends all debugging messages to a specified file '
'(e.g., "--debug-file trace.log" sends all output to '
'"trace.log")')),
- # This is actually handled during the initial GDebug import
- # but is added here so that applications won't abort with
- # an unknown option
+ # This is actually handled during the initial GDebug import but is added
+ # here so that applications won't abort with an unknown option.
CommandOption ('debug-imports', category = "dev",
- help = _('All python imports are logged to stdout'
- )),
+ help = _('All python imports are logged to stdout')),
CommandOption ('silent', category = "base",
help = _('Displays no output at all.')),
@@ -153,6 +172,13 @@
CommandOption ('interactive-debugger', category = "dev",
help = _("Run the app inside Python's built-in debugger ")),
+
+ CommandOption ('debug-gc', 'g', 'debug-gc', True, None, "logfile",
+ category = "dev", action = self.__installGCHandler,
+ help = _("Debug Python's garbage collection on a SIGUSR1. If the "
+ "argument is empty 'garbage.log' will be used as
logfile.")),
+
+ #CommandOption ('garbagelog', 'l'
]
if self.USE_DATABASE_OPTIONS:
@@ -205,16 +231,17 @@
# Get all command line options and arguments
#
shortoptions = ""
- longoptions = []
- lookup = {}
- actions = {}
+ longoptions = []
+ lookup = {}
+ actions = {}
# Convert old-style options to new-style
- if self.COMMAND_OPTIONS and type(self.COMMAND_OPTIONS[0]) ==
types.ListType:
- options=self.COMMAND_OPTIONS
+ if self.COMMAND_OPTIONS and \
+ isinstance (self.COMMAND_OPTIONS [0], types.ListType):
+ options = self.COMMAND_OPTIONS
self.COMMAND_OPTIONS = []
for option in options:
- self.COMMAND_OPTIONS.append(CommandOption(*option))
+ self.COMMAND_OPTIONS.append (CommandOption (*option))
for optionset in [self._base_options, self.COMMAND_OPTIONS]:
for option in optionset:
@@ -294,8 +321,8 @@
self.run = self._debugger
try:
- GDebug.setDebug("%s" % self.OPTIONS['debug-level'],
- self.OPTIONS['debug-file'])
+ GDebug.setDebug ("%s" % self.OPTIONS ['debug-level'],
+ self.OPTIONS ['debug-file'])
except ValueError:
raise StartupError, \
_('The debug_level option ("-d") expects numerical values.')
@@ -307,8 +334,9 @@
# Read the config files
if application:
try:
- self.configurationManager = GConfig.GConfig (application,
self.configDefaults,
- configFilename = self.CONFIGFILE)
+ self.configurationManager = GConfig.GConfig (application,
+ self.configDefaults, configFilename = self.CONFIGFILE)
+
except ConfigParser.NoSectionError, msg:
raise errors.AdminError, \
u_("The gnue.conf file is incomplete:\n %s") % msg
@@ -360,38 +388,77 @@
u_("Unable to load the connections definition file: %s.") \
% self.connections_file
+
+ # ---------------------------------------------------------------------------
+ # Run the program
+ # ---------------------------------------------------------------------------
+
+ def run(self):
+ """
+ Run the program. This function will be overriden by a descendant.
+ """
+
+ pass
+
+
+ # ---------------------------------------------------------------------------
+ # Add a new option to the program
+ # ---------------------------------------------------------------------------
+
def addCommandOption(self, *args, **parms):
- self.COMMAND_OPTIONS.append(CommandOption(*args, **parms))
+ """
+ Create a new command option and add it to the options sequence.
- #
- # Display version information for this application
- #
- def printVersion(self):
+ @param args: positional arguments for the command option's constructor
+ @param parms: keyword arguments for the command option's constructor
+ """
+
+ self.COMMAND_OPTIONS.append (CommandOption (*args, **parms))
+
+
+ # ---------------------------------------------------------------------------
+ # Display version information for this application
+ # ---------------------------------------------------------------------------
+
+ def printVersion (self):
+ """
+ Display version information for this application
+ """
+
from gnue.common import VERSION as commonVersion
print o(u_("\n%(name)s\nVersion %(version)s\n") \
% {'name': self.NAME, 'version': self.VERSION})
print o(u_("GNUe Common Version %s\n") % commonVersion)
- #
- # "Help" helpers
- #
- def buildHelpOptions(self, category=None):
- allOptions = {}
+ # ---------------------------------------------------------------------------
+ # Build help options
+ # ---------------------------------------------------------------------------
+
+ def buildHelpOptions (self, category = None):
+ """
+ Build 'help text' for all options of the given category. If no category is
+ given all options except the 'dev' options will be included.
+
+ @param category: if not None only options of this category will be
included.
+
+ @return: string with the help text for all matching options.
+ """
+
+ allOptions = {}
descriptors = {}
- maxLength = 0
+ maxLength = 0
for optionset in [self._base_options, self.COMMAND_OPTIONS]:
for option in optionset:
-
- # Limit this to the correct category. A category
- # of None implies all options except "dev".
+ # Limit this to the correct category. A category of None implies all
+ # options except "dev".
if not ( ( category is None and \
option.category != "dev" ) or \
( category == option.category ) ):
continue
- allOptions[string.upper(option.longOption)] = option
+ allOptions [string.upper (option.longOption)] = option
if option.acceptsArgument:
descr = '--%s <%s>' % (option.longOption, option.argumentName)
@@ -400,37 +467,36 @@
if option.shortOption:
descr += ', -%s' % option.shortOption
- descriptors[string.upper(option.longOption)] = descr
+ descriptors [string.upper (option.longOption)] = descr
- if len(descr) > maxLength:
- maxLength = len(descr)
+ if len (descr) > maxLength:
+ maxLength = len (descr)
if maxLength > 10:
maxLength = 10
- sorted = allOptions.keys()
- sorted.sort()
+ sorted = allOptions.keys ()
+ sorted.sort ()
dispOptions = ""
for optionKey in sorted:
-
margin = maxLength + 4
- width = 78 - margin
- pos = 0
+ width = 78 - margin
+ pos = 0
- if len(descriptors[optionKey]) > maxLength:
- dispOptions += "\n %s\n%s" % (descriptors[optionKey], " " * margin)
+ if len (descriptors [optionKey]) > maxLength:
+ dispOptions += "\n %s\n%s" % (descriptors [optionKey], " " * margin)
else:
- dispOptions += "\n %s %s" % (descriptors[optionKey],
- " " * (maxLength - len(descriptors[optionKey])))
+ dispOptions += "\n %s %s" % (descriptors [optionKey],
+ " " * (maxLength - len (descriptors [optionKey])))
- for word in string.split(allOptions[optionKey].help):
- if (len(word) + pos) > width:
+ for word in string.split (allOptions [optionKey].help):
+ if (len (word) + pos) > width:
pos = 0
dispOptions += "\n" + " " * margin
- pos = pos + len(word) + 1
+ pos = pos + len (word) + 1
dispOptions += word + " "
@@ -438,161 +504,241 @@
return dispOptions
+
+ # ---------------------------------------------------------------------------
+ # Print a usage header
+ # ---------------------------------------------------------------------------
+
def printHelpHeader(self):
- self.printVersion()
+ """
+ Print version information and the usage header
+ """
+
+ self.printVersion ()
print _("Usage: ") + self.COMMAND + ' ' + self.USAGE
print
+
+ # ---------------------------------------------------------------------------
+ # Print help footer
+ # ---------------------------------------------------------------------------
+
def printHelpFooter(self):
+ """
+ Print the help footer including the address for bug reports.
+ """
+
print
print "%s\n" % self.REPORT_BUGS_TO
- #
- # Display help information for this program
- #
- def printHelp(self):
- self.printVersion()
- print _("Usage: ") + self.COMMAND + ' ' + self.USAGE
+
+ # ---------------------------------------------------------------------------
+ # Display help information for this program
+ # ---------------------------------------------------------------------------
+
+ def printHelp (self):
+ """
+ Print help information for this application and quit the program. This
+ includes the version, the usage, the application's summary and all
+ available command options (without the 'developer' options).
+ """
+
+ self.printHelpHeader ()
print "\n" + self.SUMMARY + '\n'
print _('Available command line options:')
- print self.buildHelpOptions()
- self.printHelpFooter()
- sys.exit()
+ print self.buildHelpOptions ()
+ self.printHelpFooter ()
- #
- # Display dev help information for this program
- #
- def printHelpDev(self):
- self.printHelpHeader()
+ sys.exit ()
+
+
+ # ---------------------------------------------------------------------------
+ # Display dev help information for this program
+ # ---------------------------------------------------------------------------
+
+ def printHelpDev (self):
+ """
+ Print help information for this application and quit the program. This
+ includes the version, usage and all available developer's command options.
+ """
+
+ self.printHelpHeader ()
+
print _("The following options are mainly of interest to GNUe developers.")
print _("To view general help, run this command with the --help option.")
print
print _('Developer-specific command line options:')
- print self.buildHelpOptions("dev")
- self.printHelpFooter()
- sys.exit()
+ print self.buildHelpOptions ("dev")
+ self.printHelpFooter ()
- def printHelpConn(self):
- self.printHelpHeader()
+ sys.exit ()
+
+
+ # ---------------------------------------------------------------------------
+ # Print connection-specific help information
+ # ---------------------------------------------------------------------------
+
+ def printHelpConn (self):
+ """
+ Print connection/database-related help information and quit the program.
+ """
+
+ self.printHelpHeader ()
+
print _("The following connection/database-related options are available.")
print _("To view general help, run this command with the --help option.")
print
print _('Database/connection command line options:')
- print self.buildHelpOptions("connections")
+ print self.buildHelpOptions ("connections")
print
print _('The following database drivers are installed on your system:')
print " TODO\n"
# print self.connections.getAvailableDrivers()
- self.printHelpFooter()
- sys.exit()
+ self.printHelpFooter ()
- def printHelpRPC(self):
- self.printHelpHeader()
- print _("The following options are mainly of interest to GNUe developers.")
- print _("To view general help, run this command with the --help option.")
- print
- print _('Developer-specific command line options:')
- print self.buildHelpOptions()
- self.printHelpFooter()
- sys.exit()
+ sys.exit ()
- def selfdoc(self, command, handle, format=None, options={}):
+
+ # ---------------------------------------------------------------------------
+ # Run the self-documentation for a program
+ # ---------------------------------------------------------------------------
+
+ def selfdoc (self, command, handle, format = None, options = {}):
+ """
+ Run the self-documentation for an application. Currently only the command
+ 'manpage' is supported.
+
+ @param command: can be 'manpage' only atm
+ @param handle: file-like object to write the documentation contents to.
+ This file handle must be already opened for writing.
+ @param format: not used in the current version
+ @param options: not used in the current version
+ """
+
if command == 'manpage':
import manpage
- manpage.ManPage(self, handle, format, options)
+ manpage.ManPage (self, handle, format, options)
- #
- # Converts a
- #
- def getCommandLineParameters(self,paramList):
+
+ # ---------------------------------------------------------------------------
+ #
+ # ---------------------------------------------------------------------------
+
+ def getCommandLineParameters (self, paramList):
+ """
+ Convert a sequence of parameters (i.e. '--foo=bar') into a parameter
+ dictionary, where the paramter ('--foo') is the key and it's argument
+ ('bar') is the value.
+
+ @param paramList: sequence of parameters (usually from self.ARGUMENTS)
+ @return: dictionary of parameters, splitted by the first '='
+
+ @raises StartupError: if a parameter has no value assigned
+ """
+
parameters = {}
for param in paramList:
- psplit = string.split(param,'=',1)
- if len(psplit) == 1:
+ psplit = string.split (param, '=', 1)
+ if len (psplit) == 1:
raise StartupError, \
- 'Parameter "%s" specified, but no value supplied.' % psplit[0]
- parameters[string.lower(psplit[0])] = psplit[1]
+ 'Parameter "%s" specified, but no value supplied.' % psplit [0]
+ parameters [string.lower (psplit [0])] = psplit [1]
- gDebug(2,'Param "%s"="%s" ' % \
- (string.lower(psplit[0]), psplit[1]))
+ gDebug (2,'Param "%s"="%s" ' % (string.lower (psplit [0]), psplit [1]))
+
return parameters
- #
- # Display a startup error and exit gracefully with a message on
- # how to get help
- #
- def handleStartupError(self, msg):
- self.printVersion()
+ # ---------------------------------------------------------------------------
+ # Display a startup error and exit gracefully
+ # ---------------------------------------------------------------------------
+
+ def handleStartupError (self, msg):
+ """
+ Display a startup error and exit gracefully. This function is depreciated.
+ Descendants should use the concpet of exceptions instead, which will be
+ handled by the exception hook installed by this class.
+ """
+
+ self.printVersion ()
+
# if msg is multiline, then surround with dashes to set it apart
- if string.find("%s" % msg, "\n") + 1:
+ if string.find ("%s" % msg, "\n") + 1:
print '-' * 60
+
print o(u_("Error: %s") % msg)
- if string.find("%s" % msg, "\n") + 1:
+ if string.find ("%s" % msg, "\n") + 1:
print '-' * 60
+
print o(u_("\nFor help, type:\n %s --help\n") % self.COMMAND)
- sys.exit()
+ sys.exit ()
- #
- # Used when interactive debugger in use
- #
- def _debugger(self):
- import pdb
- debugger = pdb.Pdb()
- GDebug.setDebugger(debugger)
- debugger.runctx( 'self._run()', globals(), locals() )
+ # ---------------------------------------------------------------------------
+ # Display version information
+ # ---------------------------------------------------------------------------
- #
- # Used when profiling
- #
- def _profile(self):
+ def doVersion (self):
+ """
+ Display the version information and quit the program.
+ """
- import profile
- prof = profile.Profile()
- prof.runctx( 'self._run()', globals(), locals() )
+ self.printVersion ()
+ sys.exit ()
- import pstats
- p = pstats.Stats(prof)
- p.sort_stats('time').print_stats(50)
- p.sort_stats('cumulative').print_stats(50)
- p.sort_stats('calls').print_stats(50)
+ # ---------------------------------------------------------------------------
+ # Run the self documentation
+ # ---------------------------------------------------------------------------
- #
- # Commands run by the COMMAND_OPTIONS list
- #
- def doVersion(self):
- self.printVersion()
- sys.exit()
+ def doSelfDoc (self):
+ """
+ Run the self documentation. If a documentation file is specified the
+ contents will be written to that file, otherwise it will be printed to
+ stdout.
+ """
- def doSelfDoc(self):
- if self.OPTIONS['selfdoc-file']:
+ if self.OPTIONS ['selfdoc-file']:
doprint = False
- handle = open(self.OPTIONS['selfdoc-file'],'w')
+ handle = open (self.OPTIONS ['selfdoc-file'], 'w')
else:
doprint = True
import StringIO
handle = StringIO.StringIO
- self.selfdoc(self.OPTIONS['selfdoc'], handle,
- self.OPTIONS['selfdoc-format'],
- self.OPTIONS['selfdoc-options'])
- if doprint:
- handle.seek(0)
- print handle.read()
- handle.close()
- sys.exit()
+ try:
+ self.selfdoc (self.OPTIONS ['selfdoc'], handle,
+ self.OPTIONS ['selfdoc-format'],
+ self.OPTIONS ['selfdoc-options'])
+ if doprint:
+ handle.seek (0)
+ print handle.read ()
- def doHelpConfig(self):
- self.printHelpHeader()
- print GConfig.printableConfigOptions(self.configDefaults)
- sys.exit()
+ finally:
+ handle.close ()
+ sys.exit ()
+
# ---------------------------------------------------------------------------
+ # Display information about the configuration settings
+ # ---------------------------------------------------------------------------
+
+ def doHelpConfig (self):
+ """
+ Display all configuration settings and their default values and quit the
+ program.
+ """
+
+ self.printHelpHeader ()
+ print GConfig.printableConfigOptions (self.configDefaults)
+
+ sys.exit ()
+
+
+ # ---------------------------------------------------------------------------
# Catch an exception
# ---------------------------------------------------------------------------
@@ -608,6 +754,43 @@
# ---------------------------------------------------------------------------
+ # Used when interactive debugger in use
+ # ---------------------------------------------------------------------------
+
+ def _debugger (self):
+ """
+ Run the application in the python debugger.
+ """
+
+ import pdb
+
+ debugger = pdb.Pdb ()
+ GDebug.setDebugger (debugger)
+ debugger.runctx ('self._run ()', globals (), locals ())
+
+
+ # ---------------------------------------------------------------------------
+ # Used when profiling
+ # ---------------------------------------------------------------------------
+
+ def _profile (self):
+ """
+ Run the application through the python profiler and print some statistics
+ afterwards.
+ """
+
+ import profile
+ prof = profile.Profile ()
+ prof.runctx ('self._run ()', globals (), locals ())
+
+ import pstats
+ p = pstats.Stats (prof)
+ p.sort_stats ('time').print_stats (50)
+ p.sort_stats ('cumulative').print_stats (50)
+ p.sort_stats ('calls').print_stats (50)
+
+
+ # ---------------------------------------------------------------------------
# Show an exception
# ---------------------------------------------------------------------------
@@ -626,3 +809,215 @@
o(u_("For help, type: %s --help") % self.COMMAND))
else:
sys.stderr.write ("%s\n" % o(detail))
+
+
+ # ---------------------------------------------------------------------------
+ # Install a signal handler for SIGUSR1
+ # ---------------------------------------------------------------------------
+
+ def __installGCHandler (self):
+ """
+ Enable automatic garbage collection, set it's debug-level to DEBUG_LEAK and
+ install a signal handler for SIGUSR1, which actually performs the
+ debugging.
+ """
+
+ gc.enable
+ gc.set_debug (gc.DEBUG_LEAK)
+
+ signal.signal (signal.SIGUSR1, self.debugGarbageCollection)
+
+
+ # ---------------------------------------------------------------------------
+ # Debug Python's garbage collection
+ # ---------------------------------------------------------------------------
+
+ def debugGarbageCollection (self, signal, frame):
+ """
+ Debug Python's garbage collection.
+
+ @param signal: signal number caught by this handler (=SIGUSR1)
+ @param frame: the current stack frame or None
+ """
+
+ filename = "garbage.log"
+ if isinstance (self.OPTIONS ['debug-gc'], basestring):
+ filename = self.OPTIONS ['debug-gc']
+
+ log = open (filename, 'w')
+ try:
+ unreachable = gc.collect ()
+
+ try:
+ self.__gcLog (log, "Number of unreachable objects: %d" % unreachable)
+ self.__gcLog (log,
+ "Items in gc.garbage sequence : %d" % len (gc.garbage))
+ self.__gcLog (log, "Dump of gc.garbage sequence:")
+ self.__gcLog (log, "-" * 70)
+
+ for item in gc.garbage:
+ try:
+ itemrep = "%s: %s" % (type (item), repr (item))
+ cycle = self.findCycle (item, item)
+
+ except:
+ itemrep = "No representation available for object (weakref/proxy?)"
+ cycle = None
+
+ self.__gcLog (log, "%s" % itemrep)
+
+ if cycle:
+ self.__gcLog (log, "=" * 70)
+
+ for line in self.analyzeCycle (cycle):
+ self.__gcLog (log, line)
+
+ self.__gcLog (log, "=" * 70)
+
+ self.__gcLog (log, "-" * 70)
+
+ finally:
+ del gc.garbage [:]
+
+ finally:
+ log.close ()
+
+
+ # ---------------------------------------------------------------------------
+
+ def __gcLog (self, filehandle, message):
+
+ filehandle.write ("%s%s" % (message, os.linesep))
+ print message
+
+
+ # ---------------------------------------------------------------------------
+ # Find a reference cycle starting from a given object
+ # ---------------------------------------------------------------------------
+
+ def findCycle (self, search, current, last = None, path = [], seen = {}):
+ """
+ Find a reference cycle starting from a given object (current) and ending
+ with a given object (search). The result is either None if no such cycle
+ exists, or a sequence of tuples (repr, propertyname) describing the
+ reference cycle. 'repr' is either a string representation of an object
+ holding the reference or None. 'propertyname' could be one of the
+ following:
+ 'name' name of the property within 'repr' holding the reference
+ '[n]' the reference is the n-th element of a sequence
+ '[name'] the reference is the value of key 'name' in a dictionary
+ '{}' the reference is a key in a dictionary
+
+ The latter three variants could be cumulative (i.e. [1][3]['foo']) and the
+ corresponding propertyname is the last one encountered.
+
+ @return: None or sequence of tuples (repr, propertyname)
+ """
+
+ if last is not None:
+ path = path + [last]
+
+ currentId = id (current)
+
+ # If we have already visited this object, no need to do further processing
+ if seen.has_key (currentId):
+ return None
+
+ seen [currentId] = path
+
+ # If the current object has a __dict__ property, iterate over all it's
+ # properties
+ if hasattr (current, '__dict__'):
+ for (name, attr) in current.__dict__.items ():
+ prop = (repr (current), name)
+
+ if attr == search:
+ return path + [prop]
+
+ else:
+ newpath = self.findCycle (search, attr, prop, path, seen)
+ if newpath:
+ return newpath
+
+ # A bound method has a reference to self
+ elif isinstance (current, types.MethodType):
+ if current.im_self == search:
+ return path + [(repr (current), "im_self")]
+
+ # For Sequences or Tuples iterate over all elements
+ elif isinstance (current, types.ListType) or \
+ isinstance (current, types.TupleType):
+
+ for (index, element) in enumerate (current):
+ prop = (None, "[%d]" % index)
+ if element == search:
+ return path + [prop]
+
+ else:
+ newpath = self.findCycle (search, element, prop, path, seen)
+ if newpath:
+ return newpath
+
+ # For dictionaries iterate over all items
+ elif isinstance (current, types.DictType):
+ for (key, element) in current.items ():
+ prop = (None, "[%s]" % key)
+
+ if element == search:
+ return path + [prop]
+
+ elif key == search:
+ return path + [(None, "{}")]
+
+ else:
+ newpath = self.findCycle (search, element, prop, path, seen)
+ if newpath:
+ return newpath
+
+ # a generator keeps has always reference to self, so we have to iterate
+ # it's local namespace
+ elif isinstance (current, types.GeneratorType):
+ for (key, element) in current.gi_frame.f_locals.items ():
+ prop = (None, "[%s]" % key)
+ if element == search:
+ return path + [prop]
+
+ elif key == search:
+ return path + [(None, "{}")]
+
+ else:
+ newpath = self.findCycle (search, element, prop, path, seen)
+ if newpath:
+ return newpath
+
+ return None
+
+
+ # ---------------------------------------------------------------------------
+ # Analyze a given reference cycle
+ # ---------------------------------------------------------------------------
+
+ def analyzeCycle (self, cycle):
+ """
+ Return a generator for iterating a given reference cycle.
+
+ @param cycle: None or a sequence of tuples (repr, propertyname)
+ @return: iterator
+ """
+
+ if cycle:
+ for (index, (rep, name)) in enumerate (cycle):
+ if index == 0:
+ yield 'self = %s' % rep
+ lastname = 'self.%s' % name
+ else:
+ if rep is not None:
+ yield '%s = %s' % (lastname, rep)
+ jsymb = '.'
+ else:
+ jsymb = ' '
+
+ lastname = jsymb.join ([lastname, name])
+
+ yield '%s = self' % lastname
+
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnue] r7392 - trunk/gnue-common/src/apps,
johannes <=