This commit is contained in:
Waylon Walker 2023-06-14 13:43:06 -05:00
parent 892a3c9a8a
commit 3f28a10b16
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
5 changed files with 98 additions and 111 deletions

View file

@ -3,12 +3,15 @@ import time
from rich.console import Console from rich.console import Console
from websocket import create_connection from websocket import create_connection
from learn_sql_model.models.hero import Hero from learn_sql_model.config import get_config
from learn_sql_model.models.hero import Heros
config = get_config()
def connect(): def connect():
id = 1 url = f"ws://{config.api_client.url.replace('https://', '')}/ws/heros"
url = f"ws://localhost:5000/ws/{id}" # url = f"ws://localhost:5000/ws/heros"
Console().log(f"connecting to: {url}") Console().log(f"connecting to: {url}")
ws = create_connection(url) ws = create_connection(url)
Console().log(f"connected to: {url}") Console().log(f"connected to: {url}")
@ -23,7 +26,8 @@ def watch(ws):
try: try:
data.append(ws.recv()) data.append(ws.recv())
if data[-1].startswith("{"): if data[-1].startswith("{"):
Console().log(Hero.parse_raw(data[-1])) # Console().log(data[-1])
Console().log(Heros.parse_raw(data[-1]))
else: else:
Console().log(data[-1]) Console().log(data[-1])
except Exception as e: except Exception as e:

View file

@ -3,8 +3,10 @@ import time
from rich.console import Console from rich.console import Console
from websocket import create_connection from websocket import create_connection
id = 1 from learn_sql_model.config import get_config
url = f"ws://localhost:5000/ws/{id}"
config = get_config()
url = f"ws://{config.api_client.url.replace('https://', '')}/ws/heros"
Console().log(f"connecting to: {url}") Console().log(f"connecting to: {url}")
ws = create_connection(url) ws = create_connection(url)

View file

@ -1,25 +1,38 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from learn_sql_model.api.hero import hero_router from learn_sql_model.api.hero import hero_router
from learn_sql_model.api.user import user_router from learn_sql_model.api.user import user_router
from learn_sql_model.api.websocket import web_socket_router from learn_sql_model.api.websocket import web_socket_router
# from fastapi_socketio import SocketManager
app = FastAPI() app = FastAPI()
# socket_manager = SocketManager(app=app)
app.include_router(hero_router) app.include_router(hero_router)
app.include_router(user_router) app.include_router(user_router)
app.include_router(web_socket_router) app.include_router(web_socket_router)
# @app.sio.on("join") html = """
# def handle_join(sid, *args, **kwargs): <!DOCTYPE html>
# app.sio.emit("lobby", "User joined") <html>
<head>
<title>Learn SQL Model</title>
</head>
<body>
<h1>Learn SQL Model</h1>
<p>Join the game with the following command.
</p>
<p>
pipx run --spec git+https://github.com/WaylonWalker/learn-sql-model lsm game run
</p>
<p>
You can watch player data at <a href='/watch'>watch</a>
</p>
</body>
</html>
"""
# @app.sio.on("leave") @app.get("/")
# def handle_leave(sid, *args, **kwargs): async def get():
# sm.emit("lobby", "User left") return HTMLResponse(html)

View file

@ -1,16 +1,13 @@
from contextlib import contextmanager
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from sqlalchemy import create_engine from rich.console import Console
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlmodel import Session from sqlmodel import Session
from websockets.exceptions import ConnectionClosed from websockets.exceptions import ConnectionClosed
from learn_sql_model.api.websocket_connection_manager import manager from learn_sql_model.api.websocket_connection_manager import manager
from learn_sql_model.config import get_config, get_session from learn_sql_model.config import get_session
from learn_sql_model.console import console from learn_sql_model.console import console
from learn_sql_model.models.hero import HeroUpdate, Heros from learn_sql_model.models.hero import HeroDelete, HeroUpdate, Heros
web_socket_router = APIRouter() web_socket_router = APIRouter()
@ -21,28 +18,15 @@ html = """
<title>Chat</title> <title>Chat</title>
</head> </head>
<body> <body>
<h1>WebSocket Chat</h1> <h1>Heros Stream</h1>
<form action="" onsubmit="sendMessage(event)"> <code id='messages'>
<input type="text" id="messageText" autocomplete="off"/> </code>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script> <script>
var ws = new WebSocket("ws://localhost:5000/ws"); var ws = new WebSocket("wss://learn-sql-model.fly.dev/ws/heros");
ws.onmessage = function(event) { ws.onmessage = function(event) {
var messages = document.getElementById('messages') var messages = document.getElementById('messages')
var message = document.createElement('li') messages.innerHTML = event.data
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
}; };
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script> </script>
</body> </body>
</html> </html>
@ -54,18 +38,24 @@ async def get():
return HTMLResponse(html) return HTMLResponse(html)
@web_socket_router.websocket("/ws/{id}") @web_socket_router.websocket("/ws/{channel}")
async def websocket_endpoint_connect(websocket: WebSocket, id: int): async def websocket_endpoint_connect(
await manager.connect(websocket, id) websocket: WebSocket,
channel: str,
session: Session = Depends(get_session),
):
Console().log(f"Client #{id} connecting")
await manager.connect(websocket, channel)
heros = Heros.list(session=session)
await websocket.send_text(heros.json())
try: try:
while True: while True:
data = await websocket.receive_text() await websocket.receive_text()
await websocket.send_text(f"[gold]You Said: {data}")
await manager.broadcast(f"[blue]USER: {data}", id)
except WebSocketDisconnect: except WebSocketDisconnect:
manager.disconnect(websocket, id) manager.disconnect(websocket, id)
await manager.broadcast(f"Client #{id} left the chat", id) await manager.broadcast(f"Client #{channel} left the chat", channel)
@web_socket_router.websocket("/ws") @web_socket_router.websocket("/ws")
@ -81,71 +71,39 @@ async def websocket_endpoint(websocket: WebSocket):
await manager.broadcast(f"Client #{id} left the chat", id) await manager.broadcast(f"Client #{id} left the chat", id)
@contextmanager
def db_session(db_url):
"""Creates a context with an open SQLAlchemy session."""
engine = create_engine(db_url, convert_unicode=True)
connection = engine.connect()
db_session = scoped_session(
sessionmaker(autocommit=False, autoflush=True, bind=engine)
)
yield db_session
db_session.close()
connection.close()
@web_socket_router.websocket("/wsecho") @web_socket_router.websocket("/wsecho")
async def websocket_endpoint_hero_echo( async def websocket_endpoint_hero_echo(
websocket: WebSocket, websocket: WebSocket,
session: Session = Depends(get_session), session: Session = Depends(get_session),
): ):
config = get_config()
await websocket.accept() await websocket.accept()
last_heros = None last_heros = None
try:
with config.database.engine.connect() as con:
while True:
data = await websocket.receive_text()
hero = HeroUpdate.parse_raw(data)
# heros = con.execute("SELECT * FROM hero").fetchall()
# heros = Heros.parse_obj({"heros": heros})
heros = Heros.list(session=session)
if heros != last_heros:
await manager.broadcast(heros.json(), "heros")
last_heros = heros
hero.update(session=session)
console.print(heros)
await websocket.send_text(heros.json())
except WebSocketDisconnect:
print("disconnected")
except ConnectionClosed:
print("connection closed")
@web_socket_router.websocket("/ws-heros'")
async def websocket_endpoint_heros(
websocket: WebSocket,
session: Session = Depends(get_session),
):
await manager.connect(websocket, "heros")
await websocket.accept()
try: try:
while True: while True:
... data = await websocket.receive_text()
# data = await websocket.receive_text() hero = HeroUpdate.parse_raw(data)
# hero = HeroUpdate.parse_raw(data) heros = Heros.list(session=session)
# heros = con.execute("SELECT * FROM hero").fetchall() if heros != last_heros:
# heros = Heros.parse_obj({"heros": heros}) await manager.broadcast(heros.json(), "heros")
# hero.update(session=session) last_heros = heros
# console.print(heros) hero.update(session=session)
# await websocket.send_text(heros.json()) console.print(heros)
await websocket.send_text(heros.json())
except WebSocketDisconnect: except WebSocketDisconnect:
try:
HeroDelete(id=hero.id).delete(session=session)
except Exception:
...
heros = Heros.list(session=session)
await manager.broadcast(heros.json(), "heros")
print("disconnected") print("disconnected")
manager.disconnect(websocket, "heros")
except ConnectionClosed: except ConnectionClosed:
manager.disconnect(websocket, "heros") try:
HeroDelete(id=hero.id).delete(session=session)
except Exception:
...
heros = Heros.list(session=session)
await manager.broadcast(heros.json(), "heros")
print("connection closed") print("connection closed")

