style: apply ruff format to OCR modules

This commit is contained in:
2026-07-22 01:43:55 -04:00
parent 18d4f60665
commit fdd36e0e1f
5 changed files with 43 additions and 20 deletions
+10 -6
View File
@@ -295,11 +295,15 @@ class OcrClient:
await self._record_quietly(self._quota.mark_progress()) await self._record_quietly(self._quota.mark_progress())
self._feed_outcome(source.name, ok=True) self._feed_outcome(source.name, ok=True)
latency_ms = int((self._now() - started) * 1000) latency_ms = int((self._now() - started) * 1000)
await self._emit(kind, image, source, call_id, started, session_id, parent_call_id, result) await self._emit(
kind, image, source, call_id, started, session_id, parent_call_id, result
)
return _AttemptOutcome(result, source, call_id, latency_ms) return _AttemptOutcome(result, source, call_id, latency_ms)
except (RequestRejectedError, ResultInvalidError) as exc: except (RequestRejectedError, ResultInvalidError) as exc:
await self._gate_on_terminal(exc, entry) await self._gate_on_terminal(exc, entry)
await self._emit(kind, image, source, call_id, started, session_id, parent_call_id, error=exc) await self._emit(
kind, image, source, call_id, started, session_id, parent_call_id, error=exc
)
raise raise
except asyncio.CancelledError: except asyncio.CancelledError:
if entry.is_probe: if entry.is_probe:
@@ -314,7 +318,9 @@ class OcrClient:
reasons[source.name] = reason reasons[source.name] = reason
await self._record_quietly(self._breaker.record_failure(entry, reason, dead)) await self._record_quietly(self._breaker.record_failure(entry, reason, dead))
self._feed_outcome(source.name, ok=False) self._feed_outcome(source.name, ok=False)
await self._emit(kind, image, source, call_id, started, session_id, parent_call_id, error=exc) await self._emit(
kind, image, source, call_id, started, session_id, parent_call_id, error=exc
)
return _FailedAttempt(exc, immediate=dead) return _FailedAttempt(exc, immediate=dead)
finally: finally:
await self._settle_and_release(permit) await self._settle_and_release(permit)
@@ -474,9 +480,7 @@ class OcrClient:
# 严禁静默用 MonkeyOcrTransport 打别家端点(默认值掩盖错误) # 严禁静默用 MonkeyOcrTransport 打别家端点(默认值掩盖错误)
alien = sorted({s.provider for s in sources if s.provider != "monkey"}) alien = sorted({s.provider for s in sources if s.provider != "monkey"})
if alien: if alien:
raise ValueError( raise ValueError(f"OCR 装配仅支持 provider=monkey(D9 其余后端预留未实现): 发现 {alien}")
f"OCR 装配仅支持 provider=monkey(D9 其余后端预留未实现): 发现 {alien}"
)
return cls( return cls(
scope=gw.scope, scope=gw.scope,
sources=sources, sources=sources,
+8 -2
View File
@@ -75,7 +75,11 @@ def _classify_status(
exc: httpx.HTTPStatusError, source_name: str, operation: str exc: httpx.HTTPStatusError, source_name: str, operation: str
) -> TransientError | SourceDeadError | RequestRejectedError: ) -> TransientError | SourceDeadError | RequestRejectedError:
status = exc.response.status_code status = exc.response.status_code
ctx: dict[str, Any] = {"source_name": source_name, "status_code": status, "operation": operation} ctx: dict[str, Any] = {
"source_name": source_name,
"status_code": status,
"operation": operation,
}
message = f"{source_name} OCR {operation} HTTP {status}" message = f"{source_name} OCR {operation} HTTP {status}"
if status >= 500 or status == 429: if status >= 500 or status == 429:
return TransientError(message, **ctx) return TransientError(message, **ctx)
@@ -146,7 +150,9 @@ def _parse_pages(payload: object) -> tuple[list[OcrLayoutElement], list[tuple[fl
return elements, page_sizes return elements, page_sizes
def _parse_middle_json(zip_bytes: bytes) -> tuple[list[OcrLayoutElement], list[tuple[float, float]]]: def _parse_middle_json(
zip_bytes: bytes,
) -> tuple[list[OcrLayoutElement], list[tuple[float, float]]]:
"""ZIP → (elements, page_sizes);一切形态异常归 ResultInvalid(坏图≠坏服务)。 """ZIP → (elements, page_sizes);一切形态异常归 ResultInvalid(坏图≠坏服务)。
数值防御全量下沉自 CHS `_parse_table_result`(invokers.py:437-479), 数值防御全量下沉自 CHS `_parse_table_result`(invokers.py:437-479),
+16 -5
View File
@@ -249,14 +249,20 @@ class TestMiddleJsonDefense:
self._expect_invalid(_zip_bytes(_middle_bytes([page]))) self._expect_invalid(_zip_bytes(_middle_bytes([page])))
def test_bbox_order_invalid(self): def test_bbox_order_invalid(self):
self._expect_invalid(_zip_bytes(_middle_bytes([_page([_block("table", (218, 48, 41, 282))])]))) self._expect_invalid(
_zip_bytes(_middle_bytes([_page([_block("table", (218, 48, 41, 282))])]))
)
def test_bbox_int_degenerate(self): def test_bbox_int_degenerate(self):
# float 合法但 int() 后宽度为零: 专为 CHS shim 的裁剪路径兜底 # float 合法但 int() 后宽度为零: 专为 CHS shim 的裁剪路径兜底
self._expect_invalid(_zip_bytes(_middle_bytes([_page([_block("table", (1.2, 1.2, 1.8, 5))])]))) self._expect_invalid(
_zip_bytes(_middle_bytes([_page([_block("table", (1.2, 1.2, 1.8, 5))])]))
)
def test_bbox_non_finite(self): def test_bbox_non_finite(self):
self._expect_invalid(_zip_bytes(_middle_bytes([_page([_block("table", (1, 2, float("inf"), 4))])]))) self._expect_invalid(
_zip_bytes(_middle_bytes([_page([_block("table", (1, 2, float("inf"), 4))])]))
)
def test_type_missing(self): def test_type_missing(self):
block = {"bbox": [1, 2, 30, 40], "index": 0} block = {"bbox": [1, 2, 30, 40], "index": 0}
@@ -278,8 +284,13 @@ class TestMiddleJsonDefense:
class TestErrorTranslation: class TestErrorTranslation:
@pytest.mark.parametrize( @pytest.mark.parametrize(
("status", "exc_type"), ("status", "exc_type"),
[(502, TransientError), (429, TransientError), (401, SourceDeadError), [
(403, SourceDeadError), (404, RequestRejectedError)], (502, TransientError),
(429, TransientError),
(401, SourceDeadError),
(403, SourceDeadError),
(404, RequestRejectedError),
],
) )
async def test_http_status(self, status, exc_type): async def test_http_status(self, status, exc_type):
t = _transport_for(_routes(text_resp=httpx.Response(status))) t = _transport_for(_routes(text_resp=httpx.Response(status)))
+2 -6
View File
@@ -234,9 +234,7 @@ class TestTerminalOutcomes:
assert client._selector.outcomes == [] # 坏结果 ≠ 坏服务,不喂健康 assert client._selector.outcomes == [] # 坏结果 ≠ 坏服务,不喂健康
async def test_rejected_with_status_counts_no_attempt(self): async def test_rejected_with_status_counts_no_attempt(self):
client, _, gate = _client( client, _, gate = _client([_src()], [RequestRejectedError("parse failed", status_code=200)])
[_src()], [RequestRejectedError("parse failed", status_code=200)]
)
with pytest.raises(RequestRejectedError): with pytest.raises(RequestRejectedError):
await client.parse_layout(b"jpg") await client.parse_layout(b"jpg")
assert gate.successes == [("m1", False)] assert gate.successes == [("m1", False)]
@@ -378,9 +376,7 @@ class TestAssembly:
await client.aclose() await client.aclose()
async def test_non_monkey_provider_rejected(self): async def test_non_monkey_provider_rejected(self):
env = { env = {k.replace("MONKEY", "GLM"): v for k, v in self._ENV.items()}
k.replace("MONKEY", "GLM"): v for k, v in self._ENV.items()
}
with pytest.raises(ValueError, match="monkey"): with pytest.raises(ValueError, match="monkey"):
OcrClient.from_env("OCR", env=env) OcrClient.from_env("OCR", env=env)
+7 -1
View File
@@ -11,6 +11,12 @@ def test_ocr_public_surface_exported():
"""M3 OCR 公共 API 面(设计 §3;transport 结果与 Protocol 不出顶层)。""" """M3 OCR 公共 API 面(设计 §3;transport 结果与 Protocol 不出顶层)。"""
import polygateway import polygateway
for name in ("OcrClient", "OcrSettings", "OcrTextResult", "OcrLayoutResult", "OcrLayoutElement"): for name in (
"OcrClient",
"OcrSettings",
"OcrTextResult",
"OcrLayoutResult",
"OcrLayoutElement",
):
assert hasattr(polygateway, name), name assert hasattr(polygateway, name), name
assert name in polygateway.__all__, name assert name in polygateway.__all__, name