Modified:
/trunk/namespace.py
/trunk/reloader.py
/trunk/tests/test_reloading.py
=======================================
--- /trunk/namespace.py Fri Feb 12 18:43:15 2010
+++ /trunk/namespace.py Fri Feb 12 23:34:45 2010
@@ -19,7 +19,7 @@
class ScriptFile(object):
lastError = None
- contributedAttributes = None
+ namespaceContributions = None
def __init__(self, filePath, namespacePath, implicitLoad=True,
delGlobals=False):
self.filePath = filePath
@@ -48,11 +48,11 @@
def GetAttributeValue(self, attributeName):
return self.scriptGlobals[attributeName]
- def SetContributedAttributes(self, contributedAttributes):
- self.contributedAttributes = contributedAttributes
-
- def AddContributedAttributes(self, contributedAttributes):
- self.contributedAttributes |= contributedAttributes
+ def SetNamespaceContributions(self, namespaceContributions):
+ self.namespaceContributions = namespaceContributions
+
+ def AddNamespaceContributions(self, namespaceContributions):
+ self.namespaceContributions |= namespaceContributions
def Run(self):
self.scriptGlobals = {}
@@ -141,19 +141,19 @@
continue
valueType = type(v)
+ exportable = True
# Modules will have been imported from elsewhere.
- #if isinstance(v, types.ModuleType):
- # continue
-
- if valueType in (types.ClassType, types.TypeType):
+ if isinstance(v, types.ModuleType):
+ exportable = False
+ elif valueType in (types.ClassType, types.TypeType):
# Classes with valid modules will have been imported from
elsewhere.
if v.__module__ != "__builtin__":
- continue
+ exportable = False
# Skip actual builtin objects.
- if v in builtinValues:
- continue
-
- yield k, v, valueType
+ elif v in builtinValues:
+ exportable = False
+
+ yield k, v, valueType, exportable
class ScriptDirectory(object):
@@ -379,29 +379,32 @@
namespace.__file__ += ";"
namespace.__file__ += scriptFile.filePath
- contributedAttributes = set()
- for k, v, valueType in scriptFile.GetExportableAttributes():
+ namespaceContributions = set()
+ for k, v, valueType, exportable in
scriptFile.GetExportableAttributes():
+ logger.debug("InsertModuleAttribute %s.%s exported=%s",
moduleName, k, exportable)
+
+ if not exportable:
+ logger.debug("Added a non-exported global: %s %s", k,
valueType)
+ continue
+
# By default we never overwrite. This way we can identify
duplicate contributions.
if hasattr(namespace, k) and k not in overwritableAttributes:
- logger.error("Duplicate namespace contribution for '%s.%s'
from '%s', our class = %s", moduleName, k, scriptFile.filePath, v.__file__
== scriptFile.filePath)
+ logger.error("Duplicate namespace contribution for '%s.%s'
from '%s', our class = %s", moduleName, k, scriptFile.filePath, v.__file__
== scriptFile.filePath)
continue
- logger.debug("InsertModuleAttribute %s.%s", moduleName, k)
-
if valueType in (types.ClassType, types.TypeType):
v.__module__ = moduleName
v.__file__ = scriptFile.filePath
setattr(namespace, k, v)
- contributedAttributes.add(k)
-
- logger.info("Added '%s.%s'", moduleName, k)
-
+
+ namespaceContributions.add(k)
+
if type(v) in (types.TypeType, types.ClassType):
self.BroadcastClassCreationEvent(namespace, k, v)
# print namespace, k, type(v)
- scriptFile.SetContributedAttributes(contributedAttributes)
+ scriptFile.SetNamespaceContributions(namespaceContributions)
def BroadcastClassCreationEvent(self, *args):
if self.classCreationCallback:
@@ -417,7 +420,7 @@
def RemoveModuleAttributes(self, scriptFile, namespace):
logger.debug("RemoveModuleAttributes %s", scriptFile.filePath)
- if scriptFile.contributedAttributes is None:
+ if scriptFile.namespaceContributions is None:
return True
paths = namespace.__file__.split(";")
@@ -426,7 +429,7 @@
paths.remove(scriptFile.filePath)
namespace.__file__ = ";".join(paths)
- for k in scriptFile.contributedAttributes:
+ for k in scriptFile.namespaceContributions:
delattr(namespace, k)
return True
=======================================
--- /trunk/reloader.py Fri Feb 12 18:43:15 2010
+++ /trunk/reloader.py Fri Feb 12 23:34:45 2010
@@ -9,17 +9,18 @@
logger = logging.getLogger("reloader")
-import namespace
+# TODO: rename 'namespace.py' to 'namespaces.py' ... need to think about
it...
+import namespace as namespaces
MODE_OVERWRITE = 1
MODE_UPDATE = 2
class NonExistentValue: pass
-class ReloadableScriptFile(namespace.ScriptFile):
+class ReloadableScriptFile(namespaces.ScriptFile):
version = 1
-class ReloadableScriptDirectory(namespace.ScriptDirectory):
+class ReloadableScriptDirectory(namespaces.ScriptDirectory):
scriptFileClass = ReloadableScriptFile
unitTest = True
@@ -33,7 +34,7 @@
self.monitorFileChanges = monitorFileChanges
self.directoriesByPath = {}
- self.leakedAttributes = {}
+ self.namespaceLeaks = {}
self.classCreationCallback = None
self.classUpdateCallback = None
@@ -212,12 +213,12 @@
scriptDirectory.UnregisterScript(oldScriptFile)
scriptDirectory.RegisterScript(newScriptFile)
- scriptDirectory.SetModuleAttributes(newScriptFile, namespace,
overwritableAttributes=self.leakedAttributes)
+ scriptDirectory.SetModuleAttributes(newScriptFile, namespace,
overwritableAttributes=self.namespaceLeaks)
# Remove as leaks the attributes the new version contributed.
self.RemoveLeakedAttributes(newScriptFile)
elif self.mode == MODE_UPDATE:
- self.UpdateModuleAttributes(oldScriptFile, newScriptFile,
namespace, overwritableAttributes=self.leakedAttributes)
+ self.UpdateModuleAttributes(oldScriptFile, newScriptFile,
namespace, overwritableAttributes=self.namespaceLeaks)
oldScriptFile.version += 1
# Remove as leaks the attributes the new version contributed.
@@ -237,28 +238,37 @@
attributeChanges = {}
# Collect existing values for entries.
- for k in scriptFile.contributedAttributes:
+ for k in scriptFile.namespaceContributions:
v = getattr(namespace, k)
valueType = type(v)
attributeChanges[k] = [ (v, valueType), (NonExistentValue,
None) ]
- # Collect entries for the attributes contributed by the new script
file.
- for k, v, valueType in newScriptFile.GetExportableAttributes():
- if k not in attributeChanges:
- attributeChanges[k] = [ (NonExistentValue, None), (v,
valueType) ]
+ # Collect entries for the attributes imported or defined by the
new script file.
+ for k, v, valueType, exportable in
newScriptFile.GetExportableAttributes():
+ if exportable:
+ if hasattr(namespace, k) and k not in
overwritableAttributes:
+ logger.error("Duplicate namespace contribution
for '%s.%s' from '%s', our class = %s", moduleName, k, scriptFile.filePath,
v.__file__ == scriptFile.filePath)
+ continue
+
+ if k not in attributeChanges:
+ attributeChanges[k] = [ (NonExistentValue, None), (v,
valueType) ]
+ else:
+ attributeChanges[k][1] = (v, valueType)
else:
- attributeChanges[k][1] = (v, valueType)
+ if k in scriptFile.scriptGlobals:
+ logger.debug("Updated a non-exported global: %s %s",
k, valueType)
+ else:
+ logger.debug("Added a non-exported global: %s %s", k,
valueType)
+ scriptFile.scriptGlobals[k] = v
# The globals dictionary of the retained original script file.
globals_ = scriptFile.scriptGlobals
- contributedAttributes = set()
- leakedAttributes = set()
+ namespaceContributions = set()
for attrName, ((oldValue, oldType), (newValue, newType)) in
attributeChanges.iteritems():
# No new value -> the old value is being leaked.
if newValue is NonExistentValue:
- leakedAttributes.add(attrName)
continue
if newType is types.ClassType or newType is types.TypeType:
@@ -266,7 +276,7 @@
# If there was an old value, it is updated.
if oldValue is not None:
- contributedAttributes.add(attrName)
+ namespaceContributions.add(attrName)
continue
# Otherwise, the new value is being added.
@@ -276,7 +286,7 @@
logger.debug("Encountered new class '%s'", attrName)
elif oldType is newType and oldValue == newValue:
# Skip constants whose value has not changed.
- logger.debug("Skippped unchanged attribute '%s'", attrName)
+ logger.debug("Skipped unchanged attribute '%s'", attrName)
continue
elif isinstance(newValue, types.FunctionType):
logger.debug("Rebound method '%s'", attrName)
@@ -289,11 +299,10 @@
globals_[attrName] = newValue
setattr(namespace, attrName, newValue)
- contributedAttributes.add(attrName)
-
- scriptFile.AddContributedAttributes(contributedAttributes)
- newScriptFile.SetContributedAttributes(contributedAttributes)
- # self.leakedAttributes |= leakedAttributes
+ namespaceContributions.add(attrName)
+
+ scriptFile.AddNamespaceContributions(namespaceContributions)
+ newScriptFile.SetNamespaceContributions(namespaceContributions)
def UpdateClass(self, value, newValue, globals_):
if value is None or value is NonExistentValue:
@@ -335,21 +344,21 @@
# Leaked attribute support
def IsAttributeLeaked(self, attributeName):
- return attributeName in self.leakedAttributes
+ return attributeName in self.namespaceLeaks
def GetLeakedAttributeVersion(self, attributeName):
- return self.leakedAttributes[attributeName][1]
+ return self.namespaceLeaks[attributeName][1]
def AddLeakedAttributes(self, oldScriptFile):
filePath = oldScriptFile.filePath
- for attributeName in oldScriptFile.contributedAttributes:
- self.leakedAttributes[attributeName] = (filePath,
oldScriptFile.version)
+ for attributeName in oldScriptFile.namespaceContributions:
+ self.namespaceLeaks[attributeName] = (filePath,
oldScriptFile.version)
def RemoveLeakedAttributes(self, newScriptFile):
- for attributeName in newScriptFile.contributedAttributes:
- if attributeName in self.leakedAttributes:
- del self.leakedAttributes[attributeName]
+ for attributeName in newScriptFile.namespaceContributions:
+ if attributeName in self.namespaceLeaks:
+ del self.namespaceLeaks[attributeName]
#
------------------------------------------------------------------------
# Attribute compatibility support
=======================================
--- /trunk/tests/test_reloading.py Fri Feb 12 18:43:15 2010
+++ /trunk/tests/test_reloading.py Fri Feb 12 23:34:45 2010
@@ -706,7 +706,13 @@
# Test that a bad path will not find a handler when there are
valid ones for other paths.
self.failUnless(cr.FindDirectory("unregistered path") is
None, "Got a script directory handler for an unregistered path")
- def testAttributeLeaking(self):
+ def testUpdateAttributeLeaking(self):
+ self.gtestAttributeLeaking(reloader.MODE_UPDATE)
+
+ def testOverwriteAttributeLeaking(self):
+ self.gtestAttributeLeaking(reloader.MODE_OVERWRITE)
+
+ def gtestAttributeLeaking(self, reloadingMode):
"""
This test is intended to exercise the leaked attribute tracking.
@@ -733,7 +739,7 @@
leakName = "NewStyleSubclassViaClassReference"
scriptDirPath = GetScriptDirectory()
- cr = self.codeReloader = reloader.CodeReloader()
+ cr = self.codeReloader = reloader.CodeReloader(mode=reloadingMode)
cr.scriptDirectoryClass = ReloadableScriptDirectoryNoUnitTesting
scriptDirectory = cr.AddDirectory("game", scriptDirPath)
self.failUnless(scriptDirectory is not None, "Script loading
failure")