View file

@ -2,9 +2,9 @@ from textual.app import App, ComposeResult
from textual.containers import ScrollableContainer from textual.containers import ScrollableContainer
from textual.widgets import Footer, Header, Static from textual.widgets import Footer, Header, Static
import typer import typer
from websocket import create_connection
from learn_sql_model.cli.common import verbose_callback from learn_sql_model.cli.common import verbose_callback
from learn_sql_model.models.hero import Heros
dashboard_app = typer.Typer() dashboard_app = typer.Typer()
@ -24,16 +24,8 @@ class HeroName(Static):
"""A stopwatch widget.""" """A stopwatch widget."""
class DashboardApp(App): class HerosDisplay(Static):
"""A Textual app to manage stopwatches.""" """A stopwatch widget."""
BINDINGS = [("d", "toggle_dark", "Toggle dark mode")]
def compose(self) -> ComposeResult:
"""Create child widgets for the app."""
yield Header()
yield Footer()
yield ScrollableContainer(*[HeroName(hero.name) for hero in Heros.list().heros])
@property @property
def ws(self): def ws(self):
@ -48,6 +40,24 @@ class DashboardApp(App):
connect() connect()
return self._ws return self._ws
def compose(self) -> ComposeResult:
"""Create child widgets of a stopwatch."""
for hero in self.heros:
yield HeroName(hero.name)
class DashboardApp(App):
"""A Textual app to manage stopwatches."""
BINDINGS = [("d", "toggle_dark", "Toggle dark mode")]
# heros = reactive(Heros.list())
def compose(self) -> ComposeResult:
"""Create child widgets for the app."""
yield Header()
yield Footer()
yield ScrollableContainer(HerosDisplay())
def action_toggle_dark(self) -> None: def action_toggle_dark(self) -> None:
"""An action to toggle dark mode.""" """An action to toggle dark mode."""
self.dark = not self.dark self.dark = not self.dark