{ "info": { "author": "Alex Kouznetsov", "author_email": "alex@eat-up.org", "bugtrack_url": null, "classifiers": [], "description": "> **contexture** _|k\u0259n\u02c8teks\u02ccCH\u0259r|_\n>\n> noun\n> * the fact or manner of being woven or linked together to form a connected whole.\n> * a mass of things interwoven together; a fabric.\n> * the putting together of words and sentences in connected composition; the construction of a text.\n> * a connected literary structure; a continuous text.\n\nContexture is a thin framework for sharing objects' current state across network. This is useful for distributed structured logging, monitoring, data propagation/collection, synchronization and general elevated states of awareness.\n\nContexture is built on a messaging system (powered by RabbitMQ, see the tail of this document). It solves problems associated with log parsing and directly coupled systems.\n\n\n\n# Design\n\nContexture is designed for use in high availability systems. As such, it has the following features:\n\n* low and bounded impact on the running system\n* low and bounded propagation delay (a few seconds, depending on configuration)\n* high capacity (depending on configuration, as memory allows)\n* resistance to problems upstream, such as broker outages\n\nIn other words, your change may not propagate instantly but always within a few seconds, and the channel is wide.\n\nThe default behavior is to prefer losing messages over blocking or running out of memory\n(possible depending on messaging rates and queue size limits), this is configurable.\n\n# Input\n\nObjects go in, objects go out. We can explain that.\n\nThere are several ways to get your data into the system. One is to think in terms of contexts. A typical context is a collection of objects associated with a request/event, that you wish to monitor.\n\nConsider a common pattern:\n\n```python\ndef handle(request):\n # Request is received\n url = request['url']\n start = time.time()\n\n # Stuff happens\n log.debug('Starting to fetch %s' % url)\n result = requests.get(url)\n log.debug('Status code: %s' % result.status_code)\n status = result.status_code\n if status == 200:\n log.debug('Content: %s' % result.content)\n\n # And we're done\n log.debug('Finished in %f seconds' % (time.time() - start))\n return result\n...\nrequest = {'url': 'http://foo.com'}\nresult = handle(request)\n```\n\nUse Contexture to hold your objects and the above code becomes:\n\n```python\nrequest = {'url': 'http://foo.com'}\nwith Context(context=request) as ctx:\n result = requests.get(request['url'])\n ctx.status_code = result.status_code\n if ctx.status_code == 200:\n ctx.content = result.content\n```\n\nDone. This produces the following messages:\n\n1. object is born, object has one field `url` set to 'http://foo.com'\n1. object has a field `status_code` set to XXX\n1. object has a field `content` set to YYY\n1. object is gone after N seconds\n\nThese messages are automatically broadcast to the message bus and, optionally, written to the local logfile. Downstream, you can reconstruct the object or examine its evolution, in realtime.\n\n## Usage & integration\n\nContexture is designed to be dropped in with minimal impact on the existing ecosystem.\n\nContexture has several personalities, one of which is `contextmanager`. In most cases it will work without the `with` \u2014 the object will properly terminate once it goes out of scope, unless you hold on to a reference. A `with` block is recommended, but you can also `del` the object manually (plus sometimes the object lives beyond the `with` block).\n\n> #### Names and keys\n> Once messages are published to the server, they can be selectively routed by consumers using _routing keys_ and _headers_. By default, all messages aggregate into a single stream, routing lets you pick out just your messages.\n>\n> A routing key is an arbitrary dot-separated alphanumeric string, every message sent by Contexture gets one. Contexture tries to smart about generating default keys by deriving them from current module names and stack traces. When unsure, use `name` or `routing_key` argument when constructing a Contexture instance.\n>\n> You can see the default name that LC picked by casting it to `str`.\n>\n\n### Direct\n\nCreate a new object whenever you need a context.\n\n```python\ndef handle(request): # Outgoing messages (approximately):\n ctx = Context() # {status: 'born', obj: {}}\n ctx.request = request # {obj: {request: }}\n ...\n result = process_request(request)\n ctx.result = result # {obj: {result: }}\n return # {status: 'finished', elapsed: 0.1234}\n```\n\nYou may also preload an existing context dict by passing a `context` argument to the initializer.\n\n```python\nContext(context={'a': 1, 'foo': 'bar'}) # {status: 'born', obj: {a: 1, foo: 'bar'}}\n```\n\n#### Reserved keywords and direct access\nBecause Contexture is not a real dict but a thing of magic, some attribute names cannot be used for direct access (*_*, *context*, *log*, *update*). For example, you cannot do\n\n```python\nctx.log = 'something'\nprint ctx.log\n```\n\nDo it by accessing the context dict directly:\n\n```python\nctx.update(log='something')\nprint ctx.context['log']\n```\n\nNote the use of `ctx.update()` instead of `ctx.context.update()`. Don't worry about accidentally using a reserved keyword, Contexture will not let you.\n\n### Subclass\n\nYou can sublass Context. It will try to derive a name/routing_key from the name of your new class.\n\n### Object proxy\n\nYou have an existing object that you want to monitor and do not want to reimplement it as a subclass of LC. No problem, wrap it:\n\n```python\nclass MyObj(object):\n x = 1\n\n @property\n def y(self):\n return 2\n\n def f(self):\n return 3\n\nmyobject = MyObj()\noriginal_object = myobject\n\nmyobject = Context(obj=myobject)\nmyobject.f() == 3 # True\nmyobject.y == 2 # True\nmyobject.x == 1 # True\n\nmyobject.y = 10 # works! y is now part of context, shadowing the original\nmyobject.y == 10 # True\noriginal_object.y == 2 # True\n\nmyobject.x = 111\nmyobject.x == original_object.x == 1 # True, as expected\n```\n\nThis wrapped object behaves like the original object (function calls, @properties, etc.), but it is also a context: it will capture all attribute assignments and allow creation of new attributes (shadowing the existing read-only ones).\n\nIn other words, wrap an existing object in Contexture and it will do its best to make sure neither the object nor the code that is using it notice a difference.\n\n\n\n### Exclusions\n\nYou have a complex dict but you only care to broadcast a part of it. Add the private bits to `ignore` and it will not be reported.\n\n```python\nmydict = dict(x=1, y=2, privatestuff=whatever)\nctx = Context(context=mydict,\n ignore=['privatestuff', 'foo']) # obj: {x: 1, y: 2}\nctx.x = 5 # obj: {x: 5}\nctx.foo = 1234 # nothing\n```\n\n### Actually logging to the message bus\n\nIt slices, it dices, it logs! You can still use it as a conventional logger, via the `log` attribute.\n\n```python\nctx.log.info('OHAI')\n```\n\nUse the context to format your messages\n\n```python\nctx = Context(context={'foo': 1}) # obj: {foo: 1}\nctx.bar = 2 # obj: {bar: 2}\n...\nctx.log.debug('my foo is {foo} and my bar is {bar}') # obj:{}, message: 'my foo is 1 and my bar is 2'\n```\n\n### Logging to a logfile\n\nProvide your own logger instance and Contexture will log context changes to a log file for you, automatically:\n\n```python\nctx = Context(logger=logging.getLogger(__name__)) # : status = born\nctx.update(x=1, y=2) # : x = 1, y = 2\n```\n\nNote that this will work even without an AMQP handler configured.\n\n## Usage notes\n\nData must be JSON serializeable. The entire context entity must fit into JSON.\n\n### Can't we give an attribute a serializer?\n\n\n\n### pika.exceptions.AMQPConnectionError\n\nThis means the backend could not connect to the broker. This harms nothing, only means your messages won't make it upstream. See below.\n\n### Will this break my code if X happens downstream?\n\nNo.\n\nInside the handler, your messages are buffered in a queue and picked up by a publisher thread every second. If the publisher cannot publish to the RabbitMQ server, it will stop publishing, the queue will fill up and the new messages will be discarded. The backend will keep trying to reconnect and things will return to normal once it does.\n\nThis means Contexture can survive some broker downtime with no loss of traffic. How long \u2014 depends on your message rates and the size of the queue (currently 5000 but we can make this configurable).\n\n### Clean exit\n\nIt is possible to ensure the context transmits all of its messages before the process exits. Declare your context with ``wait=True`` and it will block on exit until the publish queue is empty.\n\nThe old way was to call `time.sleep(2)` before exiting your program, that still works.\n\n### Unicode headers\n\nPika does not like your unicode headers. If using `unicode_literals`, be sure to do something like:\n\n```python\nContext(headers={b'myheader': b'something'})\n```\n\n### Packing & sequencing\n\nWhen updating multiple fields, you have a choice of how the updates will be packed\non the messages stream. For example, this will create 3 separate messages:\n\n ctx.foo = 1\n ctx.bar = 2\n ctx.baz = 5\n\nand this only one:\n\n ctx.update(foo=1, bar=2, baz=5)\n\nThe spreading of the updates exposes more of the internal behavior and invidivual timing.\nWhen not needed, it is preferable to pack multiple udpates into one.\n\nSame goes for preloading contexts during initialization.\n\n### Transient objects\n\nNormally the context object announces its birth and death. You can bypass that and send your object upstream in the most minimalist way:\n\n```python\ndef push(obj):\n 'Convenience function for publishing a short-lived object'\n Context(name='my.routing.key', transient=True, context=obj)\n...\nmyobject = dict(foo='whatever', ...)\n...\npush(myobject)\n```\n\n### Long lived objects\n\nThe other extreme. You can think of a long lived object as a simple streaming database handle. That is, you ignore the object's own lifetime and treat it as a delta emitter, while listening for those deltas on the other end.\n\n```python\ndb = Context(name='faux_db')\ndb.mystatus = 'now this'\n...\ndb.mystatus = 'and now that'\n```\n\nAnd on the other end you might do (see the section on _lcmon_ below).\n\n $ lcmon -v -r faux_db -k obj\n {\"mystatus\": \"now this\"}\n {\"mystatus\": \"and now that\"}\n\n### Objects with custom IDs\n\n```python\nctx = Context(guid='1234')\n```\n\nContext objects are assigned UUIDs automatically, but you can provide your own. This may be helpful with transient objects in particular.\n\n## Configuration\n\nPut this in your config:\n\n```\n[logger_contexture]\nlevel=DEBUG\nhandlers=AMQPHandler\nqualname=contexture\npropagate=0\n\n[handler_AMQPHandler]\nclass=contexture.backend.amqp_handler.AMQPHandler\nlevel=DEBUG\nargs=(\"amqp://guest:guest@localhost:5672/%2F\", \"lc-topic\", \"topic\")\n```\n\nAdd the logger to loggers and handler to handlers (see `contexture/config.conf` for an example). The arguments to AMQP handler are:\n\n1. AMQP connection string\n1. Exchange to which to publish (will be created as needed)\n1. Exchange type\n1. (optional) A dict of headers to be included with every message\n\n# Output\n\nSo you know everything (almost) about getting data onto the message bus. Now what?\n\nInspect the messages as they fly by or grab them from the stream for your own devilish purpose.\n\n## Monitoring: lcmon\n\nIncluded in _contexture_ package is a handy monitoring utility.\n\n $ lcmon -h\n usage: lcmon [-h] [-r RKEYS] [-a ARGS [ARGS ...]] [-x {all,any}] [-e EXCHANGE]\n [-H HOSTNAME] [-u URL] [-q QUEUE] [-s] [-k KEYS [KEYS ...]] [-c]\n [-p] [-v]\n\n Simple AMQP monitor\n\n optional arguments:\n -h, --help show this help message and exit\n -H HOSTNAME, --hostname HOSTNAME\n AMQP server hostname (default: localhost)\n -u URL, --url URL fully qualified url (overrides hostname) (default:\n None)\n -e EXCHANGE, --exchange EXCHANGE\n exchange to bind to (default: lc-topic)\n -q QUEUE, --queue QUEUE\n consume from an existing queue (default: None)\n -r RKEY [RKEY ...], --rkey RKEY [RKEY ...]\n routing keys (default: ['#'])\n -a ARG [ARG ...], --arg ARG [ARG ...]\n binding arguments (key=value pairs) (default: None)\n -x {all,any}, --xmatch {all,any}\n x-match (default: all)\n -s, --stdin get input from stdin instead of queue (default: False)\n -k KEYS [KEYS ...], --keys KEYS [KEYS ...]\n keys to extract (*key for all matches) (default: None)\n -c, --collate extract collated objects from message stream (default:\n None)\n -p, --pretty pretty print (default: False)\n -v, --verbose\n\nThis will print traffic on the message bus in realtime, until terminated.\n\n### -H, --hostname\n\nBy default _lcmon_ connects to localhost. Open an SSH tunnel (port 5672) or provide the hosname explicitly:\n\n $ lcmon -H \n\nThis will expand to a default URL of `amqp://guest:guest@:5672/%2F`. Alternatively you can use `-U` to provide the full URL.\n\n### -q, --queue\n\nConsume from an existing queue.\n\nBy default, _lcmon_ creates an anonymous queue which disappears after the connection is closed (this happens _almost_ always). You can declare a more permanent queue and have messages survive _lcmon_ and even broker restarts.\n\n**Warning!** Take care to not consume from queues not belonging to you. This is a destructive operation.\n\n### -r, --rkeys\n\nOne or more routing keys or wildcards to filter on. Wildcards will only work with topic exchanges.\n\n### Binding args (-a)\n\nArguments for use with headers exchange. Use with `-x`.\n\n### -s, --stdin\n\nYou can store the output in a file, then read it back in and reprocess with options `-c`, `-k`, `-v` and `-p`. This is useful for temporary local storage (`-v` is important here):\n\n $ lcmon -v > myfile\n ^C\n $ lcmon -sc < myfile\n\n### -k, --keys\n\nExtract occurences of keys from the object.\n\nObjects can be big, and you might be interested only in a subset of keys. Here is a real message:\n\n {\"status\": \"finished\", \"obj\": {\"elapsed\": 2.7338700294494629}, \"id\": \"c529c182-62b8-49db-a0aa-2ba4cc991af2\", \"handler_id\": \"76bb5a4e-34b7-43a0-8ffc-19e448d33ea0\", \"elapsed\": 1.1670801639556885, \"queue\": 0, \"time_in\": 1360720859.8556099, \"time_out\": 1360720861.0226901}\n\nSuppose you only care about elapsed times. Running `lcmon -k elapsed` prints this:\n\n {'elapsed': 1.1670801639556885}\n\nNotice there are actually two `elapsed` fields in the message. By default, `-k` prints the first one found by breadth-first search. Prefixing the name of the key with `*` gets all of them:\n\n In [6]: monitor.extract_keys(obj, ['elapsed'])\n Out[6]: {'elapsed': 1.1670801639556885}\n\n In [7]: monitor.extract_keys(obj, ['*elapsed'])\n Out[7]: {'elapsed': (1.1670801639556885, 2.733870029449463)}\n\n### -c, --collate\n\nReconstruct complete context objects from the stream, so instead of a sequence like\n\n status: 'born', obj: {}\n obj: {x: 1, y: 2}\n obj: {x: 5}\n status: 'finished'\n\nyou will see the object\n\n {x: 5, y: 2}\n\nThis only works if the object terminates properly, and is not useful with long-lived objects. It does work with transient objects.\n\nNote `-k` option works on collated objects as well.\n\n### -v\n\nInclude the headers and routing key in the final object, resulting in a slightly different structure. You will want this often (hint: grep).\n\n## Consuming: messages and objects\n\nThe easiest part.\n\n```python\nfrom contexture.monitor import messages, objects\n\nfor message in monitor.messages(binding_keys=['#']):\n outer_obj = message['object']\n my_obj = outer_obj['obj']\n my_id = outer_obj['id']\n```\n\nThe heart of _lcmon_, `monitor` module provides two important functions: `messages` and `objects`. They return iterators over messages and collated objects. The iterators are also _contextmanagers_, so you can do:\n\n```python\nwith messages() as m_iter:\n for message in m_iter:\n dostuff()\n```\n\nThey take same arguments as `lcmon`, see `monitor.py` for details. Again, when using a named queue, remember to delete it when done (`message.channel.queue_delete()` works, see below).\n\nA full example with objects. This also demonstrates publishing:\n\n```python\nobjects = monitor.objects(verbose=True, capture_messages=True, queue='analytics.es')\nchannel = objects.channel\n\nprint 'Pushing objects to ElasticSearch River'\nbuffer_.clear()\nfor obj in objects:\n buffer_.append({'index': dict(_ttl='7d',\n _index='lc',\n _type=obj.pop('rkey'),\n _id=obj.pop('id'),\n )})\n buffer_.append(obj)\n channel.basic_publish('elasticsearch',\n 'elasticsearch',\n '\\n'.join(map(json.dumps, buffer_)) + '\\n')\n```\n\n### Queue safety\n\n* Q: What if I don't consume my messages fast enough?\n* A: You laggard!\n\nBut don't worry, RabbitMQ has tolerance for the likes of you. By default, Contexture declares all queues with a TTL (usually 60 seconds) \u2014 messages left unconsumed longer than that are automatically sent to /dev/null, providing a bound on queue growth. So even if your consumer gets terminally stuck, the world will go on.\n\n# Storage\n\nLeft as an exercise.\n\n# Miscellanea\n\nWhat's in the box:\n* an ActiveRecord-style data container (`Context`), for exposing your crazy world to the backend\n* an AMQP backend handler, for marshalling updates to the message bus\n* utilities for monitoring the message bus (lcmon)\nContexture helps you remotely monitor and analyze your code, in realtime, as it runs on one or more machines across a network. More backends can be added with relative ease.\n\n\nDesign considerations include:\n\n* Easy integration: minimal effort is required to start sending and receiving context objects.\n* Zero impact: LC is guaranteed to not impact the rest of the system. It is\nasyncronous and handles downstream failures gracefully. Worst\nthat it will do is a little memory usage (= size of the internal queue) and loss of updates\n(once the queue fills up).\n\n\n### RabbitMQ\n\nThe default backend is RabbitMQ. Things to know about AMQP:\n\n* The server is a __broker__.\n* __Routing key__ and __headers__ are assigned to the message by the publisher and are used by the\nbroker to route the message.\n* __Exchange__ is where the published message arrives first.\n* The consumer gets its messages from a __queue__. To start receiving messages the consumer must\nfirst declare a queue. Queues are often dynamic and disposable, but can be made permanent.\n* __Binding__ routes messages from exchange to queue. Bindings can use routing keys\nand/or headers to filter messages.\n\n----\n\n## What's wrong with direct coupling?\n\n1. Producer -> consumer dependency: the consumer must be up and runnning, and be able to consume messages fast enough.\n1. Consumer -> producer dependency: the producer is responsible for knowing how to talk to the consumer and for maintaining an open channel.\n1. Complex systems and intercepting streams: adding multiple consumers and/or producers is a challenge, as is reloading individual components in long running processes.\n\nThese problems can be addressed by implementing plumbing in all of your components or by using a buffered message queue to connect them.\n\n## blah\n\n* how is this better than syslog?\n 1. updates assemble back into objects\n 2. context objects are linkable\n\n## Congratulations\n\nYou have read the whole thing.\n", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/unthingable/contexture", "keywords": null, "license": "Apache 2", "maintainer": null, "maintainer_email": null, "name": "contexture", "package_url": "https://pypi.org/project/contexture/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/contexture/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/unthingable/contexture" }, "release_url": "https://pypi.org/project/contexture/1.3.1/", "requires_dist": null, "requires_python": null, "summary": "Magic Automatic Logging Context", "version": "1.3.1" }, "last_serial": 1072227, "releases": { "0.10.0b": [ { "comment_text": "", "digests": { "md5": "35e3d815999c66eba3620e2e64ee4e46", "sha256": "60aeb2d81d8ea6400aec9e2dd5eb37eaae1d4f2f9e817e2a91b92bee25283134" }, "downloads": -1, "filename": "contexture-0.10.0b.tar.gz", "has_sig": false, "md5_digest": "35e3d815999c66eba3620e2e64ee4e46", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25610, "upload_time": "2013-08-02T00:30:35", "url": "https://files.pythonhosted.org/packages/ce/4a/6ce26a01b90bbf6e97715603629ddb82a82bddf37502b478ab1822af730d/contexture-0.10.0b.tar.gz" } ], "0.10.1": [ { "comment_text": "", "digests": { "md5": "e0d7bc388c21359b44a565f67bb9d8d4", "sha256": "b336b3bd6f861699aa429fcc832bb5524efc973927299bc229752f5601510bf2" }, "downloads": -1, "filename": "contexture-0.10.1.tar.gz", "has_sig": false, "md5_digest": "e0d7bc388c21359b44a565f67bb9d8d4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25733, "upload_time": "2013-08-07T01:07:01", "url": "https://files.pythonhosted.org/packages/69/c6/ac28ada1ffa4a109cadb1d1ef3e2954ab5c90a38934645e0981f7f5b8417/contexture-0.10.1.tar.gz" } ], "0.10.2": [ { "comment_text": "", "digests": { "md5": "6d425d2d6d3dcea93a236cecb22d9e5c", "sha256": "0e7a32b3a911787530eb458edb51c44612d1099bc4195eb79ba3e2f01415593d" }, "downloads": -1, "filename": "contexture-0.10.2.tar.gz", "has_sig": false, "md5_digest": "6d425d2d6d3dcea93a236cecb22d9e5c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25857, "upload_time": "2013-08-07T20:45:32", "url": "https://files.pythonhosted.org/packages/4d/a3/8115893b6176180136c48c15b706b4285d4bcc2d146795b63b5f4f21e374/contexture-0.10.2.tar.gz" } ], "0.10.3": [ { "comment_text": "", "digests": { "md5": "27574b0a740ef1bdb2ebcfdcf1a14472", "sha256": "22e2691fbe47ca44a347cceb16999e5ab8e7f0942fbc031df369628b559a05c2" }, "downloads": -1, "filename": "contexture-0.10.3.tar.gz", "has_sig": false, "md5_digest": "27574b0a740ef1bdb2ebcfdcf1a14472", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26112, "upload_time": "2013-08-27T21:58:14", "url": "https://files.pythonhosted.org/packages/e1/d8/9238c4c8b0831b98371862eb1c1a31d3c09e06f6294753c7b73d7240ebf6/contexture-0.10.3.tar.gz" } ], "0.11.0": [ { "comment_text": "", "digests": { "md5": "1a2794b0d125524a4649bf7b74aa77aa", "sha256": "22e4c5c7dad013295f59cbb8169d24f8d2a3d712c5081b8f16b89700520090a6" }, "downloads": -1, "filename": "contexture-0.11.0.tar.gz", "has_sig": false, "md5_digest": "1a2794b0d125524a4649bf7b74aa77aa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27787, "upload_time": "2014-03-04T01:50:19", "url": "https://files.pythonhosted.org/packages/e8/bf/267475a3a92385c445f789b5770f5d1cd14d52556e132eba4ccaa42a03c9/contexture-0.11.0.tar.gz" } ], "0.9.0": [ { "comment_text": "", "digests": { "md5": "a85d53ca1a84a336c45e9cf199654ae3", "sha256": "6a35c1da9ee39a3dcf3ac6b60c7190d9912eb9ebfd4523f1f622d2ea1479205d" }, "downloads": -1, "filename": "contexture-0.9.0.tar.gz", "has_sig": false, "md5_digest": "a85d53ca1a84a336c45e9cf199654ae3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13826, "upload_time": "2013-03-22T00:17:19", "url": "https://files.pythonhosted.org/packages/10/50/5d28a7bca31fca0ff78daa5c9e3b2f50e5883b5afac9cb6b48fafbf97160/contexture-0.9.0.tar.gz" } ], "0.9.0.1": [ { "comment_text": "", "digests": { "md5": "4266f2a06eb421050d21f288a791b8fe", "sha256": "63843a5f4fdefc68a7540f5a08b783fd669e8935f3e03fa9d23fd36102dc09e9" }, "downloads": -1, "filename": "contexture-0.9.0.1.tar.gz", "has_sig": false, "md5_digest": "4266f2a06eb421050d21f288a791b8fe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14233, "upload_time": "2013-03-22T19:45:29", "url": "https://files.pythonhosted.org/packages/4b/db/d3a719796ee660b362d9f02c616d09d922d720b0e4eb52f3bdbda5617eb3/contexture-0.9.0.1.tar.gz" } ], "0.9.0.2": [ { "comment_text": "", "digests": { "md5": "04d0cc7b00100da25db9e7ce3feeb63d", "sha256": "41ba558bc3fe872e8f385146b2e20a245685413da0694a3dd0dbc77a3c9cd8c7" }, "downloads": -1, "filename": "contexture-0.9.0.2.tar.gz", "has_sig": false, "md5_digest": "04d0cc7b00100da25db9e7ce3feeb63d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22419, "upload_time": "2013-03-22T19:51:42", "url": "https://files.pythonhosted.org/packages/f2/c7/f4eaa3d2b1f7a36fa20d491b2f79842b571f8ee0d8633ec9a863f51a5c98/contexture-0.9.0.2.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "65e2c127d03518a4f8613d6e4dc1c072", "sha256": "a9ceb5aef55e1fc87cb1b44bbeaf3b0a327f560cf97d73702fcbfd9c640cbffd" }, "downloads": -1, "filename": "contexture-0.9.1.tar.gz", "has_sig": false, "md5_digest": "65e2c127d03518a4f8613d6e4dc1c072", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23022, "upload_time": "2013-03-29T00:58:02", "url": "https://files.pythonhosted.org/packages/5f/86/aa5db05641b77def891ab4520c04a48c77ed16c8565443dba952ab0ec0ff/contexture-0.9.1.tar.gz" } ], "0.9.3": [], "0.9.4": [ { "comment_text": "", "digests": { "md5": "5796a648fa72b03413d8b687ade2caa1", "sha256": "1259b16160b81562f38696759063d037f9a8845ab52ef9cb3be6dac88ce2d3b6" }, "downloads": -1, "filename": "contexture-0.9.4.tar.gz", "has_sig": false, "md5_digest": "5796a648fa72b03413d8b687ade2caa1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23180, "upload_time": "2013-05-17T23:06:52", "url": "https://files.pythonhosted.org/packages/fd/09/3c9f3bbe2b20e2268f3b1460bbb3d155c20cff3cbaeb13c6dddeb047488b/contexture-0.9.4.tar.gz" } ], "0.9.6": [ { "comment_text": "", "digests": { "md5": "a1b7966e113350c0a02e2a09effbdff7", "sha256": "9bc42d7875b0dd978c3f2dae364e10affa56df36bbd18ee76edaa95c790c4c3f" }, "downloads": -1, "filename": "contexture-0.9.6.tar.gz", "has_sig": false, "md5_digest": "a1b7966e113350c0a02e2a09effbdff7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24714, "upload_time": "2013-06-03T23:07:44", "url": "https://files.pythonhosted.org/packages/0f/9b/d1b68ba3a1137b6274d0745dc44fce5d61098bdd081a84750b2afa16795b/contexture-0.9.6.tar.gz" } ], "0.9.7": [ { "comment_text": "", "digests": { "md5": "6e837cbb5ab7ea5a7d20513e30c8c6a8", "sha256": "22fb3e754f4938965249e6ccc317cfba4bad3ce13f2628774f2acb39547a4f6a" }, "downloads": -1, "filename": "contexture-0.9.7.tar.gz", "has_sig": false, "md5_digest": "6e837cbb5ab7ea5a7d20513e30c8c6a8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24945, "upload_time": "2013-06-05T18:26:44", "url": "https://files.pythonhosted.org/packages/15/e0/54d4a46373782f7a96daba580221e00ae3e9455a6c04682b8e6f0fb68b0b/contexture-0.9.7.tar.gz" } ], "0.9.8": [ { "comment_text": "", "digests": { "md5": "6bea453848a6e8b4fdaa963e3e876349", "sha256": "d8f404fe9a382d79bc058f46dd79957f4f3442a75eda562ad2a9edcac7c6b126" }, "downloads": -1, "filename": "contexture-0.9.8.tar.gz", "has_sig": false, "md5_digest": "6bea453848a6e8b4fdaa963e3e876349", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24975, "upload_time": "2013-06-05T21:28:28", "url": "https://files.pythonhosted.org/packages/e7/99/1c8e8cfadcd384430c5051024ac95eb10f395d5e11d45152518e8b13a73c/contexture-0.9.8.tar.gz" } ], "0.9.9": [ { "comment_text": "", "digests": { "md5": "fc1a07351f73067265823ba09ed775c1", "sha256": "c975154ceb827ff3593034ac21f7c2921101adda695b099915189422a5ed28f2" }, "downloads": -1, "filename": "contexture-0.9.9.tar.gz", "has_sig": false, "md5_digest": "fc1a07351f73067265823ba09ed775c1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25706, "upload_time": "2013-06-28T00:32:02", "url": "https://files.pythonhosted.org/packages/19/64/c3df9f2b3891e89e677b08a7b43800d2e599703e0f17e0d4cd173d4b4844/contexture-0.9.9.tar.gz" } ], "0.9.9.1": [ { "comment_text": "", "digests": { "md5": "55706a2efc05d42f40f296bdc315240c", "sha256": "62257d7d8368c7599b8d877a5a9136bbf0e79b01c588aaef6a16d843a69c72dd" }, "downloads": -1, "filename": "contexture-0.9.9.1.tar.gz", "has_sig": false, "md5_digest": "55706a2efc05d42f40f296bdc315240c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25732, "upload_time": "2013-07-30T04:45:53", "url": "https://files.pythonhosted.org/packages/9b/8e/8176004171519409126dd0da579eab508e395d832e48a5df9e19d487501c/contexture-0.9.9.1.tar.gz" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "ecce528a9a7e03fbe9ed19748106b813", "sha256": "562d2daf3b0a940d4f23c25874ee56cef8593540950f53ce7813fc3e111f8d7c" }, "downloads": -1, "filename": "contexture-1.1.tar.gz", "has_sig": false, "md5_digest": "ecce528a9a7e03fbe9ed19748106b813", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27813, "upload_time": "2014-03-04T01:51:39", "url": "https://files.pythonhosted.org/packages/a5/58/45f52075e1f27b0bbccdbe0caf8c19b39a7b514d0e4dcc3c2b25c471ce2e/contexture-1.1.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "3dbf7053b800d2e1e155319c2ed58353", "sha256": "fae097dc280842579e7640f160c6c3c3225ea1a2ec51e319f22d6aa01e23c4d0" }, "downloads": -1, "filename": "contexture-1.2.tar.gz", "has_sig": false, "md5_digest": "3dbf7053b800d2e1e155319c2ed58353", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27904, "upload_time": "2014-03-05T00:48:23", "url": "https://files.pythonhosted.org/packages/0a/dc/2887e5ac7e5d288135b7ba61a1a0d0c0e822364256eb13d551474633afe5/contexture-1.2.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "54afeffa93eec7a294be67c66de18611", "sha256": "bb8481838f7954fe1368d9a10949af36f3e13448eab52575234688445b22f0f1" }, "downloads": -1, "filename": "contexture-1.2.1.tar.gz", "has_sig": false, "md5_digest": "54afeffa93eec7a294be67c66de18611", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27913, "upload_time": "2014-03-05T00:52:29", "url": "https://files.pythonhosted.org/packages/82/32/034bde2dd25d22b449d9dc832acac80fa3e90b6e0b99172fdfd4442c3705/contexture-1.2.1.tar.gz" } ], "1.3": [ { "comment_text": "", "digests": { "md5": "045d479d5538121553ea4ad4811259e0", "sha256": "fc235d58ee7bf36d000dd9ebc360f0f7f80b1d6eccc945d156425b8416036e8a" }, "downloads": -1, "filename": "contexture-1.3.tar.gz", "has_sig": false, "md5_digest": "045d479d5538121553ea4ad4811259e0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27933, "upload_time": "2014-03-07T02:04:32", "url": "https://files.pythonhosted.org/packages/68/0e/46c46386f711a50ffa120c37da7806f0ba7aba6f150f4350683462d42fc4/contexture-1.3.tar.gz" } ], "1.3.1": [ { "comment_text": "", "digests": { "md5": "46fea5b8178436def91b4df3a93df48d", "sha256": "2a400a2921420cc3e09cc6cd6af65fc21099200fffbc99f40473841b31e174ff" }, "downloads": -1, "filename": "contexture-1.3.1.tar.gz", "has_sig": false, "md5_digest": "46fea5b8178436def91b4df3a93df48d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27958, "upload_time": "2014-04-26T01:45:46", "url": "https://files.pythonhosted.org/packages/03/70/6fe28b932526649887793301059e39676cc8e79d981f1fcd23958f122af2/contexture-1.3.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "46fea5b8178436def91b4df3a93df48d", "sha256": "2a400a2921420cc3e09cc6cd6af65fc21099200fffbc99f40473841b31e174ff" }, "downloads": -1, "filename": "contexture-1.3.1.tar.gz", "has_sig": false, "md5_digest": "46fea5b8178436def91b4df3a93df48d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27958, "upload_time": "2014-04-26T01:45:46", "url": "https://files.pythonhosted.org/packages/03/70/6fe28b932526649887793301059e39676cc8e79d981f1fcd23958f122af2/contexture-1.3.1.tar.gz" } ] }