{ "info": { "author": "Kevin L. Mitchell", "author_email": "kevin.mitchell@rackspace.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Paste", "Intended Audience :: System Administrators", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware" ], "description": "==============================================\nTurnstile Distributed Rate-Limiting Middleware\n==============================================\n\nTurnstile is a piece of WSGI middleware that performs true distributed\nrate-limiting. System administrators can run an API on multiple\nnodes, then place this middleware in the pipeline prior to the\napplication. Turnstile uses a Redis database to track the rate at\nwhich users are hitting the API, and can then apply configured rate\nlimits, even if each request was made against a different API node.\n\nInstalling Turnstile\n====================\n\nTurnstile can be easily installed like many Python packages, using\n`PIP`_::\n\n pip install turnstile\n\nYou can install the dependencies required by Turnstile by issuing the\nfollowing command::\n\n pip install -r .requires\n\nFrom within your Turnstile source directory.\n\nIf you would like to run the tests, you can install the additional\ntest dependencies in the same way::\n\n pip install -r .test-requires\n\nNote that the test suite is currently written to work with Python 2.7,\neven though Turnstile itself should work with Python 2.6.\n\nAdding and Configuring Turnstile\n================================\n\nTurnstile is intended for use with PasteDeploy-style configuration\nfiles. It is a filter, and should be placed in an appropriate place\nin the WSGI pipeline such that the limit classes used with Turnstile\ncan access the information necessary to make rate-limiting decisions.\n(With the ``turnstile.limits:Limit`` class provided by Turnstile, no\nadditional information is required, as that class does not\ndifferentiate between users of your application.)\n\nThe filter section of the PasteDeploy configuration file will also\nneed to contain enough information to allow Turnstile to access the\ndatabase. Other options may be configured from here as well, such as\nthe ``preprocess`` configuration variable. The simplest example of\nTurnstile configuration would be::\n\n [filter:turnstile]\n use = egg:turnstile#turnstile\n redis.host = \n\nThe following are the recognized configuration options:\n\nconfig\n Allows specification of an alternate configuration file. This can\n be used to generate a single file which can be shared by WSGI\n servers using the Turnstile middleware and the various provided\n tools. This can also allow for separation of code-related options,\n such as the ``preprocess`` option, from pure configuration, such as\n the ``redis.host`` option. The configuration file is an\n INI-formatted file, with section names corresponding to the first\n segment of the configuration option name. That is, the\n ``redis.host`` option would be set as follows::\n\n [redis]\n host = \n\n Configuration options which have no prefix are grouped under the\n ``[turnstile]`` section of the file, as follows::\n\n [turnstile]\n status = 404 Not Found\n\n Note that specifying the ``config`` option in the ``[turnstile]``\n section will have no effect; it is not possible to cause another\n configuration file to be included in this way.\n\ncontrol.channel\n Specifies the channel that the control daemon listens on. (See\n below for more information about the purpose of the control daemon.)\n This option defaults to \"control\".\n\ncontrol.errors_channel\n Specifies the channel that the control daemon (see below) reports\n errors to. This option defaults to \"errors\".\n\ncontrol.errors_key\n Specifies the key of a set in the Redis database to which errors\n will be stored. This option defaults to \"errors\".\n\ncontrol.limits_key\n The key under which the limits are stored in the database. See the\n section on tools for more information on how to load and dump the\n limits stored in the Redis database. This option defaults to\n \"limits\".\n\ncontrol.node_name\n The name of the node. If provided, this option allows the\n specification of a recognizable name for the node. Currently, this\n node name is only reported when issuing a \"ping\" command to the\n control daemon (see below), and may be used to verify that all hosts\n responded to the ping.\n\ncontrol.reload_spread\n When limits are changed in the database, a command is sent to the\n control daemon (see below) to cause the limits to be reloaded. As\n having all nodes hit the Redis database simultaneously may overload\n the database, this option, if set, allows the reload to be spread\n out randomly within a configured interval. This option should be\n set to the size of the desired interval, in seconds. If not set,\n limits will be reloaded immediately by all nodes.\n\ncontrol.remote\n If set to \"on\", \"yes\", \"true\", or \"1\", Turnstile will connect to a\n remote control daemon (see the ``remote_daemon`` tool described\n below). This enables Turnstile to be compatible with WSGI servers\n which use multiple worker processes. Note that the configuration\n values ``control.remote.authkey``, ``control.remote.host``, and\n ``control.remote.port`` are required.\n\ncontrol.remote.authkey\n Set to an authentication key, for use when ``control.remote`` is\n enabled. Must be the value used by the invocation of\n ``remote_daemon``.\n\ncontrol.remote.host\n Set to a host name or IP address, for use when ``control.remote`` is\n enabled. Must be the value used by the invocation of\n ``remote_daemon``.\n\ncontrol.remote.port\n Set to a port number, for use when ``control.remote`` is enabled.\n Must be the value used by the invocation of ``remote_daemon``.\n\ncontrol.shard_hint\n Can be used to set a sharding hint which will be provided to the\n listening thread of the control daemon (see below). This hint is\n not used by the default Redis ``Connection`` class.\n\npreprocess\n Contains a list of preprocessor functions, specified as\n \"module:function\" pairs separated by spaces. During each request,\n each preprocessor will be called in turn, with the middleware object\n (from which can be obtained the database handle, as well as the\n configuration) and the request environment as arguments. Note that\n any exceptions thrown by the preprocessors will not be caught, and\n request processing will be halted; this will likely result in a 500\n error being returned to the user.\n\nredis.connection_pool\n Identifies the connection pool class to use. If not provided,\n defaults to ``redis.ConnectionPool``. This may be used to allow\n client-side sharding of the Redis database.\n\nredis.connection_pool.connection_class\n Identifies the connection class to use. If not provided, the\n appropriate ``redis.Connection`` subclass for the configured\n connection is used (``redis.Connection`` if ``redis.host`` is\n specified, else ``redis.UnixDomainSocketConnection``).\n\nredis.connection_pool.max_connections\n Allows specification of the maximum number of connections to the\n Redis database. Optional.\n\nredis.connection_pool.parser_class\n Identifies the parser class to use. Optional. This is an advanced\n feature of the ``redis`` package used by Turnstile.\n\nredis.connection_pool.*\n Any other configuration value provided in the\n ``redis.connection_pool.`` hierarchy will be passed as keyword\n arguments to the configured connection pool class. Note that the\n values will be passed as strings.\n\nredis.db\n Identifies the specific sub-database of the Redis database to be\n used by Turnstile. If not provided, defaults to 0.\n\nredis.host\n Identifies the host name or IP address of the Redis database to\n connect to. Either ``redis.host`` or ``redis.unix_socket_path``\n must be provided.\n\nredis.password\n If the Redis database has been configured to use a password, this\n option allows that password to be specified.\n\nredis.port\n Identifies the port the Redis database is listening on. If not\n provided, defaults to 6379.\n\nredis.socket_timeout\n If provided, specifies an integer socket timeout for the Redis\n database connection.\n\nredis.unix_socket_path\n Names the UNIX socket on the local host for the local Redis database\n to connect to. Either ``redis.host`` or ``redis.unix_socket_path``\n must be provided.\n\nstatus\n Contains the status code to return if rate limiting is tripped.\n This defaults to \"413 Request Entity Too Large\". Note that this\n value must start with the 3-digit HTTP code, followed by a space and\n the text corresponding to that status code. Also note that,\n regardless of the status code, Turnstile will include the\n ``Retry-After`` header in the response. (The value of the\n ``Retry-After`` header will be the integer number of seconds until\n the request can be retried.)\n\nturnstile\n If set, identifies an alternate class to use for the Turnstile\n middleware. This can be used in conjunction with subclassing\n ``turnstile.middleware:TurnstileMiddleware``, which may be done to\n override how over-limit conditions are formatted.\n\nOther configuration values are available to the preprocessors and the\n``turnstile.limits:Limit`` subclasses, but extreme care should be\ntaken that such configurations remain in sync across the entire\ncluster.\n\nThe Control Daemon\n==================\n\nTurnstile stores the limits configuration in the Redis database, in\naddition to the ephemeral information used to check and enforce the\nrate limits. This makes it possible to change the limits dynamically\nfrom a single, central location. In order to facilitate such changes,\neach Turnstile instance uses an eventlet thread to run a \"control\ndaemon.\" The control daemon uses the publish/subscribe support\nprovided by Redis to listen for commands, of which two are currently\nrecognized: ping and reload.\n\nSome WSGI servers cannot use Turnstile in this mode, due to using\nmultiple processes (typically through use of the \"multiprocessing\"\nPython module). In these circumstances, the control daemon may be\nstarted in its own process (see the ``remote_daemon`` tool). Enabling\nthis requires that the ``control.remote`` configuration option be\nturned on, and values provided for ``control.remote.authkey``,\n``control.remote.host``, and ``control.remote.port``. See the\ndocumentation for these options for more information.\n\nIt is possible to configure the listening thread of the control daemon\nto use alternate configuration for connecting to the Redis database.\nThe defaults will be drawn from the ``[redis]`` section of the\nconfiguration, but by specifying ``redis.*`` options in the\n``[control]`` section of the configuration, specific values may be\noverridden.\n\nThe Ping Command\n----------------\n\nThe \"ping\" command is the simplest of the control daemon commands. In\nits simplest form, the message \"ping:\" is written to the control\nchannel, which will cause all running Turnstile instances to return\nthe message \"pong\" to the specified channel. If the\n``control.node_name`` configuration option has been set, this node\nname will be included in the response, as \"pong:\".\nFinally, additional data (such as a timestamp) can be included in the\n\"ping\" command, as in the message \"ping::\"; this\ndata will be appended to the response, i.e., \"pong::\". This could be used to verify that all nodes are\nresponding and not too heavily loaded.\n\n(Note that if ``control.node_name`` is not specified, the response to\na \"ping\" command containing additional data such as a timestamp will\nbe \"pong::\".)\n\nNote that, at present, no tool exists for sending pings or receiving\npongs.\n\nThe Reload Command\n------------------\n\nThe \"reload\" command is the real reason for the existence of the\ncontrol daemon. This command causes the current set of limits to be\nreloaded from the database and presented to the middleware for\nenforcement.\n\nThe simplest form of the reload command is simply, \"reload\". If the\n``control.reload_spread`` configuration option was set, the reload\nwill be scheduled for some time within the configured time interval;\notherwise, it will be performed immediately.\n\nThe next simplest form of the reload command is \"reload:immediate\".\nThis causes an immediate reload of the limits, overriding any\nconfigured time spread.\n\nThe final form of the reload command is \"reload:spread:\",\nwhere the \"\" specifies a time interval, in seconds, over\nwhich to spread reloading of the limits. This specified interval is\nused in preference to that specified by ``control.reload_spread``, if\nset.\n\nNote that the ``setup_limits`` tool automatically initiates a reload\nonce the limits are updated in the database. See the section on tools\nfor more information.\n\nTurnstile Tools\n===============\n\nThe limits are stored in the Redis database using a sorted set, and\nthey are encoded using Msgpack. (Although the Msgpack format is not\nhuman-readable, it is very space and time efficient, which is why it\nwas chosen for this application.) This makes manual management of the\nlimits configuration more difficult, and so Turnstile ships with two\ntools to make management of the rate limiting configuration easier. A\nthird tool starts up a remote control daemon, for use when Turnstile\nis used with applications that run multiple processes, such as the\n``nova-api`` component of OpenStack.\n\nThe ``dump_limits`` Tool\n------------------------\n\nThe ``dump_limits`` tool may be used to dump the current limits in the\ndatabase into an XML representation. This tool requires the name of\nan INI-style configuration file; see the section on configuring the\ntools below for more information.\n\nA usage summary for ``dump_limits``::\n\n usage: dump_limits [-h] [--debug] config limits_file\n\n Dump the current limits from the Redis database.\n\n positional arguments:\n config Name of the configuration file, for connecting to the Redis\n database.\n limits_file Name of the XML file that the limits will be dumped to.\n\n optional arguments:\n -h, --help show this help message and exit\n --debug, -d Run the tool in debug mode.\n\nThe ``remote_daemon`` Tool\n--------------------------\n\nThe ``remote_daemon`` tool may be used to start a separate control\ndaemon process. This tool requires the name of an INI-style\nconfiguration file; see the section on configuring the tools below for\nmore information. Note that, in addition to the required Redis\nconfiguration values, configuration values for the\n``control.remote.authkey``, ``control.remote.host``, and\n``control.remotes.port`` options must be provided.\n\nA usage summary for ``remote_daemon``::\n\n usage: remote_daemon [-h] [--log-config LOGGING] [--debug] config\n\n Run the external control daemon.\n\n positional arguments:\n config Name of the configuration file.\n\n optional arguments:\n -h, --help show this help message and exit\n --log-config LOGGING, -l LOGGING\n Specify a logging configuration file.\n --debug, -d Run the tool in debug mode.\n\nThe ``setup_limits`` Tool\n-------------------------\n\nThe ``setup_limits`` tool may be used to read an XML file (such as\nthat produced by ``dump_limits``) and load the rate limiting\nconfiguration into the Redis database. This tool requires the name of\nan INI-style configuration file; see the section on configuring the\ntools below for more information.\n\nA usage summary for ``setup_limits``::\n\n usage: setup_limits [-h] [--debug] [--dryrun] [--noreload]\n [--reload-immediate] [--reload-spread SECS]\n config limits_file\n\n Set up or update limits in the Redis database.\n\n positional arguments:\n config Name of the configuration file, for connecting to the\n Redis database.\n limits_file Name of the XML file describing the limits to\n configure.\n\n optional arguments:\n -h, --help show this help message and exit\n --debug, -d Run the tool in debug mode.\n --dryrun, --dry_run, --dry-run, -n\n Perform a dry run; inhibits loading data into the\n database.\n --noreload, -R Inhibit issuing a reload command.\n --reload-immediate, -r\n Cause all nodes to immediately reload the limits\n configuration.\n --reload-spread SECS, -s SECS\n Cause all nodes to reload the limits configuration\n over the specified number of seconds.\n\nConfiguring the Tools\n---------------------\n\nThe tools ``dump_limits``, ``remote_daemon``, and ``setup_limits``\nrequire an INI-style configuration file, which specifies how to\nconnect to the Redis database. This file should contain the section\n\"[redis]\" and should be populated with the same \"redis.*\" options as\nthe PasteDeploy configuration file, minus the \"redis.\" prefix. For\nexample::\n\n [redis]\n host = \n\nEach \"redis.*\" option recognized by the Turnstile middleware is\nunderstood by the tools.\n\nAdditional options may be provided, such as the control channel,\nlimits key, and the ``remote_daemon`` options. The configuration file\nshould be compatible with the alternate configuration file described\nunder the ``config`` configuration option for the Turnstile\nmiddleware.\n\nRate Limit XML\n--------------\n\nThe XML file used for expressing rate limit configuration is\nrelatively straightforward, or at least as straightforward as XML can\nbe. The top-level element is ````; this should contain a\nsequence of ```` elements, each containing a number of\n```` elements. The specific attributes available for any given\nlimit class depend on the exact class, but that information is\ndocumented in the ``attrs`` attribute of the limit class. (This\ninformation is suitable for introspection.)\n\nThe ```` element has one XML attribute which must be specified:\nthe ``class`` attribute, which must be set to a \"module:class\" string\nidentifying the desired limit class. The ```` element also has\na single XML attribute which must be set: ``name``, which identifies\nthe name of the Limit attribute. The contents of the ````\nelement identify the value for the named attribute.\n\nSome limit attributes are lists; for these attributes, the ````\nelement must contain one or more ```` elements, whose contents\nidentify a single item in the attribute list. Other limit attributes\nare dictionaries; for these attributes, again the ```` element\nmust contain one or more ```` elements, but now those\n```` elements must have the XML attribute ``key`` set to the\ndictionary key corresponding to that value.\n\nAs an example, consider the following limits configuration::\n\n \n \n \n \n\t [0-9]+\n\t\n second\n\t/page/{pageid}\n\t10\n\t\n\t GET\n\t\n \n \n\nIn this example, GET access to ``/page/{pageid}`` is rate-limited to\n10 per second. The ``requirements`` attribute may be used to specify\nregular expressions to tune the matching of URI components; in this\ncase, the ``{pageid}`` value must be composed of 1 or more digits.\nThe limit class used is the basic ``turnstile.limits:Limit`` limit\nclass.\n\nCustom Limit Classes\n====================\n\nAll limit classes must descend from ``turnstile.limits:Limit``. This\nadmittedly un-Pythonic requirement has a number of advantages,\nincluding the specific machinery which allows limits to be stored into\nthe Redis database. Most limit classes only need to worry about the\n``attrs`` class attribute and the ``filter()`` method, although the\n``route()`` and ``format()`` methods may also be hooked. For more\ninformation about these methods, see the docstrings provided for their\ndefault implementations in ``turnstile.limits:Limit``.\n\nAccessing the Turnstile Configuration\n=====================================\n\nThe Turnstile configuration is available to preprocessors and to the\nLimit classes. For preprocessors, it is available directly from the\nmiddleware object (the first passed parameter) via the ``config``\nattribute. (The database handle is also available via the ``db``\nattribute, should access to the database be required.) For the\n``filter()`` method of the Limit classes, the configuration is\navailable in the request environment under the ``turnstile.conf`` key.\n\nThe Turnstile configuration is represented as a\n``turnstile.config:Config`` object. Configuration keys that do not\ncontain a \".\" are available as attributes of this object; for example,\nto obtain the configured status value, assuming the Turnstile\nconfiguration is available in the ``conf`` variable, the correct code\nwould be::\n\n status = conf.status\n\nFor those configuration keys which do contain a \".\", the part of the\nname to the left of the first \".\" becomes a dictionary key, and the\nremainder of the name will be a second key. For example, to access\nthe value of the ``redis.connection_pool.connection_class`` variable,\nthe correct code would be::\n\n connection_class = config['redis']['connection_pool.connection_class']\n\nAll values in the configuration are stored as strings. Configuration\nvalues do not need to be pre-declared in any way; Turnstile ignores\n(but maintains) configuration values that it does not use, making\nthese values available for use by preprocessors and Limit subclasses.\n\nFor convenience, the ``turnstile.config:Config`` class offers a static\nmethod ``to_bool()`` which can convert a string value to a boolean\nvalue. The strings \"t\", \"true\", \"on\", \"y\", and \"yes\" are all\nrecognized as a boolean ``True`` value, as are numeric strings which\nevaluate to non-zero values. The strings \"f\", \"false\", \"off\", \"n\",\nand \"no\" are all recognized as a boolean ``False`` value, as are\nnumeric strings which evaluate to zero values. Any other string value\nwill cause ``to_bool()`` to raise a ``ValueError``, unless the\n``do_raise`` argument is given as ``False``, in which case\n``to_bool()`` will return a boolean ``False`` value.\n\n.. _PIP: http://www.pip-installer.org/en/latest/index.html", "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/klmitch/turnstile", "keywords": null, "license": "Apache License (2.0)", "maintainer": null, "maintainer_email": null, "name": "turnstile", "package_url": "https://pypi.org/project/turnstile/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/turnstile/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/klmitch/turnstile" }, "release_url": "https://pypi.org/project/turnstile/0.6.1/", "requires_dist": null, "requires_python": null, "summary": "Distributed rate-limiting middleware", "version": "0.6.1" }, "last_serial": 801009, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "c2a61d36bc0e2331de1a3e4e3758758a", "sha256": "54d11138ac726af82f8d949c8cdb4c3bb7ea8c0d2b4cc5336ecff48d2e53b652" }, "downloads": -1, "filename": "turnstile-0.1.tar.gz", "has_sig": false, "md5_digest": "c2a61d36bc0e2331de1a3e4e3758758a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30626, "upload_time": "2012-03-01T00:11:39", "url": "https://files.pythonhosted.org/packages/d7/60/d06fa771f9deac01cc17ffd47322059d67fcfae561dfaf3f4e6890b8d872/turnstile-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "ac08dbfd8624e016fe02da1ff71303bd", "sha256": "7134f60b4cd3bfb4888d754cbc82edd0635ffb980fd1d790bce807cda668de8c" }, "downloads": -1, "filename": "turnstile-0.2.tar.gz", "has_sig": false, "md5_digest": "ac08dbfd8624e016fe02da1ff71303bd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30918, "upload_time": "2012-03-02T22:15:49", "url": "https://files.pythonhosted.org/packages/de/f0/1b854af2b7b68886b6be7cc885fab6475ef7494e3ca1bc4727c7c722517c/turnstile-0.2.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "07ced54c40648f4155a2561beef9d1cd", "sha256": "3a6210c96726f4842bdb82789dbf5a89640df638fcc1826f730e08d5fa1069d6" }, "downloads": -1, "filename": "turnstile-0.5.tar.gz", "has_sig": false, "md5_digest": "07ced54c40648f4155a2561beef9d1cd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44097, "upload_time": "2012-03-08T18:29:32", "url": "https://files.pythonhosted.org/packages/15/34/b0edab083b93aae665b9f41d9fe88d3bba77c2150d45ddf0d19fb9e6af5a/turnstile-0.5.tar.gz" } ], "0.5.1": [ { "comment_text": "", "digests": { "md5": "3cb88c840a12ae1188e053e3a3b800a2", "sha256": "432b4a35fcb450ff631e123aeed970058e5db9c3a2f86a703e2bb7437601f8f9" }, "downloads": -1, "filename": "turnstile-0.5.1.tar.gz", "has_sig": false, "md5_digest": "3cb88c840a12ae1188e053e3a3b800a2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44404, "upload_time": "2012-03-13T20:17:32", "url": "https://files.pythonhosted.org/packages/2d/87/e142bcb2acfcc3adbd15d48e4ece322b17e876bf17041f753ad85b8933a8/turnstile-0.5.1.tar.gz" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "e74e4e180748f0666ff3c5d73a6fb830", "sha256": "16986de4bd1bb9261dcd1d87b986e0e74937ea9a8a99c5829e72be518f788fe4" }, "downloads": -1, "filename": "turnstile-0.5.2.tar.gz", "has_sig": false, "md5_digest": "e74e4e180748f0666ff3c5d73a6fb830", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45193, "upload_time": "2012-03-16T20:47:06", "url": "https://files.pythonhosted.org/packages/f0/65/69882ef18f317abc839006e86430dd6b064cd903a50b423fe4600aad0566/turnstile-0.5.2.tar.gz" } ], "0.6.0": [ { "comment_text": "", "digests": { "md5": "5c72cb6378f11d9409d73b11d184ff2f", "sha256": "ea0aa8eb6435d7c94456cae74f4b113bb2b45e834deae1dc7ed249b556f59c4e" }, "downloads": -1, "filename": "turnstile-0.6.0.tar.gz", "has_sig": false, "md5_digest": "5c72cb6378f11d9409d73b11d184ff2f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 65684, "upload_time": "2012-08-30T17:22:25", "url": "https://files.pythonhosted.org/packages/28/b9/4cc385bfb4b3d024d6965ee8130c2a60bc578930c2896cb2254981f4be39/turnstile-0.6.0.tar.gz" } ], "0.6.1": [ { "comment_text": "", "digests": { "md5": "1745f066b19dd38b7a9e28c546bf3b93", "sha256": "508f1d5ceb834a3be5feaaaefe2036f47790719bd6bdf136c67c4cd5605b1449" }, "downloads": -1, "filename": "turnstile-0.6.1.tar.gz", "has_sig": false, "md5_digest": "1745f066b19dd38b7a9e28c546bf3b93", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 66587, "upload_time": "2012-10-02T21:24:15", "url": "https://files.pythonhosted.org/packages/4d/71/57f0eb271e49f47b73b67e376647e55a4ee84e0328c9afac068ec80e7d83/turnstile-0.6.1.tar.gz" } ], "0.7.0b1": [ { "comment_text": "", "digests": { "md5": "4b2188d78f4c096099a4a4e042d97835", "sha256": "1008fa5fc2a4f1f098d4636f7f7b841193a130d07f3cf21b09d8e9f9cb424277" }, "downloads": -1, "filename": "turnstile-0.7.0b1.tar.gz", "has_sig": false, "md5_digest": "4b2188d78f4c096099a4a4e042d97835", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 80325, "upload_time": "2013-04-01T15:19:25", "url": "https://files.pythonhosted.org/packages/48/7f/8b0fef3c928c8cb1b2af7c9ac02eab6063d55812acc622c0a3103a608c5b/turnstile-0.7.0b1.tar.gz" } ], "0.7.0b2": [ { "comment_text": "", "digests": { "md5": "6de1c3c4c4f963a4a8904157cbaffd95", "sha256": "00336d4a542d59efab200d38ea0208c363f5df25d407dfb9ffe48dff865121c7" }, "downloads": -1, "filename": "turnstile-0.7.0b2.tar.gz", "has_sig": false, "md5_digest": "6de1c3c4c4f963a4a8904157cbaffd95", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 80664, "upload_time": "2013-05-01T22:21:53", "url": "https://files.pythonhosted.org/packages/a1/fe/4ac621dc4f25fae25aa8b92f4904b04e0cfa6b3bc490ff948e965b95de7e/turnstile-0.7.0b2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1745f066b19dd38b7a9e28c546bf3b93", "sha256": "508f1d5ceb834a3be5feaaaefe2036f47790719bd6bdf136c67c4cd5605b1449" }, "downloads": -1, "filename": "turnstile-0.6.1.tar.gz", "has_sig": false, "md5_digest": "1745f066b19dd38b7a9e28c546bf3b93", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 66587, "upload_time": "2012-10-02T21:24:15", "url": "https://files.pythonhosted.org/packages/4d/71/57f0eb271e49f47b73b67e376647e55a4ee84e0328c9afac068ec80e7d83/turnstile-0.6.1.tar.gz" } ] }