From 026b1a314a8efe763d0352237a450300e8b4f288 Mon Sep 17 00:00:00 2001 From: Luis-Hebendanz Date: Mon, 20 Nov 2023 19:45:47 +0100 Subject: [PATCH 1/4] Added broadcaster dependency --- flake.lock | 35 +++++--- flake.nix | 1 + pkgs/clan-cli/.vscode/settings.json | 2 +- pkgs/clan-cli/clan_cli/webui/app.py | 6 +- .../clan_cli/webui/routers/socket_manager.py | 79 +++++++++++++++++++ pkgs/clan-cli/default.nix | 4 + pkgs/clan-cli/flake-module.nix | 4 +- pkgs/nix-unit/default.nix | 48 ++--------- 8 files changed, 125 insertions(+), 54 deletions(-) create mode 100644 pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py diff --git a/flake.lock b/flake.lock index add9b0c..314e233 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1696343447, - "narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=", + "lastModified": 1698882062, + "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4", + "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", "type": "github" }, "original": { @@ -42,11 +42,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1697059129, - "narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=", + "lastModified": 1700390070, + "narHash": "sha256-de9KYi8rSJpqvBfNwscWdalIJXPo8NjdIZcEJum1mH0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5e4c2ada4fcd54b99d56d7bd62f384511a7e2593", + "rev": "e4ad989506ec7d71f7302cc3067abd82730a4beb", "type": "github" }, "original": { @@ -56,11 +56,28 @@ "type": "github" } }, + "nixpkgs-for-iosl": { + "locked": { + "lastModified": 1700505490, + "narHash": "sha256-MLF5dkExensQoByZCmsR/kdcwZoaY/j6/ctSvmQHBJc=", + "owner": "Luis-Hebendanz", + "repo": "nixpkgs", + "rev": "5f9d94794badee6fcb3230ccea75628a802baeab", + "type": "github" + }, + "original": { + "owner": "Luis-Hebendanz", + "ref": "iosl", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "flake-parts": "flake-parts", "floco": "floco", "nixpkgs": "nixpkgs", + "nixpkgs-for-iosl": "nixpkgs-for-iosl", "treefmt-nix": "treefmt-nix" } }, @@ -71,11 +88,11 @@ ] }, "locked": { - "lastModified": 1695822946, - "narHash": "sha256-IQU3fYo0H+oGlqX5YrgZU3VRhbt2Oqe6KmslQKUO4II=", + "lastModified": 1699786194, + "narHash": "sha256-3h3EH1FXQkIeAuzaWB+nK0XK54uSD46pp+dMD3gAcB4=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "720bd006d855b08e60664e4683ccddb7a9ff614a", + "rev": "e82f32aa7f06bbbd56d7b12186d555223dc399d1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 9e1a4e3..6c73ad1 100644 --- a/flake.nix +++ b/flake.nix @@ -7,6 +7,7 @@ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; floco.url = "github:aakropotkin/floco"; floco.inputs.nixpkgs.follows = "nixpkgs"; + nixpkgs-for-iosl.url = "github:Luis-Hebendanz/nixpkgs/iosl"; flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; treefmt-nix.url = "github:numtide/treefmt-nix"; diff --git a/pkgs/clan-cli/.vscode/settings.json b/pkgs/clan-cli/.vscode/settings.json index e5c2632..0c7964c 100644 --- a/pkgs/clan-cli/.vscode/settings.json +++ b/pkgs/clan-cli/.vscode/settings.json @@ -18,5 +18,5 @@ "python.linting.mypyPath": "mypy", "python.linting.mypyEnabled": true, "python.linting.enabled": true, - "python.defaultInterpreterPath": "python" + "python.defaultInterpreterPath": "/nix/store/k34qdl5397mwg3k00jsl3xcynij7n0z9-python3-3.11.6-env/bin/python" } \ No newline at end of file diff --git a/pkgs/clan-cli/clan_cli/webui/app.py b/pkgs/clan-cli/clan_cli/webui/app.py index 88ff380..a9f1d2d 100644 --- a/pkgs/clan-cli/clan_cli/webui/app.py +++ b/pkgs/clan-cli/clan_cli/webui/app.py @@ -8,7 +8,7 @@ from fastapi.staticfiles import StaticFiles from ..errors import ClanError from .assets import asset_path from .error_handlers import clan_error_handler -from .routers import health, root +from .routers import health, root, socket_manager origins = [ "http://localhost:3000", @@ -29,9 +29,11 @@ def setup_app() -> FastAPI: app.include_router(health.router) + app.include_router(socket_manager.router) + # Needs to be last in register. Because of wildcard route app.include_router(root.router) - app.add_exception_handler(ClanError, clan_error_handler) + app.add_exception_handler(ClanError, clan_error_handler) # type: ignore app.mount("/static", StaticFiles(directory=asset_path()), name="static") diff --git a/pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py b/pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py new file mode 100644 index 0000000..cdfa46e --- /dev/null +++ b/pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py @@ -0,0 +1,79 @@ +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi import APIRouter, Response + +router = APIRouter() + +html = """ + + + + Chat + + +

WebSocket Chat

+

Your ID:

+
+ + +
+ + + + +""" + +class ConnectionManager: + def __init__(self) -> None: + self.active_connections: list[WebSocket] = [] + + async def connect(self, websocket: WebSocket) -> None: + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket) -> None: + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, websocket: WebSocket) -> None: + await websocket.send_text(message) + + async def broadcast(self, message: str) -> None: + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + +@router.get("/ws_example") +async def get() -> HTMLResponse: + return HTMLResponse(html) + +@router.websocket("/ws/{client_id}") +async def websocket_endpoint(websocket: WebSocket, client_id: int) -> None: + await manager.connect(websocket) + try: + while True: + data = await websocket.receive_text() + await manager.send_personal_message(f"You wrote: {data}", websocket) + await manager.broadcast(f"Client #{client_id} says: {data}") + except WebSocketDisconnect: + manager.disconnect(websocket) + await manager.broadcast(f"Client #{client_id} left the chat") \ No newline at end of file diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index a9f4b66..42347b6 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -36,6 +36,8 @@ , mypy , sqlalchemy , websockets +, deal +, broadcaster }: let @@ -45,6 +47,8 @@ let uvicorn # optional dependencies: if not enabled, webui subcommand will not work sqlalchemy websockets + broadcaster + deal ]; pytestDependencies = runtimeDependencies ++ dependencies ++ [ diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index 2f912d0..5e3719c 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -1,6 +1,6 @@ { inputs, ... }: { - perSystem = { self', pkgs, ... }: { + perSystem = { self', pkgs, system, ... }: { devShells.clan-cli = pkgs.callPackage ./shell.nix { inherit (self'.packages) clan-cli ui-assets nix-unit; }; @@ -8,6 +8,8 @@ clan-cli = pkgs.python3.pkgs.callPackage ./default.nix { inherit (self'.packages) ui-assets; inherit (inputs) nixpkgs; + inherit (inputs.nixpkgs-for-iosl.legacyPackages.${system}.python3Packages) deal; + inherit (inputs.nixpkgs-for-iosl.legacyPackages.${system}.python3Packages) broadcaster; }; inherit (self'.packages.clan-cli) clan-openapi; default = self'.packages.clan-cli; diff --git a/pkgs/nix-unit/default.nix b/pkgs/nix-unit/default.nix index 507ed57..fe917f0 100644 --- a/pkgs/nix-unit/default.nix +++ b/pkgs/nix-unit/default.nix @@ -1,43 +1,9 @@ -{ stdenv -, lib -, nixVersions -, fetchFromGitHub -, nlohmann_json -, boost -, meson -, pkg-config -, ninja -, cmake -, clang-tools -}: - -stdenv.mkDerivation { - pname = "nix-unit"; - version = "0.1"; - src = fetchFromGitHub { - owner = "adisbladis"; - repo = "nix-unit"; - rev = "3ed2378bddad85257fc508a291408f9ed9673d01"; - sha256 = "sha256-HvMq0TJGYSx37zHm4j2d+JUZx4/6X7xKEt/0DeCiwjQ="; +{ callPackage }: +let + nix-unit-src = builtins.fetchGit { + url = "https://github.com/adisbladis/nix-unit"; + rev = "7e2ee1c70f930b9b65b9fc33c3f3eca0dfae00d1"; }; - buildInputs = [ - nlohmann_json - nixVersions.stable - boost - ]; - nativeBuildInputs = [ - meson - pkg-config - ninja - # nlohmann_json can be only discovered via cmake files - cmake - ] ++ (lib.optional stdenv.cc.isClang [ clang-tools ]); +in +callPackage nix-unit-src { } - meta = { - description = "Nix unit test runner"; - homepage = "https://github.com/adisbladis/nix-unit"; - license = lib.licenses.gpl3; - maintainers = with lib.maintainers; [ adisbladis ]; - platforms = lib.platforms.unix; - }; -} -- 2.51.0 From 9d9124e00a723d3a185c244795f44860af0e8306 Mon Sep 17 00:00:00 2001 From: Luis-Hebendanz Date: Mon, 20 Nov 2023 22:58:01 +0100 Subject: [PATCH 2/4] Working broadcasting library --- flake.lock | 6 +- .../clan_cli/{types.py => clan_types.py} | 0 pkgs/clan-cli/clan_cli/webui/app.py | 14 +- .../clan_cli/webui/routers/messenger.html | 202 ++++++++++++++++++ .../clan_cli/webui/routers/socket_manager2.py | 54 +++++ 5 files changed, 271 insertions(+), 5 deletions(-) rename pkgs/clan-cli/clan_cli/{types.py => clan_types.py} (100%) create mode 100644 pkgs/clan-cli/clan_cli/webui/routers/messenger.html create mode 100644 pkgs/clan-cli/clan_cli/webui/routers/socket_manager2.py diff --git a/flake.lock b/flake.lock index 314e233..5234a63 100644 --- a/flake.lock +++ b/flake.lock @@ -58,11 +58,11 @@ }, "nixpkgs-for-iosl": { "locked": { - "lastModified": 1700505490, - "narHash": "sha256-MLF5dkExensQoByZCmsR/kdcwZoaY/j6/ctSvmQHBJc=", + "lastModified": 1700515317, + "narHash": "sha256-DSnKT3glZCKE0/Rc6ainSJUbUGC248HNKVRSbo9kxkM=", "owner": "Luis-Hebendanz", "repo": "nixpkgs", - "rev": "5f9d94794badee6fcb3230ccea75628a802baeab", + "rev": "bcd6be7a5ab22c94c775945fb16a61b5867f15d2", "type": "github" }, "original": { diff --git a/pkgs/clan-cli/clan_cli/types.py b/pkgs/clan-cli/clan_cli/clan_types.py similarity index 100% rename from pkgs/clan-cli/clan_cli/types.py rename to pkgs/clan-cli/clan_cli/clan_types.py diff --git a/pkgs/clan-cli/clan_cli/webui/app.py b/pkgs/clan-cli/clan_cli/webui/app.py index a9f1d2d..cf7195f 100644 --- a/pkgs/clan-cli/clan_cli/webui/app.py +++ b/pkgs/clan-cli/clan_cli/webui/app.py @@ -4,11 +4,13 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.routing import APIRoute from fastapi.staticfiles import StaticFiles +from contextlib import asynccontextmanager +from typing import Any from ..errors import ClanError from .assets import asset_path from .error_handlers import clan_error_handler -from .routers import health, root, socket_manager +from .routers import health, root, socket_manager, socket_manager2 origins = [ "http://localhost:3000", @@ -17,8 +19,15 @@ origins = [ log = logging.getLogger(__name__) +@asynccontextmanager +async def lifespan(app: FastAPI) -> Any: + await socket_manager2.brd.connect() + yield + await socket_manager2.brd.disconnect() + + def setup_app() -> FastAPI: - app = FastAPI() + app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -30,6 +39,7 @@ def setup_app() -> FastAPI: app.include_router(health.router) app.include_router(socket_manager.router) + app.include_router(socket_manager2.router) # Needs to be last in register. Because of wildcard route app.include_router(root.router) diff --git a/pkgs/clan-cli/clan_cli/webui/routers/messenger.html b/pkgs/clan-cli/clan_cli/webui/routers/messenger.html new file mode 100644 index 0000000..a24b097 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/webui/routers/messenger.html @@ -0,0 +1,202 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkgs/clan-cli/clan_cli/webui/routers/socket_manager2.py b/pkgs/clan-cli/clan_cli/webui/routers/socket_manager2.py new file mode 100644 index 0000000..e8bf1ca --- /dev/null +++ b/pkgs/clan-cli/clan_cli/webui/routers/socket_manager2.py @@ -0,0 +1,54 @@ +# Requires: `starlette`, `uvicorn`, `jinja2` +# Run with `uvicorn example:app` +import anyio +from broadcaster import Broadcast +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi import APIRouter, Response +import os +import logging +import asyncio + +log = logging.getLogger(__name__) +router = APIRouter() + + +brd = Broadcast("memory://") + + +@router.get("/ws2_example") +async def get() -> HTMLResponse: + + html = open(f"{os.getcwd()}/webui/routers/messenger.html").read() + return HTMLResponse(html) + +@router.websocket("/ws2") +async def chatroom_ws(websocket: WebSocket) -> None: + await websocket.accept() + + + async with anyio.create_task_group() as task_group: + # run until first is complete + async def run_chatroom_ws_receiver() -> None: + await chatroom_ws_receiver(websocket=websocket) + task_group.cancel_scope.cancel() + + task_group.start_soon(run_chatroom_ws_receiver) + log.warning("Started chatroom_ws_sender") + + await chatroom_ws_sender(websocket) + + +async def chatroom_ws_receiver(websocket: WebSocket) -> None: + async for message in websocket.iter_text(): + log.warning(f"Received message: {message}") + await brd.publish(channel="chatroom", message=message) + + +async def chatroom_ws_sender(websocket: WebSocket) -> None: + async with brd.subscribe(channel="chatroom") as subscriber: + log.warning("====>Subscribed to chatroom channel") + async for event in subscriber: + log.warning(f"Sending message: {event.message}") + await websocket.send_text(event.message) + -- 2.51.0 From b034bd11f1a73d432f198fbb13a93734978eb36e Mon Sep 17 00:00:00 2001 From: Luis-Hebendanz Date: Sun, 26 Nov 2023 12:35:27 +0100 Subject: [PATCH 3/4] Removed old socket manager --- .../clan_cli/webui/routers/socket_manager.py | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py diff --git a/pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py b/pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py deleted file mode 100644 index cdfa46e..0000000 --- a/pkgs/clan-cli/clan_cli/webui/routers/socket_manager.py +++ /dev/null @@ -1,79 +0,0 @@ -from fastapi import FastAPI, WebSocket, WebSocketDisconnect -from fastapi.responses import HTMLResponse -from fastapi import APIRouter, Response - -router = APIRouter() - -html = """ - - - - Chat - - -

WebSocket Chat

-

Your ID:

-
- - -
-
    -
- - - -""" - -class ConnectionManager: - def __init__(self) -> None: - self.active_connections: list[WebSocket] = [] - - async def connect(self, websocket: WebSocket) -> None: - await websocket.accept() - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket) -> None: - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, websocket: WebSocket) -> None: - await websocket.send_text(message) - - async def broadcast(self, message: str) -> None: - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - -@router.get("/ws_example") -async def get() -> HTMLResponse: - return HTMLResponse(html) - -@router.websocket("/ws/{client_id}") -async def websocket_endpoint(websocket: WebSocket, client_id: int) -> None: - await manager.connect(websocket) - try: - while True: - data = await websocket.receive_text() - await manager.send_personal_message(f"You wrote: {data}", websocket) - await manager.broadcast(f"Client #{client_id} says: {data}") - except WebSocketDisconnect: - manager.disconnect(websocket) - await manager.broadcast(f"Client #{client_id} left the chat") \ No newline at end of file -- 2.51.0 From 258bf656cfe8e42d092ada4295d1b929a1b856a8 Mon Sep 17 00:00:00 2001 From: Luis-Hebendanz Date: Sun, 26 Nov 2023 12:47:16 +0100 Subject: [PATCH 4/4] nix fmt --- pkgs/clan-cli/clan_cli/dirs.py | 2 +- pkgs/clan-cli/clan_cli/webui/api_inputs.py | 2 +- pkgs/clan-cli/clan_cli/webui/app.py | 9 +- .../clan_cli/webui/routers/messenger.html | 389 +++++++++--------- .../clan_cli/webui/routers/socket_manager2.py | 20 +- pkgs/clan-cli/pyproject.toml | 5 + 6 files changed, 216 insertions(+), 211 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/dirs.py b/pkgs/clan-cli/clan_cli/dirs.py index 5f11577..ce5ff14 100644 --- a/pkgs/clan-cli/clan_cli/dirs.py +++ b/pkgs/clan-cli/clan_cli/dirs.py @@ -4,8 +4,8 @@ import sys from pathlib import Path from typing import Optional +from .clan_types import FlakeName from .errors import ClanError -from .types import FlakeName log = logging.getLogger(__name__) diff --git a/pkgs/clan-cli/clan_cli/webui/api_inputs.py b/pkgs/clan-cli/clan_cli/webui/api_inputs.py index 70539e7..1148c7b 100644 --- a/pkgs/clan-cli/clan_cli/webui/api_inputs.py +++ b/pkgs/clan-cli/clan_cli/webui/api_inputs.py @@ -5,8 +5,8 @@ from typing import Any from pydantic import AnyUrl, BaseModel, validator from pydantic.tools import parse_obj_as +from ..clan_types import validate_path from ..dirs import clan_data_dir, clan_flakes_dir -from ..types import validate_path DEFAULT_URL = parse_obj_as(AnyUrl, "http://localhost:8000") diff --git a/pkgs/clan-cli/clan_cli/webui/app.py b/pkgs/clan-cli/clan_cli/webui/app.py index cf7195f..f33bbf1 100644 --- a/pkgs/clan-cli/clan_cli/webui/app.py +++ b/pkgs/clan-cli/clan_cli/webui/app.py @@ -1,16 +1,16 @@ import logging +from contextlib import asynccontextmanager +from typing import Any from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.routing import APIRoute from fastapi.staticfiles import StaticFiles -from contextlib import asynccontextmanager -from typing import Any from ..errors import ClanError from .assets import asset_path from .error_handlers import clan_error_handler -from .routers import health, root, socket_manager, socket_manager2 +from .routers import health, root, socket_manager2 origins = [ "http://localhost:3000", @@ -38,12 +38,11 @@ def setup_app() -> FastAPI: app.include_router(health.router) - app.include_router(socket_manager.router) app.include_router(socket_manager2.router) # Needs to be last in register. Because of wildcard route app.include_router(root.router) - app.add_exception_handler(ClanError, clan_error_handler) # type: ignore + app.add_exception_handler(ClanError, clan_error_handler) # type: ignore app.mount("/static", StaticFiles(directory=asset_path()), name="static") diff --git a/pkgs/clan-cli/clan_cli/webui/routers/messenger.html b/pkgs/clan-cli/clan_cli/webui/routers/messenger.html index a24b097..cdc6618 100644 --- a/pkgs/clan-cli/clan_cli/webui/routers/messenger.html +++ b/pkgs/clan-cli/clan_cli/webui/routers/messenger.html @@ -1,202 +1,205 @@ - + - - - + + - - - - -