Files
wehub-resource-sync e904b667c6
Build/Publish Develop Docs / deploy (push) Failing after 1s
PaddleOCR Code Style Check / check-code-style (push) Failing after 1s
PaddleOCR PR Tests GPU / detect-changes (push) Failing after 1s
PaddleOCR PR Tests / detect-changes (push) Failing after 1s
PaddleOCR PR Tests GPU / test-pr-gpu (push) Has been cancelled
PaddleOCR PR Tests / test-pr (push) Has been cancelled
PaddleOCR PR Tests GPU / test-pr-gpu-impl (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.13) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.8) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.9) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:26 +08:00

189 lines
6.0 KiB
Python

# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
from typing import Any, Dict, Optional
import aiohttp
from ._core import (
extract_api_message_from_payload,
extract_job_id,
raise_for_status,
unwrap_api_response,
)
from .errors import (
NetworkError,
RequestTimeoutError,
ResponseFormatError,
ResultParseError,
)
from ._http import API_PATH, DEFAULT_BASE_URL
class AsyncHTTPClient:
def __init__(
self,
token: str,
base_url: str,
timeout: float,
client_platform: Optional[str] = None,
):
self._token = token
self._base_url = base_url.rstrip("/")
self._jobs_url = f"{self._base_url}{API_PATH}"
self._timeout = timeout
self._client_platform = client_platform
self._session = None
@property
def timeout(self) -> float:
return self._timeout
async def __aenter__(self):
await self._ensure_session()
return self
async def __aexit__(self, *args):
await self.close()
async def _ensure_session(self):
if self._session is None:
self._session = aiohttp.ClientSession(
headers=self._api_headers(),
timeout=aiohttp.ClientTimeout(total=self._timeout),
)
async def close(self):
if self._session:
await self._session.close()
self._session = None
def _api_headers(self) -> dict:
headers = {"Authorization": f"Bearer {self._token}"}
if self._client_platform:
headers["Client-Platform"] = self._client_platform
return headers
async def submit_url(
self,
model: str,
file_url: str,
optional_payload: dict,
page_ranges: Optional[str] = None,
batch_id: Optional[str] = None,
) -> str:
body = {
"fileUrl": file_url,
"model": model,
"optionalPayload": optional_payload,
}
if page_ranges is not None:
body["pageRanges"] = page_ranges
if batch_id is not None:
body["batchId"] = batch_id
await self._ensure_session()
async with self._session.post(
self._jobs_url,
json=body,
headers={"Content-Type": "application/json"},
) as resp:
await self._raise_for_response(resp)
data = await self._response_data(resp)
return extract_job_id(data)
async def submit_file(
self,
model: str,
file_path: str,
optional_payload: dict,
page_ranges: Optional[str] = None,
batch_id: Optional[str] = None,
) -> str:
if not os.path.exists(file_path):
raise FileNotFoundError(file_path)
form = aiohttp.FormData()
form.add_field("model", model)
form.add_field("optionalPayload", json.dumps(optional_payload))
if page_ranges is not None:
form.add_field("pageRanges", page_ranges)
if batch_id is not None:
form.add_field("batchId", batch_id)
with open(file_path, "rb") as f:
file_data = f.read()
form.add_field(
"file",
file_data,
filename=os.path.basename(file_path),
)
await self._ensure_session()
async with self._session.post(self._jobs_url, data=form) as resp:
await self._raise_for_response(resp)
data = await self._response_data(resp)
return extract_job_id(data)
async def get_job_status(self, job_id: str) -> Dict[str, Any]:
await self._ensure_session()
async with self._session.get(f"{self._jobs_url}/{job_id}") as resp:
await self._raise_for_response(resp)
return await self._response_data(resp)
async def get_batch_status(self, batch_id: str) -> Dict[str, Any]:
await self._ensure_session()
async with self._session.get(f"{self._jobs_url}/batch/{batch_id}") as resp:
await self._raise_for_response(resp)
return await self._response_data(resp)
async def fetch_jsonl(self, url: str) -> list:
timeout = aiohttp.ClientTimeout(total=self._timeout)
async with aiohttp.ClientSession(timeout=timeout) as bare_session:
async with bare_session.get(url) as resp:
await self._raise_for_response(resp)
text = await resp.text()
try:
lines = text.strip().split("\n")
return [json.loads(line) for line in lines if line.strip()]
except json.JSONDecodeError as e:
raise ResultParseError(
f"Malformed JSONL result payload: {e}"
) from e
async def _raise_for_response(self, resp) -> None:
if 200 <= resp.status < 300:
return
try:
body = await resp.json()
msg = (
extract_api_message_from_payload(body)
if isinstance(body, dict)
else None
)
if not msg:
msg = await resp.text()
except Exception:
msg = await resp.text()
raise_for_status(resp.status, msg)
async def _response_data(self, resp) -> Dict[str, Any]:
try:
payload = await resp.json()
except Exception as e:
raise ResponseFormatError(f"Response body is not valid JSON: {e}") from e
return unwrap_api_response(payload, resp.status)