{ "info": { "author": "Devin Fee", "author_email": "devin@devinfee.com", "bugtrack_url": null, "classifiers": [ "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP" ], "description": "====================\n`aiohttp_session_ws`\n====================\n\n.. image:: https://travis-ci.org/dfee/aiohttp_session_ws.svg?branch=master\n :target: https://travis-ci.org/dfee/aiohttp_session_ws\n.. image:: https://coveralls.io/repos/github/dfee/aiohttp_session_ws/badge.svg?branch=master\n :target: https://coveralls.io/github/dfee/aiohttp_session_ws?branch=master\n\n\nSimply put: associate your websockets with a user's session, and close those connections when you see fit.\n\nFor example, let's say you're using `aiohttp_security `_ and a user chooses to log in or log out.\nUsing ``aiohttp_session_ws`` you can disconnect the open websocket subscriptions associated with their session, and force them to re-connect and re-authorize thier websocket subscriptions.\n\n.. image:: demo/demo.gif\n\n\nBasic Example\n-------------\n\nThe pieces of code in this example are taken from the ``demo`` directory of this repository.\n\n.. code-block:: python\n\n async def handle_root(request):\n return web.Response(text='Hello world', content_type=\"text/html\")\n\n async def handle_reset(request):\n session_ws_id = await get_session_ws_id(request)\n response = web.Response(\n text=f\"Reset called on session {session_ws_id}!\",\n content_type=\"text/plain\",\n )\n await schedule_close_all_session_ws(request, response)\n await new_session_ws_id(request)\n return response\n\n async def handle_websocket(request):\n async with session_ws(request) as wsr:\n connected_at = datetime.now()\n session_ws_id = await get_session_ws_id(request)\n while True:\n await wsr.send_str(\n f\"Websocket associated with session [{session_ws_id}] \"\n f\"connected for {(datetime.now() - connected_at).seconds}\"\n )\n await asyncio.sleep(1)\n return wsr\n\n def make_app():\n app = web.Application(\n middlewares=[\n aiohttp_session.session_middleware(\n aiohttp_session.SimpleCookieStorage()\n ),\n session_ws_middleware,\n ]\n )\n app.router.add_get(\"/\", handle_root)\n app.router.add_get(\"/reset\", handle_reset)\n app.router.add_get(\"/ws\", handle_websocket)\n\n setup_session_websockets(app, SessionWSRegistry())\n return app\n\nUse the code from the ``demo`` folder, which includes a simple template to interact with the websocket in your web-browser.\n\n\nNarrative API\n-------------\n\nThis package is designed to be straightforward and easy to use.\nThis lightweight documentation doesn't attempt to replace the need to read the code, so you're encouraged to go do exactly that.\n\nThere are a few moving pieces, but if (and when) you need to do something more complex, you can subclass away.\n\n\n``SessionWSRegistry``\n~~~~~~~~~~~~~~~~~~~~~\nThis is the core of ``aiohttp_session_ws``.\n\nIt's construction is noteworthy:\n\n``SessionWSRegistry(self, *, id_factory, session_key)``\n\n``id_factory`` generates a session-wide id that associates the websockets.\nThe default id_factory returns a UUID4, but you can supply your own callable (async callables are supported, too).\nthe function signature of ``id_factory`` is:\n\n``id_factory(request: aiohttp.web.Request) -> typing.Hashable``\n\nSo pretty much, return something that can be the key in a dictionary (strings, integers, etc.).\n\n``session_key`` is the name of the key in the session that maps to the session-wide websocket identifier.\nBy default it's a sensible ``aiohttp_session_ws_id``.\n\n\nHelpers\n~~~~~~~\n\nYou won't need to interact with ``SessionWSRegistry`` directly after you've created it, but know that it's available in your ``aiohttp.web.Application`` (access it like this: ``app['aiohttp_session_ws_registry']``).\n\nThe friendly global manipulators of this object are:\n\n- ``get_session_ws_id(request)``\n- ``new_session_ws_id(request)``\n- ``delete_session_ws_id(request)``\n- ``ensure_session_ws_id(request)``\n- ``schedule_close_all_session_ws(request, response)``\n\nThese methods are importable directly from ``aiohttp_session_ws``.\n\nNotice that ``schedule_close_all_session_ws`` takes a response object.\nThis allows us to end the ``keep-alive`` status of the response (via ``aiohttp.web.Response.force_close``).\nThis means that as soon as your user has finished receiveing the response, their outstanding websockets will close.\n\nThis also means that if you have users with re-connecting websockets, you should probably follow this pattern:\n\n.. code-block:: python\n\n async def handle_logout(request):\n response = web.HTTPFound('/')\n await schedule_close_all_session_ws(request, response)\n await aiohttp_session.new_session(request)\n await new_session_ws_id(request)\n return response\n\n\nsession_ws\n~~~~~~~~~~\n\nTo track the websockets, you'll use the async context manager ``session_ws``.\nThis context manager upgrades the request, and provides its ``aiothttp.web.WebSocketResponse`` counterpart.\nUse if like this:\n\n.. code-block:: python\n\n async def handle_websocket(request):\n async with session_ws(request) as wsr:\n async for msg in wsr:\n await wsr.send_str(f'Heard: {ws.data}')\n return wsr\n\nThat's it. Pretty simple, right?\nIf you'd like to provide the ``aiohttp.web.WebSocketResponse`` with initialization options (for example, the supported websocket protocols), pass those along to ``session_ws`` as named arguments.\n\n.. code-block:: python\n\n async def handle_websocket(request):\n async with session_ws(request, protocols=('graphql-ws',)) as wsr:\n async for msg in wsr:\n await wsr.send_str(f'Heard: {ws.data}')\n return wsr\n\n\nAs mentioned in the *Notes* below, it's important that your users have a ``session_ws id`` prior to attempting a websocket connection (hint: Safari).\n\nUse the ``session_ws_middleware`` to automatically add the key to your sessions.\nIt should be inside the call-stack of ``aiohttp_session.session_middleware``:\n\n.. code-block:: python\n\n web.Application(\n middlewares=[\n aiohttp_session.session_middleware(\n aiohttp_session.SimpleCookieStorage()\n ),\n session_ws_middleware,\n ]\n )\n\n\nFinally, to set all of this up, you'll want to use the ``setup`` method (feel encourged to import it as ``setup_session_ws``).\n\nBasic usage looks like this:\n\n.. code-block:: python\n\n web.Application(\n middlewares=[\n aiohttp_session.session_middleware(\n aiohttp_session.SimpleCookieStorage()\n ),\n session_ws_middleware,\n ]\n )\n setup(app, SessionWSRegistry()) # <------\n # etc...\n return app\n\n\nNotes\n-----\n\nWhile ``session_ws`` generates an ``aiohttp_session_ws_id`` upon connect (if it's not present), some browsers don't respect ``Set-Cookie`` on a websocket upgrade (e.g. Safari).\n\nTherefore it's best if you ensure that an ``aiohttp_session_ws_id`` is present in the users session prior to attempting a websocket connection (if using ``aiohttp_session.SimpleCookieStorage`` or ``aiohttp_session.EncryptedCookieStorage``).\n\nIf you're using something more advanced that stores a reference to the session in the session cookie, and stores the actual value server-side (like ``aiohttp_session.RedisStorage``), then it's not important when ``aiohttp_session_ws_id`` is set on the cookie, but it is still important that the user has a session cookie prior to a connection attempt.\n\nIf you want to put the session-ws-id (usually ``aiohttp_session_ws_id``) somewhere else in the session, or derive it from the request, you can.\nSimply subclass ``SessionWSRegistry`` and revise the ``get_id``, ``set_id``, and ``delete_id`` methods.\n\nIf you have a cluster of webservers, you'll need to subclass ``SessionWSRegistry`` and revise the ``register`` and ``unregister`` functions so listen on a message broker (for example, using ``aioredis`` and its pubsub feature).", "description_content_type": "text/x-rst", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/dfee/aiohttp_session_ws", "keywords": "aiohttp,websocket", "license": "", "maintainer": "Devin Fee", "maintainer_email": "devin@devinfee.com", "name": "aiohttp-session-ws", "package_url": "https://pypi.org/project/aiohttp-session-ws/", "platform": "", "project_url": "https://pypi.org/project/aiohttp-session-ws/", "project_urls": { "Homepage": "https://github.com/dfee/aiohttp_session_ws" }, "release_url": "https://pypi.org/project/aiohttp-session-ws/1.1.1/", "requires_dist": [ "aiohttp (>=3.4,<4.0)", "aiohttp_session (>=2.5,<3.0)" ], "requires_python": ">=3.5,<4.0", "summary": "session-managed websockets for aiohttp", "version": "1.1.1" }, "last_serial": 4290957, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "cf29e51f6ddf57077d8b257683ef69f0", "sha256": "6c2f1ed8c087b2197a5e840a164449240ce3f5a6c00f6de081266d5fd55b2bf9" }, "downloads": -1, "filename": "aiohttp_session_ws-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "cf29e51f6ddf57077d8b257683ef69f0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5,<4.0", "size": 8570, "upload_time": "2018-09-12T04:55:13", "url": "https://files.pythonhosted.org/packages/60/56/c3bcf76001a8345b37b5a93b74b1818c7620ef80434835d7aa3d58d367f4/aiohttp_session_ws-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d82c572a286757dfaae4129ada7151db", "sha256": "082d56c4412fe307abcbbb47531ca0b5efff046253de283c44e117d64314d043" }, "downloads": -1, "filename": "aiohttp_session_ws-1.0.0.tar.gz", "has_sig": false, "md5_digest": "d82c572a286757dfaae4129ada7151db", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5,<4.0", "size": 2845, "upload_time": "2018-09-12T04:55:15", "url": "https://files.pythonhosted.org/packages/b2/02/212915c792c712b4fd58414423662ea4be3bf6b313222a6a3164b3efb917/aiohttp_session_ws-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "8e0ebf7e5bdf028f4b5b83787d5c0f9c", "sha256": "c7e34a34830da0f35ff8a84a077034975b0eb6b016c2ccff57403ee86a2aa790" }, "downloads": -1, "filename": "aiohttp_session_ws-1.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "8e0ebf7e5bdf028f4b5b83787d5c0f9c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5,<4.0", "size": 9110, "upload_time": "2018-09-20T02:03:35", "url": "https://files.pythonhosted.org/packages/59/80/6cece5c02004d0b01b6e378a885d47f50dbf9304b4a47c484625a083cdb6/aiohttp_session_ws-1.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "28bdd0b9e722c9ca56779fbdfee8a888", "sha256": "8eb06c87a3d4970ca92617bda211cfdb9922735706289b796f80a30c9d1077cc" }, "downloads": -1, "filename": "aiohttp_session_ws-1.1.0.tar.gz", "has_sig": false, "md5_digest": "28bdd0b9e722c9ca56779fbdfee8a888", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5,<4.0", "size": 2992, "upload_time": "2018-09-20T02:03:36", "url": "https://files.pythonhosted.org/packages/d3/e9/9bd3a9f9e85bac1fa8391b77bbb45cf0ecc2f02bf99ab6b85c8422722f6d/aiohttp_session_ws-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "89469fc8228802d5622cdc5b960985df", "sha256": "f3cb6ae9dc1c0c87e6426f922123f62b9c97b02bd727a64d1db8c70d28c0f8e8" }, "downloads": -1, "filename": "aiohttp_session_ws-1.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "89469fc8228802d5622cdc5b960985df", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5,<4.0", "size": 13202, "upload_time": "2018-09-20T02:23:33", "url": "https://files.pythonhosted.org/packages/a6/a3/70857e7b59fa7fbb85e2f3e2e64aa951d192f5aab597845203376856af24/aiohttp_session_ws-1.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "92a512cbb05dd1fd4e6d3b21f99d7a33", "sha256": "9c697a816205e46fcabca85d32712d2ed6092aad84ef7d3b8ccd12588d6c49a5" }, "downloads": -1, "filename": "aiohttp_session_ws-1.1.1.tar.gz", "has_sig": false, "md5_digest": "92a512cbb05dd1fd4e6d3b21f99d7a33", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5,<4.0", "size": 7198, "upload_time": "2018-09-20T02:23:34", "url": "https://files.pythonhosted.org/packages/21/d4/070169c88a7d5b6cd621242c7c255caa59546d0ad904f3527027d3281275/aiohttp_session_ws-1.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "89469fc8228802d5622cdc5b960985df", "sha256": "f3cb6ae9dc1c0c87e6426f922123f62b9c97b02bd727a64d1db8c70d28c0f8e8" }, "downloads": -1, "filename": "aiohttp_session_ws-1.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "89469fc8228802d5622cdc5b960985df", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5,<4.0", "size": 13202, "upload_time": "2018-09-20T02:23:33", "url": "https://files.pythonhosted.org/packages/a6/a3/70857e7b59fa7fbb85e2f3e2e64aa951d192f5aab597845203376856af24/aiohttp_session_ws-1.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "92a512cbb05dd1fd4e6d3b21f99d7a33", "sha256": "9c697a816205e46fcabca85d32712d2ed6092aad84ef7d3b8ccd12588d6c49a5" }, "downloads": -1, "filename": "aiohttp_session_ws-1.1.1.tar.gz", "has_sig": false, "md5_digest": "92a512cbb05dd1fd4e6d3b21f99d7a33", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5,<4.0", "size": 7198, "upload_time": "2018-09-20T02:23:34", "url": "https://files.pythonhosted.org/packages/21/d4/070169c88a7d5b6cd621242c7c255caa59546d0ad904f3527027d3281275/aiohttp_session_ws-1.1.1.tar.gz" } ] }