Skip to content

Commit 983e484

Browse files
committed
Add example of routing.
1 parent 4b9caad commit 983e484

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

example/routing.py

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python
2+
3+
import asyncio
4+
import datetime
5+
import time
6+
import zoneinfo
7+
8+
from websockets.asyncio.router import route
9+
from websockets.exceptions import ConnectionClosed
10+
from werkzeug.routing import BaseConverter, Map, Rule, ValidationError
11+
12+
13+
async def clock(websocket, tzinfo):
14+
"""Send the current time in the given timezone every second."""
15+
loop = asyncio.get_running_loop()
16+
loop_offset = (loop.time() - time.time()) % 1
17+
try:
18+
while True:
19+
# Sleep until the next second according to the wall clock.
20+
await asyncio.sleep(1 - (loop.time() - loop_offset) % 1)
21+
now = datetime.datetime.now(tzinfo).replace(microsecond=0)
22+
await websocket.send(now.isoformat())
23+
except ConnectionClosed:
24+
return
25+
26+
27+
async def alarm(websocket, alarm_at, tzinfo):
28+
"""Send the alarm time in the given timezone when it is reached."""
29+
alarm_at = alarm_at.replace(tzinfo=tzinfo)
30+
now = datetime.datetime.now(tz=datetime.timezone.utc)
31+
32+
try:
33+
async with asyncio.timeout((alarm_at - now).total_seconds()):
34+
await websocket.wait_closed()
35+
except asyncio.TimeoutError:
36+
try:
37+
await websocket.send(alarm_at.isoformat())
38+
except ConnectionClosed:
39+
return
40+
41+
42+
async def timer(websocket, alarm_after):
43+
"""Send the remaining time until the alarm time every second."""
44+
alarm_at = datetime.datetime.now(tz=datetime.timezone.utc) + alarm_after
45+
loop = asyncio.get_running_loop()
46+
loop_offset = (loop.time() - time.time() + alarm_at.timestamp()) % 1
47+
48+
try:
49+
while alarm_after.total_seconds() > 0:
50+
# Sleep until the next second as a delta to the alarm time.
51+
await asyncio.sleep(1 - (loop.time() - loop_offset) % 1)
52+
alarm_after = alarm_at - datetime.datetime.now(tz=datetime.timezone.utc)
53+
# Round up to the next second.
54+
alarm_after += datetime.timedelta(
55+
seconds=1,
56+
microseconds=-alarm_after.microseconds,
57+
)
58+
await websocket.send(format_timedelta(alarm_after))
59+
except ConnectionClosed:
60+
return
61+
62+
63+
class ZoneInfoConverter(BaseConverter):
64+
regex = r"[A-Za-z0-9_/+-]+"
65+
66+
def to_python(self, value):
67+
try:
68+
return zoneinfo.ZoneInfo(value)
69+
except zoneinfo.ZoneInfoNotFoundError:
70+
raise ValidationError
71+
72+
def to_url(self, value):
73+
return value.key
74+
75+
76+
class DateTimeConverter(BaseConverter):
77+
regex = r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{3})?"
78+
79+
def to_python(self, value):
80+
try:
81+
return datetime.datetime.fromisoformat(value)
82+
except ValueError:
83+
raise ValidationError
84+
85+
def to_url(self, value):
86+
return value.isoformat()
87+
88+
89+
class TimeDeltaConverter(BaseConverter):
90+
regex = r"[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{3}(?:[0-9]{3})?)?"
91+
92+
def to_python(self, value):
93+
return datetime.timedelta(
94+
hours=int(value[0:2]),
95+
minutes=int(value[3:5]),
96+
seconds=int(value[6:8]),
97+
milliseconds=int(value[9:12]) if len(value) == 12 else 0,
98+
microseconds=int(value[9:15]) if len(value) == 15 else 0,
99+
)
100+
101+
def to_url(self, value):
102+
return format_timedelta(value)
103+
104+
105+
def format_timedelta(delta):
106+
assert 0 <= delta.seconds < 86400
107+
hours = delta.seconds // 3600
108+
minutes = (delta.seconds % 3600) // 60
109+
seconds = delta.seconds % 60
110+
if delta.microseconds:
111+
return f"{hours:02d}:{minutes:02d}:{seconds:02d}.{delta.microseconds:06d}"
112+
else:
113+
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
114+
115+
116+
url_map = Map(
117+
[
118+
Rule(
119+
"/",
120+
redirect_to="/clock",
121+
),
122+
Rule(
123+
"/clock",
124+
defaults={"tzinfo": datetime.timezone.utc},
125+
endpoint=clock,
126+
),
127+
Rule(
128+
"/clock/<tzinfo:tzinfo>",
129+
endpoint=clock,
130+
),
131+
Rule(
132+
"/alarm/<datetime:alarm_at>/<tzinfo:tzinfo>",
133+
endpoint=alarm,
134+
),
135+
Rule(
136+
"/timer/<timedelta:alarm_after>",
137+
endpoint=timer,
138+
),
139+
],
140+
converters={
141+
"tzinfo": ZoneInfoConverter,
142+
"datetime": DateTimeConverter,
143+
"timedelta": TimeDeltaConverter,
144+
},
145+
)
146+
147+
148+
async def main():
149+
async with route(url_map, "localhost", 8888):
150+
await asyncio.get_running_loop().create_future() # run forever
151+
152+
153+
if __name__ == "__main__":
154+
asyncio.run(main())

0 commit comments

Comments
 (0)