Skip to content

Annotated metadata#389

Open
Timbers-Me-Shiver wants to merge 7 commits into
pschanely:mainfrom
Timbers-Me-Shiver:annotated-metadata
Open

Annotated metadata#389
Timbers-Me-Shiver wants to merge 7 commits into
pschanely:mainfrom
Timbers-Me-Shiver:annotated-metadata

Conversation

@Timbers-Me-Shiver
Copy link
Copy Markdown

Summary

  • Interpret typing.Annotated metadata as CrossHair conditions:
    • parameters -> preconditions
    • returns -> postconditions (via return)
    • class attributes -> invariants on self.
  • Support annotated_types metadata (Gt / Ge / Lt / Le / MultipleOf / MinLen / MaxLen / Predicate)
    including grouped metadata (Interval / Len).
  • Accept custom metadata via is_valid() or any 1‑arg callable.
  • Add tests and include annotated-types in dev extras.

Notes

  • Avoids typing.get_type_hints() to stay safe under tracing.

Tests

  • PRE_COMMIT_HOME=.pre-commit-cache PATH="$(pwd)/.venv/bin:$PATH" ./.venv/bin/pre-commit run --all-files
  • PYTHONHASHSEED=0 ./.venv/bin/python -m pytest crosshair/condition_parser_test.py -q

Issue #201 comment

I’ve prepared a PR that adds typing.Annotated metadata support to CrossHair.
It treats Annotated metadata as contracts (params -> preconditions, returns ->
postconditions, class attributes -> invariants), supports annotated_types
metadata (Gt / Ge / Lt / Le / MultipleOf / MinLen / MaxLen / Predicate + Interval / Len via
GroupedMetadata), and accepts custom metadata via is_valid() or 1‑arg callables.

Tests:

  • PRE_COMMIT_HOME=.pre-commit-cache PATH="$(pwd)/.venv/bin:$PATH" ./.venv/bin/pre-commit run --all-files
  • PYTHONHASHSEED=0 ./.venv/bin/python -m pytest crosshair/condition_parser_test.py -q

@Timbers-Me-Shiver
Copy link
Copy Markdown
Author

Separate to above, noted the following failures during test suite run:

Tests run:

  • PRE_COMMIT_HOME=.pre-commit-cache PATH="$(pwd)/.venv/bin:$PATH" ./.venv/bin/pre-commit run --all-files
  • PYTHONHASHSEED=0 PATH="$(pwd)/.venv/bin:$PATH" ./.venv/bin/python -m pytest crosshair/condition_parser_test.py -q
  • env PYTHONHASHSEED=0 PATH="$(pwd)/.venv/bin:$PATH" ./.venv/bin/python -m pytest -n auto --dist worksteal (fails)

Full suite failures (no watcher changes in this PR):

  • crosshair/watcher_test.py::test_added_file_to_empty_dir (assert len(results) == 1)
  • crosshair/watcher_test.py::test_auditwall_unblock[--unblock=subprocess.Popen:echo-MessageType.CONFIRMED]
  • crosshair/watcher_test.py::test_auditwall_unblock[--unblock=subprocess.Popen:xyz-MessageType.EXEC_ERR]

Summary: 3 failed, 11919 passed, 5 skipped, 3 xfailed.

Support typing_extensions get_origin/get_args for
Annotated metadata and skip typing objects as predicates.
Fix Unpack expansion under Python 3.9 by relying on
compat get_args in annotated metadata flattening.
Handle Unpack aliases more robustly across typing/typing_extensions
and rely on Black formatting from pre-commit.
@Timbers-Me-Shiver Timbers-Me-Shiver marked this pull request as ready for review January 12, 2026 07:34
Copy link
Copy Markdown
Owner

@pschanely pschanely left a comment

Choose a reason for hiding this comment

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

Thank you SO MUCH for doing this! (and for your patience with my slow response) I've got just a few minor comments below.

Oh also, feel free to add yourself to the contributors list in doc/source/contributing.rst and link it to whatever you like!

Comment on lines +332 to +333
# - Avoid typing.get_type_hints(): CrossHair may run under tracing where
# interacting with typing internals can raise.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I wouldn't have thought that tracing should be on when we're extracting conditions. (and if it breaks under tracing, that's also a bug I'd love to file)
Do you remember under what circumstances you saw an error?

try:
import typing_extensions as _typing_extensions # type: ignore
except ModuleNotFoundError:
_typing_extensions = None # type: ignore
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

typing_extensions is an official dependency, so we don't need to guard the import (or handle None cases below)

PRECONDITION,
_eval_param,
filename,
first_line,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

In an ideal world, we'd find the line number of the annotation, in case the signature extends over multiple lines. Unfortunately, I think that's going to be pretty annoying, so maybe we call this close enough!

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