{ "info": { "author": "Dave Shawley", "author_email": "daveshawley@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy" ], "description": "\nglinda\n======\n\n|ReadTheDocs| |Travis| |CodeClimate|\n\nGlinda is a companion library for `tornado`_. It is an attempt to make your\ntime with the framework less painful. In fact, I want to make it downright\nenjoyable. I started down the path of developing HTTP endpoints in Tornado\nand needing to test them. The `tornado.testing`_ package is handy for\ntesting endpoints in isolation. But what do you do when you have a HTTP\nservice that is calling other HTTP services asynchronously. It turns out\nthat testing that is not as easy as it should be. That is the first thing\nthat I tackled and it is the first thing that this library is going to\noffer -- *a way to test non-trivial services*.\n\nOnce you can test your application, the next step is to write a well-behaved\napplication that fits into the WWW nicely. Tornado does a pretty nice job\nof handling the nitty gritty HTTP details (e.g., CTE, transfer encodings).\nIt doesn't provide a clean way to handle representations transparently so\nI decided to add that into this library as well.\n\nContent Handling\n----------------\nTornado has some internal content decoding accessible by calling the\n``get_body_arguments`` method of ``tornado.web.RequestHandler``. It will\ndecode basic form data, ``application/x-www-form-urlencoded`` and\n``multipart/form-data`` specifically. Anything else is left up to you.\n``glinda`` exposes a content handling mix-in that imbues a standard\n``RequestHandler`` with a property that is the decoded request body and\na new method to encode a response. Here's what it looks like:\n\n.. code-block:: python\n\n class MyHandler(glinda.content.HandlerMixin, web.RequestHandler):\n def post(self, *args, **kwargs):\n body_argument = self.request_body['arg']\n # do stuff\n self.send_response(response_dict)\n self.finish()\n\n if __name__ == '__main__':\n glinda.content.register_text_type('application/json',\n default_charset='utf-8',\n dumper=json.dumps, loader=json.loads)\n glinda.content.register_binary_type('application/msgpack',\n msgpack.dumpb, msgpack.loadb)\n\nWhen the client sends a post with a content type of ``application/json``, it\nwill decode the binary body to a string according to the HTTP headers and\ncall ``json.loads`` to decode the body when you reference the ``request_body``\nproperty. Failures are handled by raising a ``HTTPError(400)`` so you don't\nhave to worry about handling malformed messages. The ``send_response``\nmethod will take care of figuring out the appropriate content type based on\nany included ``Accept`` headers. All that you have to do is install\nencoding and decoding handlers for expected content types.\n\nThe ``glinda.content`` package implements content handling as described in\n`RFC7231`_. Specifically, it decodes request bodies as described in\n`section 3.1`_ and proactive content negotiation as described in sections\n`3.4.1`_ and `5.3`_.\n\nTesting\n-------\nHere's an example of testing a Tornado endpoint that asynchronously calls\nanother service. In this case, the application interacts with with the\n``/add`` endpoint of some other service. Testing in isolation can be tricky\nwithout having to have a copy of the service running. You could mock out\nthe ``AsyncHTTPClient`` and return fake futures and what not but that has\nthe nasty side-effect of hiding defects around how content type or headers\nare handled -- no HTTP requests means that you have untested assumptions.\n\nThe following snippet tests the application under test using the\n``ServiceLayer`` abstraction that ``glinda.testing`` provides.\n\n.. code-block:: python\n\n from tornado import testing\n from glinda.testing import services\n\n\n class MyServiceTests(testing.AsyncHTTPTestCase):\n\n def setUp(self):\n service_layer = services.ServiceLayer()\n self.service = service_layer['adder']\n # TODO configured your application here using\n # self.service.url_for('/add') or self.service.host\n super(MyServiceTests, self).setUp()\n\n def get_app(self):\n return MyApplication()\n\n def test_that_my_service_calls_other_service(self):\n self.service.add_response(\n services.Request('POST', '/add'),\n services.Response(200, body='{\"result\": 10}'))\n self.fetch(self.get_url('/do-stuff'), method='GET')\n\n recorded = self.service.get_request('/add')\n self.assertEqual(recorded.method, 'POST')\n self.assertEqual(recorded.body, '[1,2,3,4]')\n self.assertEqual(recorded.headers['Content-Type'], 'application/json')\n\nThe application under test is linked in by implementing the standard\n``tornado.testing.AsyncHTTPTestCase.get_app`` method. Then you add in\na ``glinda.testing.services.ServiceLayer`` object and configure it to look\nlike the services that you depend on by adding endpoints and then configuring\nyour application to point at the service layer. When you invoke the\napplication under test using ``self.fetch(...)``, it will send HTTP requests\nthrough the Tornado stack (using the testing ``ioloop``) to the service layer\nwhich will respond appropriately. The beauty is that the entire HTTP stack is\nexercised locally so that you can easily test edge cases such as correct\nhandling of status codes, custom headers, or malformed bodies without\nresorting to deep mocking.\n\nWhere?\n------\n+---------------+-------------------------------------------------+\n| Source | https://github.com/dave-shawley/glinda |\n+---------------+-------------------------------------------------+\n| Status | https://travis-ci.org/dave-shawley/glinda |\n+---------------+-------------------------------------------------+\n| Download | https://pypi.python.org/pypi/glinda |\n+---------------+-------------------------------------------------+\n| Documentation | http://glinda.readthedocs.org/en/latest |\n+---------------+-------------------------------------------------+\n| Issues | https://github.com/dave-shawley/glinda |\n+---------------+-------------------------------------------------+\n\n.. _tornado: http://tornadoweb.org/\n.. _tornado.testing: http://www.tornadoweb.org/en/latest/testing.html\n.. _RFC7231: http://tools.ietf.org/html/rfc7231\n.. _section 3.1: http://tools.ietf.org/html/rfc7231#section-3.1\n.. _3.4.1: http://tools.ietf.org/html/rfc7231#section-3.4.1\n.. _5.3: http://tools.ietf.org/html/rfc7231#section-5.3\n\n.. |ReadTheDocs| image:: https://readthedocs.org/projects/glinda/badge/\n :target: https://glinda.readthedocs.org/\n.. |Travis| image:: https://travis-ci.org/dave-shawley/glinda.svg\n :target: https://travis-ci.org/dave-shawley/glinda\n.. |CodeClimate| image:: https://codeclimate.com/github/dave-shawley/glinda/badges/gpa.svg\n :target: https://codeclimate.com/github/dave-shawley/glinda\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/dave-shawley/glinda", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "glinda", "package_url": "https://pypi.org/project/glinda/", "platform": "any", "project_url": "https://pypi.org/project/glinda/", "project_urls": { "Homepage": "http://github.com/dave-shawley/glinda" }, "release_url": "https://pypi.org/project/glinda/1.0.1/", "requires_dist": [ "ietfparse (<2,>=1.2.2)", "tornado (<6.5,>=4.5)" ], "requires_python": "", "summary": "Helping your through the Tornado.", "version": "1.0.1" }, "last_serial": 5459037, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "cb59a9840f011bb6c2a27aef3f549494", "sha256": "6c62455cf118e2a0540c02ddaef0ac384d3c5e74efdcbb5d7d52f36b84144eaa" }, "downloads": -1, "filename": "glinda-0.0.1.tar.gz", "has_sig": false, "md5_digest": "cb59a9840f011bb6c2a27aef3f549494", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13776, "upload_time": "2015-05-21T11:28:56", "url": "https://files.pythonhosted.org/packages/06/9f/d8bd64ea0d3698da22db7fd62d9aa9bdc66ec0f179e2106778252272e781/glinda-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "391cadcb9a800d776354d434a66d7b02", "sha256": "116804088358614fecfd8d4cb224be7e341b8ce2b32ec3a9acb15e07808eba1f" }, "downloads": -1, "filename": "glinda-0.0.2.tar.gz", "has_sig": false, "md5_digest": "391cadcb9a800d776354d434a66d7b02", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13936, "upload_time": "2015-05-29T13:27:40", "url": "https://files.pythonhosted.org/packages/0a/f4/f65e442b4927286fc5634f8be0e2b61499eaf2e337aa8c1e396112838cdd/glinda-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "ddeac514593218f30b08af17bfb3da8c", "sha256": "e8160bf2b220b0bc8f05b99c0840fa2159dc8cf75303ff8f2c25dcaebde64dd2" }, "downloads": -1, "filename": "glinda-0.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "ddeac514593218f30b08af17bfb3da8c", "packagetype": "bdist_wheel", "python_version": "3.4", "requires_python": null, "size": 15206, "upload_time": "2015-05-30T20:30:04", "url": "https://files.pythonhosted.org/packages/ce/77/d6934dde32c1a4b943283acf4f84bc3594f6bf46eea37b8f86422131c879/glinda-0.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "21d4ab5a0424eee8d6d4b2dd5fe1aad0", "sha256": "9993db6640efdadc112d96594d8ad73a5aa1719df7cc88a7dafd2b52942c427d" }, "downloads": -1, "filename": "glinda-0.0.3.tar.gz", "has_sig": false, "md5_digest": "21d4ab5a0424eee8d6d4b2dd5fe1aad0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21429, "upload_time": "2015-05-30T20:30:00", "url": "https://files.pythonhosted.org/packages/b1/6c/197f4989d73d7b151da415a3e06db1cefe8ceb32bf3996915c42dd7f5c88/glinda-0.0.3.tar.gz" } ], "0.1.0": [ { "comment_text": "", "digests": { "md5": "0ff681ff7c804fe41ace81fe80d7bea3", "sha256": "30f0858d837c33271374a905d52cbb4c9a62d6380bc54a51e51c1210d15d21bc" }, "downloads": -1, "filename": "glinda-0.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "0ff681ff7c804fe41ace81fe80d7bea3", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 15456, "upload_time": "2017-07-03T16:02:38", "url": "https://files.pythonhosted.org/packages/28/c6/11570e30046d8dfdf296322c72e469ad17c8f3e2da3bfe581e17745a268f/glinda-0.1.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "72fa49df1f14b2aad2e477c5576bc036", "sha256": "91535c74cf00e8e53e3d3737037ba2d65f2a685486fd77d18ab3059ad7f5af75" }, "downloads": -1, "filename": "glinda-0.1.0.tar.gz", "has_sig": false, "md5_digest": "72fa49df1f14b2aad2e477c5576bc036", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24203, "upload_time": "2017-07-03T16:02:39", "url": "https://files.pythonhosted.org/packages/ad/f7/74078c2edfa6871786c4aa9ccc733f7f67af7756c809855140db5689514b/glinda-0.1.0.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "22f2259a67dc50f73d9a00356a1fdb20", "sha256": "0208e2ca19eeb95bd9abf2294bc564665007c0b2fee6684439fbb1a9a01f985b" }, "downloads": -1, "filename": "glinda-1.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "22f2259a67dc50f73d9a00356a1fdb20", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 12797, "upload_time": "2019-04-22T15:07:38", "url": "https://files.pythonhosted.org/packages/d6/10/a6af89c373dd0e3a85c6ca1d4bd2f45a61aed92a19380f129431fc4dc9a7/glinda-1.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "70a1a02f3c8735ffda802a7d181d435a", "sha256": "29754c33e6153e5ab98d43067a27cbd295d6a02b1241e0a440f9d0c47e4f22fa" }, "downloads": -1, "filename": "glinda-1.0.0.tar.gz", "has_sig": false, "md5_digest": "70a1a02f3c8735ffda802a7d181d435a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24251, "upload_time": "2019-04-22T15:07:39", "url": "https://files.pythonhosted.org/packages/81/bb/b9b4a4a644c8c5201b1e9ee14d8374429959f4b06af8122c63f6c8ba90f5/glinda-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "9bf19a51b22e6d49418dfe82b116514d", "sha256": "3734788e1efe88af3cf51058c9f351dd323f23ebde506d6ab5754cc24edbaa2f" }, "downloads": -1, "filename": "glinda-1.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "9bf19a51b22e6d49418dfe82b116514d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 13022, "upload_time": "2019-06-27T21:32:05", "url": "https://files.pythonhosted.org/packages/0e/30/6338a36968f64ee804a3032deb7b47353c8dbe64ad54cb17a7c18e1192c5/glinda-1.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b949819907a4ec28ce0b73067b3c2de5", "sha256": "fb603f37435fe14008234d5f775af224f06e93a71c77c08bd24a24d090de327c" }, "downloads": -1, "filename": "glinda-1.0.1.tar.gz", "has_sig": false, "md5_digest": "b949819907a4ec28ce0b73067b3c2de5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24562, "upload_time": "2019-06-27T21:32:11", "url": "https://files.pythonhosted.org/packages/31/51/aa179031aa61da2c384459a61ef27fe9c043b11c3c232c659c3fbe2b23ea/glinda-1.0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "9bf19a51b22e6d49418dfe82b116514d", "sha256": "3734788e1efe88af3cf51058c9f351dd323f23ebde506d6ab5754cc24edbaa2f" }, "downloads": -1, "filename": "glinda-1.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "9bf19a51b22e6d49418dfe82b116514d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 13022, "upload_time": "2019-06-27T21:32:05", "url": "https://files.pythonhosted.org/packages/0e/30/6338a36968f64ee804a3032deb7b47353c8dbe64ad54cb17a7c18e1192c5/glinda-1.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b949819907a4ec28ce0b73067b3c2de5", "sha256": "fb603f37435fe14008234d5f775af224f06e93a71c77c08bd24a24d090de327c" }, "downloads": -1, "filename": "glinda-1.0.1.tar.gz", "has_sig": false, "md5_digest": "b949819907a4ec28ce0b73067b3c2de5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24562, "upload_time": "2019-06-27T21:32:11", "url": "https://files.pythonhosted.org/packages/31/51/aa179031aa61da2c384459a61ef27fe9c043b11c3c232c659c3fbe2b23ea/glinda-1.0.1.tar.gz" } ] }