[Subversion] / Contextual / Contextual.txt  

View of /Contextual/Contextual.txt

Parent Directory | Revision Log
Revision: 2293 - (download)
Mon Feb 26 02:16:05 2007 UTC (17 years, 2 months ago) by pje
File size: 13135 byte(s)
Implemented EigenValue-style write-until-read immutability for Config
objects.  This new algorithm is actually thread-safe without needing
any locks, by using the atomic properties of dictionaries, and by
detecting conflicts *after* a write attempt is made.  In the event of
a race between two or more threads, it's nondeterministic who will
"win", but there will be exactly one winner and all other writers
will fail.

Proof (informal):

  writes are copied into buffer[key]

  snapshot[key] is never modified except via setdefault -- therefore
  it can only ever have *one* value

  when snapshot[key] is set, its value comes from buffer[key]; thus,
  snapshot[key] is always *some* (indeterminately chosen) setting
  that was set by writing to the mapping, or by computing a default
  value.

  once a write is completed, snapshot.get(key, value_written) is
  used to verify that snapshot[key] is either *empty* OR a value
  that is the same as or equal to the value written.

  If the value is different, some other writer's value was read
  first, and our write is in conflict.  If the value is the same,
  then our write was "successful", in that there is a *chance* it
  will be the end value of the setting.

In other words, a succesful write does *not* necessarily mean that
the written value is or will be "the" value.  It is still possible
to have non-deterministic behavior if multiple threads are
simultaneously writing to the same Config, but it will not corrupt
the Config or violate the "only one value is ever seen by readers"
invariant.  If precedence of an individual setting value matters, 
you must serialze the writes, but unserialized writes will not
cause inconsistent behavior.  This is a sufficient guarantee to
allow e.g. configuration file loaders to be written.
===================================================================
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
    The ``context.Config`` service provides thread-safe access to stateless
    configuration values.  It is essentially a mapping whose contents cannot be
    changed once they have been read or set.  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 scope of that
    configuration.  (Of course, you can set up nested and/or inheriting
    configurations with different setting values, 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 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.

The ``App`` object
    The ``context.App`` object can be used to swap out the current state of all
    parameters 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():     # the easy way to say it
    ...     print count is Counter.get()    
    False

    >>> c2 = Counter()  # prove that this is the instance used in the block
    >>> with c2:
    ...     print c2 is Counter.get()
    True

    >>> 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():
    ...     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():
    ...     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():
    ...     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.  You can assemble the configuration of your entire program
into a single configuration object, and then use a single ``with`` statement
to execute your program under it.

However, before we go into all that, here's a super-simple way to set up an
application, using an ``App`` object (which will be explained further later
on)::

    >>> app = context.App()
    >>> app[Counter] = ExtendedCounter

    >>> with context.switch_to(app):
    ...     ExtendedCounter.inc_by(29)
    ...     print Counter.value
    29

Basically, you just put zero-argument callables (e.g. types or factory
functions) into the app to configure its services.

There are, of course, other things you can configure besides service factories,
and other places to configure them besides the ``App`` service.  That's what
the next few sections are about.


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


Config, @context.setting, SettingConflict


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


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


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


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


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


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


Service
replaces()

Config
@setting
SettingConflict

Action
@resource

@namespace

@parameter

App
switch_to()

Delegated


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