Skip to content

Commit 324b88e

Browse files
chore: Audit code for missing type hints and redundant comments
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent b27ff08 commit 324b88e

5 files changed

Lines changed: 35 additions & 86 deletions

File tree

src/rendezqueue/cli.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import sys
33
import threading
44
import time
5+
from typing import List
56
from .client import RendezqueueClient
67

7-
def main():
8+
def main() -> None:
89
parser = argparse.ArgumentParser(description="Rendezqueue Client")
910
parser.add_argument("--url", required=True, help="URL of the Rendezqueue server")
1011
parser.add_argument("--key", required=True, help="Session key")
@@ -14,7 +15,7 @@ def main():
1415

1516
stop_event = threading.Event()
1617

17-
def on_data(values):
18+
def on_data(values: List[bytes]) -> None:
1819
for v in values:
1920
try:
2021
# Try to decode as utf-8, fallback to repr or raw bytes
@@ -23,11 +24,8 @@ def on_data(values):
2324
print(v)
2425
sys.stdout.flush()
2526

26-
def on_error(e):
27+
def on_error(e: Exception) -> None:
2728
print(f"Error: {e}", file=sys.stderr)
28-
# Maybe we should exit on error?
29-
# But connection errors might be transient.
30-
# If it's a fatal error, we might want to stop.
3129

