This commit is contained in:
+42
-18
@@ -2,13 +2,17 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from numbers import Real
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from core.models import Bounds, SceneElement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OCRBox:
|
||||
@@ -61,9 +65,10 @@ def run_ocr(
|
||||
) -> list[SceneElement]:
|
||||
try:
|
||||
boxes = (engine or PaddleOCREngine()).extract(image)
|
||||
except ImportError:
|
||||
except Exception:
|
||||
if strict:
|
||||
raise
|
||||
logger.warning("OCR extraction failed; continuing without OCR", exc_info=True)
|
||||
return []
|
||||
return [box.to_scene_element(f"ocr-{index:03d}") for index, box in enumerate(boxes)]
|
||||
|
||||
@@ -105,20 +110,18 @@ def _flatten_pages(raw: Any) -> Iterable[Any]:
|
||||
return flattened
|
||||
json_attr = getattr(raw, "json", None)
|
||||
if callable(json_attr):
|
||||
return _flatten_pages(json_attr())
|
||||
if isinstance(json_attr, (dict, list)):
|
||||
return _flatten_pages(json_attr)
|
||||
return []
|
||||
|
||||
|
||||
def _dict_lines(data: dict[str, Any]) -> list[Any]:
|
||||
payload = data.get("res") if isinstance(data.get("res"), dict) else data
|
||||
texts = payload.get("rec_texts") or payload.get("texts") or []
|
||||
scores = payload.get("rec_scores") or payload.get("scores") or []
|
||||
boxes = (
|
||||
payload.get("rec_boxes")
|
||||
or payload.get("rec_polys")
|
||||
or payload.get("dt_polys")
|
||||
or []
|
||||
)
|
||||
result = data.get("res")
|
||||
payload = result if isinstance(result, dict) else data
|
||||
texts = _first_nonempty(payload, "rec_texts", "texts")
|
||||
scores = _first_nonempty(payload, "rec_scores", "scores")
|
||||
boxes = _first_nonempty(payload, "rec_boxes", "rec_polys", "dt_polys")
|
||||
return [
|
||||
{
|
||||
"text": text,
|
||||
@@ -136,11 +139,13 @@ def _looks_like_ocr_line(value: Any) -> bool:
|
||||
def _parse_line(line: Any) -> OCRBox | None:
|
||||
if isinstance(line, dict):
|
||||
text = line.get("text")
|
||||
points = line.get("points") or line.get("box") or line.get("bounds")
|
||||
points = _first_nonempty(line, "points", "box", "bounds")
|
||||
confidence = line.get("confidence")
|
||||
if not text or not points:
|
||||
if not _has_items(text) or not _has_items(points):
|
||||
return None
|
||||
return OCRBox(str(text), _bounds_from_points(points), _float_or_none(confidence))
|
||||
return OCRBox(
|
||||
str(text), _bounds_from_points(points), _float_or_none(confidence)
|
||||
)
|
||||
|
||||
if not _looks_like_ocr_line(line):
|
||||
return None
|
||||
@@ -161,11 +166,7 @@ def _parse_line(line: Any) -> OCRBox | None:
|
||||
def _bounds_from_points(points: Any) -> Bounds:
|
||||
if isinstance(points, dict):
|
||||
return Bounds.from_dict(points)
|
||||
if (
|
||||
isinstance(points, (list, tuple))
|
||||
and len(points) == 4
|
||||
and all(isinstance(value, (int, float)) for value in points)
|
||||
):
|
||||
if _has_length(points, 4) and all(isinstance(value, Real) for value in points):
|
||||
x1, y1, x2, y2 = [float(value) for value in points]
|
||||
return Bounds(x1, y1, x2 - x1, y2 - y1)
|
||||
|
||||
@@ -186,3 +187,26 @@ def _float_or_none(value: Any) -> float | None:
|
||||
return None
|
||||
return float(value)
|
||||
|
||||
|
||||
def _first_nonempty(data: dict[str, Any], *keys: str) -> Any:
|
||||
for key in keys:
|
||||
value = data.get(key)
|
||||
if _has_items(value):
|
||||
return value
|
||||
return []
|
||||
|
||||
|
||||
def _has_items(value: Any) -> bool:
|
||||
if value is None:
|
||||
return False
|
||||
try:
|
||||
return len(value) > 0
|
||||
except TypeError:
|
||||
return bool(value)
|
||||
|
||||
|
||||
def _has_length(value: Any, length: int) -> bool:
|
||||
try:
|
||||
return len(value) == length
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"httpx>=0.27.0",
|
||||
"mcp>=1.27,<2",
|
||||
"openai>=1.0.0",
|
||||
"paddlepaddle>=3.0.0",
|
||||
"paddleocr>=3.0.0",
|
||||
"uvicorn[standard]>=0.30.0",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from core.models import Bounds
|
||||
from perception.ocr import OCRBox, parse_paddle_result, run_ocr
|
||||
|
||||
|
||||
def test_parse_paddle_result_accepts_ndarray_fields() -> None:
|
||||
boxes = parse_paddle_result(
|
||||
{
|
||||
"rec_texts": np.array(["Search"]),
|
||||
"rec_scores": np.array([0.95]),
|
||||
"rec_boxes": np.array([[1, 2, 10, 12]]),
|
||||
}
|
||||
)
|
||||
|
||||
assert boxes == [
|
||||
OCRBox(
|
||||
text="Search",
|
||||
bounds=Bounds(x=1.0, y=2.0, width=9.0, height=10.0),
|
||||
confidence=0.95,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class CallableJsonResult:
|
||||
def json(self) -> dict[str, object]:
|
||||
return {
|
||||
"res": {
|
||||
"rec_texts": ["Search"],
|
||||
"rec_scores": [0.95],
|
||||
"rec_boxes": [[1, 2, 10, 12]],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_parse_paddle_result_accepts_callable_json_result() -> None:
|
||||
assert parse_paddle_result(CallableJsonResult()) == [
|
||||
OCRBox(
|
||||
text="Search",
|
||||
bounds=Bounds(x=1.0, y=2.0, width=9.0, height=10.0),
|
||||
confidence=0.95,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class FailingOCREngine:
|
||||
def extract(self, image: bytes | str) -> list[OCRBox]:
|
||||
raise ValueError(
|
||||
"The truth value of an array with more than one element is ambiguous"
|
||||
)
|
||||
|
||||
|
||||
def test_run_ocr_degrades_when_ocr_engine_raises() -> None:
|
||||
assert run_ocr(b"image", engine=FailingOCREngine()) == [] # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_run_ocr_strict_mode_preserves_engine_error() -> None:
|
||||
with pytest.raises(ValueError, match="truth value"):
|
||||
run_ocr(b"image", engine=FailingOCREngine(), strict=True) # type: ignore[arg-type]
|
||||
@@ -406,6 +406,7 @@ dependencies = [
|
||||
{ name = "mcp" },
|
||||
{ name = "openai" },
|
||||
{ name = "paddleocr" },
|
||||
{ name = "paddlepaddle" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
@@ -423,6 +424,7 @@ requires-dist = [
|
||||
{ name = "mcp", specifier = ">=1.27,<2" },
|
||||
{ name = "openai", specifier = ">=1.0.0" },
|
||||
{ name = "paddleocr", specifier = ">=3.0.0" },
|
||||
{ name = "paddlepaddle", specifier = ">=3.0.0" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
|
||||
]
|
||||
|
||||
@@ -950,6 +952,15 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "networkx"
|
||||
version = "3.6.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.5"
|
||||
@@ -1016,6 +1027,18 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a7/9e/7110d2c5d543ab03b9581dbb1f8e2429863e44e0c9b4960b766f230c1279/opencv_contrib_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:47ec3160dae75f70e099b286d1a2e086d20dac8b06e759f60eaf867e6bdecba7" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opt-einsum"
|
||||
version = "3.3.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/7d/bf/9257e53a0e7715bc1127e15063e831f076723c6cd60985333a1c18878fb8/opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bc/19/404708a7e54ad2798907210462fd950c3442ea51acc8790f3da48d2bee8b/opt_einsum-3.3.0-py3-none-any.whl", hash = "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outcome"
|
||||
version = "1.3.0.post0"
|
||||
@@ -1053,6 +1076,27 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d2/79/2ade66cf72074a3d2a013e2ba1f256d6116fec797b908fc1b4f9c320d8c4/paddleocr-3.7.0-py3-none-any.whl", hash = "sha256:c0f0a81ad4112727f30c6fcf986ac0ef6a120d31ee0991a01fae0357ee32d338" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paddlepaddle"
|
||||
version = "3.3.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "networkx" },
|
||||
{ name = "numpy" },
|
||||
{ name = "opt-einsum" },
|
||||
{ name = "pillow" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "safetensors" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/65/f8/bebb829227ac5574454b4342bcf512dbb02268118b62222fc7c1b420a3ef/paddlepaddle-3.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2ce30bfb0023d8c9314b1413ca7a775a4ddd076e4551e74fbed2366cf1395c98" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/56/8d/5a700dc20c0d3d581f1d429f95f5064eb766c2818c5a05992dfef66b0765/paddlepaddle-3.3.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4f28038427649bb2fcfd4d52efa85ed8311df19122b4ebedc942484a0b57b032" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f4/c8/408b9e94ac2e10a55c1122b787359f7a6f79ef894ee1d57d78c5cca838d0/paddlepaddle-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1203e2e1114b49e73a8440b68837ce5c93fdab51fe4838c5e5874ab40f58f747" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paddlex"
|
||||
version = "3.7.2"
|
||||
@@ -1204,6 +1248,21 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "7.35.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.2.2"
|
||||
@@ -1594,6 +1653,30 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "safetensors"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selenium"
|
||||
version = "4.46.0"
|
||||
|
||||
Reference in New Issue
Block a user