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 for Context 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)
on_create(callable)[source]

Registers provided callable to be called whenever a new Context object is created. The callback will receive the newly created Context object as its sole argument.

on_destroy(callable)[source]

Registers provided callable to be called whenever a Context object is destroyed. The callback will receive two arguments:

  • The Context object, which is about to be destroyed, and
  • an optional exception, which was thrown before the Context was gracefully destroyed.
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.