From 4dfdddde0d6e2f366118891d84505231a38d2eb0 Mon Sep 17 00:00:00 2001 From: Konstantin Ponomarev Date: Wed, 13 Aug 2025 17:09:28 +0300 Subject: [PATCH] feat: routers and examples for routers --- README.md | 2 +- examples/server/routers/app.py | 11 +++ examples/server/routers/routers.py | 16 ++++ pyproject.toml | 18 ++-- src/{socketio => fastsio}/__init__.py | 4 +- src/{socketio => fastsio}/admin.py | 0 src/{socketio => fastsio}/asgi.py | 0 src/{socketio => fastsio}/async_admin.py | 0 .../async_aiopika_manager.py | 0 src/{socketio => fastsio}/async_client.py | 0 src/{socketio => fastsio}/async_manager.py | 0 src/{socketio => fastsio}/async_namespace.py | 0 .../async_pubsub_manager.py | 0 .../async_redis_manager.py | 0 src/{socketio => fastsio}/async_server.py | 0 .../async_simple_client.py | 0 src/{socketio => fastsio}/base_client.py | 0 src/{socketio => fastsio}/base_manager.py | 0 src/{socketio => fastsio}/base_namespace.py | 0 src/{socketio => fastsio}/base_server.py | 18 +++- src/{socketio => fastsio}/client.py | 0 src/{socketio => fastsio}/exceptions.py | 0 src/{socketio => fastsio}/kafka_manager.py | 0 src/{socketio => fastsio}/kombu_manager.py | 0 src/{socketio => fastsio}/manager.py | 0 src/{socketio => fastsio}/middleware.py | 0 src/{socketio => fastsio}/msgpack_packet.py | 0 src/{socketio => fastsio}/namespace.py | 0 src/{socketio => fastsio}/packet.py | 0 src/{socketio => fastsio}/pubsub_manager.py | 0 src/{socketio => fastsio}/redis_manager.py | 0 src/fastsio/router.py | 85 +++++++++++++++++++ src/{socketio => fastsio}/server.py | 0 src/{socketio => fastsio}/simple_client.py | 0 src/{socketio => fastsio}/tornado.py | 0 src/{socketio => fastsio}/zmq_manager.py | 0 36 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 examples/server/routers/app.py create mode 100644 examples/server/routers/routers.py rename src/{socketio => fastsio}/__init__.py (95%) rename src/{socketio => fastsio}/admin.py (100%) rename src/{socketio => fastsio}/asgi.py (100%) rename src/{socketio => fastsio}/async_admin.py (100%) rename src/{socketio => fastsio}/async_aiopika_manager.py (100%) rename src/{socketio => fastsio}/async_client.py (100%) rename src/{socketio => fastsio}/async_manager.py (100%) rename src/{socketio => fastsio}/async_namespace.py (100%) rename src/{socketio => fastsio}/async_pubsub_manager.py (100%) rename src/{socketio => fastsio}/async_redis_manager.py (100%) rename src/{socketio => fastsio}/async_server.py (100%) rename src/{socketio => fastsio}/async_simple_client.py (100%) rename src/{socketio => fastsio}/base_client.py (100%) rename src/{socketio => fastsio}/base_manager.py (100%) rename src/{socketio => fastsio}/base_namespace.py (100%) rename src/{socketio => fastsio}/base_server.py (95%) rename src/{socketio => fastsio}/client.py (100%) rename src/{socketio => fastsio}/exceptions.py (100%) rename src/{socketio => fastsio}/kafka_manager.py (100%) rename src/{socketio => fastsio}/kombu_manager.py (100%) rename src/{socketio => fastsio}/manager.py (100%) rename src/{socketio => fastsio}/middleware.py (100%) rename src/{socketio => fastsio}/msgpack_packet.py (100%) rename src/{socketio => fastsio}/namespace.py (100%) rename src/{socketio => fastsio}/packet.py (100%) rename src/{socketio => fastsio}/pubsub_manager.py (100%) rename src/{socketio => fastsio}/redis_manager.py (100%) create mode 100644 src/fastsio/router.py rename src/{socketio => fastsio}/server.py (100%) rename src/{socketio => fastsio}/simple_client.py (100%) rename src/{socketio => fastsio}/tornado.py (100%) rename src/{socketio => fastsio}/zmq_manager.py (100%) diff --git a/README.md b/README.md index 780465f..7cef9c7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -python-socketio +fastsio (Fork from python-socketio) =============== [![Build status](https://github.com/miguelgrinberg/python-socketio/workflows/build/badge.svg)](https://github.com/miguelgrinberg/python-socketio/actions) [![codecov](https://codecov.io/gh/miguelgrinberg/python-socketio/branch/main/graph/badge.svg)](https://codecov.io/gh/miguelgrinberg/python-socketio) diff --git a/examples/server/routers/app.py b/examples/server/routers/app.py new file mode 100644 index 0000000..48ea22f --- /dev/null +++ b/examples/server/routers/app.py @@ -0,0 +1,11 @@ +import fastsio +from .routers import router + +sio = fastsio.AsyncServer( + async_mode="asgi", + cors_allowed_origins=None, +) + +# added all routers +sio.add_router(router=router) + diff --git a/examples/server/routers/routers.py b/examples/server/routers/routers.py new file mode 100644 index 0000000..53b8943 --- /dev/null +++ b/examples/server/routers/routers.py @@ -0,0 +1,16 @@ +from fastsio import RouterSIO + +router = RouterSIO(namespace="/app") + +@router.on("connect", namespace="/room") # override router namespace +async def on_connect(sid, environ): + print("connect ", sid) + +@router.on("disconnect") +async def on_disconnect(sid): + print("disconnect ", sid) + +@router.on("message") +async def on_message(sid, data): + print("message ", data) + diff --git a/pyproject.toml b/pyproject.toml index 10b773f..9bbe49f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,14 @@ [tool.poetry] -name = "fast-socketio" +name = "fastsio" version = "5.13.1.dev0" description = "light and typization Socket.IO server and client for Python" license = "MIT" +license-files = ["LICEN[CS]E*"] readme = "README.md" -homepage = "https://github.com/cicwak/fast-socketio" -repository = "https://github.com/cicwak/fast-socketio" +homepage = "https://github.com/cicwak/fastsio" +repository = "https://github.com/cicwak/fastsio" packages = [ - { include = "socketio", from = "src" }, + { include = "fastsio", from = "src" }, ] authors = [ "Miguel Grinberg ", @@ -19,8 +20,8 @@ file = "README.md" content-type = "text/markdown" [project.urls] -Homepage = "https://github.com/cicwak/fast-socketio" -#"Bug Tracker" = "https://github.com/miguelgrinberg/python-socketio/issues" +Homepage = "https://github.com/cicwak/fastsio" +"Bug Tracker" = "https://github.com/cicwak/fastsio/issues" [project.optional-dependencies] client = [ @@ -62,7 +63,7 @@ where = [ namespaces = false [build-system] -requires = ["setuptools>=61.2"] +requires = ["setuptools >= 77.0.3"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] @@ -80,9 +81,6 @@ line-length = 88 exclude = [ "migrations", "venv", - "start_service.dist", - "start_service.build", - "start_service.onefile-build", ] diff --git a/src/socketio/__init__.py b/src/fastsio/__init__.py similarity index 95% rename from src/socketio/__init__.py rename to src/fastsio/__init__.py index d2a4a89..f23af70 100644 --- a/src/socketio/__init__.py +++ b/src/fastsio/__init__.py @@ -17,7 +17,7 @@ from .redis_manager import RedisManager from .server import Server from .simple_client import SimpleClient from .tornado import get_tornado_handler -from .router import RouterSocketIO +from .router import RouterSIO from .zmq_manager import ZmqManager __all__ = [ @@ -44,5 +44,5 @@ __all__ = [ "WSGIApp", "ZmqManager", "get_tornado_handler", - "RouterSocketIO", + "RouterSIO", ] diff --git a/src/socketio/admin.py b/src/fastsio/admin.py similarity index 100% rename from src/socketio/admin.py rename to src/fastsio/admin.py diff --git a/src/socketio/asgi.py b/src/fastsio/asgi.py similarity index 100% rename from src/socketio/asgi.py rename to src/fastsio/asgi.py diff --git a/src/socketio/async_admin.py b/src/fastsio/async_admin.py similarity index 100% rename from src/socketio/async_admin.py rename to src/fastsio/async_admin.py diff --git a/src/socketio/async_aiopika_manager.py b/src/fastsio/async_aiopika_manager.py similarity index 100% rename from src/socketio/async_aiopika_manager.py rename to src/fastsio/async_aiopika_manager.py diff --git a/src/socketio/async_client.py b/src/fastsio/async_client.py similarity index 100% rename from src/socketio/async_client.py rename to src/fastsio/async_client.py diff --git a/src/socketio/async_manager.py b/src/fastsio/async_manager.py similarity index 100% rename from src/socketio/async_manager.py rename to src/fastsio/async_manager.py diff --git a/src/socketio/async_namespace.py b/src/fastsio/async_namespace.py similarity index 100% rename from src/socketio/async_namespace.py rename to src/fastsio/async_namespace.py diff --git a/src/socketio/async_pubsub_manager.py b/src/fastsio/async_pubsub_manager.py similarity index 100% rename from src/socketio/async_pubsub_manager.py rename to src/fastsio/async_pubsub_manager.py diff --git a/src/socketio/async_redis_manager.py b/src/fastsio/async_redis_manager.py similarity index 100% rename from src/socketio/async_redis_manager.py rename to src/fastsio/async_redis_manager.py diff --git a/src/socketio/async_server.py b/src/fastsio/async_server.py similarity index 100% rename from src/socketio/async_server.py rename to src/fastsio/async_server.py diff --git a/src/socketio/async_simple_client.py b/src/fastsio/async_simple_client.py similarity index 100% rename from src/socketio/async_simple_client.py rename to src/fastsio/async_simple_client.py diff --git a/src/socketio/base_client.py b/src/fastsio/base_client.py similarity index 100% rename from src/socketio/base_client.py rename to src/fastsio/base_client.py diff --git a/src/socketio/base_manager.py b/src/fastsio/base_manager.py similarity index 100% rename from src/socketio/base_manager.py rename to src/fastsio/base_manager.py diff --git a/src/socketio/base_namespace.py b/src/fastsio/base_namespace.py similarity index 100% rename from src/socketio/base_namespace.py rename to src/fastsio/base_namespace.py diff --git a/src/socketio/base_server.py b/src/fastsio/base_server.py similarity index 95% rename from src/socketio/base_server.py rename to src/fastsio/base_server.py index d1ca34e..df644f5 100644 --- a/src/socketio/base_server.py +++ b/src/fastsio/base_server.py @@ -5,6 +5,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import engineio from . import base_namespace, manager, packet +from .router import RouterSIO default_logger = logging.getLogger("socketio.server") @@ -194,14 +195,25 @@ class BaseServer: raise ValueError("Not a namespace instance") if self.is_asyncio_based() != namespace_handler.is_asyncio_based(): raise ValueError("Not a valid namespace class for this server") - namespace_handler._set_server(self) - self.namespace_handlers[namespace_handler.namespace] = namespace_handler namespace_handler._set_server(self) # type: ignore[misc] ns: str = str(namespace_handler.namespace or "/") self.namespace_handlers[ns] = namespace_handler + def add_router(self, router: RouterSIO) -> None: + """Attach a RouterSIO to this server. + + This will register all function-based handlers and any queued + class-based namespace handlers contained in the router. + """ + # function-based + for ns, event, handler in router.iter_function_handlers(): + if ns not in self.handlers: + self.handlers[ns] = {} + self.handlers[ns][event] = handler + # class-based namespaces + for ns_handler in router.iter_namespace_handlers(): + self.register_namespace(ns_handler) - def rooms(self, sid, namespace=None): def rooms(self, sid: str, namespace: Optional[str] = None) -> List[str]: """Return the rooms a client is in. diff --git a/src/socketio/client.py b/src/fastsio/client.py similarity index 100% rename from src/socketio/client.py rename to src/fastsio/client.py diff --git a/src/socketio/exceptions.py b/src/fastsio/exceptions.py similarity index 100% rename from src/socketio/exceptions.py rename to src/fastsio/exceptions.py diff --git a/src/socketio/kafka_manager.py b/src/fastsio/kafka_manager.py similarity index 100% rename from src/socketio/kafka_manager.py rename to src/fastsio/kafka_manager.py diff --git a/src/socketio/kombu_manager.py b/src/fastsio/kombu_manager.py similarity index 100% rename from src/socketio/kombu_manager.py rename to src/fastsio/kombu_manager.py diff --git a/src/socketio/manager.py b/src/fastsio/manager.py similarity index 100% rename from src/socketio/manager.py rename to src/fastsio/manager.py diff --git a/src/socketio/middleware.py b/src/fastsio/middleware.py similarity index 100% rename from src/socketio/middleware.py rename to src/fastsio/middleware.py diff --git a/src/socketio/msgpack_packet.py b/src/fastsio/msgpack_packet.py similarity index 100% rename from src/socketio/msgpack_packet.py rename to src/fastsio/msgpack_packet.py diff --git a/src/socketio/namespace.py b/src/fastsio/namespace.py similarity index 100% rename from src/socketio/namespace.py rename to src/fastsio/namespace.py diff --git a/src/socketio/packet.py b/src/fastsio/packet.py similarity index 100% rename from src/socketio/packet.py rename to src/fastsio/packet.py diff --git a/src/socketio/pubsub_manager.py b/src/fastsio/pubsub_manager.py similarity index 100% rename from src/socketio/pubsub_manager.py rename to src/fastsio/pubsub_manager.py diff --git a/src/socketio/redis_manager.py b/src/fastsio/redis_manager.py similarity index 100% rename from src/socketio/redis_manager.py rename to src/fastsio/redis_manager.py diff --git a/src/fastsio/router.py b/src/fastsio/router.py new file mode 100644 index 0000000..63615fd --- /dev/null +++ b/src/fastsio/router.py @@ -0,0 +1,85 @@ +from typing import Any, Callable, Dict, List, Optional, Tuple + +from . import base_namespace + + +class RouterSIO: + """A lightweight router for organizing Socket.IO event handlers. + + This provides a FastAPI-like developer experience for grouping and + including handlers. Handlers registered on the router can later be + attached to a server via ``sio.add_router(router)``. + + Example: + + router = RouterSIO(namespace="/chat") + + @router.on("message") + async def handle_message(sid: str, data: Any): + ... + + sio.add_router(router) + """ + + def __init__(self, namespace: Optional[str] = None) -> None: + # Default namespace applied when not provided explicitly in .on()/@event + self.default_namespace: str = namespace or "/" + # Decorator-based function handlers: {namespace: {event: handler}} + self.handlers: Dict[str, Dict[str, Callable[..., Any]]] = {} + # Class-based namespace handlers to be registered on the server + self._namespace_handlers: List[base_namespace.BaseServerNamespace] = [] + + # Public API mirrors Server.on + def on( + self, + event: str, + handler: Optional[Callable[..., Any]] = None, + namespace: Optional[str] = None, + ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + ns = namespace or self.default_namespace + + def set_handler(h: Callable[..., Any]) -> Callable[..., Any]: + if ns not in self.handlers: + self.handlers[ns] = {} + self.handlers[ns][event] = h + return h + + if handler is None: + return set_handler + set_handler(handler) + return set_handler + + # Convenience decorator mirrors Server.event + def event(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): + # invoked without arguments: @router.event + return self.on(args[0].__name__)(args[0]) + + # invoked with arguments: @router.event(namespace="...") + def set_handler(h: Callable[..., Any]) -> Callable[..., Any]: + return self.on(h.__name__, *args, **kwargs)(h) + + return set_handler + + def register_namespace(self, namespace_handler: base_namespace.BaseServerNamespace) -> None: + """Queue a class-based namespace handler for registration. + + The actual registration occurs when the router is attached to a server + via ``sio.add_router(router)``. + """ + if not isinstance(namespace_handler, base_namespace.BaseServerNamespace): # type: ignore[redundant-expr] + raise ValueError("Not a namespace instance") + self._namespace_handlers.append(namespace_handler) + + # Internal helpers used by the server when attaching the router + def iter_function_handlers(self) -> List[Tuple[str, str, Callable[..., Any]]]: + out: List[Tuple[str, str, Callable[..., Any]]] = [] + for ns, events in self.handlers.items(): + for event, handler in events.items(): + out.append((ns, event, handler)) + return out + + def iter_namespace_handlers(self) -> List[base_namespace.BaseServerNamespace]: + return list(self._namespace_handlers) + + diff --git a/src/socketio/server.py b/src/fastsio/server.py similarity index 100% rename from src/socketio/server.py rename to src/fastsio/server.py diff --git a/src/socketio/simple_client.py b/src/fastsio/simple_client.py similarity index 100% rename from src/socketio/simple_client.py rename to src/fastsio/simple_client.py diff --git a/src/socketio/tornado.py b/src/fastsio/tornado.py similarity index 100% rename from src/socketio/tornado.py rename to src/fastsio/tornado.py diff --git a/src/socketio/zmq_manager.py b/src/fastsio/zmq_manager.py similarity index 100% rename from src/socketio/zmq_manager.py rename to src/fastsio/zmq_manager.py