[Subversion] / Contextual / Contextual.txt  

View of /Contextual/Contextual.txt

Parent Directory | Revision Log
Revision: 2297 - (download)
Sat Mar 3 17:41:05 2007 UTC (17 years, 2 months ago) by pje
File size: 12684 byte(s)
Implement better scoping, by allowing states to be context managers.
Also, renamed setting->value and parameter->expression.  Expressions
scan be scoped to services, such that a "resource" is just an
"Action.expression" (i.e. an expression scoped to the nearest Action
service).  Instead of using 'with service_instance:', you now use
'with service_class.new():' to replace a service.

The next refactoring will change keys from being functions, to being
objects, allowing them to be set to either values or expressions
dynamically.  As a side-effect, this will drop the dependencies to
both the SymbolType and ProxyTypes packages, although that's not the
reason for doing it.  The real reason is that it will allow things
like this::

    with context.new():
        some_var  <<  some_value
        other_var <<= lambda:some_expression
        # code that uses these variables

as the canonical way to set variables in a context. 
===================================================================
Creating Ultralight Frameworks with "Contextual" (``peak.context``)
===================================================================

-----------------
Developer's Guide
-----------------

The ``Contextual`` library provides a safe replacement for the use of global
(or thread-local) variables, making it easy to create non-invasive frameworks
that are still highly testable and configurable.  Code based on ``Contextual``
is also safe for use in event-driven, coroutine, or "co-operative" multitasking
systems such as Twisted and ``peak.events``, because an entire thread's context
can be easily saved and restored when switching between different logical
threads, tasks, or coroutines.

Although ``Contextual`` works with Python versions 2.3 and up, the examples in
this guide are written using Python 2.5 and the ``with`` statement for ease of
reading.  If you're using an older version of, you'll need to consult the
section below on `Support for Python 2.3/2.4`_ before you try using any of the
examples that use the ``with`` statement.

Also, under Python 2.5, you'll need to do a ``__future__`` import at the top
of every module that uses the ``with`` statement, and of course you'll need
to import the ``Contextual`` library too:

    >>> from __future__ import with_statement
    >>> from peak import context


Overview and Features
=====================

The basic idea of the ``Contextual`` library is that it allows you to have
"current" instances or values, that are specific to the current call stack in
the current thread.  These "current" values can be temporarily changed
using either a Python 2.5 ``with:`` statement, or, if backward compatibility is
desired, a special decorator that simulates the ``with:`` statement in older
versions of Python.

There are three major kinds of "current" values that ``Contextual`` supports:

Services
    Services are like singletons, but have a separate instance for each thread
    and can easily be replaced as needed for e.g. testing or co-operative
    multitasking.  Services are created by subclassing ``context.Service``, and
    the resulting class's ``get()`` classmethod will return the current
    instance.  Alternative implementations of a service can be defined and used
    to replace the default implementation.

Configuration Settings (Values and Expression)
    ``context.value`` and ``context.expression`` objects provide thread-safe
    access to stateless configuration settings.  Settings can have different
    values in different contexts, but once the setting's value has been
    retrieved or computed in a given context, it can no longer be changed in
    that context.  The idea is that once any code actually *uses* a setting,
    it should be impossible for other code to see a different value for the
    same setting, within the context of all code that has "seen" that value.
    (Of course, you can easily set up nested and/or inheriting contexts with
    different values for the same settings, if you need to.)

Transaction Resources
    The ``context.Action`` service manages access to objects that may need to
    be recycled, saved, or reverted at the completion of an action such as
    a transaction, command, web request, or other atomic operation.  Requesting
    a resource (such as a database connection) from the action service causes
    it to be registered with the current ``Action``, so that it will be
    notified of the success or failure of the action.  This then allows the
    resource to commit or rollback as appropriate, return connections to
    connection pools or close them, etc.

    The boundaries of an action are indicated by the scope of a ``with:``
    statement (or substitute).  E.g.::

        with context.Action():
            # any resources used here will be notified of the
            # success or failure of this block of code

In addition, ``Contextual`` offers some related convenience features:

Parameters
    A ``context.parameter`` is somewhat like a dynamically-scoped variable in
    languages like Lisp.  ``Contextual`` uses parameters internally to keep
    track of what services are currently active, but you can also use them
    directly yourself.

``State`` objects
    State objects can be used to explicitly manipulate the current context,
    e.g. to swap out the current state of all parameters, resources, and
    services, thereby creating either a new blank slate (e.g.
    for tests) or swapping between multiple independent tasks in the same
    thread (e.g. to do co-operative multitasking).

Namespaces
    Normally, configuration settings and action resources are defined
    statically in code.  But sometimes you want to create a dynamic namespace
    where any name could potentially identify a valid setting or resource,
    possibly with some kind of rules to compute default values when they
    haven't been explicitly set.  The ``@context.namespace`` decorator lets
    you turn any setting or resource into such a namespace

Configuration Files
    ``Contextual`` offers an extensible ``.ini``-style configuration file
    format that can be used to configure arbitrary settings and resources
    across any number of modules, thereby giving you global configuration
    of an entire application from a single configuration system.

    The loaded configuration is lazy, which means that it doesn't force
    importing of any of the modules being configured, and doesn't compute any
    values until they're actually used by the application.  Thus, a
    configuration file can include optional settings for modules that may not
    even be installed!

    Although the ``.ini`` syntax is a fixed property of the configuration
    loader, the semantics can be extended to create custom section handlers
    that interpret their contents differently -- and this can even be done
    from within the ``.ini`` files themselves, because the handlers are looked
    up using the same configuration services that everything else uses.  

