# Copyright © 2015 STRG.AT GmbH, Vienna, Austria
#
# This file is part of the The SCORE Framework.
#
# The SCORE Framework and all its parts are free software: you can redistribute
# them and/or modify them under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation which is in the
# file named COPYING.LESSER.txt.
#
# The SCORE Framework and all its parts are distributed without any WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. For more details see the GNU Lesser General Public
# License.
#
# If you have not received a copy of the GNU Lesser General Public License see
# http://www.gnu.org/licenses/.
#
# The License-Agreement realised between you as Licensee and STRG.AT GmbH as
# Licenser including the issue of its valid conclusion and its pre- and
# post-contractual effects is governed by the laws of Austria. Any disputes
# concerning this License-Agreement including the issue of its valid conclusion
# and its pre- and post-contractual effects are exclusively decided by the
# competent court, in whose district STRG.AT GmbH has its registered seat, at
# the discretion of STRG.AT GmbH also the competent court, in whose district the
# Licensee has his registered seat, an establishment or assets.
from collections import namedtuple, OrderedDict
from score.init import ConfiguredModule
from transaction import TransactionManager
from transaction.interfaces import IDataManager
from zope.interface import implementer
[docs]def init(confdict={}):
"""
Initializes this module acoording to :ref:`our module initialization
guidelines <module_initialization>`.
"""
conf = ConfiguredCtxModule()
def constructor(ctx):
tx = ctx.tx_manager.get()
tx.join(_CtxDataManager(ctx))
return tx
conf.register('tx', constructor)
return conf
@implementer(IDataManager)
class _CtxDataManager:
"""
An :interface:`IDataManager <transaction.interfaces.IDataManager>`, which
will remove the `tx' :term:`context member` once the transaction is
committed or aborted.
"""
def __init__(self, ctx):
self.transaction_manager = ctx.tx_manager
self.ctx = ctx
def abort(self, transaction):
del self.ctx.tx
def tpc_begin(self, transaction):
pass
def commit(self, transaction):
pass
def tpc_vote(self, transaction):
pass
def tpc_finish(self, transaction):
del self.ctx.tx
def tpc_abort(self, transaction):
del self.ctx.tx
def sortKey(self):
return 'score.ctx(%d)' % id(self)
CtxMemberRegistration = namedtuple(
'CtxMemberRegistration', 'constructor, destructor, cached')
[docs]class Context:
"""
Base class for Contexts of a ConfiguredCtxModule. Do not use this class
directly, use the :attr:`Context <.ConfiguredCtxModule.Context>` member of a
ConfiguredCtxModule instead.
Every Context object needs to be destroyed manually by calling its
:meth:`.destroy` method. Although this method will be called in the
destructor of this class, that might already be too late. This is the reason
why the preferred way of using this class is within a `with` statement:
>>> with ctx_conf.Context() as ctx:
... ctx.logout_user()
...
"""
def __init__(self):
if not hasattr(self, '_conf'):
raise Exception('Unconfigured Context')
self.log = self._conf.log
self.log.debug('Initializing')
self._constructed_attrs = OrderedDict()
self._active = True
self.tx_manager = TransactionManager()
for callback in self._conf._create_callbacks:
callback(self)
def __del__(self):
if self._active:
self.destroy()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.destroy(value)
def __hasattr__(self, attr):
if self._active:
return attr in self._conf.registrations
return attr in self.__dict__
def __getattr__(self, attr):
if '_active' in self.__dict__ and '_conf' in self.__dict__ and \
self._active and attr in self._conf.registrations:
value = self._conf.registrations[attr].constructor(self)
if self._conf.registrations[attr].cached:
self._constructed_attrs[attr] = value
self.__dict__[attr] = value
else:
self._constructed_attrs[attr] = None
self.log.debug('Creating member %s' % attr)
return value
raise AttributeError(attr)
def __setattr__(self, attr, value):
try:
# call registered contructor, if there is one, so the destructor
# gets called with this new value
getattr(self, attr)
except AttributeError:
pass
self.__dict__[attr] = value
def __delattr__(self, attr):
if attr in self._conf.registrations:
if attr in self._constructed_attrs:
self.__delattr(attr, None)
elif attr in self.__dict__:
del self.__dict__[attr]
else:
del self.__dict__[attr]
def __delattr(self, attr, exception):
"""
Deletes a previously constructed *attr*. Its destructor will receive the
given *exception*.
Note: this function assumes that the *attr* is indeed a registered
context member. It will behave unexpectedly when called with an *attr*
that has no registration.
"""
constructor_value = self._constructed_attrs[attr]
del self._constructed_attrs[attr]
self.log.debug('Deleting member %s' % attr)
destructor = self._conf.registrations[attr].destructor
if destructor:
self.log.debug('Calling destructor of %s' % attr)
if self._conf.registrations[attr].cached:
destructor(self, constructor_value, None)
else:
destructor(self, None)
try:
del self.__dict__[attr]
except KeyError:
# destructor might have deleted self.attr already
pass
[docs] def destroy(self, exception=None):
"""
Cleans up this context and makes it unusable.
After calling this function, this object will lose all its magic and
behave like an empty class.
The optional *exception*, that is the cause of this method call, will be
passed to the destructors of every :term:`context member`.
"""
if not self._active:
return
self.log.debug('Destroying')
self._active = False
for attr in reversed(list(self._constructed_attrs.keys())):
self.__delattr(attr, exception)
for callback in self._conf._destroy_callbacks:
callback(self, exception)
tx = self.tx_manager.get()
if exception or tx.isDoomed():
tx.abort()
else:
tx.commit()