461bf6fd40
CI / lint (3.11) (push) Has been cancelled
CI / lint (3.12) (push) Has been cancelled
CI / lint (3.13) (push) Has been cancelled
CI / shellcheck (push) Has been cancelled
CI / shfmt (push) Has been cancelled
CI / setup (3.11) (push) Has been cancelled
CI / setup (3.12) (push) Has been cancelled
CI / setup (3.13) (push) Has been cancelled
CI / check-licenses (3.12) (push) Has been cancelled
CI / test_unit (3.11) (push) Has been cancelled
CI / test_unit (3.12) (push) Has been cancelled
CI / test_unit (3.13) (push) Has been cancelled
CI / test_unit_no_extras (3.11) (push) Has been cancelled
CI / test_unit_no_extras (3.12) (push) Has been cancelled
CI / test_json_to_html (3.12) (push) Has been cancelled
CI / test_unit_no_extras (3.13) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.12, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.11, --extra xlsx) (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.12, --extra xlsx) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.11, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.13, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.11, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.12, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.13, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.11, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.12, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.13, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.11, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.12, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.13, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.11, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.12, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.13, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.11, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.12, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.13, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.11, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.12, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.13, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
Build And Push Docker Image / set-short-sha (push) Has been cancelled
Partition Benchmark / setup (push) Has been cancelled
Partition Benchmark / Measure and compare partition() runtime (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.13, --extra xlsx) (push) Has been cancelled
CI / test_ingest_src (3.12) (push) Has been cancelled
CI / test_json_to_markdown (3.12) (push) Has been cancelled
CI / changelog (push) Has been cancelled
CI / test_dockerfile (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build And Push Docker Image / build-images (linux/amd64, opensource-linux-8core) (push) Has been cancelled
Build And Push Docker Image / build-images (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build And Push Docker Image / publish-images (push) Has been cancelled
188 lines
6.4 KiB
Python
188 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import csv
|
|
from functools import cached_property
|
|
from typing import IO, Any, Iterator
|
|
|
|
import pandas as pd
|
|
|
|
from unstructured.chunking import add_chunking_strategy
|
|
from unstructured.common.html_table import HtmlTable
|
|
from unstructured.documents.elements import Element, ElementMetadata, Table
|
|
from unstructured.file_utils.model import FileType
|
|
from unstructured.partition.common.metadata import apply_metadata, get_last_modified_date
|
|
from unstructured.utils import is_temp_file_path
|
|
|
|
DETECTION_ORIGIN: str = "csv"
|
|
CSV_FIELD_LIMIT = 10 * 1048576 # 10MiB
|
|
|
|
|
|
@apply_metadata(FileType.CSV)
|
|
@add_chunking_strategy
|
|
def partition_csv(
|
|
filename: str | None = None,
|
|
*,
|
|
file: IO[bytes] | None = None,
|
|
encoding: str | None = None,
|
|
include_header: bool = False,
|
|
infer_table_structure: bool = True,
|
|
**kwargs: Any,
|
|
) -> list[Element]:
|
|
"""Partitions Microsoft Excel Documents in .csv format into its document elements.
|
|
|
|
Parameters
|
|
----------
|
|
filename
|
|
A string defining the target filename path.
|
|
file
|
|
A file-like object using "rb" mode --> open(filename, "rb").
|
|
encoding
|
|
The encoding method used to decode the text input. If None, utf-8 will be used.
|
|
include_header
|
|
Determines whether or not header info info is included in text and medatada.text_as_html.
|
|
infer_table_structure
|
|
If True, any Table elements that are extracted will also have a metadata field
|
|
named "text_as_html" where the table's text content is rendered into an html string.
|
|
I.e., rows and cells are preserved.
|
|
Whether True or False, the "text" field is always present in any Table element
|
|
and is the text content of the table (no structure).
|
|
"""
|
|
ctx = _CsvPartitioningContext.load(
|
|
file_path=filename,
|
|
file=file,
|
|
encoding=encoding,
|
|
include_header=include_header,
|
|
infer_table_structure=infer_table_structure,
|
|
)
|
|
|
|
csv.field_size_limit(CSV_FIELD_LIMIT)
|
|
with ctx.open() as file:
|
|
read_kw: dict = {"header": ctx.header, "sep": ctx.delimiter, "encoding": ctx.encoding}
|
|
# sep=None is not supported by the C engine; use Python engine to avoid ParserWarning.
|
|
if ctx.delimiter is None:
|
|
read_kw["engine"] = "python"
|
|
dataframe = pd.read_csv(file, **read_kw)
|
|
|
|
html_table = HtmlTable.from_html_text(
|
|
dataframe.to_html(index=False, header=include_header, na_rep="")
|
|
)
|
|
|
|
metadata = ElementMetadata(
|
|
filename=filename,
|
|
last_modified=ctx.last_modified,
|
|
text_as_html=html_table.html if infer_table_structure else None,
|
|
)
|
|
|
|
# -- a CSV file becomes a single `Table` element --
|
|
return [Table(text=html_table.text, metadata=metadata, detection_origin=DETECTION_ORIGIN)]
|
|
|
|
|
|
class _CsvPartitioningContext:
|
|
"""Encapsulates the partitioning-run details.
|
|
|
|
Provides access to argument values and especially encapsulates computation of values derived
|
|
from those values so they don't obscure the core partitioning logic.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
file_path: str | None = None,
|
|
file: IO[bytes] | None = None,
|
|
encoding: str | None = None,
|
|
include_header: bool = False,
|
|
infer_table_structure: bool = True,
|
|
):
|
|
self._file_path = file_path
|
|
self._file = file
|
|
self._encoding = encoding
|
|
self._include_header = include_header
|
|
self._infer_table_structure = infer_table_structure
|
|
|
|
@classmethod
|
|
def load(
|
|
cls,
|
|
file_path: str | None,
|
|
file: IO[bytes] | None,
|
|
encoding: str | None,
|
|
include_header: bool,
|
|
infer_table_structure: bool,
|
|
) -> _CsvPartitioningContext:
|
|
return cls(
|
|
file_path=file_path,
|
|
file=file,
|
|
encoding=encoding,
|
|
include_header=include_header,
|
|
infer_table_structure=infer_table_structure,
|
|
)._validate()
|
|
|
|
@cached_property
|
|
def delimiter(self) -> str | None:
|
|
"""The CSV delimiter, nominally a comma ",".
|
|
|
|
`None` for a single-column CSV file which naturally has no delimiter.
|
|
"""
|
|
sniffer = csv.Sniffer()
|
|
num_bytes = 65536
|
|
|
|
with self.open() as file:
|
|
# -- read whole lines, sniffer can be confused by a trailing partial line --
|
|
data = "\n".join(
|
|
ln.decode(self._encoding or "utf-8") for ln in file.readlines(num_bytes)
|
|
)
|
|
|
|
try:
|
|
return sniffer.sniff(data, delimiters=",;|").delimiter
|
|
except csv.Error:
|
|
# -- sniffing will fail on single-column csv as no default can be assumed --
|
|
return None
|
|
|
|
@cached_property
|
|
def header(self) -> int | None:
|
|
"""Identifies the header row, if any, to Pandas, by idx."""
|
|
return 0 if self._include_header else None
|
|
|
|
@cached_property
|
|
def encoding(self) -> str | None:
|
|
"""The encoding to use for reading the file."""
|
|
return self._encoding
|
|
|
|
@cached_property
|
|
def last_modified(self) -> str | None:
|
|
"""The best last-modified date available, None if no sources are available."""
|
|
return (
|
|
None
|
|
if not self._file_path or is_temp_file_path(self._file_path)
|
|
else get_last_modified_date(self._file_path)
|
|
)
|
|
|
|
@contextlib.contextmanager
|
|
def open(self) -> Iterator[IO[bytes]]:
|
|
"""Encapsulates complexity of dealing with file-path or file-like-object.
|
|
|
|
Provides an `IO[bytes]` object as the "common-denominator" document source.
|
|
|
|
Must be used as a context manager using a `with` statement:
|
|
|
|
with self._file as file:
|
|
do things with file
|
|
|
|
File is guaranteed to be at read position 0 when called.
|
|
"""
|
|
if self._file_path:
|
|
with open(self._file_path, "rb") as f:
|
|
yield f
|
|
else:
|
|
file = self._file
|
|
assert file is not None # -- guaranteed by `._validate()` --
|
|
# -- Be polite on principle. Reset file-pointer both before and after use --
|
|
file.seek(0)
|
|
yield file
|
|
file.seek(0)
|
|
|
|
def _validate(self) -> _CsvPartitioningContext:
|
|
"""Raise on invalid argument values."""
|
|
if self._file_path is None and self._file is None:
|
|
raise ValueError("either file-path or file-like object must be provided")
|
|
return self
|