"with"-statement Emulation and Tools
    Finally, ``Contextual`` offers a variety of decorators, base classes, and
    utility functions that are useful for either emulating the "with" statement
    in older versions of Python, and/or working with it in Python 2.5+.
    

Creating and Using Services
===========================

Here's a simple "counter" service implementation::

    >>> class Counter(context.Service):
    ...     value = 0
    ...     def inc(self):
    ...         self.value += 1

You can get the current instance of the service using its ``get()``
classmethod::

    >>> count = Counter.get()   # get the current instance
    >>> count.value
    0
    >>> count.inc()
    >>> count.value
    1

By default, each thread will have its own unique instance, that's created upon
first use, and is cached thereafter::

    >>> count is Counter.get()  # still using the same one
    True

    >>> def run_in_another_thread():
    ...     c2 = Counter.get()
    ...     print c2 is count           # this will be different
    ...     print c2 is Counter.get()   # but the same as long as thread runs

    >>> import threading
    >>> t = threading.Thread(target=run_in_another_thread)

    >>> t.start(); t.join() # run and wait for it to finish
    False
    True

You can replace the current instance with another instance, by using the
``with:`` statement (or a special function in older versions of Python; see
the section below on `Support for Python 2.3/2.4`_ for more info)::

    >>> with Counter.new():
    ...     print count is Counter.get()    
    False

    >>> count is Counter.get()  # old value is restored
    True


Easy Access Shortcuts
---------------------

Although up to this point we've been using ``Counter.get()`` to get the current
``Counter`` instance, note that we can also just use class attributes and
methods of ``Counter``, and they will automatically be redirected to the
corresponding attributes and methods of the current instance::

    >>> Counter.value
    1
    >>> Counter.inc()
    >>> Counter.value
    2
    >>> Counter.value = 42
    >>> Counter.get().value
    42

Any attribute that is defined in the service class that is *not* a
staticmethod, classmethod, or language-defined class attribute (like
``__new__`` and ``__slots__``) is automatically set up to redirect to the
instances, when retrieved from the class.

In other words, ``Counter.inc`` and ``Counter.value`` are just
shorter ways to spell ``Counter.get().inc``, and ``Counter.get().value``.  The
same shorthand works for getting, setting, or deleting class attributes.


Service Replacements
--------------------

You can create alternative implementations for a service, and substitute
them for the original service.  They do *not* have to be subclasses of the
original service for this to work, but they should of course be a suitable
replacement (i.e., implement the same interface), and they *must* use the
``context.replaces()`` class decorator to specify what service they're a
replacement for (even if they are a subclass of that service class).

For example, let's replace our ``Counter`` with an extended version that counts
by twos, and can optionally increment by arbitrary amounts::

    >>> class ExtendedCounter(context.Service):
    ...     context.replaces(Counter)
    ...     value = 0
    ...     def inc(self):
    ...         self.value += 2
    ...     def inc_by(self, increment):
    ...         self.value += increment

    >>> with ExtendedCounter.new():
    ...     print Counter.value
    ...     Counter.inc()
    ...     print Counter.value
    0
    2

    >>> Counter.value
    42

As you can see, our count went back to 0, because inside the ``with`` block,
the current ``Counter`` is actually the ``ExtendedCounter`` instance.  Its
value also went up by 2.  Outside the ``with`` block, the original ``Counter``
instance became current again, and thus ``Counter.value`` now returns the same
value it did before.

Note, by the way, that although ``ExtendedCounter`` has added a new method, it
is not acessible from the ``Counter`` class namespace, even if an
``ExtendedCounter`` is the current counter::

    >>> with ExtendedCounter.new():
    ...     Counter.inc_by(42)
    Traceback (most recent call last):
      ...
    AttributeError: type object 'Counter' has no attribute 'inc_by'

To access such additional methods, you need to either get an instance of the
service using the ``get()`` classmethod, or use the replacement class
directly::

    >>> with ExtendedCounter.new():
    ...     Counter.get().inc_by(42)
    ...     print Counter.value
    ...     ExtendedCounter.inc_by(24)
    ...     print Counter.value
    42
    66

However, the extra method will produce an attribute error at runtime if the
current service instance doesn't actually have that method.  For example, the
code below fails because the default ``Counter`` service is a ``Counter``,
not an ``ExtendedCounter``::

    >>> ExtendedCounter.inc_by
    Traceback (most recent call last):
      ...
    AttributeError: 'Counter' object has no attribute 'inc_by'


Configuring Replacement Services
--------------------------------

Now, you might be wondering at this point how your programs will ever be able
to use an ``ExtendedCounter``, if the default instance is a ``Counter``.  Will
you have to use a huge assortment of ``with`` statements to set up all the
service implementations you want to use?

Fortunately, no, because Contextual provides a configuration system that can be
used to set up service implementations as well as other types of configuration,
using either its built-in ``.ini`` format, or using whatever other mechanism
you might want.  So you can assemble the configuration of your entire program
into a single object, and then use a single ``with`` statement to execute your
program under it.

Or, if you are doing configuration in code, you can stack multiple services,
settings, etc. in a single ``with context.using(*items):`` statement.
XXX example


Configuration and Settings
==========================





Actions and Resources
=====================


Namespaces and Configuration Files
==================================


Dynamic Parameters
==================


The Application Object
======================


Co-operative Multi-tasking
--------------------------


-------------
API Reference
-------------

Service
replaces()

@value
@resource
@expression

Scope

using, only, State

RuleConflict
DynamicRuleError

Action

@namespace

lookup


Support for Python 2.3/2.4
==========================
@manager
reraise,
with_
@call_with



cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help