mineru-parser 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mineru_parser/__init__.py +41 -0
- mineru_parser/_client/__init__.py +47 -0
- mineru_parser/_client/_telemetry.py +7 -0
- mineru_parser/_client/_time.py +13 -0
- mineru_parser/_client/_validation.py +161 -0
- mineru_parser/_client/api.py +233 -0
- mineru_parser/_client/async_result.py +254 -0
- mineru_parser/_client/errors.py +138 -0
- mineru_parser/_client/models.py +211 -0
- mineru_parser/_client/polling.py +309 -0
- mineru_parser/_client/transport.py +289 -0
- mineru_parser/_downloader/__init__.py +40 -0
- mineru_parser/_downloader/downloader.py +1099 -0
- mineru_parser/_downloader/errors.py +122 -0
- mineru_parser/_downloader/policy.py +89 -0
- mineru_parser/_downloader/security.py +380 -0
- mineru_parser/_extractor/__init__.py +38 -0
- mineru_parser/_extractor/_manifest.py +127 -0
- mineru_parser/_extractor/_members.py +351 -0
- mineru_parser/_extractor/errors.py +130 -0
- mineru_parser/_extractor/extractor.py +310 -0
- mineru_parser/_extractor/limits.py +35 -0
- mineru_parser/_extractor/models.py +31 -0
- mineru_parser/_http.py +77 -0
- mineru_parser/_telemetry.py +62 -0
- mineru_parser/_version.py +10 -0
- mineru_parser/errors.py +15 -0
- mineru_parser/logging.py +0 -0
- mineru_parser/pipeline/__init__.py +29 -0
- mineru_parser/pipeline/budget.py +109 -0
- mineru_parser/pipeline/errors.py +64 -0
- mineru_parser/pipeline/orchestrator.py +586 -0
- mineru_parser/pipeline/result.py +32 -0
- mineru_parser/pipeline/stages.py +22 -0
- mineru_parser/schema/content_v2.py +198 -0
- mineru_parser-0.1.0.dist-info/METADATA +409 -0
- mineru_parser-0.1.0.dist-info/RECORD +39 -0
- mineru_parser-0.1.0.dist-info/WHEEL +4 -0
- mineru_parser-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""MinerU Parser 公共 API。
|
|
2
|
+
|
|
3
|
+
整个库对外只暴露一个函数 :func:`parse`(SPEC-004 AC-2.1 / AC-2.6);
|
|
4
|
+
其余导出都是 ``parse`` 的签名或返回值所引用的类型、异常类与版本常量。
|
|
5
|
+
三个组件(client / downloader / extractor)是私有实现,不提供公开导入路径
|
|
6
|
+
(SPEC-004 AC-2.3)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from ._client.models import MinerUTaskStatus
|
|
12
|
+
from ._extractor.limits import ExtractionLimits
|
|
13
|
+
from ._extractor.models import ExtractedImage, ExtractedResult
|
|
14
|
+
from ._telemetry import MetricHook
|
|
15
|
+
from ._version import __version__
|
|
16
|
+
from .errors import MinerUParserError
|
|
17
|
+
from .pipeline import parse
|
|
18
|
+
from .pipeline.errors import (
|
|
19
|
+
PipelineConfigurationError,
|
|
20
|
+
PipelineError,
|
|
21
|
+
PipelineTimeoutError,
|
|
22
|
+
)
|
|
23
|
+
from .pipeline.result import ErrorMessage, ParseResult
|
|
24
|
+
from .pipeline.stages import PipelineStage
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ErrorMessage",
|
|
28
|
+
"ExtractedImage",
|
|
29
|
+
"ExtractedResult",
|
|
30
|
+
"ExtractionLimits",
|
|
31
|
+
"MetricHook",
|
|
32
|
+
"MinerUParserError",
|
|
33
|
+
"MinerUTaskStatus",
|
|
34
|
+
"ParseResult",
|
|
35
|
+
"PipelineConfigurationError",
|
|
36
|
+
"PipelineError",
|
|
37
|
+
"PipelineStage",
|
|
38
|
+
"PipelineTimeoutError",
|
|
39
|
+
"__version__",
|
|
40
|
+
"parse",
|
|
41
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""MinerU 客户端实现子包(内部实现,不对外)。
|
|
2
|
+
|
|
3
|
+
本包是 SPEC-001 的私有实现,仅由 :mod:`mineru_parser.pipeline` 内部使用;
|
|
4
|
+
不提供公开导入路径,也不遵循 SemVer。行为契约见 SPEC-001。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ._telemetry import MetricHook
|
|
10
|
+
from .api import MinerUApiClient, MinerUClient
|
|
11
|
+
from .async_result import AsyncResult
|
|
12
|
+
from .errors import (
|
|
13
|
+
MinerUAuthenticationError,
|
|
14
|
+
MinerUConfigurationError,
|
|
15
|
+
MinerUError,
|
|
16
|
+
MinerUExecutionError,
|
|
17
|
+
MinerUQuotaError,
|
|
18
|
+
MinerUTimeoutError,
|
|
19
|
+
MinerUTransientError,
|
|
20
|
+
)
|
|
21
|
+
from .models import (
|
|
22
|
+
MinerUProgress,
|
|
23
|
+
MinerUTaskResult,
|
|
24
|
+
MinerUTaskState,
|
|
25
|
+
MinerUTaskStatus,
|
|
26
|
+
)
|
|
27
|
+
from .polling import DEFAULT_POLL_POLICY, PollPolicy
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"DEFAULT_POLL_POLICY",
|
|
31
|
+
"AsyncResult",
|
|
32
|
+
"MetricHook",
|
|
33
|
+
"MinerUApiClient",
|
|
34
|
+
"MinerUAuthenticationError",
|
|
35
|
+
"MinerUClient",
|
|
36
|
+
"MinerUConfigurationError",
|
|
37
|
+
"MinerUError",
|
|
38
|
+
"MinerUExecutionError",
|
|
39
|
+
"MinerUProgress",
|
|
40
|
+
"MinerUQuotaError",
|
|
41
|
+
"MinerUTaskResult",
|
|
42
|
+
"MinerUTaskState",
|
|
43
|
+
"MinerUTaskStatus",
|
|
44
|
+
"MinerUTimeoutError",
|
|
45
|
+
"MinerUTransientError",
|
|
46
|
+
"PollPolicy",
|
|
47
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""时间辅助函数。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def elapsed_ms(started: float) -> float:
|
|
9
|
+
"""返回自 ``started``(``time.perf_counter()``)以来的毫秒数。"""
|
|
10
|
+
return round((time.perf_counter() - started) * 1000, 3)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ["elapsed_ms"]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""配置参数校验:纯函数,不依赖网络、日志或客户端状态。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from urllib.parse import urlsplit
|
|
7
|
+
|
|
8
|
+
from .errors import MinerUConfigurationError
|
|
9
|
+
|
|
10
|
+
_HTTP_SCHEMES = frozenset({"http", "https"})
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_finite_number(value: object) -> bool:
|
|
14
|
+
"""拒绝 NaN / Infinity:它们能通过 ``<= 0`` 比较,却会在 sleep
|
|
15
|
+
或 socket 超时处抛出未分类异常。"""
|
|
16
|
+
return not (isinstance(value, float) and not math.isfinite(value))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_absolute_http_url(value: object) -> bool:
|
|
20
|
+
if not isinstance(value, str) or not value.strip():
|
|
21
|
+
return False
|
|
22
|
+
try:
|
|
23
|
+
parsed = urlsplit(value.strip())
|
|
24
|
+
_ = parsed.port # 触发非法端口校验
|
|
25
|
+
except ValueError:
|
|
26
|
+
return False
|
|
27
|
+
return parsed.scheme.lower() in _HTTP_SCHEMES and bool(parsed.hostname)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def validate_api_key(api_key: str | None) -> str | None:
|
|
31
|
+
if api_key is None:
|
|
32
|
+
return None
|
|
33
|
+
if not isinstance(api_key, str) or not api_key.strip():
|
|
34
|
+
raise MinerUConfigurationError("MinerU API key must not be blank.")
|
|
35
|
+
return api_key.strip()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_base_url(base_url: str) -> str:
|
|
39
|
+
if not isinstance(base_url, str) or not base_url.strip():
|
|
40
|
+
raise MinerUConfigurationError("MinerU base_url must not be empty.")
|
|
41
|
+
value = base_url.strip().rstrip("/")
|
|
42
|
+
try:
|
|
43
|
+
parsed = urlsplit(value)
|
|
44
|
+
_ = parsed.port # 触发非法端口校验
|
|
45
|
+
hostname = parsed.hostname
|
|
46
|
+
except ValueError as exc:
|
|
47
|
+
raise MinerUConfigurationError("MinerU base_url is not a valid URL.") from exc
|
|
48
|
+
if parsed.scheme.lower() not in _HTTP_SCHEMES or not hostname:
|
|
49
|
+
raise MinerUConfigurationError(
|
|
50
|
+
"MinerU base_url must be an absolute http(s) URL."
|
|
51
|
+
)
|
|
52
|
+
if parsed.query or parsed.fragment:
|
|
53
|
+
raise MinerUConfigurationError(
|
|
54
|
+
"MinerU base_url must not contain a query or fragment."
|
|
55
|
+
)
|
|
56
|
+
if any(
|
|
57
|
+
ch.isspace() or ord(ch) < 32 or ord(ch) == 127 or ch == "\\" for ch in hostname
|
|
58
|
+
):
|
|
59
|
+
raise MinerUConfigurationError("MinerU base_url contains an invalid host.")
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def validate_request_timeout(
|
|
64
|
+
value: float | tuple[float, float],
|
|
65
|
+
) -> float | tuple[float, float]:
|
|
66
|
+
if isinstance(value, bool):
|
|
67
|
+
raise MinerUConfigurationError("request_timeout must be positive.")
|
|
68
|
+
if isinstance(value, (int, float)):
|
|
69
|
+
if not is_finite_number(value) or value <= 0:
|
|
70
|
+
raise MinerUConfigurationError("request_timeout must be positive.")
|
|
71
|
+
return float(value)
|
|
72
|
+
if isinstance(value, (tuple, list)) and len(value) == 2:
|
|
73
|
+
connect, read = value
|
|
74
|
+
for item in (connect, read):
|
|
75
|
+
if (
|
|
76
|
+
isinstance(item, bool)
|
|
77
|
+
or not isinstance(item, (int, float))
|
|
78
|
+
or not is_finite_number(item)
|
|
79
|
+
or item <= 0
|
|
80
|
+
):
|
|
81
|
+
raise MinerUConfigurationError(
|
|
82
|
+
"request_timeout tuple values must both be positive."
|
|
83
|
+
)
|
|
84
|
+
return (float(connect), float(read))
|
|
85
|
+
raise MinerUConfigurationError(
|
|
86
|
+
"request_timeout must be a positive number or a (connect, read) tuple."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def validate_positive(value: float, name: str) -> float:
|
|
91
|
+
if (
|
|
92
|
+
isinstance(value, bool)
|
|
93
|
+
or not isinstance(value, (int, float))
|
|
94
|
+
or not is_finite_number(value)
|
|
95
|
+
):
|
|
96
|
+
raise MinerUConfigurationError(f"{name} must be a positive number.")
|
|
97
|
+
if value <= 0:
|
|
98
|
+
raise MinerUConfigurationError(f"{name} must be a positive number.")
|
|
99
|
+
return float(value)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def validate_non_negative(value: float, name: str) -> float:
|
|
103
|
+
if (
|
|
104
|
+
isinstance(value, bool)
|
|
105
|
+
or not isinstance(value, (int, float))
|
|
106
|
+
or not is_finite_number(value)
|
|
107
|
+
):
|
|
108
|
+
raise MinerUConfigurationError(f"{name} must be zero or positive.")
|
|
109
|
+
if value < 0:
|
|
110
|
+
raise MinerUConfigurationError(f"{name} must be zero or positive.")
|
|
111
|
+
return float(value)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def validate_max_errors(value: int) -> int:
|
|
115
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
|
|
116
|
+
raise MinerUConfigurationError(
|
|
117
|
+
"max_consecutive_errors must be an integer >= 1."
|
|
118
|
+
)
|
|
119
|
+
return value
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def validate_task_id(task_id: str) -> str:
|
|
123
|
+
if not isinstance(task_id, str) or not task_id.strip():
|
|
124
|
+
raise MinerUConfigurationError("task_id must be a non-empty string.")
|
|
125
|
+
return task_id.strip()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def validate_deadline_epoch(value: float) -> float:
|
|
129
|
+
if (
|
|
130
|
+
isinstance(value, bool)
|
|
131
|
+
or not isinstance(value, (int, float))
|
|
132
|
+
or not is_finite_number(value)
|
|
133
|
+
):
|
|
134
|
+
raise MinerUConfigurationError("deadline_epoch must be a finite number.")
|
|
135
|
+
return float(value)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def validate_data_id(value: str | None) -> str | None:
|
|
139
|
+
if value is None:
|
|
140
|
+
return None
|
|
141
|
+
if not isinstance(value, str) or not value.strip():
|
|
142
|
+
raise MinerUConfigurationError("data_id must not be blank.")
|
|
143
|
+
value = value.strip()
|
|
144
|
+
if len(value) > 128:
|
|
145
|
+
raise MinerUConfigurationError("data_id must be at most 128 characters.")
|
|
146
|
+
return value
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
__all__ = [
|
|
150
|
+
"is_absolute_http_url",
|
|
151
|
+
"is_finite_number",
|
|
152
|
+
"validate_api_key",
|
|
153
|
+
"validate_base_url",
|
|
154
|
+
"validate_data_id",
|
|
155
|
+
"validate_deadline_epoch",
|
|
156
|
+
"validate_max_errors",
|
|
157
|
+
"validate_non_negative",
|
|
158
|
+
"validate_positive",
|
|
159
|
+
"validate_request_timeout",
|
|
160
|
+
"validate_task_id",
|
|
161
|
+
]
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""``MinerUApiClient``:配置、凭证与结果句柄工厂。
|
|
2
|
+
|
|
3
|
+
客户端本身不保存任务状态,也不执行轮询;``submit`` / ``resume`` 返回
|
|
4
|
+
:class:`~mineru_parser._client.async_result.AsyncResult` 句柄,由句柄负责等待。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import random
|
|
12
|
+
import time
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
from pydantic import ValidationError
|
|
17
|
+
|
|
18
|
+
from ._telemetry import MetricHook, Telemetry
|
|
19
|
+
from ._time import elapsed_ms
|
|
20
|
+
from ._validation import (
|
|
21
|
+
validate_api_key,
|
|
22
|
+
validate_base_url,
|
|
23
|
+
validate_data_id,
|
|
24
|
+
validate_request_timeout,
|
|
25
|
+
validate_task_id,
|
|
26
|
+
)
|
|
27
|
+
from .async_result import AsyncResult
|
|
28
|
+
from .errors import (
|
|
29
|
+
MinerUAuthenticationError,
|
|
30
|
+
MinerUConfigurationError,
|
|
31
|
+
MinerUError,
|
|
32
|
+
)
|
|
33
|
+
from .models import (
|
|
34
|
+
ExtractionRequest,
|
|
35
|
+
MinerUTaskStatus,
|
|
36
|
+
normalize_status,
|
|
37
|
+
)
|
|
38
|
+
from .polling import (
|
|
39
|
+
DEFAULT_POLL_POLICY,
|
|
40
|
+
Poller,
|
|
41
|
+
PollPolicy,
|
|
42
|
+
PollRuntime,
|
|
43
|
+
)
|
|
44
|
+
from .transport import MINERU_BASE_URL, MinerUTransport
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MinerUApiClient:
|
|
48
|
+
"""MinerU API 的同步客户端。
|
|
49
|
+
|
|
50
|
+
客户端可以在多个任务之间共享。客户端自行创建并管理底层
|
|
51
|
+
``requests.Session``(复用连接池);也可以注入外部 Session,此时其
|
|
52
|
+
生命周期由调用方负责,``close()`` 不会关闭它。
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
api_key: str | None = None,
|
|
59
|
+
base_url: str = MINERU_BASE_URL,
|
|
60
|
+
request_timeout: float | tuple[float, float] = 10.0,
|
|
61
|
+
poll_policy: PollPolicy | None = None,
|
|
62
|
+
session: requests.Session | None = None,
|
|
63
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
64
|
+
monotonic: Callable[[], float] = time.monotonic,
|
|
65
|
+
wall_clock: Callable[[], float] = time.time,
|
|
66
|
+
random_fn: Callable[[], float] = random.random,
|
|
67
|
+
on_metric: MetricHook | None = None,
|
|
68
|
+
correlation_id: str | None = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
self._api_key = validate_api_key(api_key)
|
|
71
|
+
self._base_url = validate_base_url(base_url)
|
|
72
|
+
self._request_timeout = validate_request_timeout(request_timeout)
|
|
73
|
+
if poll_policy is None:
|
|
74
|
+
poll_policy = DEFAULT_POLL_POLICY
|
|
75
|
+
if not isinstance(poll_policy, PollPolicy):
|
|
76
|
+
raise MinerUConfigurationError("poll_policy must be a PollPolicy instance.")
|
|
77
|
+
self._poll_policy = poll_policy
|
|
78
|
+
self._owns_session = session is None
|
|
79
|
+
self._session = session if session is not None else requests.Session()
|
|
80
|
+
self._runtime = PollRuntime(
|
|
81
|
+
sleep=sleep,
|
|
82
|
+
monotonic=monotonic,
|
|
83
|
+
wall_clock=wall_clock,
|
|
84
|
+
random_fn=random_fn,
|
|
85
|
+
)
|
|
86
|
+
self._telemetry = Telemetry(
|
|
87
|
+
on_metric=on_metric,
|
|
88
|
+
correlation_id=correlation_id,
|
|
89
|
+
)
|
|
90
|
+
self._transport = MinerUTransport(
|
|
91
|
+
base_url=self._base_url,
|
|
92
|
+
timeout=self._request_timeout,
|
|
93
|
+
session=self._session,
|
|
94
|
+
telemetry=self._telemetry,
|
|
95
|
+
wall_clock=wall_clock,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# --- 生命周期 ---------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def close(self) -> None:
|
|
101
|
+
"""关闭客户端自建的 Session;注入的 Session 由调用方管理。"""
|
|
102
|
+
if self._owns_session:
|
|
103
|
+
self._session.close()
|
|
104
|
+
|
|
105
|
+
def __enter__(self) -> MinerUApiClient:
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
109
|
+
self.close()
|
|
110
|
+
|
|
111
|
+
# --- 只读配置 ---------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def base_url(self) -> str:
|
|
115
|
+
return self._base_url
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def poll_policy(self) -> PollPolicy:
|
|
119
|
+
return self._poll_policy
|
|
120
|
+
|
|
121
|
+
# --- 提交 / 查询 ------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def submit(
|
|
124
|
+
self,
|
|
125
|
+
uri: str,
|
|
126
|
+
*,
|
|
127
|
+
model_version: str = "vlm",
|
|
128
|
+
data_id: str | None = None,
|
|
129
|
+
) -> AsyncResult:
|
|
130
|
+
"""提交任务,返回可等待的结果句柄。
|
|
131
|
+
|
|
132
|
+
只有“确定请求未发出”的错误或 HTTP 429 抛
|
|
133
|
+
:class:`MinerUTransientError`;其余错误(包括结果未知、HTTP 4xx/5xx)
|
|
134
|
+
抛 :class:`MinerUExecutionError`。``data_id`` 仅用于对账,不是幂等键。
|
|
135
|
+
"""
|
|
136
|
+
api_key = self._resolve_api_key()
|
|
137
|
+
|
|
138
|
+
# 请求模型校验必须在 HTTP 异常处理块之外:Pydantic ValidationError
|
|
139
|
+
# 不能被误映射为 MinerUExecutionError。
|
|
140
|
+
try:
|
|
141
|
+
request = ExtractionRequest(
|
|
142
|
+
uri=uri,
|
|
143
|
+
model_version=model_version,
|
|
144
|
+
data_id=data_id,
|
|
145
|
+
)
|
|
146
|
+
except ValidationError as exc:
|
|
147
|
+
raise MinerUConfigurationError(
|
|
148
|
+
f"Invalid MinerU submit request: {exc}"
|
|
149
|
+
) from exc
|
|
150
|
+
|
|
151
|
+
started = time.perf_counter()
|
|
152
|
+
try:
|
|
153
|
+
provider_task_id = self._transport.submit(request, api_key=api_key)
|
|
154
|
+
except MinerUError:
|
|
155
|
+
duration_ms = elapsed_ms(started)
|
|
156
|
+
self._telemetry.log(
|
|
157
|
+
"mineru.submit",
|
|
158
|
+
level=logging.WARNING,
|
|
159
|
+
data_id=request.data_id,
|
|
160
|
+
duration_ms=duration_ms,
|
|
161
|
+
outcome="error",
|
|
162
|
+
)
|
|
163
|
+
self._telemetry.metric("mineru.submit.total", 1, {"outcome": "error"})
|
|
164
|
+
self._telemetry.metric(
|
|
165
|
+
"mineru.submit.duration_ms",
|
|
166
|
+
duration_ms,
|
|
167
|
+
{"outcome": "error"},
|
|
168
|
+
)
|
|
169
|
+
raise
|
|
170
|
+
|
|
171
|
+
duration_ms = elapsed_ms(started)
|
|
172
|
+
self._telemetry.log(
|
|
173
|
+
"mineru.submit",
|
|
174
|
+
provider_task_id=provider_task_id,
|
|
175
|
+
data_id=request.data_id,
|
|
176
|
+
duration_ms=duration_ms,
|
|
177
|
+
outcome="success",
|
|
178
|
+
)
|
|
179
|
+
self._telemetry.metric("mineru.submit.total", 1, {"outcome": "success"})
|
|
180
|
+
self._telemetry.metric(
|
|
181
|
+
"mineru.submit.duration_ms",
|
|
182
|
+
duration_ms,
|
|
183
|
+
{"outcome": "success"},
|
|
184
|
+
)
|
|
185
|
+
return AsyncResult(
|
|
186
|
+
client=self,
|
|
187
|
+
task_id=provider_task_id,
|
|
188
|
+
data_id=request.data_id,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def resume(
|
|
192
|
+
self,
|
|
193
|
+
task_id: str,
|
|
194
|
+
*,
|
|
195
|
+
data_id: str | None = None,
|
|
196
|
+
) -> AsyncResult:
|
|
197
|
+
"""从已持久化的 task_id 恢复结果句柄,不发起网络请求。"""
|
|
198
|
+
return AsyncResult(
|
|
199
|
+
client=self,
|
|
200
|
+
task_id=validate_task_id(task_id),
|
|
201
|
+
data_id=validate_data_id(data_id),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# --- 内部 -------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
def _trace(self, task_id: str) -> MinerUTaskStatus:
|
|
207
|
+
"""内部状态查询:仅供 ``AsyncResult.refresh()`` 调用。"""
|
|
208
|
+
validated = validate_task_id(task_id)
|
|
209
|
+
api_key = self._resolve_api_key()
|
|
210
|
+
detail = self._transport.trace(validated, api_key=api_key)
|
|
211
|
+
return normalize_status(validated, detail)
|
|
212
|
+
|
|
213
|
+
def _resolve_api_key(self) -> str:
|
|
214
|
+
key = self._api_key
|
|
215
|
+
if key is None:
|
|
216
|
+
key = os.getenv("MINERU_API_KEY")
|
|
217
|
+
if key is None or not key.strip():
|
|
218
|
+
raise MinerUAuthenticationError(
|
|
219
|
+
"MinerU API key is not configured; set MINERU_API_KEY."
|
|
220
|
+
)
|
|
221
|
+
return key.strip()
|
|
222
|
+
|
|
223
|
+
def _poller(self, policy: PollPolicy | None = None) -> Poller:
|
|
224
|
+
return Poller(
|
|
225
|
+
policy=policy if policy is not None else self._poll_policy,
|
|
226
|
+
runtime=self._runtime,
|
|
227
|
+
telemetry=self._telemetry,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
MinerUClient = MinerUApiClient
|
|
232
|
+
|
|
233
|
+
__all__ = ["MetricHook", "MinerUApiClient", "MinerUClient"]
|