fix(partition): classify text as an email address only when all of it is one

Merged
CH
12d ago
+31-4

The docstring of is_email_address() says it checks whether the text "is the email address" (unstructured/partition/text_type.py:319). But line 320 calls EMAIL_ADDRESS_PATTERN_RE.match, so only the start of the text must be an address. partition_text, partition_html, partition_docx and partition_pptx all send text through this check.

# base 9a376447, probe builds each document from the same sentence:
# "billing@example.com is the address for every invoice question you have."
$ python probe_email.py
text ['EmailAddress']
html ['EmailAddress']
docx ['EmailAddress']
pptx ['EmailAddress']

On this branch the same probe prints ['NarrativeText'] for all four. The change is fullmatch on the stripped text, with two new test_is_email_address cases and one partition_text test. #957 added EmailAddress to stop a bare address from becoming a Title. A bare address still gives EmailAddress. Both EmailAddress elements in test_unstructured_ingest/expected-structured-output still pass is_email_address(), so the fixtures stay the same.

Verification

# base 9a376447, python 3.12, uv sync --frozen --group test --group lint --extra docx --extra pptx
$ pytest test_unstructured/partition/test_text_type.py test_unstructured/partition/test_text.py test_unstructured/partition/html test_unstructured/partition/test_docx.py test_unstructured/partition/test_pptx.py -q -n 4
3 failed, 895 passed   # base
3 failed, 898 passed   # this branch
# the 3 are the *_last_modified_from_filesystem tests, failing identically on both runs in an exported tree
$ pytest test_unstructured/partition/test_text_type.py test_unstructured/partition/test_text.py -q   # text_type.py reverted, tests kept
3 failed, 186 passed   # both new test_is_email_address cases and the partition_text test
$ ruff check . && ruff format --check .   # ruff 0.15.10
All checks passed!
353 files already formatted
$ make check-version
version sync would make no changes to unstructured/__version__.py.

No issue exists for this, and no open pull request touches is_email_address(). The CHANGELOG entry goes under 0.27.34-dev2. The existing test keeps "Fake@gmail.com" at False, so a mixed-case address is still a Title. This diff does not change that.

With fullmatch, any text after the address disqualifies it. Examples are a trailing period (billing@example.com.), a second address (a@x.com, b@y.com) and a signature line such as jane@acme.com | +1 555 0100. Before, the prefix match made all three an EmailAddress. The strict check keeps the element from carrying text that is not the address. @cragwolfe, should trailing punctuation still count as an EmailAddress?