Files
unstructured-io--unstructured/test_unstructured/test_utils.py
T
wehub-resource-sync 461bf6fd40
CI / lint (3.11) (push) Blocked by required conditions
CI / lint (3.12) (push) Blocked by required conditions
CI / lint (3.13) (push) Blocked by required conditions
CI / shellcheck (push) Waiting to run
CI / shfmt (push) Waiting to run
CI / setup (3.11) (push) Waiting to run
CI / setup (3.12) (push) Waiting to run
CI / setup (3.13) (push) Waiting to run
CI / check-licenses (3.12) (push) Blocked by required conditions
CI / test_unit (3.11) (push) Blocked by required conditions
CI / test_unit (3.12) (push) Blocked by required conditions
CI / test_unit (3.13) (push) Blocked by required conditions
CI / test_unit_no_extras (3.11) (push) Blocked by required conditions
CI / test_unit_no_extras (3.12) (push) Blocked by required conditions
CI / test_json_to_html (3.12) (push) Blocked by required conditions
CI / test_unit_no_extras (3.13) (push) Blocked by required conditions
CI / test_unit_dependency_extras (csv, 3.11, --extra csv) (push) Blocked by required conditions
CI / test_unit_dependency_extras (csv, 3.12, --extra csv) (push) Blocked by required conditions
CI / test_unit_dependency_extras (csv, 3.13, --extra csv) (push) Blocked by required conditions
CI / test_unit_dependency_extras (docx, 3.11, --extra docx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (docx, 3.12, --extra docx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (docx, 3.13, --extra docx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (markdown, 3.11, --extra md) (push) Blocked by required conditions
CI / test_unit_dependency_extras (markdown, 3.12, --extra md) (push) Blocked by required conditions
CI / test_unit_dependency_extras (markdown, 3.13, --extra md) (push) Blocked by required conditions
CI / test_unit_dependency_extras (odt, 3.11, --extra odt) (push) Blocked by required conditions
CI / test_unit_dependency_extras (odt, 3.12, --extra odt) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pdf-image, 3.12, --extra pdf --extra image --extra paddleocr) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pdf-image, 3.13, --extra pdf --extra image --extra paddleocr) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pptx, 3.13, --extra pptx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (odt, 3.13, --extra odt) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pdf-image, 3.11, --extra pdf --extra image --extra paddleocr) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pptx, 3.11, --extra pptx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pptx, 3.12, --extra pptx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pypandoc, 3.11, --extra epub --extra org --extra rtf --extra rst) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pypandoc, 3.12, --extra epub --extra org --extra rtf --extra rst) (push) Blocked by required conditions
CI / test_unit_dependency_extras (pypandoc, 3.13, --extra epub --extra org --extra rtf --extra rst) (push) Blocked by required conditions
CI / test_unit_dependency_extras (xlsx, 3.11, --extra xlsx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (xlsx, 3.12, --extra xlsx) (push) Blocked by required conditions
CI / test_unit_dependency_extras (xlsx, 3.13, --extra xlsx) (push) Blocked by required conditions
CI / test_ingest_src (3.12) (push) Blocked by required conditions
CI / test_json_to_markdown (3.12) (push) Blocked by required conditions
CI / changelog (push) Waiting to run
CI / test_dockerfile (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Build And Push Docker Image / set-short-sha (push) Waiting to run
Build And Push Docker Image / build-images (linux/amd64, opensource-linux-8core) (push) Blocked by required conditions
Build And Push Docker Image / build-images (linux/arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
Build And Push Docker Image / publish-images (push) Blocked by required conditions
Partition Benchmark / setup (push) Waiting to run
Partition Benchmark / Measure and compare partition() runtime (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:33:56 +08:00

386 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
import os
import pytest
from unstructured import utils
from unstructured.documents.coordinates import PixelSpace
from unstructured.documents.elements import ElementMetadata, NarrativeText, Title
@pytest.fixture()
def input_data():
return [
{"text": "This is a sentence."},
{"text": "This is another sentence.", "meta": {"score": 0.1}},
]
@pytest.fixture()
def output_jsonl_file(tmp_path):
return os.path.join(tmp_path, "output.jsonl")
@pytest.fixture()
def input_jsonl_file(tmp_path, input_data):
file_path = os.path.join(tmp_path, "input.jsonl")
with open(file_path, "w+") as input_file:
input_file.writelines([json.dumps(obj) + "\n" for obj in input_data])
return file_path
def test_save_as_jsonl(input_data, output_jsonl_file):
utils.save_as_jsonl(input_data, output_jsonl_file)
with open(output_jsonl_file) as output_file:
file_data = [json.loads(line) for line in output_file]
assert file_data == input_data
def test_read_as_jsonl(input_jsonl_file, input_data):
file_data = utils.read_from_jsonl(input_jsonl_file)
assert file_data == input_data
def test_requires_dependencies_decorator():
@utils.requires_dependencies(dependencies="numpy")
def test_func():
import numpy # noqa: F401
test_func()
def test_requires_dependencies_decorator_multiple():
@utils.requires_dependencies(dependencies=["numpy", "pandas"])
def test_func():
import numpy # noqa: F401
import pandas # noqa: F401
test_func()
def test_requires_dependencies_decorator_import_error():
@utils.requires_dependencies(dependencies="not_a_package")
def test_func():
import not_a_package # noqa: F401
with pytest.raises(ImportError):
test_func()
def test_requires_dependencies_decorator_import_error_multiple():
@utils.requires_dependencies(dependencies=["not_a_package", "numpy"])
def test_func():
import not_a_package # noqa: F401
import numpy # noqa: F401
with pytest.raises(ImportError):
test_func()
def test_requires_dependencies_decorator_in_class():
@utils.requires_dependencies(dependencies="numpy")
class TestClass:
def __init__(self):
import numpy # noqa: F401
TestClass()
@pytest.mark.parametrize("iterator", [[0, 1], (0, 1), range(10), [0], (0,), range(1)])
def test_first_gives_first(iterator):
assert utils.first(iterator) == 0
@pytest.mark.parametrize("iterator", [[], ()])
def test_first_raises_if_empty(iterator):
with pytest.raises(ValueError):
utils.first(iterator)
@pytest.mark.parametrize("iterator", [[0], (0,), range(1)])
def test_only_gives_only(iterator):
assert utils.first(iterator) == 0
@pytest.mark.parametrize("iterator", [[0, 1], (0, 1), range(10)])
def test_only_raises_when_len_more_than_1(iterator):
with pytest.raises(ValueError):
utils.only(iterator)
@pytest.mark.parametrize("iterator", [[], ()])
def test_only_raises_if_empty(iterator):
with pytest.raises(ValueError):
utils.only(iterator)
@pytest.mark.parametrize(
("coords1", "coords2", "text1", "text2", "nested_error_tolerance_px", "expectation"),
[
(
((4, 5), (4, 8), (7, 8), (7, 5)),
((2, 3), (2, 6), (5, 6), (5, 3)),
"Some lovely title",
"Some lovely text",
5, # large nested_error_tolerance_px
{
"overlapping_elements": ["Title(ix=0)", "NarrativeText(ix=1)"],
"parent_element": "Title(ix=0)",
"overlapping_case": "nested NarrativeText in Title",
"overlap_percentage": "100%",
"metadata": {
"largest_ngram_percentage": 0,
"overlap_percentage_total": "5.88%",
"max_area": "9pxˆ2",
"min_area": "9pxˆ2",
"total_area": "18pxˆ2",
},
},
),
(
((4, 5), (4, 8), (7, 8), (7, 5)),
((2, 3), (2, 6), (5, 6), (5, 3)),
"Some lovely title",
"Some lovely text",
1, # small nested_error_tolerance_px
{
"overlapping_elements": ["0. Title(ix=0)", "1. NarrativeText(ix=1)"],
"parent_element": None,
"overlapping_case": "partial overlap sharing 50.0% of the text from1. "
"NarrativeText(2-gram)",
"overlap_percentage": "11.11%",
"metadata": {
"largest_ngram_percentage": 50.0,
"overlap_percentage_total": "5.88%",
"max_area": "9pxˆ2",
"min_area": "9pxˆ2",
"total_area": "18pxˆ2",
},
},
),
(
((4, 5), (4, 8), (7, 8), (7, 5)),
((2, 3), (2, 6), (5, 6), (5, 3)),
"Some lovely title",
"Some lovely title", # same title
1,
{
"overlapping_elements": ["0. Title(ix=0)", "1. NarrativeText(ix=1)"],
"parent_element": None,
"overlapping_case": "partial overlap with duplicate text",
"overlap_percentage": "11.11%",
"metadata": {
"largest_ngram_percentage": 0,
"overlap_percentage_total": "5.88%",
"max_area": "9pxˆ2",
"min_area": "9pxˆ2",
"total_area": "18pxˆ2",
},
},
),
(
((4, 5), (4, 8), (7, 8), (7, 5)),
((2, 3), (2, 6), (5, 6), (5, 3)),
"Some lovely title",
"", # empty title
1,
{
"overlapping_elements": ["1. NarrativeText(ix=1)", "0. Title(ix=0)"],
"parent_element": None,
"overlapping_case": ("partial overlap with empty content in 1. NarrativeText"),
"overlap_percentage": "11.11%",
"metadata": {
"largest_ngram_percentage": 0,
"overlap_percentage_total": "5.88%",
"max_area": "9pxˆ2",
"min_area": "9pxˆ2",
"total_area": "18pxˆ2",
},
},
),
(
((4, 5), (4, 8), (7, 8), (7, 5)),
((2, 3), (2, 6), (5, 6), (5, 3)),
"", # empty 1st title
"Some lovely title",
1,
{
"overlapping_elements": ["0. Title(ix=0)", "1. NarrativeText(ix=1)"],
"parent_element": None,
"overlapping_case": "partial overlap with empty content in 0. Title",
"overlap_percentage": "11.11%",
"metadata": {
"largest_ngram_percentage": 0,
"overlap_percentage_total": "5.88%",
"max_area": "9pxˆ2",
"min_area": "9pxˆ2",
"total_area": "18pxˆ2",
},
},
),
(
((4, 5), (4, 8), (7, 8), (7, 5)),
((2, 3), (2, 6), (5, 6), (5, 3)),
"Some lovely title",
"Something totally different here", # diff text
1,
{
"overlapping_elements": ["0. Title(ix=0)", "1. NarrativeText(ix=1)"],
"parent_element": None,
"overlapping_case": "partial overlap without sharing text",
"overlap_percentage": "11.11%",
"metadata": {
"largest_ngram_percentage": 0,
"overlap_percentage_total": "5.88%",
"max_area": "9pxˆ2",
"min_area": "9pxˆ2",
"total_area": "18pxˆ2",
},
},
),
(
((5, 6), (5, 10), (8, 10), (8, 6)), # diff coordinates
((1, 3), (2, 7), (6, 7), (5, 3)),
"Some lovely title",
"Some lovely text",
1,
{
"overlapping_elements": ["0. Title(ix=0)", "1. NarrativeText(ix=1)"],
"parent_element": None,
"overlapping_case": "Small partial overlap",
"overlap_percentage": "8.33%",
"metadata": {
"largest_ngram_percentage": 0,
"overlap_percentage_total": "3.23%",
"max_area": "20pxˆ2",
"min_area": "12pxˆ2",
"total_area": "32pxˆ2",
},
},
),
],
)
def test_catch_overlapping_and_nested_bboxes(
coords1, coords2, text1, text2, nested_error_tolerance_px, expectation
):
elements = [
Title(
text=text1,
coordinates=coords1,
coordinate_system=PixelSpace(width=20, height=20),
metadata=ElementMetadata(page_number=1),
),
NarrativeText(
text=text2,
coordinates=coords2,
coordinate_system=PixelSpace(width=20, height=20),
metadata=ElementMetadata(page_number=1),
),
]
overlapping_flag, overlapping_cases = utils.catch_overlapping_and_nested_bboxes(
elements,
nested_error_tolerance_px,
sm_overlap_threshold=10.0,
)
assert overlapping_flag is True
assert overlapping_cases[0] == expectation
def test_catch_overlapping_and_nested_bboxes_non_overlapping_case():
elements = [
Title(
text="Some lovely title",
coordinates=((4, 6), (4, 7), (7, 7), (7, 6)),
coordinate_system=PixelSpace(width=20, height=20),
metadata=ElementMetadata(page_number=1),
),
NarrativeText(
text="Some lovely text",
coordinates=((6, 8), (6, 9), (9, 9), (9, 8)),
coordinate_system=PixelSpace(width=20, height=20),
metadata=ElementMetadata(page_number=1),
),
]
overlapping_flag, overlapping_cases = utils.catch_overlapping_and_nested_bboxes(
elements,
1,
sm_overlap_threshold=10.0,
)
assert overlapping_flag is False
assert overlapping_cases == []
def test_only_returns_singleton_iterable():
singleton_iterable = [42]
result = utils.only(singleton_iterable)
assert result == 42
def test_only_raises_on_non_singleton_iterable():
singleton_iterable = [42, 0]
with pytest.raises(ValueError):
utils.only(singleton_iterable)
def test_calculate_shared_ngram_percentage_returns_null_vals_for_empty_str():
str1 = ""
str2 = "banana orange pineapple"
n = 2
percent, common_ngrams = utils.calculate_shared_ngram_percentage(str1, str2, n)
assert percent == 0
assert not bool(common_ngrams)
class DescribeGroupElementsByParentId:
"""Unit tests for group_elements_by_parent_id function."""
def it_groups_elements_by_parent_id_with_orphans_in_none_group(self):
e1 = Title("Title 1")
e1.metadata.parent_id = "parent_A"
e2 = NarrativeText("Child of A")
e2.metadata.parent_id = "parent_A"
e3 = NarrativeText("Orphan 1") # parent_id = None
e4 = Title("Title 2")
e4.metadata.parent_id = "parent_B"
e5 = NarrativeText("Orphan 2") # parent_id = None
elements = [e1, e2, e3, e4, e5]
result = utils.group_elements_by_parent_id(elements)
assert list(result.keys()) == ["parent_A", None, "parent_B"]
assert [e.text for e in result["parent_A"]] == ["Title 1", "Child of A"]
assert [e.text for e in result[None]] == ["Orphan 1", "Orphan 2"]
assert [e.text for e in result["parent_B"]] == ["Title 2"]
def it_assigns_orphans_to_previous_element_group_when_assign_orphans_is_true(self):
e1 = Title("Title 1")
e1.metadata.parent_id = "parent_A"
e2 = NarrativeText("Child of A")
e2.metadata.parent_id = "parent_A"
e3 = NarrativeText("Orphan 1") # parent_id = None
e4 = Title("Title 2")
e4.metadata.parent_id = "parent_B"
e5 = NarrativeText("Orphan 2") # parent_id = None
elements = [e1, e2, e3, e4, e5]
result = utils.group_elements_by_parent_id(elements, assign_orphans=True)
assert list(result.keys()) == ["parent_A", "parent_B"]
assert [e.text for e in result["parent_A"]] == ["Title 1", "Child of A", "Orphan 1"]
assert [e.text for e in result["parent_B"]] == ["Title 2", "Orphan 2"]
def it_keeps_first_orphan_in_none_group_when_assign_orphans_is_true(self):
e1 = NarrativeText("First orphan") # parent_id = None
e2 = Title("Title 1")
e2.metadata.parent_id = "parent_A"
e3 = NarrativeText("Orphan 2") # parent_id = None
elements = [e1, e2, e3]
result = utils.group_elements_by_parent_id(elements, assign_orphans=True)
assert list(result.keys()) == [None, "parent_A"]
assert [e.text for e in result[None]] == ["First orphan"]
assert [e.text for e in result["parent_A"]] == ["Title 1", "Orphan 2"]