diff --git a/.gitignore b/.gitignore index 53605b7..c8d5518 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ venv .pytest_cache *.egg-info .DS_Store +.vscode/ \ No newline at end of file diff --git a/Pipfile b/Pipfile index ce0493f..c4c9f41 100644 --- a/Pipfile +++ b/Pipfile @@ -11,8 +11,9 @@ neovim = "*" fastapi = "==0.61.1" netifaces = "==0.10.6" pydantic = "==1.6.1" -socketio = "==0.2.1" starlette = "==0.13.6" +python-socketio = "==4.6.0" +python-engineio = "*" [test] pytest = "==6.0.1" diff --git a/Pipfile.lock b/Pipfile.lock index cdf56c2..f6f4147 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6d7a65f2de4b9a5bf419065e9bff670075383a34571a6fe0ddba3ded4e747c23" + "sha256": "ccf9ac43f22dd96a47acfd5c28086eb1484a4965b19a1a004b97a5517999fc90" }, "pipfile-spec": 6, "requires": { @@ -16,14 +16,6 @@ ] }, "default": { - "attrs": { - "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.2.0" - }, "fastapi": { "hashes": [ "sha256:61ed73b4304413a2ea618d1b95ea866ee386e0e62dd8659c4f5059286f4a39c2", @@ -32,21 +24,6 @@ "index": "pypi", "version": "==0.61.1" }, - "iniconfig": { - "hashes": [ - "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", - "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" - ], - "version": "==1.0.1" - }, - "more-itertools": { - "hashes": [ - "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20", - "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c" - ], - "markers": "python_version >= '3.5'", - "version": "==8.5.0" - }, "netifaces": { "hashes": [ "sha256:0c4da523f36d36f1ef92ee183f2512f3ceb9a9d2a45f7d19cda5a42c6689ebe0", @@ -72,30 +49,6 @@ "index": "pypi", "version": "==0.10.6" }, - "packaging": { - "hashes": [ - "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", - "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.4" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.13.1" - }, - "py": { - "hashes": [ - "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", - "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.9.0" - }, "pydantic": { "hashes": [ "sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c", @@ -119,37 +72,30 @@ "index": "pypi", "version": "==1.6.1" }, - "pyparsing": { + "python-engineio": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:36b33c6aa702d9b6a7f527eec6387a2da1a9a24484ec2f086d76576413cef04b", + "sha256:cfded18156862f94544a9f8ef37f56727df731c8552d7023f5afee8369be2db6" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.4.7" + "index": "pypi", + "version": "==3.13.2" }, - "pytest": { + "python-socketio": { "hashes": [ - "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4", - "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad" + "sha256:358d8fbbc029c4538ea25bcaa283e47f375be0017fcba829de8a3a731c9df25a", + "sha256:d437f797c44b6efba2f201867cf02b8c96b97dff26d4e4281ac08b45817cd522" ], "index": "pypi", - "version": "==6.0.1" + "version": "==4.6.0" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, - "socketio": { - "hashes": [ - "sha256:dee5abde39c6021d9d1874582479a4e7f8a8352b38bc7731fb6b27b76976f62c" - ], - "index": "pypi", - "version": "==0.2.1" - }, "starlette": { "hashes": [ "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9", @@ -157,14 +103,82 @@ ], "index": "pypi", "version": "==0.13.6" + } + }, + "develop": { + "greenlet": { + "hashes": [ + "sha256:1000038ba0ea9032948e2156a9c15f5686f36945e8f9906e6b8db49f358e7b52", + "sha256:133ba06bad4e5f2f8bf6a0ac434e0fd686df749a86b3478903b92ec3a9c0c90b", + "sha256:1429dc183b36ec972055e13250d96e174491559433eb3061691b446899b87384", + "sha256:1b805231bfb7b2900a16638c3c8b45c694334c811f84463e52451e00c9412691", + "sha256:3a35e33902b2e6079949feed7a2dafa5ac6f019da97bd255842bb22de3c11bf5", + "sha256:5ea034d040e6ab1d2ae04ab05a3f37dbd719c4dee3804b13903d4cc794b1336e", + "sha256:682328aa576ec393c1872615bcb877cf32d800d4a2f150e1a5dc7e56644010b1", + "sha256:6e06eac722676797e8fce4adb8ad3dc57a1bb3adfb0dd3fdf8306c055a38456c", + "sha256:7eed31f4efc8356e200568ba05ad645525f1fbd8674f1e5be61a493e715e3873", + "sha256:80cb0380838bf4e48da6adedb0c7cd060c187bb4a75f67a5aa9ec33689b84872", + "sha256:b0b2a984bbfc543d144d88caad6cc7ff4a71be77102014bd617bd88cfb038727", + "sha256:c196a5394c56352e21cb7224739c6dd0075b69dd56f758505951d1d8d68cf8a9", + "sha256:d83c1d38658b0f81c282b41238092ed89d8f93c6e342224ab73fb39e16848721", + "sha256:df7de669cbf21de4b04a3ffc9920bc8426cab4c61365fa84d79bf97401a8bef7", + "sha256:e5db19d4a7d41bbeb3dd89b49fc1bc7e6e515b51bbf32589c618655a0ebe0bf0", + "sha256:e695ac8c3efe124d998230b219eb51afb6ef10524a50b3c45109c4b77a8a3a92", + "sha256:eac2a3f659d5f41d6bbfb6a97733bc7800ea5e906dc873732e00cebb98cec9e4" + ], + "version": "==0.4.16" + }, + "jedi": { + "hashes": [ + "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20", + "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5" + ], + "index": "pypi", + "version": "==0.17.2" }, - "toml": { + "msgpack": { "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + "sha256:002a0d813e1f7b60da599bdf969e632074f9eec1b96cbed8fb0973a63160a408", + "sha256:25b3bc3190f3d9d965b818123b7752c5dfb953f0d774b454fd206c18fe384fb8", + "sha256:271b489499a43af001a2e42f42d876bb98ccaa7e20512ff37ca78c8e12e68f84", + "sha256:39c54fdebf5fa4dda733369012c59e7d085ebdfe35b6cf648f09d16708f1be5d", + "sha256:4233b7f86c1208190c78a525cd3828ca1623359ef48f78a6fea4b91bb995775a", + "sha256:5bea44181fc8e18eed1d0cd76e355073f00ce232ff9653a0ae88cb7d9e643322", + "sha256:5dba6d074fac9b24f29aaf1d2d032306c27f04187651511257e7831733293ec2", + "sha256:7a22c965588baeb07242cb561b63f309db27a07382825fc98aecaf0827c1538e", + "sha256:908944e3f038bca67fcfedb7845c4a257c7749bf9818632586b53bcf06ba4b97", + "sha256:9534d5cc480d4aff720233411a1f765be90885750b07df772380b34c10ecb5c0", + "sha256:aa5c057eab4f40ec47ea6f5a9825846be2ff6bf34102c560bad5cad5a677c5be", + "sha256:b3758dfd3423e358bbb18a7cccd1c74228dffa7a697e5be6cb9535de625c0dbf", + "sha256:c901e8058dd6653307906c5f157f26ed09eb94a850dddd989621098d347926ab", + "sha256:cec8bf10981ed70998d98431cd814db0ecf3384e6b113366e7f36af71a0fca08", + "sha256:db685187a415f51d6b937257474ca72199f393dad89534ebbdd7d7a3b000080e", + "sha256:e35b051077fc2f3ce12e7c6a34cf309680c63a842db3a0616ea6ed25ad20d272", + "sha256:e7bbdd8e2b277b77782f3ce34734b0dfde6cbe94ddb74de8d733d603c7f9e2b1", + "sha256:ea41c9219c597f1d2bf6b374d951d310d58684b5de9dc4bd2976db9e1e22c140" ], - "version": "==0.10.1" + "version": "==1.0.0" + }, + "neovim": { + "hashes": [ + "sha256:a6a0e7a5b4433bf4e6ddcbc5c5ff44170be7d84259d002b8e8d8fb4ee78af60f" + ], + "index": "pypi", + "version": "==0.3.1" + }, + "parso": { + "hashes": [ + "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea", + "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.7.1" + }, + "pynvim": { + "hashes": [ + "sha256:6bc6204d465de5888a0c5e3e783fe01988b032e22ae87875912280bef0e40f8f" + ], + "version": "==0.4.2" } - }, - "develop": {} + } } diff --git a/README.md b/README.md index c9f2bea..a3d232b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,40 @@ Install this plugin using `pip`: ## Usage -Usage instructions go here. +To add SocketIO support to FastAPI all you need to do is import `SocketManager` and pass it `FastAPI` object. + +```python +# app.py +from fastapi import FastAPI +from fastapi_socketio import SocketManager + +app = FastAPI() +socket_manager = SocketManager(app=app) +``` + + +Now you can use SocketIO directly from your `FastAPI` app object. +```python +# socket_handlers.py +from .app import app + +@app.sio.on('join') +async def handle_join(sid, *args, **kwargs): + await app.sio.emit('lobby', 'User joined') + +``` + +Or you can import `SocketManager` object that exposes most of the SocketIO functionality. + +```python +# socket_handlers2.py +from .app import socket_manager as sm + +@sm.on('leave') +async def handle_leave(sid, *args, **kwargs): + await sm.emit('lobby', 'User left') + +``` ## Development diff --git a/examples/app.py b/examples/app.py new file mode 100644 index 0000000..8909fdf --- /dev/null +++ b/examples/app.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI +from fastapi_socketio import SocketManager + +app = FastAPI() +sio = SocketManager(app=app) + + +@app.sio.on('join') +async def handle_join(sid, *args, **kwargs): + await sio.emit('lobby', 'User joined') + + +@sio.on('test') +async def test(sid, *args, **kwargs): + await sio.emit('hey', 'joe') + + + +if __name__ == '__main__': + import logging + import sys + + logging.basicConfig(level=logging.DEBUG, + stream=sys.stdout) + + import uvicorn + + uvicorn.run("examples:app", host='0.0.0.0', port=8000, reload=True, debug=False) diff --git a/fastapi_socketio/__init__.py b/fastapi_socketio/__init__.py index 923334e..8c57d9f 100644 --- a/fastapi_socketio/__init__.py +++ b/fastapi_socketio/__init__.py @@ -1,2 +1,4 @@ +from .socket_manager import SocketManager + def example_function(): return 1 + 1 diff --git a/fastapi_socketio/socket_manager.py b/fastapi_socketio/socket_manager.py new file mode 100644 index 0000000..653d6b8 --- /dev/null +++ b/fastapi_socketio/socket_manager.py @@ -0,0 +1,88 @@ +import socketio +from fastapi import FastAPI + + +class SocketManager: + """ + Integrates SocketIO with FastAPI app. + Adds `sio` property to FastAPI object (app). + + Default mount location for SocketIO app is at `/ws` + and defautl SocketIO path is `socket.io`. + (e.g. full path: `ws://www.example.com/ws/socket.io/) + + SocketManager exposes basic underlying SocketIO functionality + + e.g. emit, on, send, call, etc. + """ + + def __init__( + self, + app: FastAPI, + mount_location: str = "/ws", + socketio_path: str = "socket.io", + cors_allowed_origins: list = [], + ) -> None: + # TODO: Change Cors policy based on fastapi cors Middleware + self._sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*") + self._app = socketio.ASGIApp( + socketio_server=self._sio, socketio_path=socketio_path + ) + + app.mount(mount_location, self._app) + app.sio = self._sio + + def is_asyncio_based(self) -> bool: + return True + + @property + def on(self): + return self._sio.on + + @property + def attach(self): + return self._sio.attach + + @property + def emit(self): + return self._sio.emit + + @property + def send(self): + return self._sio.send + + @property + def call(self): + return self._sio.call + + @property + def close_room(self): + return self._sio.close_room + + @property + def get_session(self): + return self._sio.get_session + + @property + def save_session(self): + return self._sio.save_session + + @property + def session(self): + return self._sio.session + + @property + def disconnect(self): + return self._sio.disconnect + + @property + def handle_request(self): + return self._sio.handle_request + + @property + def start_background_task(self): + return self._sio.start_background_task + + @property + def sleep(self): + return self._sio.sleep