• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pyta-uoft / pyta / 14812056853

02 May 2025 09:28PM UTC coverage: 93.284% (+0.7%) from 92.587%
14812056853

Pull #1167

github

web-flow
Merge 8cb1fd341 into 9ecb04f04
Pull Request #1167: Initialized the Websocket Functionality

56 of 57 new or added lines in 2 files covered. (98.25%)

3431 of 3678 relevant lines covered (93.28%)

17.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.73
/python_ta/reporters/persistent_server.py
1
"""This module defines a HTML server using aiohttp that supports reloading via WebSockets."""
2

3
import asyncio
20✔
4
import logging
20✔
5
import threading
20✔
6
import webbrowser
20✔
7

8
from aiohttp import WSMsgType, web
20✔
9

10
logging.getLogger("aiohttp.access").disabled = True
20✔
11
latest_html: bytes = b"<h1>Loading report...</h1>"
20✔
12
websockets = set()
20✔
13
server_started = False
20✔
14
loop = asyncio.get_event_loop()
20✔
15

16

17
async def handle_report(request):
20✔
18
    """Serve the current HTML content at the root endpoint ('/')."""
19
    return web.Response(body=latest_html, content_type="text/html")
10✔
20

21

22
async def websocket_handler(request):
20✔
23
    """Handle WebSocket connections, tracking clients and listening for errors."""
24
    ws = web.WebSocketResponse()
10✔
25
    await ws.prepare(request)
10✔
26

27
    websockets.add(ws)
10✔
28

29
    try:
10✔
30
        async for msg in ws:
10✔
NEW
31
            if msg.type == WSMsgType.ERROR:
×
32
                print(f"WebSocket error: {ws.exception()}")
2✔
33
    finally:
34
        websockets.remove(ws)
10✔
35
    return ws
10✔
36

37

38
async def update_report(new_html: bytes, port: int):
20✔
39
    """Update the served HTML content and notify connected WebSocket clients to reload."""
40
    global latest_html
41
    latest_html = new_html
10✔
42

43
    active = [ws for ws in websockets if not ws.closed]
10✔
44
    if active:
10✔
45
        for ws in active:
10✔
46
            await ws.send_str("reload")
10✔
47
    else:
48
        webbrowser.open(f"http://localhost:{port}", new=2)
10✔
49

50

51
def start_server_once(initial_html: bytes, port: int = 8000):
20✔
52
    """Start the HTML and WebSocket server if not already running, or update content if running."""
53
    global server_started
54
    if not server_started:
10✔
55
        server_started = True
10✔
56
        loop.create_task(run_server(port))
10✔
57
        loop.create_task(update_report(initial_html, port))
10✔
58
        threading.Thread(target=loop.run_forever, daemon=True).start()
10✔
59
    else:
60
        asyncio.run_coroutine_threadsafe(update_report(initial_html, port), loop)
10✔
61

62

63
async def run_server(port: int):
20✔
64
    """Launch the aiohttp web server on the specified port with routes for HTML and WebSocket."""
65
    app = web.Application()
10✔
66
    app.router.add_get("/", handle_report)
10✔
67
    app.router.add_get("/ws", websocket_handler)
10✔
68

69
    runner = web.AppRunner(app)
10✔
70
    await runner.setup()
10✔
71
    site = web.TCPSite(runner, "localhost", port)
10✔
72
    await site.start()
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc