{ "info": { "author": "Tobias Dammers", "author_email": "dammers@tracksinspector.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Internet :: WWW/HTTP :: Dynamic Content" ], "description": "papi\n====\n\nLow-Boilerplate RESTful APIs\n----------------------------\n\nIntroduction\n------------\n\npapi is a library that allows you to build powerful\n`RESTful `__ web services on top\nof plain WSGI by writing backends as simple and semantic classes, and\nthen feeding them to its equally simple WSGI wrapper function.\n\nFeatures\n--------\n\n- Proper RESTful semantics over HTTP(S): GET, PUT, POST, DELETE map to\n retrieve / list resources, create, update, delete\n- Automatic routing\n- Automatic `HATEOAS `__\n decoration (adds links to parent, self, and children, on every JSON\n response)\n- Semi-automatic content type negotiation: JSON is handled\n transparently, other content types are easy to support in your\n backend code\n- Automatic translations of failures to HTTP error responses; uses the\n 4xx range of status codes correctly\n- Runs on any compliant WSGI host, making it suitable for deployment\n under a wide range of web servers and protocols\n- Method override: fake unsupported HTTP methods through GET parameters\n or headers\n\nInstalling\n----------\n\nInstalling with pip:\n\n.. code:: bash\n\n pip install papi\n\nYou probably want to do this in a virtualenv.\n\nConceptual Model\n----------------\n\nPapi's concept of a RESTful API is that of a tree-shaped data structure,\nconsisting of \"leaf\" nodes called \"documents\" and \"branch\" nodes called\n\"collections\". Both are modelled as *resources*, and a resource can act\nas a document, as a collection, or both. Documents have a body\n(potentially available in multiple flavors, matching different MIME\ncontent types); collections have child resources, and the library code\nmaps this resource tree onto a URL path structure. As RESTfulness has\nit, HTTP methods indicate the kind of operation on this tree, and the\nHATEOAS philosophy is applied by tagging documents and collections with\nmetadata when possible.\n\nUsage\n-----\n\nDefining A Resource\n~~~~~~~~~~~~~~~~~~~\n\nTo implement a working RESTful API, you need to define a root resource.\nResources can act as documents (having a body), collections (having\nchild resources), or hybrids (having both a body and child resources).\nFor the root resource, you almost certainly want a collection-style\nresource, otherwise your API will only ever contain one document.\n\n Note that ``Resource`` is not a base class, it's just an implicit\n interface. Papi resolves method calls through duck typing, there is\n no need to inherit or formally implement anything, just add the\n methods you need, and that's it. Adding other methods is of course no\n problem at all.\n\nThe relevant methods for a resource are:\n\n.. code:: python\n\n def get_structured_body(self, digest=False)\n def get_typed_body(self, mime_pattern)\n\nGet the payload data for the resource itself; implementing these methods\nmakes the resource a document.\n\n``get_typed_body`` is always tried first; it should return a pair of\n``(mime_type, body)`` to indicate that a body is available that matches\nthe ``mime_pattern``, or ``None`` to tell Papi that this MIME type\ncannot be satisfied.\n\nFor some \"special\" MIME types (currently only ``text/json`` and\n``application/json``), the ``get_structured_body`` method is tried when\n``get_typed_body`` fails; this method is supposed to return a native\nPython data structure. Currently, the only requirement is that the\nreturned data must be JSON-encodable, but in the future, other types may\nbe supported (e.g. XML, plain text, HTML, ...), so it's best to stick\nwith \"vanilla\" data structures that directly correspond to JSON types:\n``dict``, ``list``, ``tuple``, ``int``, ``float``, ``bool``, ``str`` and\n``None`` are all safe to use, others might not. The ``digest`` argument\nindicates whether the full body should be returned, or a \"digest\"\nversion that contains only the essential properties. ``digest`` will be\n``True`` when called on a child resource in a collection listing\ncontext, ``False`` when the resource is requested directly.\n\n One thing to keep in mind with these two methods is that **documents\n derived from ``get_typed_body`` are never parsed, and no metadata is\n ever added**. This means that if you want to have Papi add HATEOAS\n links and a list of child resources to the response, you must\n implement ``get_structured_body``, and if you also implement\n ``get_typed_body``, it must return ``None`` for at least the JSON\n content types (and, in the future, any content type you want to have\n tagged with metadata).\n\n.. code:: python\n\n def get_children(self, offset=0, count=10, filters=None, order=None)\n def get_child(self, name)\n\nThese methods need to be implemented for resources that act as\ncollections. ``get_children`` returns a list of ``(name, resource)``\npairs, and can take the following keyword arguments to alter its behavior:\n\n- ``filters``: a list of ``Filter`` objects. A ``Filter`` object has three\n properties: ``operator``, ``value``, and ``propname``, where ``propname``\n indicates which property of the document to compare, ``operator`` indicates\n how to compare (currently only ``\"equals\"`` is used), and ``value`` is a\n (string) value that the property is compared against.\n- ``order``: a list of ``(descending, order-key)`` pairs, from most-significant\n to least-significant. If ``descending`` is ``True``, the result must be\n ordered in descending order. ``order-key`` is specific to the resource, no\n further interpretation is performed by Papi.\n- ``offset``: the number of items to skip from the beginning of the\n list. Works like Python's ``x[offset:]`` construct, or the ``OFFSET``\n part in an SQL ``LIMIT`` clause.\n- ``count``: the number of items to return, starting at the ``offset``\n if provided. Works like Python's ``x[:count]`` construct, or the\n ``COUNT`` part in an SQL ``LIMIT`` clause.\n- ``page``: when ``count`` is specified, you can provide a page number\n instead of an ``offset``. Page numbers are 1-based, and each page\n contains ``count`` entries, so ``page=2, count=10`` retrieves items\n 10 through 19.\n\nIt is recommended to implement ``get_children`` with additional ``*args`` and\n``**kwargs`` arguments, such that future Papi versions can add additional\narguments without breaking compatibility.\n\n``get_child`` gets a single child resource; the ``name`` parameter,\nthroughout Papi's Python API, refers to a resource's primary key. We\ncall it \"name\", because ideally, it should be a somewhat descriptive,\nmeaningful natural identifier for the object it represents, which, when\npossible, is more in line with the RESTful philosophy, and makes for\nnaturally beautiful URIs.\n``http://example.org/api/fruit/apples/granny_smith`` is a much nicer URI\nthan ``http://example.org/api/5d75e3/35b0bd/d68c481bb1f4``.\n\n.. code:: python\n\n def create(self, input, content_type=None)\n def store(self, input, name, content_type=None)\n def delete(self, name)\n\nThese methods can optionally be implemented to turn a readonly resource\ninto a writeable collection. Note that *all* write operations are\ndefined on the parent resource, even though at the HTTP level, some are\nexposed on the resource itself - for example, ``POST /root/child1`` maps\nto the resource named ``\"child1\"`` under the parent resource ``\"root\"``,\nbut the method that gets called is the ``store`` method of the ``root``\nresource. This is for two reasons: one, the child resource to store may\nnot exist yet (this is the case for ``PUT`` requests), and two, the\nresource itself does not know its own name, nor does it need to.\n\nSome notes on these methods:\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe ``input`` argument will contain a file-like object, which means\nyou can use the usual ``read()`` etc. methods on it to extract the\nbody. Parsing is your own responsibility, Papi does not do this for\nyou. Particularly, there is no write equivalent to the\n``get_structured_body`` method; however, processing JSON documents is\nusually a simple matter of calling ``json.loads``.\n\nThe difference between ``create`` and ``store`` is that ``create``\nmust generate a name for the received document, and return a\n``name, body`` tuple (where ``body`` is a digest that describes the\ndocument that has been created, in a JSON-encodable data structure\naccording to the same rules as ``get_structured_body``); multiple\ncalls to ``create`` should create multiple distinct documents, and\nreturn distinct names. Conceptually, ``create`` *always* creates a\nnew document. By contrast, ``store`` takes a document name as an\nargument, so it does not generate one itself, and multiple calls with\nthe same name will overwrite one another. While ``store`` may also\ncreate new documents (if the ``name`` does not exist yet), it should\noverwrite (update) documents when the name already exists.\n\nServing A Resource\n~~~~~~~~~~~~~~~~~~\n\nServing a resource is simple; the ``serve_resource`` function can be\nused to turn a valid resource into a WSGI application, like this:\n\n.. code:: python\n\n application = serve_resource(root_resource)\n\nAnd from there, it's a matter of feeding that function to a WSGI server\n(see the `WSGI documentation `__\nfor details).\n\nGive It A Spin\n~~~~~~~~~~~~~~\n\nThe included example application (``example/app.py``) implements a\nsimple in-memory database that supports plain-text payloads for\ndocuments; all the resources in it are read/write document/collection\nhybrids, which means that data can be added at any point in the tree.\nAssuming that this application runs in a WSGI server on localhost:5000,\nwe can try a few requests (we'll use cURL for these examples):\n\n.. code:: bash\n\n > curl 'http://localhost:5000/' # Fetch the root resource\n\n {\"_parent\": {\"href\": \"/\"}, \"_self\": {\"href\": \"/\"}, \"_items\": [{\"_parent\":\n {\"href\": \"/\"}, \"_self\": {\"href\": \"/things\"}, \"_name\": \"things\"}]}\n\nThat's not very readable, but we can use the ``pretty`` parameter to\npretty-print JSON output:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/?pretty=1'\n {\n \"_parent\": {\n \"href\": \"/\"\n },\n \"_self\": {\n \"href\": \"/\"\n },\n \"_items\": [\n {\n \"_parent\": {\n \"href\": \"/\"\n },\n \"_self\": {\n \"href\": \"/things\"\n },\n \"_name\": \"things\"\n }\n ]\n }\n\nThis tells us a few things:\n\n- The URI for this resource (``_self``) is ``/``\n- The URI for this resource's parent (``_parent``) is also '/' (this is\n actually a misfeature currently; the root node should not actually\n report a parent)\n- The resource contains child resources (``_items``)\n- To be specific, it contains *one* child resource, named ``things``,\n with a URI of ``/things``.\n\nAs you can see, this HATEOAS metadata makes the API fully discoverable;\nthe resource tells us its own location within the API, as well as those\nof its parent and children.\n\nLet's look at the child resource \"things\":\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/?pretty=1'\n {\n \"_parent\": {\n \"href\": \"/\"\n },\n \"_self\": {\n \"href\": \"/things\"\n },\n \"_items\": [\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/apple\"\n },\n \"_value\": \"I am an apple. Eat me.\",\n \"_name\": \"apple\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/banana\"\n },\n \"_value\": \"I'll bend either way for you.\",\n \"_name\": \"banana\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/nut\"\n },\n \"_value\": \"I'm nuts!\",\n \"_name\": \"nut\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/onion\"\n },\n \"_value\": \"Hurt me, and I will make you cry.\",\n \"_name\": \"onion\"\n }\n ],\n \"_name\": \"things\"\n }\n\nOh joy! What a bunch of things! And they're still fully\nHATEOAS-discoverable, so let's see what happens when we try to fetch an\nonion:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/onion/?pretty=1'\n Hurt me, and I will make you cry.\n\nThat's weird. No JSON. Why is that? Right, content negotiation. Our\nexample resource supports ``text/plain`` as well as JSON; curl, by\ndefault, specifies that it accepts ``*/*``, that is, *anything*, and\nbecause Papi prefers \"typed\" bodies over \"structured\" bodies, the first\ntype that matches (which happens to be ``text/plain``) is what we get.\nIf we were serving, say, images through our API, this would be *exactly*\nthe desired behavior. We can still request JSON though, we just have to\noverride the ``Accept`` header:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/onion/?pretty=1' -H 'Accept: text/json'\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/onion\"\n },\n \"_value\": \"Hurt me, and I will make you cry.\",\n \"_name\": \"onion\"\n }\n\nAll is well!\n\nSo far, we have only requested things that existed. Of course requesting\nsomething that doesn't exist yields a 404 error; we'll use cURL's ``-i``\noption to show HTTP headers:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/nope/?pretty=1' -i\n HTTP/1.1 404 Not Found\n Content-type: text/plain;charset=utf8\n\n Not Found\n\nThat makes sense.\n\nWhat happens if we request a content type that the resource doesn't\nsupport?\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/onion/?pretty=1' -i -H 'Accept: img/png'\n HTTP/1.1 406 Not Acceptable\n Content-type: text/plain;charset=utf8\n\n Not Acceptable\n\nIt does the right thing.\n\nSo far we've only been *reading* from the API; let's try *writing*\nthings. According to standard RESTful procedures, we can create new\ndocuments by using the HTTP ``PUT`` method:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/potato' -XPUT -i -H 'Content-Type: text/plain'\n HTTP/1.1 200 OK\n Content-type: application/json\n\n {\"_parent\": {\"href\": \"/things\"}, \"_self\": {\"href\": \"/things/potato\"}, \"_value\": \"Slice me, dice me, fry me\"}\n\nThe status code ``200`` indicates that the document was indeed created,\nand fetching the ``_self`` URI confirms this:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/potato/?pretty=1'\n Slice me, dice me, fry me\n\nAnd of course, this new document supports JSON as well:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/potato/?pretty=1' -H 'Accept: text/json'\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/potato\"\n },\n \"_value\": \"Slice me, dice me, fry me\",\n \"_name\": \"potato\"\n }\n\nNote that if you want to access the API from a web browser, it will\nalmost certainly not support any HTTP methods other than ``GET`` and\n``POST`` (plus a few that we don't care much about here, such as\n``HEAD`` and ``OPTIONS``); ``PUT`` and ``DELETE``, in particular, will\nnot work. Because of this, Papi has a method override feature: if you\nadd a ``_method`` parameter to the query string, or a\n``X-Method-Override`` header to the request, the value of that will\noverride the actual request method. So the following curl requests would\nall produce the same behavior:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/potato' -XPUT -i -H 'Content-Type: text/plain'\n > curl 'http://localhost:5000/things/potato?_method=PUT' -XPOST -i -H 'Content-Type: text/plain'\n > curl 'http://localhost:5000/things/potato' -XPOST -i -H 'X-Method-Override: PUT' -H 'Content-Type: text/plain'\n\nAn alternative way of creating new documents is using the HTTP method\n``POST`` on the *parent* resource, leaving the responsibility of\ngenerating a suitable unique name for the new document to the parent\nresource. This is what that looks like:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things?pretty=1' -XPOST -i -H 'Content-Type: text/plain' -d'Carrot on a stick'\n HTTP/1.1 200 OK\n Content-type: application/json\n\n {\"_parent\": {\"href\": \"/things\"}, \"_self\": {\"href\": \"/things/carrot\"}, \"_value\": \"Carrot on a stick\"}\n\nOur example resource is configured to generate names based on the first\nword of the input, so that's what we get: ``\"carrot\"``.\n\nOther than the ``PUT`` method, however, ``POST`` will always create a\nnew document, rather than overwrite an existing one, so if we ``POST``\nthe same thing again, the API is required to either deny the request\nwith a ``Conflict`` response, or create a new document with a different\nunique name. Our example application opts for the second solution:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things?pretty=1' -XPOST -i -H 'Content-Type: text/plain' -d'Carrot on a stick'\n HTTP/1.1 200 OK\n Content-type: application/json\n\n {\"_parent\": {\"href\": \"/things\"}, \"_self\": {\"href\": \"/things/BL6yCijd8x4Mwzcf-carrot\"}, \"_value\": \"Carrot on a stick\"}\n\nAs you can see, the name is disambiguated by prepending a random token.\nListing the ``/things`` resource shows that two documents have actually\nbeen created:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things?pretty=1' -H 'Accept: text/json'\n {\n \"_parent\": {\n \"href\": \"/\"\n },\n \"_self\": {\n \"href\": \"/things\"\n },\n \"_items\": [\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/BL6yCijd8x4Mwzcf-carrot\"\n },\n \"_value\": \"Carrot on a stick\",\n \"_name\": \"BL6yCijd8x4Mwzcf-carrot\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/apple\"\n },\n \"_value\": \"I am an apple. Eat me.\",\n \"_name\": \"apple\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/banana\"\n },\n \"_value\": \"I'll bend either way for you.\",\n \"_name\": \"banana\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/carrot\"\n },\n \"_value\": \"Carrot on a stick\",\n \"_name\": \"carrot\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/nut\"\n },\n \"_value\": \"I'm nuts!\",\n \"_name\": \"nut\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/onion\"\n },\n \"_value\": \"Hurt me, and I will make you cry.\",\n \"_name\": \"onion\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/potato\"\n },\n \"_value\": \"Slice me, dice me, fry me\",\n \"_name\": \"potato\"\n }\n ],\n \"_name\": \"things\"\n }\n\nAnd of course our example application also supports deleting items,\nusing the ``DELETE`` method:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things/potato/?pretty=1' -i -XDELETE\n HTTP/1.1 204 No Content\n Content-type: text/plain\n\nNote the use of the ``204 No Content`` status line; since we've deleted\na resource, there is no meaningful content to return, all we get is an\nempty success response. And to confirm that the potato has indeed been\ndeleted:\n\n.. code:: bash\n\n > curl 'http://localhost:5000/things?pretty=1' -H 'Accept: text/json'\n {\n \"_parent\": {\n \"href\": \"/\"\n },\n \"_self\": {\n \"href\": \"/things\"\n },\n \"_items\": [\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/BL6yCijd8x4Mwzcf-carrot\"\n },\n \"_value\": \"Carrot on a stick\",\n \"_name\": \"BL6yCijd8x4Mwzcf-carrot\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/apple\"\n },\n \"_value\": \"I am an apple. Eat me.\",\n \"_name\": \"apple\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/banana\"\n },\n \"_value\": \"I'll bend either way for you.\",\n \"_name\": \"banana\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/carrot\"\n },\n \"_value\": \"Carrot on a stick\",\n \"_name\": \"carrot\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/nut\"\n },\n \"_value\": \"I'm nuts!\",\n \"_name\": \"nut\"\n },\n {\n \"_parent\": {\n \"href\": \"/things\"\n },\n \"_self\": {\n \"href\": \"/things/onion\"\n },\n \"_value\": \"Hurt me, and I will make you cry.\",\n \"_name\": \"onion\"\n }\n ],\n \"_name\": \"things\"\n }", "description_content_type": null, "docs_url": "https://pythonhosted.org/papi/", "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/tracksinspector/papi", "keywords": "restful rest webservice json http hateoas", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "papi", "package_url": "https://pypi.org/project/papi/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/papi/", "project_urls": { "Homepage": "https://github.com/tracksinspector/papi" }, "release_url": "https://pypi.org/project/papi/0.1.8/", "requires_dist": null, "requires_python": "", "summary": "Build RESTful APIs with minimal boilerplate", "version": "0.1.8" }, "last_serial": 2534806, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "1ffbb0f9f5b5bfaf7390d6971dd008dd", "sha256": "d0e1b4f7ee16ef85e2b1802b0b088cd1e14497dbabff48d7e8c0b85890865b9a" }, "downloads": -1, "filename": "papi-0.0.1-py3.4.egg", "has_sig": false, "md5_digest": "1ffbb0f9f5b5bfaf7390d6971dd008dd", "packagetype": "bdist_egg", "python_version": "3.4", "requires_python": null, "size": 33751, "upload_time": "2016-10-25T12:22:25", "url": "https://files.pythonhosted.org/packages/0b/ef/1e74893940f96d787969e89e908ab62844702e263ea19f1de32a5fc0e5c8/papi-0.0.1-py3.4.egg" }, { "comment_text": "", "digests": { "md5": "a47fda203ee12af097d2d792deb31f87", "sha256": "743007cb4928c68868937837641dc1ea4e166b50eb52c084b2bd7343b13234e6" }, "downloads": -1, "filename": "papi-0.0.1.tar.gz", "has_sig": false, "md5_digest": "a47fda203ee12af097d2d792deb31f87", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22783, "upload_time": "2016-10-25T12:07:44", "url": "https://files.pythonhosted.org/packages/9d/a8/0c12ec527c38945adffd116715c3b183a37bb48b630e662ca27acf356aa7/papi-0.0.1.tar.gz" } ], "0.1.0": [], "0.1.1": [ { "comment_text": "", "digests": { "md5": "246e524d30df5fe7265f956f25d53941", "sha256": "55f5f63c5a0bf70423f3947a1ee2a14ea8615c222cbc44246d4cf32f59290afd" }, "downloads": -1, "filename": "papi-0.1.1.tar.gz", "has_sig": false, "md5_digest": "246e524d30df5fe7265f956f25d53941", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23616, "upload_time": "2016-10-25T18:27:33", "url": "https://files.pythonhosted.org/packages/e0/2d/0f98454cc16328b1bc77e78c2bc2e06cc4fa29935e3ab5e44fd2a97d5fd3/papi-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "b99f211155c6b3f7c14e6a80b0983ea8", "sha256": "eb77ab960e85bee0a80d233c99e02ac1a068f49c2561c35a684ab656c4a6b39b" }, "downloads": -1, "filename": "papi-0.1.2.tar.gz", "has_sig": false, "md5_digest": "b99f211155c6b3f7c14e6a80b0983ea8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25157, "upload_time": "2016-10-25T18:40:31", "url": "https://files.pythonhosted.org/packages/20/2a/5168cf3b3e10ebd8bbd75a062c12a8d77c39bf3416ea69b86b7c05c8dba8/papi-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "5414af899a8b066a0dd927beda340eb8", "sha256": "b604449fd1f8d22086107940d0e028b0349a6bad4968f929e7fd248fe798a8d8" }, "downloads": -1, "filename": "papi-0.1.3.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "5414af899a8b066a0dd927beda340eb8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31370, "upload_time": "2016-11-24T14:32:39", "url": "https://files.pythonhosted.org/packages/a8/21/2c4ac5056010a551586f2fc2002e3466126efbbdaa4d038260cda51e7aaf/papi-0.1.3.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "a5db34e9995ab53a79dc56f1f9555110", "sha256": "f951373a9ff0162951e1e449ac4b90ce12ced59277b7559718d34760f20c32e0" }, "downloads": -1, "filename": "papi-0.1.3.tar.gz", "has_sig": false, "md5_digest": "a5db34e9995ab53a79dc56f1f9555110", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26873, "upload_time": "2016-11-24T14:32:42", "url": "https://files.pythonhosted.org/packages/68/2e/2e82d5aeb6e1a8c7651537a6e7edb31374da3adf7e5685e9e8c0d34040df/papi-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "ff044f91741eeaaafda09d4fc10a2c4e", "sha256": "86427fdf6a3bc2f622664083a7e840a5c02f719a3465bf801f00d1d680c392d3" }, "downloads": -1, "filename": "papi-0.1.4.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "ff044f91741eeaaafda09d4fc10a2c4e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31764, "upload_time": "2016-11-29T14:21:26", "url": "https://files.pythonhosted.org/packages/23/50/dcfd41d8f998c425bdee28ba8b8a3069d7805ab5c3af95099e5b242e26bb/papi-0.1.4.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "051581dcf4a2c7f001fd56e8c56cee19", "sha256": "e45f0f4511fa5692db08e3763c78f508cdb3c0c130371c24a81af5f15dbc1c75" }, "downloads": -1, "filename": "papi-0.1.4.tar.gz", "has_sig": false, "md5_digest": "051581dcf4a2c7f001fd56e8c56cee19", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26984, "upload_time": "2016-11-29T14:21:29", "url": "https://files.pythonhosted.org/packages/3b/94/c6e1d6c76422d5b68fa670102dda08e1c09cf0d9cac032e4264be4442216/papi-0.1.4.tar.gz" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "1b30a16b47514485b723c74f365aaf1c", "sha256": "68ec6979bef3d223269edd20b617221d1a2d71f4da1658b56101e291bb79fec7" }, "downloads": -1, "filename": "papi-0.1.5.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "1b30a16b47514485b723c74f365aaf1c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33016, "upload_time": "2016-12-02T12:21:13", "url": "https://files.pythonhosted.org/packages/ee/89/9c84fd70a3b61b7915dc52539dec63422f444dd23de0918f4e85acdaa613/papi-0.1.5.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "c737b6f7372072f8b89a6dc4f62f5d0f", "sha256": "31c84bba4324964a961a9406b3004c7043bd6cf6ca9bc2430099742e692d24f7" }, "downloads": -1, "filename": "papi-0.1.5.tar.gz", "has_sig": false, "md5_digest": "c737b6f7372072f8b89a6dc4f62f5d0f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27905, "upload_time": "2016-12-02T12:21:16", "url": "https://files.pythonhosted.org/packages/2c/64/3daee062dc7cf2c6616bb67843e7cc9a7bc266e6bae16a9213fb02aafe1a/papi-0.1.5.tar.gz" } ], "0.1.6": [ { "comment_text": "", "digests": { "md5": "c162e85ca8accccf91429e97d7eb08f9", "sha256": "a467297539cd895c4f92a9340795f7f68a087f2aa8dd9f99e439df565236ccde" }, "downloads": -1, "filename": "papi-0.1.6.tar.gz", "has_sig": false, "md5_digest": "c162e85ca8accccf91429e97d7eb08f9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27946, "upload_time": "2016-12-21T13:32:38", "url": "https://files.pythonhosted.org/packages/a9/d8/b25299bded0754510d30d796e9f9d0178583486992f395629a5bc8d99438/papi-0.1.6.tar.gz" } ], "0.1.7": [ { "comment_text": "", "digests": { "md5": "c6d210e11098ccff990e18436bba8af8", "sha256": "18fb60c0ba3e97d130cb34bef7961d6b7312e88984f7e5a8d9d2df95437b6677" }, "downloads": -1, "filename": "papi-0.1.7.tar.gz", "has_sig": false, "md5_digest": "c6d210e11098ccff990e18436bba8af8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29130, "upload_time": "2016-12-22T13:38:06", "url": "https://files.pythonhosted.org/packages/84/c7/d25fb8109b3b9eaccc9501a9a1bc9aa1717f30c2b7ecd9bb5b6099dc9b19/papi-0.1.7.tar.gz" } ], "0.1.8": [ { "comment_text": "", "digests": { "md5": "bdee2d6c291092833799c290764a631b", "sha256": "a40a9877f90900f5b0431405f67b1157b97dba1691a98b73ce945069c5d8d04d" }, "downloads": -1, "filename": "papi-0.1.8.tar.gz", "has_sig": false, "md5_digest": "bdee2d6c291092833799c290764a631b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29124, "upload_time": "2016-12-22T13:46:08", "url": "https://files.pythonhosted.org/packages/7e/2a/4e63f5183c7e16ea229f4c4674e215bfb13b89300ad011b75420a2cac88c/papi-0.1.8.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "bdee2d6c291092833799c290764a631b", "sha256": "a40a9877f90900f5b0431405f67b1157b97dba1691a98b73ce945069c5d8d04d" }, "downloads": -1, "filename": "papi-0.1.8.tar.gz", "has_sig": false, "md5_digest": "bdee2d6c291092833799c290764a631b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29124, "upload_time": "2016-12-22T13:46:08", "url": "https://files.pythonhosted.org/packages/7e/2a/4e63f5183c7e16ea229f4c4674e215bfb13b89300ad011b75420a2cac88c/papi-0.1.8.tar.gz" } ] }