{ "info": { "author": "Phillip J. Eby", "author_email": "peak@eby-sarna.com", "bugtrack_url": null, "classifiers": [], "description": "So you're writing a library, and you have this object that keeps showing up\nin parameters or attributes everywhere, even though there's only ever *one*\nof that thing at a given moment in time. Should you use a global variable or\nsingleton?\n\nMost of us know we \"shouldn't\" use globals, and some of us know that singletons\nare just another kind of global! But there are times when they both just seem\nso darn attractive. They're *so* easy to create and use, even though they're\nalso the bane of testability, maintainability, configurability,\nthread-safety... Heck, you can pretty much name it, and it's a problem with\nglobals and singletons.\n\nProgramming pundits talk about using \"dependency injection\" or \"inversion of\ncontrol\" (IoC) to get rid of global variables. And there are many dependency\ninjection frameworks for Python (including Zope 3 and ``peak.config``).\n\nThe problem is, these frameworks typically require you to declare interfaces,\nregister services, create XML configuration files, and/or ensure that every\nobject in your application knows where to look up services -- replacing one\n\"globals\" problem with another! Not only does all this make things more\ncomplex than they need to be, it disrupts your programming flow by making you\ndo busywork that doesn't provide any new benefits to your application.\n\nSo, most of us end up stuck between various unpalatable choices:\n\n1. use a global and get it over with (but suffer a guilty conscience and\n the fear of later disasters in retribution for our sins),\n\n2. attempt to use a dependency injection framework, paying extra now to be\n reassured that things will work out later, or\n\n3. use a thread-local variable, and bear the cost of introducing a possible\n threading dependency, and still not having a reasonable way to test or\n configure alternate implementations. Plus, thread-locals don't really\n support asynchronous programming or co-operative multitasking. What if\n somebody wants to use your library under Twisted, and needs private\n instances for each socket connection?\n\nBut now there's a better choice.\n\nThe \"Contextual\" library (``peak.context``) lets you create pseudo-singletons\nand pseudo-global variables that are context-sensitive and easily replaceable.\nThey look and feel just like old-fashioned globals and singletons, but because\nthey are safely scalable to threads and tasks (as well as being replaceable for\ntesting or other dynamic contexts), you don't have to worry about what happens\n\"later\".\n\nContextual singletons are even better than thread-local variables, because they\nsupport asynchronous programming with microthreads, coroutines, or frameworks\nlike Twisted. A simple context-switching API lets you instantly swap out all\nthe services and variables from one logical task, with those of another task.\nThis just isn't possible with ordinary thread-locals.\n\nMeanwhile, \"client\" code that uses context-sensitive objects remains unchanged:\nthe code simply uses whatever the \"current\" object is supposed to be.\n\nAnd isn't that all you wanted to do in the first place?\n\n\nReplaceable Singletons\n----------------------\n\nHere's what a simple \"global\" counter service implemented with ``peak.context``\nlooks like::\n\n >>> from peak import context\n\n >>> class Counter(context.Service):\n ... value = 0\n ...\n ... def inc(self):\n ... self.value += 1\n ...\n\n >>> Counter.value\n 0\n >>> Counter.inc()\n >>> Counter.value\n 1\n\nCode that wants to use this global counter just calls ``Counter.inc()`` or\naccesses ``Counter.value``, and it will automatically use the right ``Counter``\ninstance for the current thread or task. Want to use a fresh counter for\na test? Just do this::\n\n with Counter.new():\n # code that uses the standard count.* API\n\nWithin the ``with`` block, any code that refers to ``count`` will be using the\nnew ``Counter`` instance you provide. If you need to support Python 2.4, the\n``context`` library also includes a decorator that emulates a ``with``\nstatement::\n\n >>> Counter.value # before using a different counter\n 1\n\n >>> @context.call_with(Counter.new())\n ... def do_it(c):\n ... print Counter.value\n 0\n\n >>> Counter.value # The original counter is now in use again\n 1\n\nThe ``@call_with`` decorator is a bit uglier than a ``with`` statement, but\nit works about as well. You can also use an old-fashioned try-finally block,\nor some other before-and-after mechanism like the ``setUp()`` and\n``tearDown()`` methods of a test to replace and restore the active instance.\n\n\nPluggable Services\n------------------\n\nWant to create an alternative implementation of the same service, that can\nbe plugged in to replace it? That's simple too::\n\n >>> class DoubleCounter(context.Service):\n ... context.replaces(Counter)\n ... value = 0\n ... def inc(self):\n ... self.value += 2\n\nTo use it, just do::\n\n with DoubleCounter.new():\n # code in this block that calls ``Counter.inc()`` will be incrementing\n # a ``DoubleCounter`` instance by 2\n\nOr, in Python 2.4, you can do something like::\n\n >>> @context.call_with(DoubleCounter.new())\n ... def do_it(c):\n ... print Counter.value\n ... Counter.inc()\n ... print Counter.value\n 0\n 2\n\nAnd of course, once a replacement is no longer in use, the original instance\nbecomes active again::\n\n >>> Counter.value\n 1\n\nAll this, with no interfaces to declare or register, and no XML or\nconfiguration files to write. However, if you *want* to use configuration\nfiles to select implementations of global services, you can still have them:\nsetting ``Counter <<= DoubleCounter`` will set the current ``Counter`` factory\nto ``DoubleCounter``, so you can just have a configuration file loader set up\nwhatever services you want. You can even take a snapshot of the entire current\ncontext and restore all the previous values::\n\n with context.empty():\n # code to read config file and set up services\n # code that uses the configured services\n\nThis code won't share any services with the code that calls it; it will not\nonly get its own private ``Counter`` instance, but a private instance of any\nother ``Service`` objects it uses as well. (Instances are created lazily\nin new contexts, so if you don't use a particular service, it's never created.)\nTry doing that with global or thread-local variables!\n\nIn addition to these simple pseudo-global objects, ``peak.context`` also\nsupports other kinds of context-sensitivity, like the concept of \"settings\"\nin a \"current configuration\" and the concept of \"resources\" in a \"current\naction\" (that are notified whether the action completed successfully or exited\nwith an error). These features are orders of magnitude simpler in their\nimplementation and use than the corresponding features in the earlier\n``peak.config`` and ``peak.storage`` frameworks, but provide equivalent or\nbetter functionality.\n\nFor more details, please consult the Contextual developer's guide.\n\n\nTODO\n----\n\n0.7\n * Finish the developer's guide!\n \n * Configuration files\n\n * Components w/state binding and ``**kw`` attrs update on ``__init__`` and\n ``.new()``\n\n0.8\n * State ``__enter__`` should lock the state to the current thread, w/o\n ``__exit__`` or ``swap()`` or on_exit being possible from other threads,\n so that they will be thread-safe.\n\n * Detect value calculation cycles\n\n * Resource pooling/caching\n\n\nSTATUS\n------\n\nThis package is in active development, but not all features are stable and\ndocumented. ``Service`` objects work as advertised, as does the support for\nusing \"with\"-like operations in older versions of Python. Most of the other\nfeatures haven't been used (or even documented!) in any real way yet, and so\nthe designs are still subject to change prior to an actual 0.7a1 release.\n\n(All the included code is covered by tests, though, so you can always dig\nthrough them for technical documentation; the developer guide and tutorial\nis just woefully incomplete as yet.)\n\nSource distribution snapshots of Contextual are generated daily, but you can\nalso update directly from the `development version`_ in SVN.\n\n.. _development version: svn://svn.eby-sarna.com/svnroot/Contextual#egg=Contextual-dev", "description_content_type": null, "docs_url": null, "download_url": "http://peak.telecommunity.com/snapshots/", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypi.python.org/pypi/Contextual", "keywords": null, "license": "PSF or ZPL", "maintainer": null, "maintainer_email": null, "name": "Contextual", "package_url": "https://pypi.org/project/Contextual/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/Contextual/", "project_urls": { "Download": "http://peak.telecommunity.com/snapshots/", "Homepage": "http://pypi.python.org/pypi/Contextual" }, "release_url": "https://pypi.org/project/Contextual/0.7a1.dev/", "requires_dist": null, "requires_python": null, "summary": "Replace globals with context-safe variables and services", "version": "0.7a1.dev" }, "last_serial": 47700, "releases": { "0.7a1.dev": [] }, "urls": [] }