divmod-dev team mailing list archive
-
divmod-dev team
-
Mailing list archive
-
Message #00437
[Merge] lp:~divmod-dev/divmod.org/829879-pypy-finalization-semantics into lp:divmod.org
Tristan Seligmann has proposed merging lp:~divmod-dev/divmod.org/829879-pypy-finalization-semantics into lp:divmod.org.
Requested reviews:
Laurens Van Houtven (lvh)
Related bugs:
Bug #829879 in Divmod Axiom: "Axiom finalization cache does not work on PyPy"
https://bugs.launchpad.net/divmod-axiom/+bug/829879
For more details, see:
https://code.launchpad.net/~divmod-dev/divmod.org/829879-pypy-finalization-semantics/+merge/172946
Resubmitting without the "optional-pycrypto" dependency, as this is only relevant to Mantissa.
--
https://code.launchpad.net/~divmod-dev/divmod.org/829879-pypy-finalization-semantics/+merge/172946
Your team Divmod-dev is subscribed to branch lp:divmod.org.
=== modified file 'Axiom/axiom/_fincache.py'
--- Axiom/axiom/_fincache.py 2006-04-01 19:46:29 +0000
+++ Axiom/axiom/_fincache.py 2013-07-04 03:46:28 +0000
@@ -1,4 +1,3 @@
-
from weakref import ref
from traceback import print_exc
@@ -6,11 +5,12 @@
from axiom import iaxiom
-class CacheFault(RuntimeError):
- """
- A serious problem has occurred within the cache. This error is internal
- and should never really be trapped.
- """
+class CacheFault(KeyError):
+ """
+ An item has fallen out of cache, but the weakref callback has not yet run.
+ """
+
+
def logErrorNoMatterWhat():
try:
@@ -27,6 +27,8 @@
# to. Don't bother.
return
+
+
def createCacheRemoveCallback(w, k, f):
def remove(self):
# Weakref callbacks cannot raise exceptions or DOOM ensues
@@ -37,12 +39,16 @@
try:
self = w()
if self is not None:
- del self.data[k]
+ try:
+ del self.data[k]
+ except KeyError:
+ # Already gone
+ pass
except:
logErrorNoMatterWhat()
return remove
-PROFILING = False
+
class FinalizingCache:
"""Possibly useful for infrastructure? This would be a nice addition (or
@@ -50,9 +56,7 @@
"""
def __init__(self):
self.data = {}
- if not PROFILING:
- # see docstring for 'has'
- self.has = self.data.has_key
+
def cache(self, key, value):
fin = value.__finalizer__()
@@ -61,28 +65,28 @@
ref(self), key, fin))
return value
+
def uncache(self, key, value):
- assert self.get(key) is value
- del self.data[key]
-
- def has(self, key):
- """Does the cache have this key?
-
- (This implementation is only used if the system is being profiled, due
- to bugs in Python's old profiler and its interaction with weakrefs.
- Set the module attribute PROFILING to True at startup for this.)
- """
- if key in self.data:
- o = self.data[key]()
- if o is None:
- del self.data[key]
- return False
- return True
- return False
+ """
+ Remove a key from the cache.
+
+ As a sanity check, if the specified key is present in the cache, it
+ must have the given value.
+
+ @param key: The key to remove.
+ @param value: The expected value for the key.
+ """
+ try:
+ assert self.get(key) is value
+ del self.data[key]
+ except KeyError:
+ pass
+
def get(self, key):
o = self.data[key]()
if o is None:
+ del self.data[key]
raise CacheFault(
"FinalizingCache has %r but its value is no more." % (key,))
log.msg(interface=iaxiom.IStatEvent, stat_cache_hits=1, key=key)
=== modified file 'Axiom/axiom/store.py'
--- Axiom/axiom/store.py 2012-08-04 11:07:11 +0000
+++ Axiom/axiom/store.py 2013-07-04 03:46:28 +0000
@@ -1747,10 +1747,10 @@
self.executeSQL(sql, insertArgs)
def _loadedItem(self, itemClass, storeID, attrs):
- if self.objectCache.has(storeID):
+ try:
result = self.objectCache.get(storeID)
# XXX do checks on consistency between attrs and DB object, maybe?
- else:
+ except KeyError:
result = itemClass.existingInStore(self, storeID, attrs)
if not result.__legacy__:
self.objectCache.cache(storeID, result)
@@ -2205,8 +2205,10 @@
type(storeID).__name__,))
if storeID == STORE_SELF_ID:
return self
- if self.objectCache.has(storeID):
+ try:
return self.objectCache.get(storeID)
+ except KeyError:
+ pass
log.msg(interface=iaxiom.IStatEvent, stat_cache_misses=1, key=storeID)
results = self.querySchemaSQL(_schema.TYPEOF_QUERY, [storeID])
assert (len(results) in [1, 0]),\
=== modified file 'Axiom/axiom/substore.py'
--- Axiom/axiom/substore.py 2007-11-15 19:58:09 +0000
+++ Axiom/axiom/substore.py 2013-07-04 03:46:28 +0000
@@ -26,6 +26,9 @@
"""
Create a new SubStore, allocating a new file space for it.
"""
+ if isinstance(pathSegments, basestring):
+ raise ValueError(
+ 'Received %s instead of a sequence' % (pathSegments,))
if store.dbdir is None:
self = cls(store=store, storepath=None)
else:
@@ -37,11 +40,13 @@
createNew = classmethod(createNew)
+
def close(self):
self.substore.close()
del self.substore._openSubStore
del self.substore
+
def open(self, debug=False):
if hasattr(self, 'substore'):
return self.substore
@@ -51,6 +56,7 @@
# store is alive!
return s
+
def createStore(self, debug):
"""
Create the actual Store this Substore represents.
@@ -73,6 +79,7 @@
idInParent=self.storeID,
debug=debug)
+
def __conform__(self, interface):
"""
I adapt my store object to whatever interface I am adapted to. This
@@ -84,6 +91,7 @@
ifa = interface(self.open(debug=self.store.debug), None)
return ifa
+
def indirect(self, interface):
"""
Like __conform__, I adapt my store to whatever interface I am asked to
@@ -94,6 +102,7 @@
return interface(self)
+
class SubStoreStartupService(Item, service.Service):
"""
This class no longer exists. It is here simply to trigger an upgrade which
=== modified file 'Axiom/axiom/test/test_batch.py'
--- Axiom/axiom/test/test_batch.py 2012-07-05 13:37:40 +0000
+++ Axiom/axiom/test/test_batch.py 2013-07-04 03:46:28 +0000
@@ -560,7 +560,7 @@
"""
dbdir = filepath.FilePath(self.mktemp())
s = store.Store(dbdir)
- ss = substore.SubStore.createNew(s, 'substore')
+ ss = substore.SubStore.createNew(s, ['substore'])
bs = iaxiom.IBatchService(ss)
self.failUnless(iaxiom.IBatchService.providedBy(bs))
@@ -582,12 +582,15 @@
"""
dbdir = filepath.FilePath(self.mktemp())
s = store.Store(dbdir)
- ss = substore.SubStore.createNew(s, 'substore')
+ ss = substore.SubStore.createNew(s, ['substore'])
service.IService(s).startService()
- d = iaxiom.IBatchService(ss).call(BatchCallTestItem(store=ss.open()).callIt)
+ d = iaxiom.IBatchService(ss).call(
+ BatchCallTestItem(store=ss.open()).callIt)
ss.close()
def called(ign):
- self.failUnless(ss.open().findUnique(BatchCallTestItem).called, "Was not called")
+ self.assertTrue(
+ ss.open().findUnique(BatchCallTestItem).called,
+ "Was not called")
return service.IService(s).stopService()
return d.addCallback(called)
=== modified file 'Axiom/axiom/test/test_reference.py'
--- Axiom/axiom/test/test_reference.py 2009-07-06 12:35:46 +0000
+++ Axiom/axiom/test/test_reference.py 2013-07-04 03:46:28 +0000
@@ -186,7 +186,7 @@
store = Store()
t = nonUpgradedItem(store=store)
self.assertEquals(t.__legacy__, True)
- self.assertFalse(store.objectCache.has(t.storeID))
+ self.assertRaises(KeyError, store.objectCache.get, t.storeID)
t2 = store.getItemByID(t.storeID)
self.assertNotIdentical(t, t2)
self.assertTrue(isinstance(t2, UpgradedItem))
=== modified file 'Axiom/axiom/test/test_substore.py'
--- Axiom/axiom/test/test_substore.py 2008-05-06 13:33:30 +0000
+++ Axiom/axiom/test/test_substore.py 2013-07-04 03:46:28 +0000
@@ -12,15 +12,14 @@
class SubStored(Item):
-
schemaVersion = 1
typeName = 'substoredthing'
a = text()
b = bytes()
+
class YouCantStartThis(Item, Service):
-
parent = inmemory()
running = inmemory()
name = inmemory()
@@ -30,8 +29,9 @@
def startService(self):
self.started = True
+
+
class YouShouldStartThis(Item, Service):
-
parent = inmemory()
running = inmemory()
name = inmemory()
@@ -42,11 +42,11 @@
self.started = True
+
class SubStoreTest(unittest.TestCase):
"""
Test on-disk creation of substores.
"""
-
def testOneThing(self):
"""
Ensure that items can be inserted into substores and
@@ -72,6 +72,7 @@
self.assertEquals(reopenssd.a, u'hello world')
self.assertEquals(reopenssd.b, 'what, its text')
+
def test_oneThingMemory(self):
"""
Ensure that items put into in-memory substores are retrievable.
@@ -113,6 +114,7 @@
self.assertEquals(item.a, u'hello world')
self.assertEquals(item.b, 'what, its text')
+
def test_memorySubstoreFile(self):
"""
In-memory substores whose stores have file directories should be able
@@ -127,6 +129,17 @@
f.close()
self.assertEqual(open(f.finalpath.path).read(), "yay")
+
+ def test_createNewStringPath(self):
+ """
+ Passing a string instead of a sequence of strings to
+ L{SubStore.createNew} results in an exception.
+ """
+ s = Store()
+ self.assertRaises(ValueError, SubStore.createNew, s, 'notasequence')
+
+
+
class SubStoreStartupSemantics(unittest.TestCase):
"""
These tests verify that interactions between store and substore services
@@ -134,10 +147,10 @@
behavior. Read the code if you are interested in how to get startup
notifications from substore items.
"""
-
def setUp(self):
"""
- Set up the tests by creating a store and a substore and opening them both.
+ Set up the tests by creating a store and a substore and opening them
+ both.
"""
self.topdb = topdb = Store(filepath.FilePath(self.mktemp()))
self.ssitem = ssitem = SubStore.createNew(
@@ -147,8 +160,8 @@
def testDontStartNormally(self):
"""
- Substores' services are not supposed to be started when their parent stores
- are.
+ Substores' services are not supposed to be started when their parent
+ stores are.
"""
ss = self.ss
ycst = YouCantStartThis(store=ss)
@@ -183,4 +196,3 @@
"""
if self.serviceStarted:
return IService(self.topdb).stopService()
-
=== modified file 'Axiom/axiom/test/test_xatop.py'
--- Axiom/axiom/test/test_xatop.py 2010-04-03 12:38:34 +0000
+++ Axiom/axiom/test/test_xatop.py 2013-07-04 03:46:28 +0000
@@ -203,40 +203,6 @@
schema[2], schema[3], schema[4])] + schema[1:]})
- def test_inMemorySchemaCacheReset(self):
- """
- The global in-memory table schema cache should not change the behavior
- of consistency checking with respect to the redefinition of in-memory
- schemas.
-
- This test is verifying the behavior which is granted by the use of a
- WeakKeyDictionary for _inMemorySchemaCache. If that cache kept strong
- references to item types or used a (typeName, schemaVersion) key,
- either the second C{SoonToChange} class definition in this method would
- fail or the schema defined by the first C{SoonToChange} class would be
- used, even after it should have been replaced by the second definition.
- """
- class SoonToChange(item.Item):
- attribute = attributes.integer()
-
- dbpath = self.mktemp()
- s = store.Store(dbpath)
- SoonToChange(store=s)
- s.close()
-
- # This causes a Store._checkTypeSchemaConsistency to cache
- # SoonToChange.
- s = store.Store(dbpath)
- s.close()
-
- del SoonToChange, s
-
- class SoonToChange(item.Item):
- attribute = attributes.boolean()
-
- self.assertRaises(RuntimeError, store.Store, dbpath)
-
-
def test_checkOutdatedTypeSchema(self):
"""
L{Store._checkTypeSchemaConsistency} raises L{RuntimeError} if the type
@@ -270,11 +236,10 @@
SoonToChange(store=s)
s.close()
- # Get rid of both the type and the store so that we can define a new
- # incompatible version. It might be nice if closed stores didn't keep
- # references to types, but whatever. This kind of behavior isn't
- # really supported, only the unit tests need to do it for now.
- del SoonToChange, s
+ # Get rid of the cached information about this type
+ from axiom.item import _typeNameToMostRecentClass
+ del _typeNameToMostRecentClass[SoonToChange.typeName]
+ del SoonToChange
class SoonToChange(item.Item):
attribute = attributes.boolean()
@@ -353,48 +318,6 @@
secondary._indexNameOf(TestItem, ['bar', 'baz'])]))
- def test_inMemoryIndexCacheReset(self):
- """
- The global in-memory index schema cache should not change the behavior
- of index creation with respect to the redefinition of in-memory
- schemas.
-
- This test is verifying the behavior which is granted by the use of a
- WeakKeyDictionary for _requiredTableIndexes. If that cache kept strong
- references to item types or used a (typeName, schemaVersion) key,
- either the second C{SoonToChange} class definition in this method would
- fail or the indexes on the schema defined by the first C{SoonToChange}
- class would be used, even after it should have been replaced by the
- second definition.
- """
- class SoonToChange(item.Item):
- attribute = attributes.integer()
-
- dbpath = self.mktemp()
- s = store.Store(dbpath)
-
- before = s._loadExistingIndexes()
- SoonToChange(store=s)
- after = s._loadExistingIndexes()
-
- # Sanity check - this version of SoonToChange has no indexes.
- self.assertEqual(before, after)
-
- s.close()
- del SoonToChange, s
-
- class SoonToChange(item.Item):
- attribute = attributes.boolean(indexed=True)
-
- s = store.Store()
- before = s._loadExistingIndexes()
- SoonToChange(store=s)
- after = s._loadExistingIndexes()
- self.assertEqual(
- after - before,
- set([s._indexNameOf(SoonToChange, ['attribute'])]))
-
-
def test_loadPythonModuleHint(self):
"""
If the Python definition of a type found in a Store has not yet been
=== modified file 'Mantissa/xmantissa/terminal.py'
--- Mantissa/xmantissa/terminal.py 2011-07-18 12:37:13 +0000
+++ Mantissa/xmantissa/terminal.py 2013-07-04 03:46:28 +0000
@@ -10,7 +10,10 @@
from hashlib import md5
-from Crypto.PublicKey import RSA
+try:
+ from Crypto.PublicKey import RSA
+except ImportError:
+ RSA = None
from zope.interface import implements
@@ -20,12 +23,16 @@
from twisted.cred.portal import IRealm, Portal
from twisted.cred.checkers import ICredentialsChecker
from twisted.conch.interfaces import IConchUser, ISession
-from twisted.conch.ssh.factory import SSHFactory
-from twisted.conch.ssh.keys import Key
-from twisted.conch.manhole_ssh import TerminalUser, TerminalSession, TerminalSessionTransport
-from twisted.conch.insults.insults import ServerProtocol, TerminalProtocol
-from twisted.conch.insults.window import TopWindow, VBox, Border, Button
-from twisted.conch.manhole import ColoredManhole
+try:
+ from twisted.conch.ssh.factory import SSHFactory
+ from twisted.conch.ssh.keys import Key
+ from twisted.conch.manhole_ssh import TerminalUser, TerminalSession, TerminalSessionTransport
+ from twisted.conch.insults.insults import ServerProtocol, TerminalProtocol
+ from twisted.conch.insults.window import TopWindow, VBox, Border, Button
+ from twisted.conch.manhole import ColoredManhole
+ conch = True
+except ImportError:
+ conch = False
from axiom.iaxiom import IPowerupIndirector
from axiom.item import Item
@@ -140,156 +147,162 @@
-class ShellServer(TerminalProtocol):
- """
- A terminal protocol which finds L{ITerminalServerFactory} powerups in the
- same store and presents the option of beginning a session with one of them.
-
- @ivar _store: The L{Store} which will be searched for
- L{ITerminalServerFactory} powerups.
-
- @ivar _protocol: If an L{ITerminalServerFactory} has been selected to
- interact with, then this attribute refers to the L{ITerminalProtocol}
- produced by that factory's C{buildTerminalProtocol} method. Input from
- the terminal is delivered to this protocol. This attribute is C{None}
- whenever the "main menu" user interface is being displayed.
-
- @ivar _window: A L{TopWindow} instance which contains the "main menu" user
- interface. Whenever the C{_protocol} attribute is C{None}, input is
- directed to this object instead. Whenever the C{_protocol} attribute
- is not C{None}, this window is hidden.
- """
- _width = 80
- _height = 24
-
- _protocol = None
-
- def __init__(self, store):
- TerminalProtocol.__init__(self)
- self._store = store
-
-
- def _draw(self):
- """
- Call the drawing API for the main menu widget with the current known
- terminal size and the terminal.
- """
- self._window.draw(self._width, self._height, self.terminal)
-
-
- def _appButtons(self):
- for factory in self._store.powerupsFor(ITerminalServerFactory):
- yield Button(
- factory.name.encode('utf-8'),
- lambda factory=factory: self.switchTo(factory))
-
-
- def _logoffButton(self):
- return Button("logoff", self.logoff)
-
-
- def _makeWindow(self):
- buttons = VBox()
- for button in self._appButtons():
- buttons.addChild(Border(button))
- buttons.addChild(Border(self._logoffButton()))
-
- from twisted.internet import reactor
- window = TopWindow(self._draw, lambda f: reactor.callLater(0, f))
- window.addChild(Border(buttons))
- return window
-
-
- def connectionMade(self):
- """
- Reset the terminal and create a UI for selecting an application to use.
- """
- self.terminal.reset()
- self._window = self._makeWindow()
-
-
- def reactivate(self):
- """
- Called when a sub-protocol is finished. This disconnects the
- sub-protocol and redraws the main menu UI.
- """
- self._protocol.connectionLost(None)
- self._protocol = None
- self.terminal.reset()
- self._window.filthy()
- self._window.repaint()
-
-
- def switchTo(self, app):
- """
- Use the given L{ITerminalServerFactory} to create a new
- L{ITerminalProtocol} and connect it to C{self.terminal} (such that it
- cannot actually disconnect, but can do most anything else). Control of
- the terminal is delegated to it until it gives up that control by
- disconnecting itself from the terminal.
-
- @type app: L{ITerminalServerFactory} provider
- @param app: The factory which will be used to create a protocol
- instance.
- """
- viewer = _AuthenticatedShellViewer(list(getAccountNames(self._store)))
- self._protocol = app.buildTerminalProtocol(viewer)
- self._protocol.makeConnection(_ReturnToMenuWrapper(self, self.terminal))
-
-
- def keystrokeReceived(self, keyID, modifier):
- """
- Forward input events to the application-supplied protocol if one is
- currently active, otherwise forward them to the main menu UI.
- """
- if self._protocol is not None:
- self._protocol.keystrokeReceived(keyID, modifier)
- else:
- self._window.keystrokeReceived(keyID, modifier)
-
-
- def logoff(self):
- """
- Disconnect from the terminal completely.
- """
- self.terminal.loseConnection()
-
-
-
-class _BetterTerminalSession(TerminalSession):
- """
- L{TerminalSession} is missing C{windowChanged} and C{eofReceived} for some
- reason. Add it here until it's fixed in Twisted. See Twisted ticket
- #3303.
- """
- def windowChanged(self, newWindowSize):
- """
- Ignore window size change events.
- """
-
-
- def eofReceived(self):
- """
- Ignore the eof event.
- """
-
-
-class _BetterTerminalUser(TerminalUser):
- """
- L{TerminalUser} is missing C{conn} for some reason reason (probably the
- reason that it's not a very great thing and generally an implementation
- will be missing it for a while). Add it here until it's fixed in Twisted.
- See Twisted ticket #3863.
- """
- # Some code in conch will rudely rebind this attribute later. For now,
- # make sure that it is at least bound to something so that the object
- # appears to fully implement IConchUser. Most likely, TerminalUser should
- # be taking care of this, not us. Or even better, this attribute shouldn't
- # be part of the interface; some better means should be provided for
- # informing the IConchUser avatar of the connection object (I'm not even
- # sure why the avatar would care about having a reference to the connection
- # object).
- conn = None
+if conch:
+ class ShellServer(TerminalProtocol):
+ """
+ A terminal protocol which finds L{ITerminalServerFactory} powerups in
+ the same store and presents the option of beginning a session with one
+ of them.
+
+ @ivar _store: The L{Store} which will be searched for
+ L{ITerminalServerFactory} powerups.
+
+ @ivar _protocol: If an L{ITerminalServerFactory} has been selected to
+ interact with, then this attribute refers to the
+ L{ITerminalProtocol} produced by that factory's
+ C{buildTerminalProtocol} method. Input from the terminal is
+ delivered to this protocol. This attribute is C{None} whenever the
+ "main menu" user interface is being displayed.
+
+ @ivar _window: A L{TopWindow} instance which contains the "main menu"
+ user interface. Whenever the C{_protocol} attribute is C{None},
+ input is directed to this object instead. Whenever the
+ C{_protocol} attribute is not C{None}, this window is hidden.
+ """
+ _width = 80
+ _height = 24
+
+ _protocol = None
+
+ def __init__(self, store):
+ TerminalProtocol.__init__(self)
+ self._store = store
+
+
+ def _draw(self):
+ """
+ Call the drawing API for the main menu widget with the current
+ known terminal size and the terminal.
+ """
+ self._window.draw(self._width, self._height, self.terminal)
+
+
+ def _appButtons(self):
+ for factory in self._store.powerupsFor(ITerminalServerFactory):
+ yield Button(
+ factory.name.encode('utf-8'),
+ lambda factory=factory: self.switchTo(factory))
+
+
+ def _logoffButton(self):
+ return Button("logoff", self.logoff)
+
+
+ def _makeWindow(self):
+ buttons = VBox()
+ for button in self._appButtons():
+ buttons.addChild(Border(button))
+ buttons.addChild(Border(self._logoffButton()))
+
+ from twisted.internet import reactor
+ window = TopWindow(self._draw, lambda f: reactor.callLater(0, f))
+ window.addChild(Border(buttons))
+ return window
+
+
+ def connectionMade(self):
+ """
+ Reset the terminal and create a UI for selecting an application to
+ use.
+ """
+ self.terminal.reset()
+ self._window = self._makeWindow()
+
+
+ def reactivate(self):
+ """
+ Called when a sub-protocol is finished. This disconnects the
+ sub-protocol and redraws the main menu UI.
+ """
+ self._protocol.connectionLost(None)
+ self._protocol = None
+ self.terminal.reset()
+ self._window.filthy()
+ self._window.repaint()
+
+
+ def switchTo(self, app):
+ """
+ Use the given L{ITerminalServerFactory} to create a new
+ L{ITerminalProtocol} and connect it to C{self.terminal} (such that
+ it cannot actually disconnect, but can do most anything else).
+ Control of the terminal is delegated to it until it gives up that
+ control by disconnecting itself from the terminal.
+
+ @type app: L{ITerminalServerFactory} provider
+ @param app: The factory which will be used to create a protocol
+ instance.
+ """
+ viewer = _AuthenticatedShellViewer(
+ list(getAccountNames(self._store)))
+ self._protocol = app.buildTerminalProtocol(viewer)
+ self._protocol.makeConnection(
+ _ReturnToMenuWrapper(self, self.terminal))
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ """
+ Forward input events to the application-supplied protocol if one is
+ currently active, otherwise forward them to the main menu UI.
+ """
+ if self._protocol is not None:
+ self._protocol.keystrokeReceived(keyID, modifier)
+ else:
+ self._window.keystrokeReceived(keyID, modifier)
+
+
+ def logoff(self):
+ """
+ Disconnect from the terminal completely.
+ """
+ self.terminal.loseConnection()
+
+
+
+ class _BetterTerminalSession(TerminalSession):
+ """
+ L{TerminalSession} is missing C{windowChanged} and C{eofReceived} for
+ some reason. Add it here until it's fixed in Twisted. See Twisted
+ ticket #3303.
+ """
+ def windowChanged(self, newWindowSize):
+ """
+ Ignore window size change events.
+ """
+
+
+ def eofReceived(self):
+ """
+ Ignore the eof event.
+ """
+
+
+ class _BetterTerminalUser(TerminalUser):
+ """
+ L{TerminalUser} is missing C{conn} for some reason reason (probably the
+ reason that it's not a very great thing and generally an implementation
+ will be missing it for a while). Add it here until it's fixed in
+ Twisted. See Twisted ticket #3863.
+ """
+ # Some code in conch will rudely rebind this attribute later. For now,
+ # make sure that it is at least bound to something so that the object
+ # appears to fully implement IConchUser. Most likely, TerminalUser
+ # should be taking care of this, not us. Or even better, this
+ # attribute shouldn't be part of the interface; some better means
+ # should be provided for informing the IConchUser avatar of the
+ # connection object (I'm not even sure why the avatar would care about
+ # having a reference to the connection object).
+ conn = None
class ShellAccount(Item):
=== modified file 'Mantissa/xmantissa/webadmin.py'
--- Mantissa/xmantissa/webadmin.py 2009-07-29 10:19:26 +0000
+++ Mantissa/xmantissa/webadmin.py 2013-07-04 03:46:28 +0000
@@ -34,7 +34,10 @@
from xmantissa.scrolltable import ScrollingFragment
from xmantissa.webapp import PrivateApplication
from xmantissa.website import WebSite, PrefixURLMixin
-from xmantissa.terminal import TerminalManhole
+try:
+ from xmantissa.terminal import TerminalManhole
+except ImportError:
+ TerminalManhole = None
from xmantissa.ixmantissa import (
INavigableElement, INavigableFragment, ISessionlessSiteRootPlugin,
IProtocolFactoryFactory)
Follow ups