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 |