chore: modernize typing imports

This commit is contained in:
banteg
2025-12-29 03:17:19 +04:00
parent e4ef1b85d3
commit db5c0c824b
3 changed files with 51 additions and 50 deletions
@@ -14,7 +14,8 @@ import threading
import time import time
import logging import logging
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import Any, Callable, Dict, Optional, Tuple from typing import Any
from collections.abc import Callable
import typer import typer
@@ -34,10 +35,10 @@ def log(msg: str) -> None:
log_debug(line) log_debug(line)
_file_logger: Optional[logging.Logger] = None _file_logger: logging.Logger | None = None
def setup_file_logger(path: Optional[str]) -> None: def setup_file_logger(path: str | None) -> None:
global _file_logger global _file_logger
if not path: if not path:
return return
@@ -56,7 +57,7 @@ def log_debug(msg: str) -> None:
_file_logger.debug(msg) _file_logger.debug(msg)
def _one_line(text: Optional[str]) -> str: def _one_line(text: str | None) -> str:
if text is None: if text is None:
return "None" return "None"
return text.replace("\r", "\\r").replace("\n", "\\n") return text.replace("\r", "\\r").replace("\n", "\\n")
@@ -77,9 +78,9 @@ def _send_markdown(
*, *,
chat_id: int, chat_id: int,
text: str, text: str,
reply_to_message_id: Optional[int] = None, reply_to_message_id: int | None = None,
disable_notification: bool = False, disable_notification: bool = False,
) -> Dict[str, Any]: ) -> dict[str, Any]:
rendered_text, entities = render_markdown(text) rendered_text, entities = render_markdown(text)
if len(rendered_text) > TELEGRAM_MARKDOWN_LIMIT: if len(rendered_text) > TELEGRAM_MARKDOWN_LIMIT:
sep = "\n" + ELLIPSIS + "\n" sep = "\n" + ELLIPSIS + "\n"
@@ -104,8 +105,8 @@ class ProgressEditor:
chat_id: int, chat_id: int,
message_id: int, message_id: int,
edit_every_s: float, edit_every_s: float,
initial_text: Optional[str] = None, initial_text: str | None = None,
initial_entities: Optional[list[dict[str, Any]]] = None, initial_entities: list[dict[str, Any]] | None = None,
) -> None: ) -> None:
self.bot = bot self.bot = bot
self.chat_id = chat_id self.chat_id = chat_id
@@ -113,8 +114,8 @@ class ProgressEditor:
self.edit_every_s = edit_every_s self.edit_every_s = edit_every_s
self._lock = threading.Lock() self._lock = threading.Lock()
self._pending: Optional[tuple[str, Optional[list[dict[str, Any]]]]] = None self._pending: tuple[str, list[dict[str, Any]] | None] | None = None
self._last_sent: Optional[tuple[str, Optional[list[dict[str, Any]]]]] = None self._last_sent: tuple[str, list[dict[str, Any]] | None] | None = None
self._last_edit_at = 0.0 self._last_edit_at = 0.0
if initial_text is not None: if initial_text is not None:
@@ -125,7 +126,7 @@ class ProgressEditor:
self._thread = threading.Thread(target=self._run, daemon=True) self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start() self._thread.start()
def set(self, text: str, entities: Optional[list[dict[str, Any]]] = None) -> None: def set(self, text: str, entities: list[dict[str, Any]] | None = None) -> None:
text = _clamp_tg_text(text) text = _clamp_tg_text(text)
with self._lock: with self._lock:
self._pending = (text, entities) self._pending = (text, entities)
@@ -139,7 +140,7 @@ class ProgressEditor:
self._stop.set() self._stop.set()
self._thread.join(timeout=1.0) self._thread.join(timeout=1.0)
def _edit(self, text: str, entities: Optional[list[dict[str, Any]]]) -> None: def _edit(self, text: str, entities: list[dict[str, Any]] | None) -> None:
try: try:
self.bot.edit_message_text( self.bot.edit_message_text(
chat_id=self.chat_id, chat_id=self.chat_id,
@@ -161,7 +162,7 @@ class ProgressEditor:
def _run(self) -> None: def _run(self) -> None:
while not self._stop.is_set(): while not self._stop.is_set():
to_send: Optional[tuple[str, Optional[list[dict[str, Any]]]]] = None to_send: tuple[str, list[dict[str, Any]] | None] | None = None
now = time.monotonic() now = time.monotonic()
with self._lock: with self._lock:
if self._pending is not None and (now - self._last_edit_at) >= self.edit_every_s: if self._pending is not None and (now - self._last_edit_at) >= self.edit_every_s:
@@ -184,7 +185,7 @@ class CodexExecRunner:
- resume: codex exec --json ... resume <SESSION_ID> - - resume: codex exec --json ... resume <SESSION_ID> -
""" """
def __init__(self, codex_cmd: str, workspace: Optional[str], extra_args: list[str]) -> None: def __init__(self, codex_cmd: str, workspace: str | None, extra_args: list[str]) -> None:
self.codex_cmd = codex_cmd self.codex_cmd = codex_cmd
self.workspace = workspace self.workspace = workspace
self.extra_args = extra_args self.extra_args = extra_args
@@ -202,9 +203,9 @@ class CodexExecRunner:
def run( def run(
self, self,
prompt: str, prompt: str,
session_id: Optional[str], session_id: str | None,
on_event: Optional[Callable[[Dict[str, Any]], None]] = None, on_event: Callable[[dict[str, Any]], None] | None = None,
) -> Tuple[str, str, bool]: ) -> tuple[str, str, bool]:
""" """
Returns (session_id, final_agent_message_text) Returns (session_id, final_agent_message_text)
""" """
@@ -246,8 +247,8 @@ class CodexExecRunner:
t = threading.Thread(target=_drain_stderr, daemon=True) t = threading.Thread(target=_drain_stderr, daemon=True)
t.start() t.start()
found_session: Optional[str] = session_id found_session: str | None = session_id
last_agent_text: Optional[str] = None last_agent_text: str | None = None
saw_agent_message = False saw_agent_message = False
cli_last_turn = None cli_last_turn = None
@@ -296,9 +297,9 @@ class CodexExecRunner:
def run_serialized( def run_serialized(
self, self,
prompt: str, prompt: str,
session_id: Optional[str], session_id: str | None,
on_event: Optional[Callable[[Dict[str, Any]], None]] = None, on_event: Callable[[dict[str, Any]], None] | None = None,
) -> Tuple[str, str, bool]: ) -> tuple[str, str, bool]:
""" """
If resuming, serialize per-session. If resuming, serialize per-session.
""" """
@@ -334,17 +335,17 @@ def run(
"--ignore-backlog/--process-backlog", "--ignore-backlog/--process-backlog",
help="Skip pending Telegram updates that arrived before startup.", help="Skip pending Telegram updates that arrived before startup.",
), ),
log_file: Optional[str] = typer.Option( log_file: str | None = typer.Option(
"exec_bridge.log", "exec_bridge.log",
"--log-file", "--log-file",
help="Write detailed debug logs to this file (set to empty to disable).", help="Write detailed debug logs to this file (set to empty to disable).",
), ),
workdir: Optional[str] = typer.Option( workdir: str | None = typer.Option(
None, None,
"--workdir", "--workdir",
help="Override codex workspace (--cd) for this exec-bridge run.", help="Override codex workspace (--cd) for this exec-bridge run.",
), ),
model: Optional[str] = typer.Option( model: str | None = typer.Option(
None, None,
"--model", "--model",
help="Codex model to pass to `codex exec`.", help="Codex model to pass to `codex exec`.",
@@ -402,7 +403,7 @@ def run(
max_workers = config.get("max_workers") max_workers = config.get("max_workers")
pool = ThreadPoolExecutor(max_workers=max_workers or 4) pool = ThreadPoolExecutor(max_workers=max_workers or 4)
offset: Optional[int] = None offset: int | None = None
ignore_backlog = bool(ignore_backlog) ignore_backlog = bool(ignore_backlog)
if ignore_backlog: if ignore_backlog:
@@ -427,7 +428,7 @@ def run(
else: else:
log("[startup] no chat_id configured; skipping startup message") log("[startup] no chat_id configured; skipping startup message")
def handle(chat_id: int, user_msg_id: int, text: str, resume_session: Optional[str]) -> None: def handle(chat_id: int, user_msg_id: int, text: str, resume_session: str | None) -> None:
log( log(
"[handle] start " "[handle] start "
f"chat_id={chat_id} user_msg_id={user_msg_id} resume_session={resume_session!r}" f"chat_id={chat_id} user_msg_id={user_msg_id} resume_session={resume_session!r}"
@@ -441,11 +442,11 @@ def run(
loud_final = final_notify loud_final = final_notify
started_at = time.monotonic() started_at = time.monotonic()
session_box: dict[str, Optional[str]] = {"id": resume_session} session_box: dict[str, str | None] = {"id": resume_session}
progress_renderer = ExecProgressRenderer(max_actions=5) progress_renderer = ExecProgressRenderer(max_actions=5)
progress_id: Optional[int] = None progress_id: int | None = None
progress: Optional[ProgressEditor] = None progress: ProgressEditor | None = None
try: try:
initial_text = progress_renderer.render_progress(0.0) initial_text = progress_renderer.render_progress(0.0)
initial_rendered, initial_entities = render_markdown(initial_text) initial_rendered, initial_entities = render_markdown(initial_text)
@@ -472,7 +473,7 @@ def run(
initial_entities=initial_entities or None, initial_entities=initial_entities or None,
) )
def on_event(evt: Dict[str, Any]) -> None: def on_event(evt: dict[str, Any]) -> None:
event_type = evt.get("type") event_type = evt.get("type")
item = evt.get("item") or {} item = evt.get("item") or {}
log_debug( log_debug(
@@ -609,7 +610,7 @@ def run(
r"(?i)\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b" r"(?i)\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b"
) )
def _extract_session_id(value: Optional[str]) -> Optional[str]: def _extract_session_id(value: str | None) -> str | None:
if not value: if not value:
return None return None
m = uuid_re.search(value) m = uuid_re.search(value)
@@ -1,20 +1,20 @@
from __future__ import annotations from __future__ import annotations
import re import re
from typing import Any, Dict, List, Tuple from typing import Any
from markdown_it import MarkdownIt from markdown_it import MarkdownIt
from sulguk import transform_html from sulguk import transform_html
def render_markdown(md: str) -> Tuple[str, List[Dict[str, Any]]]: def render_markdown(md: str) -> tuple[str, list[dict[str, Any]]]:
html = MarkdownIt("commonmark", {"html": False}).render(md or "") html = MarkdownIt("commonmark", {"html": False}).render(md or "")
rendered = transform_html(html) rendered = transform_html(html)
text = re.sub("(?m)^(\\s*)\u2022", r"\1-", rendered.text) text = re.sub("(?m)^(\\s*)\u2022", r"\1-", rendered.text)
# FIX: Telegram requires MessageEntity.language (if present) to be a String. # FIX: Telegram requires MessageEntity.language (if present) to be a String.
entities: List[Dict[str, Any]] = [] entities: list[dict[str, Any]] = []
for e in rendered.entities: for e in rendered.entities:
d = dict(e) d = dict(e)
if "language" in d and not isinstance(d["language"], str): if "language" in d and not isinstance(d["language"], str):
@@ -3,7 +3,7 @@ from __future__ import annotations
import json import json
import urllib.error import urllib.error
import urllib.request import urllib.request
from typing import Any, Dict, List, Optional from typing import Any
class TelegramClient: class TelegramClient:
""" """
@@ -16,7 +16,7 @@ class TelegramClient:
self._base = f"https://api.telegram.org/bot{token}" self._base = f"https://api.telegram.org/bot{token}"
self._timeout_s = timeout_s self._timeout_s = timeout_s
def _call(self, method: str, params: Dict[str, Any]) -> Any: def _call(self, method: str, params: dict[str, Any]) -> Any:
url = f"{self._base}/{method}" url = f"{self._base}/{method}"
data = json.dumps(params).encode("utf-8") data = json.dumps(params).encode("utf-8")
req = urllib.request.Request( req = urllib.request.Request(
@@ -40,11 +40,11 @@ class TelegramClient:
def get_updates( def get_updates(
self, self,
offset: Optional[int], offset: int | None,
timeout_s: int = 50, timeout_s: int = 50,
allowed_updates: Optional[List[str]] = None, allowed_updates: list[str] | None = None,
) -> List[Dict[str, Any]]: ) -> list[dict[str, Any]]:
params: Dict[str, Any] = {"timeout": timeout_s} params: dict[str, Any] = {"timeout": timeout_s}
if offset is not None: if offset is not None:
params["offset"] = offset params["offset"] = offset
if allowed_updates is not None: if allowed_updates is not None:
@@ -55,11 +55,11 @@ class TelegramClient:
self, self,
chat_id: int, chat_id: int,
text: str, text: str,
reply_to_message_id: Optional[int] = None, reply_to_message_id: int | None = None,
disable_notification: Optional[bool] = False, disable_notification: bool | None = False,
entities: Optional[List[Dict[str, Any]]] = None, entities: list[dict[str, Any]] | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
params: Dict[str, Any] = { params: dict[str, Any] = {
"chat_id": chat_id, "chat_id": chat_id,
"text": text, "text": text,
} }
@@ -76,9 +76,9 @@ class TelegramClient:
chat_id: int, chat_id: int,
message_id: int, message_id: int,
text: str, text: str,
entities: Optional[List[Dict[str, Any]]] = None, entities: list[dict[str, Any]] | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
params: Dict[str, Any] = { params: dict[str, Any] = {
"chat_id": chat_id, "chat_id": chat_id,
"message_id": message_id, "message_id": message_id,
"text": text, "text": text,
@@ -88,7 +88,7 @@ class TelegramClient:
return self._call("editMessageText", params) return self._call("editMessageText", params)
def delete_message(self, chat_id: int, message_id: int) -> bool: def delete_message(self, chat_id: int, message_id: int) -> bool:
params: Dict[str, Any] = { params: dict[str, Any] = {
"chat_id": chat_id, "chat_id": chat_id,
"message_id": message_id, "message_id": message_id,
} }