From e62132a96a11d9d5a27edbf28ebc57216161d769 Mon Sep 17 00:00:00 2001 From: Naman Tamrakar Date: Thu, 13 Jan 2022 17:27:50 +0530 Subject: [PATCH] Bump version 0.3.0 --- README.md | 12 ++--- channels_easy/__init__.py | 2 +- channels_easy/generic.py | 78 +++++++++++++++++-------------- docs/apis.md | 1 - docs/index.md | 18 +++---- example/channels_app/consumers.py | 2 +- mkdocs.yml | 3 +- poetry.lock | 46 +++++++++++++++++- pyproject.toml | 3 +- tests/test_channels_easy.py | 8 ++-- 10 files changed, 112 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 2935cc2..8f2929c 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,14 @@ A thin wrapper around channel consumers to make things **EASY** ***Note***: This library currently support only text data which is JSON serializable. **What problem does this library solve?** + This library simplifies two tasks for now + 1. Parse incoming text data as JSON and vice versa. 2. Generate event on the basis of type passed from client side. **Table of Contents** + - [Installation](#installation) - [Example](#example) - [API Usage](#api-usage) @@ -26,11 +29,6 @@ To get the latest stable release from PyPi ```bash pip install channels-easy ``` -To get the latest commit from GitHub - -```bash -pip install -e git+git://github.com/namantam1/channels-easy.git#egg=channels-easy -``` As `channels-easy` is a thin wrapper around `channels` so channels must be in your `INSTALLED_APPS` in `settings.py`. @@ -68,7 +66,7 @@ class NewConsumer(AsyncWebsocketConsumer): # output: # message from client {'text': 'hello'} - await self.emit("message", "room1", {"message": "hello from server"}) + await self.emit("message", {"message": "hello from server"}, "room1") ``` @@ -144,8 +142,8 @@ def on_message(self, data): self.emit( "message", # type + {"text": "hello"}, # message dict | str | int | list ["room1"], # room list or string - {"text": "hello"} # message dict | str | int | list ) ``` diff --git a/channels_easy/__init__.py b/channels_easy/__init__.py index d31c31e..493f741 100644 --- a/channels_easy/__init__.py +++ b/channels_easy/__init__.py @@ -1 +1 @@ -__version__ = "0.2.3" +__version__ = "0.3.0" diff --git a/channels_easy/generic.py b/channels_easy/generic.py index 03118ad..bbba4fc 100644 --- a/channels_easy/generic.py +++ b/channels_easy/generic.py @@ -1,5 +1,6 @@ from logging import Logger from typing import Iterable, Union +from xmlrpc.client import boolean from channels.generic.websocket import AsyncWebsocketConsumer as BaseConsumer @@ -16,50 +17,56 @@ def get_handler_name(typ): class AsyncWebsocketConsumer(BaseConsumer): - async def close_with_error(self, error_data, code=None): - """Close socket after emitting error message - - Args: - error_data (Any): Any json serializable data - code (int): Close code pass to close - """ - await self.emit_error(error_data) - await self.close(code) - - async def emit(self, typ: str, to: Union[str, int, Iterable], data): + async def emit( + self, + typ: str, + data, + to: Union[str, int, Iterable] = None, + close: Union[int, boolean] = None, + ): """Send message to given rooms Args: typ (str): message type - to (Union[str, Iterable]): List of rooms or a single room data (Any): data which is json serializable + to (Union[str, int, Iterable], optional): List of rooms or a single room + close (Union[int, boolean], optional): Boolean or error code, If passed \ + close socket after emitting message + + *Note*: If `to` is not passed, emit message to self user """ - if not isinstance(to, (list, tuple, set)): - to = [to] - # send to each channels - for group in set(to): - await self.channel_layer.group_send( - str(group), - { - "type": "send_message", - "message": {"type": typ, "data": data}, - }, - ) - - async def emit_error(self, data): + text_data = json.dumps({"type": typ, "data": data}) + + if to is None: + await self.send(text_data=text_data) + else: + if not isinstance(to, (list, tuple, set)): + to = [to] + # send to each group + for group in set(to): + await self.channel_layer.group_send( + str(group), + { + "type": "send_message", + "text_data": text_data, + }, + ) + + if close is not None: + if isinstance(close, boolean): + await self.close() + else: + await self.close(close) + + async def emit_error(self, data, close: Union[int, boolean] = None): """Emit message with `error` type and data Args: data (Any): Any json serializable value + close (Union[int, boolean], optional): Boolean or error code. If passed \ + close socket after emitting error """ - await self.send( - json.dumps( - dict( - type="error", - data=data, - ) - ) - ) + await self.emit("error", data, close=close) async def join(self, room: Union[str, int, Iterable]): """Join room with passed name @@ -114,7 +121,6 @@ async def receive(self, text_data): async def send_message(self, event): """ - Send message to client + Send message down to Websocket """ - # send a message down to client - await self.send(json.dumps(event["message"])) + await self.send(text_data=event["text_data"]) diff --git a/docs/apis.md b/docs/apis.md index 42479cd..d0222b7 100644 --- a/docs/apis.md +++ b/docs/apis.md @@ -3,7 +3,6 @@ ## ::: channels_easy.generic.AsyncWebsocketConsumer selection: members: - - close_with_error - emit - emit_error - join diff --git a/docs/index.md b/docs/index.md index 5d0836b..8f2929c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,9 +11,16 @@ A thin wrapper around channel consumers to make things **EASY** **What problem does this library solve?** This library simplifies two tasks for now + 1. Parse incoming text data as JSON and vice versa. 2. Generate event on the basis of type passed from client side. +**Table of Contents** + +- [Installation](#installation) +- [Example](#example) +- [API Usage](#api-usage) +- [Contribute](#contribute) ## Installation @@ -22,11 +29,6 @@ To get the latest stable release from PyPi ```bash pip install channels-easy ``` -To get the latest commit from GitHub - -```bash -pip install -e git+git://github.com/namantam1/channels-easy.git#egg=channels-easy -``` As `channels-easy` is a thin wrapper around `channels` so channels must be in your `INSTALLED_APPS` in `settings.py`. @@ -41,7 +43,7 @@ INSTALLED_APPS = ( All the naming convention used to implement this library is inspired from [socket.io](https://socket.io/) to make server implementation simple. -Get full example project [here](https://github.com/namantam1/channels-easy/tree/main/example). +Get full example project [here](./example). **Server side** ```python @@ -64,7 +66,7 @@ class NewConsumer(AsyncWebsocketConsumer): # output: # message from client {'text': 'hello'} - await self.emit("message", "room1", {"message": "hello from server"}) + await self.emit("message", {"message": "hello from server"}, "room1") ``` @@ -140,8 +142,8 @@ def on_message(self, data): self.emit( "message", # type + {"text": "hello"}, # message dict | str | int | list ["room1"], # room list or string - {"text": "hello"} # message dict | str | int | list ) ``` diff --git a/example/channels_app/consumers.py b/example/channels_app/consumers.py index 57d359d..f0b1aad 100644 --- a/example/channels_app/consumers.py +++ b/example/channels_app/consumers.py @@ -13,4 +13,4 @@ async def disconnect(self, close_code): async def on_message(self, data): print("message from client", data) - await self.emit("message", "room1", {"message": "hello from server"}) + await self.emit("message", {"message": "hello from server"}, "room1") diff --git a/mkdocs.yml b/mkdocs.yml index e5c2946..5d9a5ea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,7 +4,8 @@ nav: - Home: index.md - API Reference: apis.md -theme: readthedocs +theme: + name: "material" plugins: - search diff --git a/poetry.lock b/poetry.lock index d706b44..c7b50d3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -449,6 +449,30 @@ python-versions = ">=3.6.2,<4.0.0" Markdown = ">=3.3,<4.0" mkdocs = ">=1.1,<2.0" +[[package]] +name = "mkdocs-material" +version = "8.1.6" +description = "A Material Design theme for MkDocs" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +jinja2 = ">=2.11.1" +markdown = ">=3.2" +mkdocs = ">=1.2.3" +mkdocs-material-extensions = ">=1.0" +pygments = ">=2.10" +pymdown-extensions = ">=9.0" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.0.3" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "mkdocstrings" version = "0.17.0" @@ -603,6 +627,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pygments" +version = "2.11.2" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "pymdown-extensions" version = "9.1" @@ -939,7 +971,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = ">=3.7,<4.0" -content-hash = "7a22a819fe0f290d343c5f836c1dfdbf930e4f2ab9d7f390a4c07014f6e1d072" +content-hash = "4069bd69215bc80e85cfb48ed2d7ae947015179b7092d519c758beb5e4ee7a91" [metadata.files] asgiref = [ @@ -1224,6 +1256,14 @@ mkdocs-autorefs = [ {file = "mkdocs-autorefs-0.3.1.tar.gz", hash = "sha256:12baad29359f468b44d980ed35b713715409097a1d8e3d0ef90962db95205eda"}, {file = "mkdocs_autorefs-0.3.1-py3-none-any.whl", hash = "sha256:f0fd7c115eaafda7fb16bf5ff5d70eda55d7c0599eac64f8b25eacf864312a85"}, ] +mkdocs-material = [ + {file = "mkdocs-material-8.1.6.tar.gz", hash = "sha256:12eb74faf018950f51261a773f9bea12cc296ec4bdbb2c8cf74102ee35b6df79"}, + {file = "mkdocs_material-8.1.6-py2.py3-none-any.whl", hash = "sha256:b2303413e3154502759f90ee2720b451be8855f769c385d8fb06a93ce54aafe2"}, +] +mkdocs-material-extensions = [ + {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, + {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, +] mkdocstrings = [ {file = "mkdocstrings-0.17.0-py3-none-any.whl", hash = "sha256:103fc1dd58cb23b7e0a6da5292435f01b29dc6fa0ba829132537f3f556f985de"}, {file = "mkdocstrings-0.17.0.tar.gz", hash = "sha256:75b5cfa2039aeaf3a5f5cf0aa438507b0330ce76c8478da149d692daa7213a98"}, @@ -1306,6 +1346,10 @@ pyflakes = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] +pygments = [ + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, +] pymdown-extensions = [ {file = "pymdown-extensions-9.1.tar.gz", hash = "sha256:74247f2c80f1d9e3c7242abe1c16317da36c6f26c7ad4b8a7f457f0ec20f0365"}, {file = "pymdown_extensions-9.1-py3-none-any.whl", hash = "sha256:b03e66f91f33af4a6e7a0e20c740313522995f69a03d86316b1449766c473d0e"}, diff --git a/pyproject.toml b/pyproject.toml index 71fdb22..ddf5a76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "channels-easy" -version = "0.2.3" +version = "0.3.0" description = "A thin wrapper around channels consumer to make things EASY" authors = ["Naman Tamrakar "] license = "MIT" @@ -26,6 +26,7 @@ isort = "^5.10.1" flake8 = "^4.0.1" mkdocs = "^1.2.3" mkdocstrings = "^0.17.0" +mkdocs-material = "^8.1.6" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/test_channels_easy.py b/tests/test_channels_easy.py index 6b38575..a69a1c3 100644 --- a/tests/test_channels_easy.py +++ b/tests/test_channels_easy.py @@ -6,7 +6,7 @@ def test_version(): - assert __version__ == "0.2.3" + assert __version__ == "0.3.0" @pytest.mark.django_db @@ -24,7 +24,7 @@ async def connect(self): async def on_message(self, data): results["received"] = data - await self.emit("message", room, data) + await self.emit("message", data, room) async def disconnect(self, code): await self.leave(room) @@ -62,7 +62,7 @@ async def connect(self): async def on_message(self, data): results["received"] = data - await self.emit("message", room, data) + await self.emit("message", data, room) async def disconnect(self, code): await self.leave(room) @@ -99,7 +99,7 @@ async def connect(self): async def on_message(self, data): results["received"] = data - await self.close_with_error("some error!") + await self.emit_error("some error!", close=True) async def disconnect(self, code): results["disconnected"] = True