3230
client = RendezqueueClient(
3331
url=args.url,
@@ -41,7 +39,7 @@ def on_error(e):
4139
client.start()
4240

4341
# Reader thread
44-
def read_stdin():
42+
def read_stdin() -> None:
4543
try:
4644
for line in sys.stdin:
4745
if stop_event.is_set():
@@ -53,11 +51,6 @@ def read_stdin():
5351
# stdin closed or error
5452
pass
5553
finally:
56-
# If stdin closes, we might want to stop eventually?
57-
# Or keep running to receive?
58-
# A typical chat client exits when stdin closes.
59-
# However, for rendezqueue, we might want to wait for a response.
60-
# So we don't stop automatically on EOF.
6154
pass
6255

6356
input_thread = threading.Thread(target=read_stdin, daemon=True)

src/rendezqueue/client.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
import time
88
import urllib.request
99
import urllib.error
10-
from typing import Callable, Optional, List
10+
from typing import Callable, Optional, List, Dict, Any
1111

1212
logger = logging.getLogger(__name__)
1313

1414
class RendezqueueClient:
15-
def __init__(self, url: str, key: str, hue: str, on_data: Optional[Callable[[List[bytes]], None]] = None, on_error: Optional[Callable[[Exception], None]] = None, poll_interval_ms: int = 2000):
15+
def __init__(self, url: str, key: str, hue: str, on_data: Optional[Callable[[List[bytes]], None]] = None, on_error: Optional[Callable[[Exception], None]] = None, poll_interval_ms: int = 2000) -> None:
1616
self.url = url
1717
self.key = key
1818
self.hue = hue
@@ -23,44 +23,42 @@ def __init__(self, url: str, key: str, hue: str, on_data: Optional[Callable[[Lis
2323
self.sid_counter = 1
2424
self.sid = self._generate_sid()
2525
self.offset = 0
26-
self.outgoing_queue = []
26+
self.outgoing_queue: List[bytes] = []
2727
self.lock = threading.Lock()
2828

2929
self.is_polling = False
3030
self.is_stopped = True
31-
self.poll_thread = None
31+
self.poll_thread: Optional[threading.Thread] = None
3232

33-
def _generate_sid(self):
33+
def _generate_sid(self) -> str:
3434
random_part = ''.join(random.choices(string.ascii_lowercase + string.digits, k=7))
3535
return f"{self.hue}-{self.sid_counter}-{random_part}"
3636

37-
def start(self):
37+
def start(self) -> None:
3838
if not self.is_stopped:
3939
return
4040
self.is_stopped = False
4141
self.poll_thread = threading.Thread(target=self._poll_loop, daemon=True)
4242
self.poll_thread.start()
4343

44-
def stop(self):
44+
def stop(self) -> None:
4545
if self.is_stopped:
4646
return
4747
self.is_stopped = True
48-
# We don't join here because stop might be called from within a callback
49-
# which might be running in the poll thread (if on_data is synchronous)
5048

51-
def send(self, value):
49+
def send(self, value: Any) -> None:
5250
if isinstance(value, str):
5351
value = value.encode('utf-8')
5452
with self.lock:
5553
self.outgoing_queue.append(value)
5654

57-
def _start_new_session(self):
55+
def _start_new_session(self) -> None:
5856
with self.lock:
5957
self.sid_counter += 1
6058
self.sid = self._generate_sid()
6159
self.offset = 0
6260

63-
def _poll_loop(self):
61+
def _poll_loop(self) -> None:
6462
# Initial poll
6563
self._poll()
6664
while not self.is_stopped:
@@ -69,7 +67,7 @@ def _poll_loop(self):
6967
break
7068
self._poll()
7169

72-
def _poll(self):
70+
def _poll(self) -> None:
7371
if self.is_polling:
7472
return
7573
self.is_polling = True
@@ -145,7 +143,7 @@ def _poll(self):
145143
finally:
146144
self.is_polling = False
147145

148-
def _decode_response(self, msg):
146+
def _decode_response(self, msg: Dict[str, Any]) -> Dict[str, Any]:
149147
b64_flags = msg.get('b64', 0)
150148
if b64_flags & 4:
151149
msg['key'] = self._b64decode_padded(msg['key']).decode('utf-8')
@@ -155,7 +153,7 @@ def _decode_response(self, msg):
155153
msg['values'] = [self._b64decode_padded(v) for v in msg['values']]
156154
return msg
157155

158-
def _b64decode_padded(self, s):
156+
def _b64decode_padded(self, s: str) -> bytes:
159157
missing_padding = len(s) % 4
160158
if missing_padding:
161159
s += '=' * (4 - missing_padding)

src/rendezqueue/impl.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def inplace_decode_tryswap_message(msg: Dict[str, Any]) -> str:
5858
if msg['b64'] & 1:
5959
msg['values'] = [atob(v) for v in msg['values']]
6060
except Exception:
61-
return "b64_decode_error" # Custom error, JS might just crash or throw
61+
return "b64_decode_error"
6262

6363
return ""
6464

@@ -82,19 +82,17 @@ def inplace_encode_tryswap_message(msg: Dict[str, Any]) -> None:
8282
del msg['ttl']
8383
if 'offset' in msg and msg['offset'] == 0:
8484
del msg['offset']
85-
# If values is None, remove it (TrySwapResponse handles optional)
8685
if 'values' in msg and msg['values'] is None:
8786
del msg['values']
8887

8988
class RendezqueueImpl:
90-
def __init__(self):
89+
def __init__(self) -> None:
9190
self.swapstore = SwapStore()
9291

9392
def tryswap(self, msg: Any, now_ms: Optional[float] = None) -> Union[int, TrySwapResponse]:
9493
if not msg:
9594
return 400
9695

97-
# We assume msg is a dict here, validated by inplace_decode... but we should be careful
9896
if not isinstance(msg, dict):
9997
return 400
10098

@@ -114,47 +112,33 @@ def tryswap(self, msg: Any, now_ms: Optional[float] = None) -> Union[int, TrySwa
114112

115113
if now_ms is None:
116114
now_ms = time.time() * 1000
117-
if now_ms == 0: # unlikely but matches JS
115+
if now_ms == 0:
118116
return 500
119-
now_ms = int(now_ms) # JS uses Math.floor
117+
now_ms = int(now_ms)
120118

121119
result = self.swapstore.tryswap(
122120
key, sid, offset, values,
123121
now_ms, msg.get('ttl', 0),
124122
)
125123

126-
if not isinstance(result, int):
127-
# It's a TrySwapResponse object. We need to convert it to dict for encoding
128-
# or wrap it.
129-
# But the caller expects to encode it.
130-
# Python dataclass to dict:
131-
# We can use vars(result) or similar
132-
# But we need to preserve b64 from request
133-
pass
134-
135124
return result
136125

137126
def tryswap_string(self, request_text: str) -> Union[int, str]:
138-
msg = None
127+
msg: Optional[Dict[str, Any]] = None
139128
try:
140129
msg = json.loads(request_text)
141130
e = inplace_decode_tryswap_message(msg)
142131
if e:
143-
# In JS: throw e
144-
# Here we can return error code or message?
145-
# The JS code catches 'e' and logs it, then sets msg = null.
146-
# So if decoding fails, msg is null.
147132
raise Exception(e)
148-
except Exception as e:
149-
# print(e) # Optional logging
133+
except Exception:
150134
msg = None
151135

152136
result = self.tryswap(msg)
153137
if isinstance(result, int):
154138
return result
155139

156140
# Convert dataclass to dict
157-
res_dict = {
141+
res_dict: Dict[str, Any] = {
158142
'key': result.key,
159143
'sid': result.sid,
160144
'offset': result.offset,

src/rendezqueue/server.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
import argparse
44
from urllib.parse import urlparse
55
from rendezqueue.impl import RendezqueueImpl
6-
from typing import cast
6+
from typing import cast, Tuple, Any
77

88
class RendezqueueServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
9-
def __init__(self, server_address, RequestHandlerClass, impl, http_path="/"):
9+
def __init__(self, server_address: Tuple[str, int], RequestHandlerClass: Any, impl: RendezqueueImpl, http_path: str = "/") -> None:
1010
self.impl = impl
1111
self.http_path = http_path
1212
super().__init__(server_address, RequestHandlerClass)
1313

1414
class RendezqueueHandler(http.server.BaseHTTPRequestHandler):
15-
def do_OPTIONS(self):
15+
def do_OPTIONS(self) -> None:
1616
self.send_response(204)
1717
if "Access-Control-Request-Headers" in self.headers:
1818
self.send_header("Access-Control-Allow-Headers", self.headers["Access-Control-Request-Headers"])
@@ -22,8 +22,7 @@ def do_OPTIONS(self):
2222
self.send_header("Access-Control-Allow-Origin", self.headers["Origin"])
2323
self.end_headers()
2424

25-
def do_POST(self):
26-
# Cast self.server to RendezqueueServer to access custom attributes
25+
def do_POST(self) -> None:
2726
server = cast(RendezqueueServer, self.server)
2827

2928
parsed_path = urlparse(self.path)
@@ -53,14 +52,13 @@ def do_POST(self):
5352
self.end_headers()
5453
self.wfile.write(result.encode("utf-8"))
5554

56-
def run():
55+
def run() -> None:
5756
parser = argparse.ArgumentParser(description="Rendezqueue Server")
5857
parser.add_argument("--http_host", default="127.0.0.1", help="Host to bind to")
5958
parser.add_argument("--http_port", type=int, default=0, help="Port to bind to (0 for random)")
6059
parser.add_argument("--http_path", default="/", help="Path for RPC")
6160
parser.add_argument("--port_filepath", help="File to write the port number to")
6261

63-
# Allow unknown args to be ignored (e.g. for testing or future flags)
6462
args, unknown = parser.parse_known_args()
6563

6664
impl = RendezqueueImpl()
@@ -72,8 +70,6 @@ def run():
7270
http_path=args.http_path
7371
)
7472

75-
# server_address might be (host, port) or more depending on family
76-
# For AF_INET it is (host, port)
7773
host, port = server.server_address[:2]
7874
print(f"Server running at http://{host}:{port}{args.http_path}")
7975

src/rendezqueue/swapstore.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@ class TrySwapResponse:
2424
ttl: Optional[int] = None
2525

2626
class SwapStore:
27-
def __init__(self):
27+
def __init__(self) -> None:
2828
# key -> UnmatchedOffer
2929
self.unmatched_offer_map: Dict[str, UnmatchedOffer] = {}
3030
# key -> sid -> SwappedAnswer
3131
self.swapped_answer_multimap: Dict[str, Dict[str, SwappedAnswer]] = {}
3232
self.ttl = MAX_TTL_SECONDS
3333

3434
def expire_swapped_answers(self, key: str, now_ms: float) -> bool:
35-
"""Presumably, data was exchanged."""
3635
answer_map = self.swapped_answer_multimap.get(key)
3736
if not answer_map:
3837
return True
@@ -52,7 +51,6 @@ def expire_swapped_answers(self, key: str, now_ms: float) -> bool:
5251
return False
5352

5453
def expire_unmatched_offers(self, now_ms: float) -> None:
55-
"""No takers."""
5654
expiring_offers = []
5755
# Python dicts maintain insertion order, allowing this logic to work like JS Map
5856
for key, v in self.unmatched_offer_map.items():
@@ -68,16 +66,11 @@ def expire_unmatched_offers(self, now_ms: float) -> None:
6866
@staticmethod
6967
def matches_original(original_values: List[str], offset: int, values: List[str]) -> bool:
7068
if len(original_values) < offset:
71-
return False # Too far ahead. Out of place.
69+
return False
7270
if len(original_values) > offset + len(values):
73-
return False # Too short. Out of place.
71+
return False
7472

7573
original_slice = original_values[offset:]
76-
# Compare element by element
77-
# JS logic: original_slice.every((v, i) => values[i] == v)
78-
# This implies values must match original_slice where they overlap.
79-
# Since we already checked that original_values isn't longer than offset+values,
80-
# we know values covers original_slice entirely (or extends it).
8174

8275
for i, v in enumerate(original_slice):
8376
if values[i] != v:
@@ -111,10 +104,9 @@ def tryswap(self, key: str, sid: str, offset: int, values: List[str], now_ms: fl
111104
offer = self.unmatched_offer_map.get(key)
112105
if offer and offer.expiry_ms == 0:
113106
del self.unmatched_offer_map[key]
114-
offer = None # Fall through to next case.
115-
self.expire_swapped_answers(key, now_ms) # Ensure stuff would expire.
107+
offer = None
108+
self.expire_swapped_answers(key, now_ms)
116109

117-
# We'll need to make an offer.
118110
if not offer:
119111
if offset != 0:
120112
return 404
@@ -131,15 +123,9 @@ def tryswap(self, key: str, sid: str, offset: int, values: List[str], now_ms: fl
131123
ttl=ttl,
132124
)
133125

134-
# Still no match? Might as well reset expiry.
135126
if offer.sid == sid:
136127
original_values = offer.values or []
137128
if SwapStore.matches_original(original_values, offset, values):
138-
# Update offer with new values appended
139-
# Note: original code slices then concats.
140-
# original_values.slice(0, offset).concat(values)
141-
# But match checks that values matches suffix of original_values starting at offset.
142-
# So if matched, we are extending the offer.
143129
new_values = original_values[:offset] + values
144130

145131
self.unmatched_offer_map[key] = UnmatchedOffer(
@@ -156,7 +142,6 @@ def tryswap(self, key: str, sid: str, offset: int, values: List[str], now_ms: fl
156142
return 404
157143

158144
if offset != 0:
159-
# Invalid offset. We had no existing data!
160145
return 404
161146

162147
if answer_map is None:
@@ -169,20 +154,13 @@ def tryswap(self, key: str, sid: str, offset: int, values: List[str], now_ms: fl
169154
expiry_ms=now_ms + ttl * 1000,
170155
)
171156

172-
# offer.sid is guaranteed to exist and offer.sid != sid here.
173157
if offer.sid:
174158
answer_map[offer.sid] = SwappedAnswer(
175159
original_values=offer.values or [],
176160
values=values,
177161
expiry_ms=now_ms + ttl * 1000,
178162
)
179163

180-
# Remove the unmatched offer as it is now matched
181-
# But instead of deleting, we mark it as expired (expiry_ms=0)
182-
# so it can be cleaned up later or overwritten?
183-
# JS code:
184-
# this.unmatched_offer_map.delete(key);
185-
# this.unmatched_offer_map.set(key, { expiry_ms: 0 });
186164
del self.unmatched_offer_map[key]
187165
self.unmatched_offer_map[key] = UnmatchedOffer(expiry_ms=0)
188166

0 commit comments

Comments
 (0)