score.ctx¶
Every single interaction with an application is strongly tied to an environment, where the interaction is taking place. There is probably an authenticated user, a running database transaction, connections to remote servers, etc.
This module provides a framework for defining the parameters of these environments, allowing other modules to provide valuable information relevant to the current interaction. It basically implements the Context Object Pattern.
A context, as defined by this module, can be regarded as a smaller sibling of the HTTP session: It contains all required data to serve an HTTP request, for example.
A context is not tied to interaction through HTTP, though. When a user opens a shell to the application, the application should also create a new context, where it could store the id of the authenticated user.
Quickstart¶
Once the module is initialized, you can register context members by calling score.ctx.ConfiguredCtxModule.register()
:
>>> import random
>>> import score.ctx
>>> ctx_conf = score.ctx.init()
>>> ctx_conf.register('randnum', lambda: random.randint(0, 10))
These registered context members are available under the given name in every
Context
:
>>> ctx = ctx_conf.Context()
>>> ctx.randnum
4
>>> ctx.randnum
4
>>> ctx.randnum
4
As you can see, the value of the context member is cached by default. If you want your value to be evaluated anew each time, you will have to disable caching during registration:
>>> import random
>>> import score.ctx
>>> ctx_conf = score.ctx.init()
>>> ctx_conf.register('randnum', lambda: random.randint(0, 10), cached=False)
>>> ctx = ctx_conf.Context()
>>> ctx.randnum
2
>>> ctx.randnum
8
Configuration¶
This module adheres to our module initialization guiedlines, but does not require any configuration: calling its
init()
without arguments is sufficient:
>>> import score.ctx
>>> ctx_conf = score.ctx.init()
Details¶
Transactions¶
Every context object also provides an ITransactionManager
. This transaction manager will be
used to implement a :term: context member called tx, that contains a zope
transaction. That transaction will be committed at the end of the
Context
lifetime. This means that the application does not need to
operate on the global “current” transaction.
Member Destructors¶
It is possible to provide a member destructor for a member during registration:
def construct(ctx):
if hasattr(ctx, 'session_id'):
return load_session(ctx.session_id)
return new_session()
def destruct(ctx, session, exception):
if exception:
session.discard_changes()
else:
session.save()
session.close()
ctx_conf.register('session', construct, destruct)
As you can see, the destructor receives three arguments:
- The context object,
- the value returned by the constructor, and
- the exception, that terminated the context pre-maturely (or None, if the context terminated successfully).
The parameter list changes, though, if the context member is not cached: the second parameter does not really exist in that case.
def construct(ctx):
if not hasattr(ctx, '_num_calls'):
ctx._num_calls = 0
ctx._num_calls += 1
def destruct(ctx, exception):
count = ctx._num_calls if hasattr(ctx, '_num_calls') else 0
logger.debug('Counter called %d times', count)
ctx_conf.register('counter', construct, destruct, cached=False)
API¶
Configuration¶
-
score.ctx.
init
(confdict={})[source]¶ Initializes this module acoording to our module initialization guidelines.
-
class
score.ctx.
ConfiguredCtxModule
[source]¶ This module’s
configuration class
. It acts as a factory forContext
objects and provides an API for registering members on new Context objects, as well as hooks for context construction and destruction events.-
Context
¶ A configured
Context
class, which can be instantiated directly:>>> ctx = ctx_conf.Context()
-
register
(name, constructor, destructor=None, cached=True)[source]¶ Registers a new member on Context objects. This is the function to use when populating future Context objects. An example for fetching the current user from the session:
>>> def constructor(ctx): ... if not ctx.user_id: ... return None ... return ctx.db.query(User).filter(ctx.user_id).first() ... >>> ctx_conf.register('user', constructor) >>> with ctx_conf.Context() as ctx: ... print(ctx.user.age()) ... 25
The only required parameter constructor is a callable, that will be invoked the first time the attribute is accessed on a new Context.
If the object created by the constructor needs to be cleaned up at the end of the context lifetime, it possible to do so in a separate destructor. That callable will receive three parameters:
- The Context object,
- whatever the constructor had returned, and
- an exception, that was caught during the lifetime of the context. This last value is None, if the Context was destroyed without exception.
The value returned by the constructor will be cached in the Context object by default, i.e. the constructor will be called at most once for each Context. It is possible to add a context member, which will be called every time it is accessed by passing a False value as the cached parameter. Note that the destructor will only receive two parameters in this case (the context object and the optional exception).
>>> from datetime import datetime >>> def constructor(ctx): ... return datetime.now() ... >>> def destructor(ctx, exception): ... pass ... >>> ctx_conf.register('now', constructor, destructor, cached=False)
-
-
class
score.ctx.
Context
[source]¶ Base class for Contexts of a ConfiguredCtxModule. Do not use this class directly, use the
Context
member of a ConfiguredCtxModule instead.Every Context object needs to be destroyed manually by calling its
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() ...
-
destroy
(exception=None)[source]¶ 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 context member.
-