Skip to content

Add type annotations - #264

Merged
obukhov-sergey merged 3 commits into
mailgun:masterfrom
andersk:type-annotations
Sep 30, 2026
Merged

obukhov-sergey merged 3 commits into
mailgun:masterfrom
andersk:type-annotations

Conversation

@andersk

@andersk andersk commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Annotate the talon package, train.py, and tests, and check them with mypy --strict in a new CI job. Ship a py.typed marker so downstream type checkers use the inline annotations (PEP 561).

andersk and others added 3 commits September 25, 2026 07:58
root.iter() also yields comments and processing instructions, whose
tag is a factory function rather than a string, so calling
child.tag.rfind() raised AttributeError. Skip non-string tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The test was missing its test_ prefix, so pytest never collected it,
and it shadowed the binary_regex_match helper star-imported from
talon.signature.learning.helpers. Its prog parameter was unused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Annotate the talon package, train.py, and tests, and check them with
mypy --strict in a new CI job. Ship a py.typed marker so downstream
type checkers use the inline annotations (PEP 561).

`from __future__ import annotations` lets modern annotation syntax work
on Python 3.7. XPath extension function contexts are described by a
TYPE_CHECKING-only Protocol, since typing.Protocol needs Python 3.8.

Small code changes needed for type checking:

- Give variables that changed type partway through separate names.
- Return False instead of falling through to None from cut_*
  functions, and return None explicitly from is_splitter.
- Return a real bool from is_signature_line instead of a one-element
  numpy array.
- Build the parser with lxml.html.html5parser.HTMLParser, the same
  html5lib parser and lxml tree builder that the stubs expect.
- Check for a missing parent before removing elements in cut_by_id and
  html_tree_to_text.
- Re-export talon.signature.extract explicitly, so it works under
  --no-implicit-reexport.
- Fix the existing annotations of cssselect and _contains_charset_spec.

Test changes: pass True rather than 1 for sender_known, patch the
regex module directly rather than through talon's namespace, and
simplify body_iterator, whose str branch could never run.

mypy, types-lxml, types-regex, and types-six are added to
test-requirements.txt for Python 3.10 and newer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread talon/quotations.py
Comment on lines +26 to +30
class _XPathContext(Protocol):
"""The context lxml passes to XPath extension functions."""

@property
def context_node(self) -> _Element: ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened an upstream PR for this (but writing this as a Protocol means we don’t need to block on it):

@obukhov-sergey obukhov-sergey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

talon/py.typed opts every downstream typechecker into these annotations, but the stub packages live only in test-requirements.txt (types-lxml, types-regex, types-six). A core install does not pull them in. Public signatures use lxml.etree._Element, and utils.py imports regex and six, so mypy on a dependent project fails until those stubs are installed. Talon's own [tool.mypy.overrides] for sklearn and joblib apply only when Talon is the project being checked.

The same import also drags in the ML extra. talon/signature/__init__.py re-exports extract like this:

if TYPE_CHECKING:
    from talon.signature import extraction
    from talon.signature.extraction import extract as extract

Type checkers follow that import. extraction.py imports numpy at module level, so import talon.signature.bruteforce — which is supported without talon[ml] — fails typechecking unless numpy is installed.

Stub extract in the TYPE_CHECKING block instead of importing extraction, and document a typing extra for types-lxml, types-regex, and types-six. Talon's CI already typechecks extraction.py directly, so the signatures stay checked without forcing that import on core users.

@andersk

andersk commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

I can’t reproduce the talon.signature.bruteforce issue; it typechecks correctly even without numpy installed.

$ echo 'import talon.signature.bruteforce' >test.py
$ echo 'reveal_type(talon.signature.bruteforce.extract_signature)' >>test.py
$ uv run --with=mypy,talon@git+https://github.com/andersk/talon.git@type-annotations mypy --strict test.py
test.py:2: note: Revealed type is "def (msg_body: str) -> tuple[str, str | None]"
Success: no issues found in 1 source file
$ uv run --with=pyright,talon@git+https://github.com/andersk/talon.git@type-annotations pyright test.py
/tmp/test.py
  /tmp/test.py:2:13 - information: Type of "talon.signature.bruteforce.extract_signature" is "(msg_body: str) -> tuple[str, str | None]"
0 errors, 0 warnings, 1 information

Type checkers resolve imported types to Any when annotations aren’t available; that’s not an error. It’s possible to configure them to complain when such an imported Any is actually used, but that’s not the case here.

types-six is not used in any exposed signatures. (We can easily remove six since we already dropped Python 2 support, but that’s a separate topic.)

types-regex is only used in the types of internal utilities: talon.quotations.is_splitter, talon.helpers.binary_regex_search, talon.helpers.binary_regex_match, and constants like talon.quotations.SPLITTER_PATTERNS. I don’t think users interact with any of these directly.

types-lxml is used in the type of talon.quotations.extract_from_html_tree(html_tree). However, a typed user who’s calling that will already have types-lxml installed in order to construct html_tree in the first place.

So I think it’s unnecessary to add any public dependencies here.

@obukhov-sergey
obukhov-sergey merged commit e463ceb into mailgun:master Sep 30, 2026
11 checks passed
@obukhov-sergey

Copy link
Copy Markdown
Member

@andersk fyi, added ci matrix for mypy, core talon install failed:

Снимок экрана 2026-10-01 004936

because of numpy:

Снимок экрана 2026-10-01 005030

fixed it in #266

@andersk

andersk commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Sure, if you like. It doesn’t affect users either way: import-not-found errors shown when checking the talon project itself are not shown when checking downstream user projects. And developers working on the talon project should be working with all dependencies installed.

@andersk
andersk deleted the type-annotations branch September 30, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants