mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
ci: fix remaining ASYNCXXX linter errors
This commit is contained in:
1 parent
bda3aea496
commit
c57fbf4584
25 files changed
+49
-48
No files matched your search
@@ -108,7 +108,7 @@ async def run(repo: str, out: str):
|
||||
"violations": [{"method": v["method"], "body_keys": sorted(v["body"].keys())} for v in violations],
|
||||
"results": results,
|
||||
}
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
with open(out, "w", encoding="utf-8") as f: # noqa: ASYNC230 -- small local write; a local open() is non-blocking in practice
|
||||
json.dump(summary, f, indent=2)
|
||||
seq = [(e["method"], sorted(e["body"].keys()), e["response"].get("error")) for e in log]
|
||||
for s in seq:
|
||||
|
||||
@@ -1240,7 +1240,7 @@ class QQAdapter(OwnAccessPolicyMixin, BasePlatformAdapter):
|
||||
|
||||
base_url, api_key, model = stt_cfg["base_url"], stt_cfg["api_key"], stt_cfg["model"]
|
||||
try:
|
||||
with open(wav_path, "rb") as f:
|
||||
with open(wav_path, "rb") as f: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
resp = await self._http_client.post(
|
||||
f"{base_url}/audio/transcriptions",
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
|
||||
@@ -535,7 +535,7 @@ class WhatsAppCloudAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter):
|
||||
return None, f"File {os.path.basename(file_path)} is {size} bytes; Cloud API {media_kind} cap is {cap} bytes"
|
||||
mime_type = mime_type or mimetypes.guess_type(file_path)[0] or _DEFAULT_MIME.get(media_kind, "application/octet-stream")
|
||||
try:
|
||||
with open(file_path, "rb") as fh:
|
||||
with open(file_path, "rb") as fh: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
files = {"file": (os.path.basename(file_path), fh, mime_type), "messaging_product": (None, "whatsapp"), "type": (None, mime_type)}
|
||||
resp = await self._http_client.post(self._graph_url("media"), headers=self._auth_headers(json_body=False), files=files)
|
||||
except Exception as exc:
|
||||
|
||||
@@ -1495,7 +1495,7 @@ class GatewayShutdownMixin(GatewaySessionEndMixin):
|
||||
)
|
||||
setsid_bin = shutil.which("setsid")
|
||||
argv = [setsid_bin, "bash", "-lc", shell_cmd] if setsid_bin else ["bash", "-lc", shell_cmd]
|
||||
subprocess.Popen(
|
||||
subprocess.Popen( # noqa: ASYNC220 -- fire-and-forget spawn; Popen returns immediately and callers need the Popen object
|
||||
argv, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
env=GatewayShutdownMixin._restart_watcher_env(), start_new_session=True,
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ Shared helpers are reached via the late-binding seam in :mod:`hermes_cli.web_dep
|
||||
so a test's ``monkeypatch.setattr(<owning module>, "_helper", ...)`` keeps working.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import copy
|
||||
import functools
|
||||
@@ -939,16 +940,16 @@ async def open_profile_terminal_endpoint(name: str):
|
||||
command = _profile_setup_command(name)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
subprocess.Popen(["cmd.exe", "/c", "start", "", command])
|
||||
subprocess.Popen(["cmd.exe", "/c", "start", "", command]) # noqa: ASYNC220 -- fire-and-forget spawn; Popen returns immediately and callers need the Popen object
|
||||
elif sys.platform == "darwin":
|
||||
escaped = command.replace("\\", "\\\\").replace('"', '\\"')
|
||||
subprocess.Popen(["osascript", "-e",
|
||||
subprocess.Popen(["osascript", "-e", # noqa: ASYNC220 -- fire-and-forget spawn; Popen returns immediately and callers need the Popen object
|
||||
f'tell application "Terminal"\nactivate\ndo script "{escaped}"\nend tell'])
|
||||
else:
|
||||
for executable, popen_args in _linux_terminal_commands(command):
|
||||
if subprocess.call(["which", executable], stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL) == 0:
|
||||
subprocess.Popen(popen_args)
|
||||
if await asyncio.to_thread(subprocess.call, ["which", executable], stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL) == 0:
|
||||
subprocess.Popen(popen_args) # noqa: ASYNC220 -- fire-and-forget spawn; Popen returns immediately and callers need the Popen object
|
||||
break
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="No supported terminal emulator found")
|
||||
|
||||
@@ -484,7 +484,7 @@ async def upload_task_attachment(
|
||||
dest_path = _collision_free_path(dest_dir, safe_name) # foo.pdf → foo (1).pdf …
|
||||
total = 0 # stream in chunks with a hard size cap so one upload can't fill the disk
|
||||
try:
|
||||
with open(dest_path, "wb") as out:
|
||||
with open(dest_path, "wb") as out: # noqa: ASYNC230 -- chunked local write of the upload stream; open() is non-blocking in practice
|
||||
while chunk := await file.read(1024 * 1024):
|
||||
total += len(chunk)
|
||||
if total > KANBAN_ATTACHMENT_MAX_BYTES:
|
||||
|
||||
@@ -7209,11 +7209,10 @@ async def _standalone_send(
|
||||
form.add_field("payload_json", payload_json, content_type="application/json")
|
||||
try:
|
||||
for idx, media_path in enumerate(valid_media):
|
||||
with open(media_path, "rb") as fh:
|
||||
form.add_field(
|
||||
f"files[{idx}]", fh.read(),
|
||||
filename=os.path.basename(media_path),
|
||||
)
|
||||
form.add_field(
|
||||
f"files[{idx}]", await asyncio.to_thread(_Path(media_path).read_bytes),
|
||||
filename=os.path.basename(media_path),
|
||||
)
|
||||
async with session.post(thread_url, headers=auth_headers, data=form, **_req_kw) as resp:
|
||||
data, err = await _standalone_response_json_or_error(resp, "Discord forum thread creation error")
|
||||
if err:
|
||||
@@ -7273,7 +7272,7 @@ async def _standalone_send(
|
||||
content_type="application/json",
|
||||
)
|
||||
caption_pending = False
|
||||
with open(media_path, "rb") as f:
|
||||
with open(media_path, "rb") as f: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
form.add_field("files[0]", f, filename=filename)
|
||||
async with session.post(url, headers=auth_headers, data=form, **_req_kw) as resp:
|
||||
data, err = await _standalone_response_json_or_error(resp, "Discord API error")
|
||||
|
||||
@@ -5,6 +5,7 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from agent.i18n import t
|
||||
@@ -293,8 +294,7 @@ class DiscordMediaMixin:
|
||||
if rejected is not None:
|
||||
return rejected
|
||||
reference = self._reply_reference_for_send(reply_to, channel)
|
||||
with open(audio_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
file_data = await asyncio.to_thread(Path(audio_path).read_bytes)
|
||||
# Forum channels reject POST /messages (native voice path too); create a thread post instead.
|
||||
if self._is_forum_parent(channel):
|
||||
forum_file = discord.File(io.BytesIO(file_data), filename=filename)
|
||||
|
||||
@@ -1917,8 +1917,7 @@ class FeishuAdapter(BasePlatformAdapter):
|
||||
return SendResult(success=False, error=f"Image file not found: {image_path}")
|
||||
try:
|
||||
import io as _io
|
||||
with open(image_path, "rb") as f:
|
||||
image_file = _io.BytesIO(f.read()) # lark's MultipartEncoder needs .name and .tell()
|
||||
image_file = _io.BytesIO(await asyncio.to_thread(Path(image_path).read_bytes)) # lark's MultipartEncoder needs .name and .tell()
|
||||
image_file.name = os.path.basename(image_path)
|
||||
body = self._build_image_upload_body(image_type=_FEISHU_IMAGE_UPLOAD_TYPE, image=image_file)
|
||||
request = self._build_image_upload_request(body)
|
||||
@@ -3668,7 +3667,7 @@ class FeishuAdapter(BasePlatformAdapter):
|
||||
)
|
||||
try:
|
||||
duration_ms = self._get_audio_duration_ms(file_path) if upload_file_type == "opus" else 0
|
||||
with open(file_path, "rb") as file_obj:
|
||||
with open(file_path, "rb") as file_obj: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
body = self._build_file_upload_body(
|
||||
file_type=upload_file_type, file_name=display_name, file=file_obj, duration=duration_ms,
|
||||
)
|
||||
|
||||
@@ -622,8 +622,8 @@ async def _standalone_send(pconfig, chat_id: str, message: str, *, thread_id: Op
|
||||
continue
|
||||
form = aiohttp.FormData()
|
||||
form.add_field("channel_id", chat_id) # required so the server can attribute the upload
|
||||
with open(file_path, "rb") as fh:
|
||||
form.add_field("files", fh.read(), filename=os.path.basename(file_path))
|
||||
form.add_field("files", await asyncio.to_thread(Path(file_path).read_bytes),
|
||||
filename=os.path.basename(file_path))
|
||||
async with session.post(f"{base_url}/api/v4/files", data=form, headers=upload_headers,
|
||||
**_req_kw) as upload_resp:
|
||||
if upload_resp.status not in {200, 201}:
|
||||
|
||||
@@ -1049,7 +1049,7 @@ class PhotonAdapter(BasePlatformAdapter):
|
||||
from hermes_cli._subprocess_compat import windows_hide_flags # hide child console on Windows
|
||||
await self._apply_spectrum_patch(windows_hide_flags())
|
||||
try:
|
||||
self._sidecar_proc = subprocess.Popen(
|
||||
self._sidecar_proc = subprocess.Popen( # noqa: ASYNC220 -- persistent sidecar; callers use the Popen pipes and poll()
|
||||
[self._node_bin, str(_sidecar_dir() / "index.mjs")],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env,
|
||||
start_new_session=(sys.platform != "win32"),
|
||||
|
||||
@@ -21,6 +21,7 @@ import re
|
||||
import sys
|
||||
from collections import deque
|
||||
from contextlib import contextmanager, suppress
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterator, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -751,8 +752,8 @@ class TeamsAdapter(BasePlatformAdapter):
|
||||
else:
|
||||
path = source.removeprefix("file://")
|
||||
mime_type = mimetypes.guess_type(path)[0] or default_mime
|
||||
with open(path, "rb") as f:
|
||||
content_url = f"data:{mime_type};base64,{base64.b64encode(f.read()).decode()}"
|
||||
data = await asyncio.to_thread(Path(path).read_bytes)
|
||||
content_url = f"data:{mime_type};base64,{base64.b64encode(data).decode()}"
|
||||
activity = MessageActivityInput().add_attachments(Attachment(content_type=mime_type, content_url=content_url))
|
||||
if caption:
|
||||
activity = activity.add_text(caption)
|
||||
|
||||
@@ -5193,7 +5193,7 @@ class TelegramAdapter(TelegramHeldInboundMixin, BasePlatformAdapter):
|
||||
self.name, os.path.basename(audio_path))
|
||||
# Telegram drops duration for long clips (~5 min+, shows 0:00).
|
||||
_duration_secs = await asyncio.to_thread(_probe_voice_duration_seconds, audio_path)
|
||||
with open(audio_path, "rb") as audio_file:
|
||||
with open(audio_path, "rb") as audio_file: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
ext = os.path.splitext(audio_path)[1].lower()
|
||||
if ext in {".ogg", ".opus"}: # round playable voice bubble
|
||||
msg = await self._send_voice_bubble(audio_file, chat_id, reply_to, metadata, caption, _duration_secs)
|
||||
@@ -5260,7 +5260,7 @@ class TelegramAdapter(TelegramHeldInboundMixin, BasePlatformAdapter):
|
||||
if compressed:
|
||||
temp_paths.append(compressed)
|
||||
local_path = compressed
|
||||
source = open(local_path, "rb")
|
||||
source = open(local_path, "rb") # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
opened_files.append(source)
|
||||
media.append(InputMediaPhoto(media=source, caption=self._caption_1024(alt_text)))
|
||||
if not media:
|
||||
@@ -5341,7 +5341,7 @@ class TelegramAdapter(TelegramHeldInboundMixin, BasePlatformAdapter):
|
||||
try:
|
||||
if not os.path.exists(path):
|
||||
return SendResult(success=False, error=self._missing_media_path_error(label, path))
|
||||
with open(path, "rb") as f:
|
||||
with open(path, "rb") as f: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
msg = await self._send_media(
|
||||
getattr(self._bot, f"send_{media_key}"), chat_id, reply_to, metadata, media_key,
|
||||
reset_media=lambda: f.seek(0), **build_kwargs(f))
|
||||
|
||||
@@ -681,11 +681,11 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter):
|
||||
await asyncio.sleep(1)
|
||||
# Bridge output goes to a log file so QR codes, errors, and reconnection messages survive for troubleshooting.
|
||||
self._bridge_log = self._session_path.parent / "bridge.log"
|
||||
self._bridge_log_fh = bridge_log_fh = open(self._bridge_log, "a", encoding="utf-8")
|
||||
self._bridge_log_fh = bridge_log_fh = open(self._bridge_log, "a", encoding="utf-8") # noqa: ASYNC230 -- long-lived log handle handed to the bridge subprocess
|
||||
node = find_node_executable("node")
|
||||
if node is None:
|
||||
raise RuntimeError("Node.js is no longer available; run `hermes pm install`")
|
||||
self._bridge_process = subprocess.Popen(
|
||||
self._bridge_process = subprocess.Popen( # noqa: ASYNC220 -- persistent bridge; callers use the Popen object (pid, poll)
|
||||
[node, str(bridge_path), "--port", str(self._bridge_port), "--session", str(self._session_path),
|
||||
"--mode", _wenv("WHATSAPP_MODE", "self-chat")], stdin=subprocess.DEVNULL, stdout=bridge_log_fh, stderr=bridge_log_fh, env=self._bridge_env(), **windows_detach_popen_kwargs())
|
||||
_write_bridge_pidfile(self._session_path, self._bridge_process.pid, self._bridge_port)
|
||||
|
||||
+4
-4
@@ -55,11 +55,11 @@ select = [
|
||||
"ASYNC115",
|
||||
"ASYNC116",
|
||||
"ASYNC210",
|
||||
# "ASYNC220",
|
||||
# "ASYNC221",
|
||||
"ASYNC220",
|
||||
"ASYNC221",
|
||||
"ASYNC222",
|
||||
# "ASYNC230",
|
||||
# "ASYNC251",
|
||||
"ASYNC230",
|
||||
"ASYNC251",
|
||||
# "S102",
|
||||
# "S110",
|
||||
# "S112",
|
||||
|
||||
@@ -220,7 +220,7 @@ def _serve_http(server) -> None:
|
||||
await asyncio.sleep(0.01)
|
||||
if port_file:
|
||||
tmp = port_file + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as fh:
|
||||
with open(tmp, "w", encoding="utf-8") as fh: # noqa: ASYNC230 -- small local write; a local open() is non-blocking in practice
|
||||
fh.write(f"{port} {os.getpid()}")
|
||||
os.replace(tmp, port_file)
|
||||
await task
|
||||
|
||||
@@ -742,7 +742,7 @@ class TestDisconnectedAgentReap:
|
||||
|
||||
deadline = time.time() + 1.0
|
||||
while not calls and time.time() < deadline:
|
||||
time.sleep(0.01)
|
||||
await asyncio.sleep(0.01)
|
||||
assert calls == [("run-stop-sess", frozenset(), "api_server_run_stop")]
|
||||
agent.interrupt.assert_called_once()
|
||||
|
||||
|
||||
@@ -2422,7 +2422,7 @@ class TestInboundMediaLocalisation:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk"
|
||||
"+A8AAQUBAScY42YAAAAASUVORK5CYII="
|
||||
)
|
||||
with open(output_path, "wb") as handle:
|
||||
with open(output_path, "wb") as handle: # noqa: ASYNC230 -- test fake writes a tiny fixture file; a local open() is non-blocking in practice
|
||||
handle.write(payload)
|
||||
return 0, "", ""
|
||||
|
||||
@@ -2591,7 +2591,7 @@ class TestInboundMediaLocalisation:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk"
|
||||
"+A8AAQUBAScY42YAAAAASUVORK5CYII="
|
||||
)
|
||||
with open(output_path, "wb") as handle:
|
||||
with open(output_path, "wb") as handle: # noqa: ASYNC230 -- test fake writes a tiny fixture file; a local open() is non-blocking in practice
|
||||
handle.write(payload)
|
||||
return 0, "", ""
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ async def test_blocked_loop_after_expiry_dumps_diagnostics(monkeypatch):
|
||||
# Margin matters: the watchdog thread only dumps if the loop is STILL
|
||||
# blocked when it wakes, and thread wakeup lags under parallel-suite load.
|
||||
# 0.2s (= deadline+grace exactly) flaked in a 40-worker full-suite run.
|
||||
_time.sleep(1.0)
|
||||
_time.sleep(1.0) # noqa: ASYNC251 -- deliberately blocks the loop past the deadline
|
||||
with pytest.raises(_asyncio.TimeoutError):
|
||||
await task
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
@@ -814,8 +815,7 @@ class TestDownloadMedia:
|
||||
assert _os.path.exists(local_path)
|
||||
assert _os.path.basename(local_path).startswith("media_xyz")
|
||||
assert _os.path.basename(local_path).endswith(".jpg")
|
||||
with open(local_path, "rb") as fh:
|
||||
assert fh.read() == b"\xff\xd8\xff\xe0jpegdata"
|
||||
assert await asyncio.to_thread(Path(local_path).read_bytes) == b"\xff\xd8\xff\xe0jpegdata"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_metadata_failure_returns_none(self):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
import pytest
|
||||
@@ -221,7 +222,7 @@ async def test_legacy_channel_connect_pops_marker_on_disconnect(pty_keepalive_ha
|
||||
|
||||
deadline = time.monotonic() + 5.0
|
||||
while "LEGACY63553" in markers and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
await asyncio.sleep(0.01)
|
||||
assert "LEGACY63553" not in markers
|
||||
|
||||
|
||||
@@ -252,7 +253,7 @@ async def test_legacy_marker_dropped_when_cancel_lands_mid_close(pty_keepalive_h
|
||||
# ... and leaving the client context cancelled the handler there.
|
||||
deadline = time.monotonic() + 5.0
|
||||
while "LEGACYCANCEL" in markers and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
await asyncio.sleep(0.01)
|
||||
assert "LEGACYCANCEL" not in markers
|
||||
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ def _start_freezeable_producer(tmp_path, block_s, errors, write_stall_s=1.5):
|
||||
await freeze_evt.wait()
|
||||
freeze_evt.clear()
|
||||
# Synchronous sleep: freezes the entire loop for block_s.
|
||||
time.sleep(block_s)
|
||||
time.sleep(block_s) # noqa: ASYNC251 -- deliberately freezes the loop for block_s
|
||||
|
||||
async def producer() -> None:
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
@@ -228,7 +228,7 @@ class TestRunAsyncWithRunningLoop:
|
||||
# Worker thread must cancel the task (not leak).
|
||||
deadline = _time.time() + 5
|
||||
while not cancel_observed.is_set() and _time.time() < deadline:
|
||||
_time.sleep(0.05)
|
||||
await asyncio.sleep(0.05)
|
||||
assert cancel_observed.is_set(), (
|
||||
"Coroutine never received CancelledError — worker thread leaked "
|
||||
"(ThreadPoolExecutor.cancel() is a no-op on a running future; "
|
||||
|
||||
@@ -219,7 +219,7 @@ async def _telegram_send_one_media(bot, chat_id, media_path, is_voice, *, captio
|
||||
if thumb_path:
|
||||
media_kwargs["thumbnail"] = thumb_path
|
||||
try:
|
||||
with open(media_path, "rb") as f:
|
||||
with open(media_path, "rb") as f: # noqa: ASYNC230 -- file handle is streamed to the upload; a local open() is non-blocking in practice
|
||||
try:
|
||||
return await _telegram_send_media(bot, chat_id, f, ext, is_voice, force_document, **media_kwargs)
|
||||
except Exception as media_err:
|
||||
|
||||
@@ -668,7 +668,7 @@ Write only the summary, starting with "[CONTEXT SUMMARY]:" prefix."""
|
||||
self._print_summary()
|
||||
if self.config.metrics_enabled:
|
||||
metrics_path = output_dir / self.config.metrics_output_file
|
||||
with open(metrics_path, 'w', encoding="utf-8") as f:
|
||||
with open(metrics_path, 'w', encoding="utf-8") as f: # noqa: ASYNC230 -- small local write; a local open() is non-blocking in practice
|
||||
json.dump(self.aggregate_metrics.to_dict(), f, indent=2)
|
||||
console.print(f"\n💾 Metrics saved to {metrics_path}")
|
||||
|
||||
|
||||
Reference in new issue
Block a user