[Checkins] SVN: grok/branches/philikon-reload/src/grok/ First shot
at module refresh w/o server restarts.
Philipp von Weitershausen
philikon at philikon.de
Mon Jan 29 18:03:53 EST 2007
Log message for revision 72257:
First shot at module refresh w/o server restarts.
Outline of the approach:
* when grokking a module or package, all grokked modules and their filenames
will be remembered
* when a new request is started, all the modification dates of those filenames
is compared to the time of the last (re)start. A reload is initiated if
appropriate.
* reload is performed in three steps:
1. ungrokking everything that has been grokked. Grokkers now should implement
an ungrok() method in which they take back everything they've done
(e.g. unregister an adapter, undefine a security checker, etc.)
2. tossing all modules that were grokked including those that were grokked
because they're modules inside a grokked package.
3. regrokking the modules that were initially grokked.
Todo:
* implement ungrok() in the remaining grokkers (currently only a few are equipped with ungrok())
* decide on an off-switch for autorefresh (developer-mode?)
* decide what to do when the refreshed code contains syntax errors and can't be regrokked.
* fix a bug in the interaction with the ZODB that currently leaves persistent objects as instances
of the old classes, not the new refreshed ones. See http://mail.zope.org/pipermail/zodb-dev/2007-January/010636.html
for details.
Changed:
U grok/branches/philikon-reload/src/grok/_grok.py
U grok/branches/philikon-reload/src/grok/components.py
U grok/branches/philikon-reload/src/grok/grokker.py
U grok/branches/philikon-reload/src/grok/meta.py
U grok/branches/philikon-reload/src/grok/publication.py
-=-
Modified: grok/branches/philikon-reload/src/grok/_grok.py
===================================================================
--- grok/branches/philikon-reload/src/grok/_grok.py 2007-01-29 22:49:12 UTC (rev 72256)
+++ grok/branches/philikon-reload/src/grok/_grok.py 2007-01-29 23:03:52 UTC (rev 72257)
@@ -13,18 +13,16 @@
##############################################################################
"""Grok
"""
-import os
+import os.path
import sys
+import time
-from zope import component
-from zope import interface
-
+from zope import component, interface
from zope.component.interfaces import IDefaultViewName
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.component.site import LocalSiteManager
import grok
-
from grok import util, scan, components, grokker, meta
from grok.error import GrokError, GrokImportError
from grok.directive import frame_is_module
@@ -35,19 +33,19 @@
component.provideAdapter(components.ModelTraverser)
component.provideAdapter(components.ContainerTraverser)
- # register the name 'index' as the default view name
+ # Register the name 'index' as the default view name.
component.provideAdapter('index',
adapts=(grok.Model, IBrowserRequest),
provides=IDefaultViewName)
component.provideAdapter('index',
adapts=(grok.Container, IBrowserRequest),
provides=IDefaultViewName)
- # register a subscriber for when grok.Sites are added to make them
- # into Zope 3 sites
+ # Register a subscriber for when grok.Sites are added to make them
+ # into Zope 3 sites.
component.provideHandler(
addSiteHandler, adapts=(grok.Site, grok.IObjectAddedEvent))
- # now grok the grokkers
+ # Now grok the grokkers.
grokker.grokkerRegistry.grok(scan.module_info_from_module(meta))
def addSiteHandler(site, event):
@@ -58,7 +56,7 @@
del sitemanager['default']
site.setSiteManager(sitemanager)
-# add a cleanup hook so that grok will bootstrap itself again whenever
+# Add a cleanup hook so that grok will bootstrap itself again whenever
# the Component Architecture is torn down.
def resetBootstrap():
global _bootstrapped
@@ -68,7 +66,7 @@
from zope.testing.cleanup import addCleanUp
addCleanUp(resetBootstrap)
-
+_grokked_modules = [] # This is deliberately a list as grokking order matters.
def do_grok(dotted_name):
global _bootstrapped
if not _bootstrapped:
@@ -77,14 +75,58 @@
module_info = scan.module_info_from_dotted_name(dotted_name)
grok_tree(module_info)
+ _grokked_modules.append(dotted_name)
-
+_grokked_tree = set() # all modules that were ever looked at for grokking
+_grokked_paths = set() # paths to watch for reload
def grok_tree(module_info):
grokker.grokkerRegistry.grok(module_info)
+ _grokked_tree.add(module_info.dotted_name)
+ # Remember the file path to look at for reload; also remember the
+ # directory it's in, in case a new module is placed next to it or
+ # it's removed.
+ _grokked_paths.add(module_info.path)
+ _grokked_paths.add(os.path.dirname(module_info.path))
+
for sub_module_info in module_info.getSubModuleInfos():
grok_tree(sub_module_info)
+_last_reload = time.time()
+def reload_grokked_modules():
+ # Throw away the registrations and reinitialize the grokker
+ # registry (including registering grok's default grokkers again).
+ grokker.grokkerRegistry.ungrok()
+ grokker.grokkerRegistry.clear()
+ grokker.grokkerRegistry.grok(scan.module_info_from_module(meta))
+
+ # Throw away the modules so they will have to be reimported.
+ for dotted_name in _grokked_tree:
+ del sys.modules[dotted_name]
+ _grokked_tree.clear()
+ _grokked_paths.clear()
+ # TODO what if the new modules contain errors?
+
+ # re-grok all the modules that were grokked before (not the tree,
+ # just the ones that were actually passed to do_grok, e.g. the
+ # packages)
+ grok_again = _grokked_modules[:]
+ _grokked_modules[:] = []
+ for dotted_name in grok_again:
+ do_grok(dotted_name)
+
+ global _last_reload
+ _last_reload = time.time()
+
+def check_reload():
+ """Determines whether reload is necessary"""
+ for path in _grokked_paths:
+ if not os.path.exists(path):
+ # The file was probably deleted, so reload.
+ return True
+ if os.stat(path).st_mtime > _last_reload:
+ return True
+
# decorators
class SubscribeDecorator:
Modified: grok/branches/philikon-reload/src/grok/components.py
===================================================================
--- grok/branches/philikon-reload/src/grok/components.py 2007-01-29 22:49:12 UTC (rev 72256)
+++ grok/branches/philikon-reload/src/grok/components.py 2007-01-29 23:03:52 UTC (rev 72257)
@@ -71,7 +71,10 @@
def register(self, context, name, factory, module_info, templates):
raise NotImplementedError
+ def ungrok(self):
+ raise NotImplementedError
+
class InstanceGrokker(GrokkerBase):
"""Grokker for particular instances in a module.
"""
@@ -83,7 +86,10 @@
def register(self, context, name, instance, module_info, templates):
raise NotImplementedError
+ def ungrok(self):
+ raise NotImplementedError
+
class ModuleGrokker(GrokkerBase):
"""Grokker that gets executed once for a module.
"""
@@ -95,7 +101,10 @@
def register(self, context, module_info, templates):
raise NotImplementedError
+ def ungrok(self):
+ raise NotImplementedError
+
class Model(Contained, persistent.Persistent):
# XXX Inheritance order is important here. If we reverse this,
# then containers can't be models anymore because no unambigous MRO
Modified: grok/branches/philikon-reload/src/grok/grokker.py
===================================================================
--- grok/branches/philikon-reload/src/grok/grokker.py 2007-01-29 22:49:12 UTC (rev 72256)
+++ grok/branches/philikon-reload/src/grok/grokker.py 2007-01-29 23:03:52 UTC (rev 72257)
@@ -87,7 +87,14 @@
templates.registerUnassociated(context, module_info)
+ def ungrok(self):
+ for grokker in self._grokkers:
+ try:
+ grokker.ungrok()
+ except NotImplementedError:
+ pass
+
# deep meta mode here - we define grokkers for grok.ClassGrokker,
# grok.InstanceGrokker, and grokker.ModuleGrokker.
Modified: grok/branches/philikon-reload/src/grok/meta.py
===================================================================
--- grok/branches/philikon-reload/src/grok/meta.py 2007-01-29 22:49:12 UTC (rev 72256)
+++ grok/branches/philikon-reload/src/grok/meta.py 2007-01-29 23:03:52 UTC (rev 72257)
@@ -2,11 +2,12 @@
import zope.component.interface
from zope import interface, component
+from zope.component.globalregistry import globalSiteManager
from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
IBrowserRequest,
IBrowserPublisher)
from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
-from zope.security.checker import NamesChecker, defineChecker
+from zope.security.checker import NamesChecker, defineChecker, undefineChecker
from zope.security.permission import Permission
from zope.security.interfaces import IPermission
from zope.annotation.interfaces import IAnnotations
@@ -37,6 +38,9 @@
class AdapterGrokker(grok.ClassGrokker):
component_class = grok.Adapter
+ def __init__(self):
+ self.adapters = []
+
def register(self, context, name, factory, module_info, templates):
adapter_context = util.determine_class_context(factory, context)
provides = util.class_annotation(factory, 'grok.provides', None)
@@ -46,10 +50,19 @@
component.provideAdapter(factory, adapts=(adapter_context,),
provides=provides,
name=name)
+ self.adapters.append((factory, (adapter_context,), provides, name))
+ def ungrok(self):
+ for factory, adapts, provides, name in self.adapters:
+ globalSiteManager.unregisterAdapter(factory, adapts, provides, name)
+ self.adapters[:] = []
+
class MultiAdapterGrokker(grok.ClassGrokker):
component_class = grok.MultiAdapter
-
+
+ def __init__(self):
+ self.adapters = []
+
def register(self, context, name, factory, module_info, templates):
provides = util.class_annotation(factory, 'grok.provides', None)
if provides is None:
@@ -57,17 +70,35 @@
util.check_adapts(factory)
name = util.class_annotation(factory, 'grok.name', '')
component.provideAdapter(factory, provides=provides, name=name)
+ self.adapters.append((factory, provides, name))
+ def ungrok(self):
+ for factory, provides, name in self.adapters:
+ globalSiteManager.unregisterAdapter(factory, provided=provided,
+ name=name)
+ self.adapters[:] = []
+
class GlobalUtilityGrokker(grok.ClassGrokker):
component_class = grok.GlobalUtility
+ def __init__(self):
+ self.utilities = []
+
def register(self, context, name, factory, module_info, templates):
provides = util.class_annotation(factory, 'grok.provides', None)
if provides is None:
util.check_implements_one(factory)
name = util.class_annotation(factory, 'grok.name', '')
- component.provideUtility(factory(), provides=provides, name=name)
+ utility = factory()
+ component.provideUtility(utility, provides=provided, name=name)
+ self.utilities.append((utility, provides, name))
+ def ungrok(self):
+ for utility, provides, name in self.utilities:
+ globalSiteManager.unregisterUtility(utility, provided=provides,
+ name=name)
+ self.utilities[:] = []
+
class XMLRPCGrokker(grok.ClassGrokker):
component_class = grok.XMLRPC
@@ -114,6 +145,9 @@
class ViewGrokker(grok.ClassGrokker):
component_class = grok.View
+ def __init__(self):
+ self.adapters = []
+
def register(self, context, name, factory, module_info, templates):
view_context = util.determine_class_context(factory, context)
@@ -188,6 +222,8 @@
adapts=(view_context, IDefaultBrowserLayer),
provides=interface.Interface,
name=view_name)
+ self.adapters.append((factory, (view_context, IDefaultBrowserLayer),
+ interface.Interface, view_name))
# protect view, public by default
permissions = util.class_annotation(factory, 'grok.require', [])
@@ -219,6 +255,12 @@
'for XML-RPC methods.'
% (method.__name__, factory), factory)
+ def ungrok(self):
+ for factory, adapts, provides, name in self.adapters:
+ globalSiteManager.unregisterAdapter(factory, adapts, provides, name)
+ undefineChecker(factory)
+ self.adapters[:] = []
+
class TraverserGrokker(grok.ClassGrokker):
component_class = grok.Traverser
Modified: grok/branches/philikon-reload/src/grok/publication.py
===================================================================
--- grok/branches/philikon-reload/src/grok/publication.py 2007-01-29 22:49:12 UTC (rev 72256)
+++ grok/branches/philikon-reload/src/grok/publication.py 2007-01-29 23:03:52 UTC (rev 72257)
@@ -13,18 +13,34 @@
##############################################################################
"""Grok publication objects
"""
+import transaction
+import ZODB.Connection
from zope.security.proxy import removeSecurityProxy
from zope.security.checker import selectChecker
+from zope.publisher.interfaces import Redirect
from zope.app.publication.http import BaseHTTPPublication
from zope.app.publication.browser import BrowserPublication
from zope.app.publication.requestpublicationfactories import \
BrowserFactory, XMLRPCFactory
+from grok._grok import check_reload, reload_grokked_modules
class ZopePublicationSansProxy(object):
+ def openedConnection(self, conn):
+ if check_reload():
+ reload_grokked_modules()
+
+ # Reset ZODB's pickle caches so that persistent objects
+ # use the new classes.
+ # XXX doesn't work?!?
+ ZODB.Connection.resetCaches()
+ transaction.commit()
+ conn._resetCache()
+ transaction.begin()
+
def getApplication(self, request):
result = super(ZopePublicationSansProxy, self).getApplication(request)
return removeSecurityProxy(result)
More information about the Checkins
mailing list