| Age | Commit message (Collapse) | Author | Files | Lines |
|
+ operator-algebra coverage (#288)
Completes the remaining non-docs library items on the v1.0.0 milestone:
a genuinely multi-locale `phone` validator, real per-locale corpus
hardening, and an end-to-end coverage layer that exercises every shipped
validator and the operator algebra.
## Multi-locale phone (#154)
`phone` is rebuilt as a table-driven multi-locale model: an optional `+`
or `00` prefix, two to eight digit groups of one to four digits each,
single-character space/dot/dash separators, and any group optionally
parenthesised. It now accepts the display forms it previously missed —
leading and inline parenthesised area codes such as `(555) 123-4567` and
`+44 (0)20 7946 0958`, and variable national grouping from three-group
North American through five-group French — plus two-to-six-digit service
and short codes. Doubled separators, bare prefixes, and letters are
still rejected. The corpus grows from a seven-string smoke test to
twenty-one real numbers across eleven locales with ten adversarial
rejects.
## Postal locale coverage (#155)
Multi-locale postal codes are delivered by the existing `postal`
validator; this pins it down with a twenty-code corpus spanning North
America, Europe, Asia, and Oceania (including alphanumeric UK, Irish,
Canadian, and Dutch shapes) with ten adversarial rejects. `zip_code`
stays deliberately US-scoped, with `postal` as its locale-complete
counterpart.
## Validator snapshots (#191)
Every per-validator migration to a callable `Pattern` has landed, so
regenerating the built-in snapshot corpus is a no-op apart from
`phone`'s new emitted shape, which is refreshed here.
## End-to-end coverage of every construct (#258)
- Every registered library validator now round-trips through both dict
and JSON serialization with its emitted regex preserved — a property
test over the full validator set, alongside the existing compiled-source
invariant.
- The operator algebra (`+`, `|`, and `.use()`) is asserted identical to
its fluent-chain equivalent across every character-class constant, at
both the emitted-string and compiled-matcher level, so the two surfaces
can never drift.
- Hardening corpora expand beyond the smoke-test handful to `phone`,
`postal`, `semver`, `slug`, `hostname`, `port`, `isbn`, and `vin`, each
with real-world accepts and adversarial rejects.
Closes #154, Closes #155, Closes #191, Closes #258
|
|
postal/semver/slug/hostname/port/isbn/vin corpora + all-validator round-trip and operator-algebra equivalence coverage
|
|
The public-surface migration gate needs a pull request's base/head SHAs,
so it can't run on a plain push — it evaluates its `if` to false and
shows up as a **skipped** check on every push, main included. This moves
it out of the shared `[push, pull_request]` workflow into its own `on:
pull_request` workflow, so on push runs the job simply doesn't exist and
no skipped check appears.
On pull requests it runs exactly as before: the bootstrap exemption
(skip when `.public-surface` is first introduced) and the
`[no-migration]` title escape hatch are both intact.
|
|
stops showing as a skipped check on pushes
|
|
codebase-wide strict typing (#286)
Ships the deprecation-and-license milestone — a written deprecation
policy, the rewritten 0.3 → 1.0 upgrade guide, the relicense to MIT, and
the machinery that keeps the public surface and upgrade notes honest —
alongside a codebase-wide pass to fully strict static typing.
## Deprecation policy & upgrade guide
- A written deprecation policy covering the stub pattern, removal
cadence, message format, and the behavior-change rule.
- The 0.3 → 1.0 upgrade guide rewritten against the shipped breaking
surface, with frozen section anchors so a warning can link straight to
its migration note.
- `docs/upgrading/` scaffolding wired into the documentation table of
contents.
- Every deprecated stub carries a warning-assertion harness, and a gate
parses each warning's URL to guarantee the anchor it points at exists in
the guide.
## Changes fragments & public-surface snapshot
- A one-file-per-change `changes/` fragment format plus a small
standard-library generator that assembles the changelog and
upgrade-guide sections from them.
- A committed `.public-surface` snapshot of the exported API,
regenerated whenever the surface moves.
- A gate that fails a pull request whose public surface shifts without
an accompanying `changes/` fragment or an explicit no-migration marker.
## Relicense to MIT
- The changelog records the relicense in a single cross-linked entry,
and the upgrade guide carries the matching license anchor.
## Trusted Publishing
- Releases publish through Trusted Publishing, retiring the long-lived
upload token.
## Fully strict typing
- The library, tests, and tooling now pass fully strict static
type-checking end to end, with zero inline suppressions and zero blanket
rule overrides.
- Optional third-party integrations that ship no type information are
reached through typed boundaries and local stubs, keeping the strict
guarantee intact without importing them at module load.
- Helper-level tests now exercise their behavior through the public API
rather than reaching into private functions, and unreachable defensive
branches were retired so every line stays covered.
Closes #199, Closes #200, Closes #201, Closes #202, Closes #203, Closes
#204, Closes #205, Closes #206, Closes #207, Closes #228, Closes #235
|
|
introduced
|
|
with zero suppressions
|
|
snapshot + CI migration gate, deprecation-warning + anchor harnesses, Trusted Publishing, relicense CHANGELOG entry
|
|
with frozen anchors; add upgrading index + deprecation policy
|
|
extras (#284)
Ships the framework-integrations bundle end to end so an
:class:`edify.Pattern` can drop straight into the field-validation
layers of the three Python frameworks most teams actually use.
## Integration modules
- **`edify.integrations.pydantic`** — `pattern_validator(pattern)`
returns a Pydantic-compatible validator callable;
`pattern_field(pattern)` returns an `Annotated[str,
AfterValidator(...)]` type you drop into a `BaseModel` field.
- **`edify.integrations.fastapi`** — `pattern_query(pattern, ...)` and
`pattern_path(pattern, ...)` return `fastapi.Query` / `fastapi.Path`
values pinned to the pattern's emitted regex string, with `default=` and
every other FastAPI kwarg forwarded through.
- **`edify.integrations.django`** — `pattern_validator(pattern,
message=..., code=...)` returns a
`django.core.validators.RegexValidator` pinned to the pattern's regex
source, with the message default derived from the pattern.
Every integration follows the same deferred-import pattern that
`edify[regex]` uses: `import edify.integrations.pydantic` (etc.)
succeeds without the framework installed; the first helper call resolves
the framework lazily and raises `MissingIntegrationDependencyError`
(annotated summary + pointer block + `= note:` + `help:` line) telling
the caller to `pip install edify[<framework>]`.
## Extras + optional-dependencies
- `[project.optional-dependencies]` gains `pydantic =
["pydantic>=2.0"]`, `fastapi = ["fastapi>=0.100"]`, `django =
["django>=4.2"]`, and `all = [everything]`.
- The `dev` group installs all three frameworks so local + CI runs cover
the integrations without gating on the extras.
## CI install-per-extra verification
- New matrix job `install-with-integration-extra` runs one entry per
extra (`pydantic`, `fastapi`, `django`, `all`).
- Each entry installs into a fresh venv, imports the integration module,
and drives a small probe script that verifies the helper accepts a
matching value and rejects a non-matching value.
- Same shape as the existing `install: edify[regex]` verification — any
drift in the `[project.optional-dependencies]` declarations fails
loudly.
## Sphinx autodoc
- New `docs/integrations/index.rst` page with `.. automodule::` blocks
for each of the three integration modules.
- Wired into `docs/index.rst` toctree.
- `.readthedocs.yml` installs `.[all]` so autodoc always has every
framework annotation available to resolve.
Closes #220, closes #221, closes #222, closes #223, closes #224, closes
#225, closes #226, closes #227.
|
|
so PyPy and every CPython matrix entry reaches 100%
|
|
hoist in-function imports, rename compound/folder-repeat files, replace generic-exception raises, bind chained comprehensions, strip __init__ docstrings, remove pragmas
|
|
deferred imports + Sphinx autodoc section + per-extra CI install verification
|
|
real-world corpus hardening + guarantee docstrings (#283)
Ships the validator-hardening epic and splits the bench-baseline
regenerate job into its own workflow.
## New validators
- **`ipv4`** — strict IPv4 dotted-quad. Splits the shape out of the
unified `ip` for callers who need v4-only.
- **`ipv6`** — every documented IPv6 form: full 8-group,
`::`-compressed, link-local `%zone`, IPv4-mapped `::ffff:1.2.3.4`, and
the hybrid IPv4-suffix. Splits the shape out of `ip` the same way.
- **`iso_date`** — strict ISO 8601 date shape `YYYY-MM-DD`. Distinct
from the existing `datetime` (which includes a time component) and the
loose `date` (which accepts many separators).
- **`zip_code`** — US ZIP or ZIP+4 postal-code shape.
- **`email_rfc_5322`** — the strict RFC 5322 mailbox subset already
inside `email`, exposed as its own callable so downstream callers can
pick permissive vs strict semantics without composing themselves.
## Real-world corpus hardening
- **`tests/library/corpora/<name>.toml`** — per-validator TOML files
with `accepts` and `rejects` lists (233 cases across 14 validators).
Adding a new validator just means dropping in a new TOML.
- **`tests/library/hardening.test.py`** — parametrized test that walks
every corpus file, resolves the validator from `edify.library`, and
asserts the boolean verdict matches the expected side. `pytest -k
hardening` runs the whole corpus in under half a second.
- Coverage: `email`, `email_rfc_5322`, `url`, `ipv4`, `ipv6`, `mac`,
`uuid`, `guid`, `ssn`, `date`, `iso_date`, `phone`, `zip_code`,
`password`.
## Guarantee documentation pass
- Every hardened validator's module docstring now spells out what it
does and does not guarantee — shape vs semantics, anchoring, case
sensitivity, structural exclusions. Callers no longer have to grep the
regex or read the source to know whether `mac(...)` accepts EUI-64 or
whether `iso_date(...)` validates calendar correctness.
- The `password` validator gets a first-class guarantee block covering
the default policy (min length, class thresholds, special-char set) and
the passphrase-strength and non-ASCII limits it deliberately does not
check.
## CI hygiene — split bench-baseline job
- The `bench: regenerate runner baseline` job moves out of `build` into
its own `.github/workflows/bench-baseline.yml`. It only registers when
workflow_dispatch fires with a typed `YES` confirmation.
- Every push and pull_request stops surfacing the two skipped
baseline-regen checks that were appearing on every commit.
- The runtime behavior is unchanged: baselines are still
runner-generated and committed back with `contents: write` permission.
Closes #78, closes #152, closes #153, closes #156, closes #157, closes
#158, closes #159, closes #160, closes #161, closes #162, closes #163,
closes #164, closes #165, closes #166, closes #167, closes #168, closes
#169.
|
|
real-world corpus hardening across 14 patterns + guarantee docstrings
|
|
stops showing as a skipped check on every push
|
|
advisory CI job, Codecov PR comments (#282)
Ships the CI quality-gates epic end to end.
## Coverage discipline
- **Global coverage enforcement.** Adds `--cov=edify
--cov-fail-under=100` to `[tool.pytest.ini_options].addopts` so every
pytest run (local and CI) enforces the gate — no separate matrix entry,
no way to accidentally run the suite without measuring coverage.
- **`exclude_also`** covers `if TYPE_CHECKING:`, `@overload`, and `raise
NotImplementedError` — the three shapes where coverage-target lines are
truly unreachable.
- **Anti-vacuous-green test.** `tests/discipline/coverage.test.py`
asserts `[tool.coverage.run].source` resolves to exactly `["edify"]` and
that the tree contains real non-`__init__` source files. Catches the
source-path misconfiguration that makes the gate pass green over zero
measured lines.
- **`# pragma: no cover` discipline.**
`tests/discipline/pragmas.test.py` uses `tokenize` to walk every Python
file, finds every pragma comment, and fails CI if any lacks an inline
reason after the pragma text. File-level pragmas are banned outright.
## Hypothesis harness + property tests
- **Conftest profiles.** `tests/conftest.py` registers `dev` (50
examples, default), `ci` (300 examples), and `nightly` (2000 examples
with verbose shrinker), selected via `EDIFY_HYPOTHESIS_PROFILE`.
- **`tests/property/emitted.test.py`** — Property: emitted regex matches
a hand-rolled reference for `.string()`, `.any_of_chars()`, and
`.exactly(n)` over digit/word/letter across the alphabet.
- **`tests/property/roundtrip.test.py`** — Property:
`Pattern.from_dict(pattern.to_dict())` and
`Pattern.from_json(pattern.to_json())` roundtrip every generated pattern
(recursive strategy covering strings, character classes, capture / group
/ one_or_more / optional wrappers).
- **`tests/property/quantifiers.test.py`** — Property: every quantifier
chain call produces exactly one quantifier suffix in the emitted pattern
— no chain silently drops a quantifier.
- **`tests/property/parsing.test.py`** — Property:
`RegexBuilder.from_regex(source).to_regex_string()` roundtrips
behaviorally — the rebuilt chain matches the same corpus as the original
raw regex.
## Benchmarks
- **`tests/bench/` scaffolding** with `[dependency-groups] bench =
["pytest-benchmark>=4.0"]` — no runtime deps added, and
`--ignore=tests/bench` in addopts so contributors without the bench
group don't hit unrecognized-option errors.
- **Case suite.** Simple (`digit().one_or_more()`), medium
(hex-number-class with anchors + bounded quantifier), complex (composed
URL shape with nested groups + lookaround + alternation over multi-char
literals). Each case has a matches / near-matches / non-matches corpus
so the match hot path is covered end to end.
- **Compile + match benches.** Two files parametrize the case suite
across `to_regex()` / `to_regex_string()` and across the three
match-kind corpora.
- **Cold/warm ratio gate** (`tests/builder/cold_warm.test.py`) — asserts
the warm `.to_regex()` call is at least 10× cheaper than the cold call.
Hardware-independent ratio, deterministic blocking signal for the
item-41A lazy compile cache.
## CI wiring
- **Bench job** — advisory-only, runs `pytest tests/bench/` with `||
true` against a committed `tests/bench/baseline.json` when it exists.
Never a required status check.
- **`workflow_dispatch` baseline regeneration** — a manual-dispatch job
(with `regenerate_bench_baseline: true` input) runs `pytest
--benchmark-json=tests/bench/baseline.json` on the runner and commits it
back to the branch. Baselines are always runner-generated, never local.
- **Codecov PR comments** — `codecov.yml` at the repo root enables
`comment.behavior: default` with the diff / flags / files layout so
every PR shows the coverage delta in a review comment. Project + patch
statuses both target 100% with zero threshold.
Closes #83, closes #198, closes #238, closes #239, closes #240, closes
#241, closes #242, closes #243, closes #244, closes #245, closes #246,
closes #247, closes #248, closes #249, closes #250, closes #251.
|
|
no end-to-end test can reach it there
|
|
advisory CI job + workflow_dispatch baseline + codecov PR comments
|
|
roundtrips, quantifier count parity, reverse parser
|
|
and pragma discipline tests
|
|
structured open-frame errors (#281)
Ships the compile-path correctness epic + the build-time validation epic
end to end.
## Snapshot suite
- **Snapshot helper.** `edify/testing/snapshots.py` — an ~80-line
difflib-based helper (`assert_snapshot(actual, path)` +
`SnapshotMismatchError` / `SnapshotMissingError`) with
`EDIFY_UPDATE_SNAPSHOTS=1` regenerate mode. Publicly re-exported through
`edify.testing`.
- **Library snapshots.** Every one of the 223 public library validators
emits a committed `.regex` snapshot under `tests/snapshots/library/`.
Any compile-path change that touches the emitted form surfaces here
first.
- **Doc snapshots.** Every `.. code-block:: python` block across `docs/`
is exec-ed in a fresh edify-populated namespace and its resulting
`Regex` / `Pattern` bodies are snapshotted under
`tests/snapshots/docs/<file-stem>/<line>.regex`. Broken blocks pinned to
planned validators (`email_rfc_5322`, `ipv4`, `ipv6`, `iso_date`, `zip`)
are explicitly deferred to the docs rewrite (epic #81).
- **Edge-case fixtures.** Deeply-nested alternation, combined
lookaround, item-4 named-backref, item-6 ReDoS-flagged constructs,
hex/email/composition — 8 fixtures under `tests/snapshots/fixtures/`. A
parallel test asserts the ReDoS warning fires for the nested-quantifier
fixture.
- **Orphan detection.** A discipline test walks all three snapshot roots
and fails CI if any snapshot has no live source (library Pattern /
registered fixture / discovered code-block). The reverse direction
(source without snapshot) is caught by `SnapshotMissingError` in the
per-source test.
## Compile-path invariants
- **Insertion-order determinism** pinned: `any_of_chars("zyxabc321")`
emits `[zyxabc321]` deterministically across runs; a discipline test
defends against future set/dict-backed rewrites.
- **`to_regex_string() == to_regex().pattern`** pinned per registered
library Pattern (223 parametrized checks). Item 5's regex-module backend
was the change most likely to silently split these — the invariant now
holds them together.
## Char-class escaping normalization (breaking)
- Introduces `edify.compile.escape.escape_for_char_class(value)` — the
minimal correct form. Inside `[...]`, only `\`, `]`, `^` (first
position), and `-` (interior position) are escaped; everything else is a
literal.
- `any_of_chars` and `anything_but_chars` now use the minimal escaper;
`string`, `char`, `anything_but_string` keep the full `re.escape` since
they live outside a class.
- **Before:** `any_of_chars('#?!@$%^&*-')` → `[\#\?!@$%\^\&\*\-]`.
**After:** `[#?!@$%^&*-]`. Match behavior is identical (equivalence
corpus test asserts this).
## AST walker substrate + ReDoS detector
- **`edify.elements.walk.walk_elements`** — the shared depth-first
walker that yields every `BaseElement` in an emission-order tree.
Serialize already had one; extracted, generalized, and reused by ReDoS.
- **Build-time ReDoS detector**
(`edify.compile.redos.warn_on_redos_constructs`) — walks the element
tree at `to_regex()` time and emits `ReDoSWarning` when it finds the
classic `(x+)+` shape (an unbounded quantifier wrapping a single-child
group whose one child is itself an unbounded quantifier). The check is
intentionally conservative — it requires the group's children tuple to
have length one, so composite patterns like `(?:X)*(?:Y*)*` aren't
false-positived.
## Structured open-frame stack in error messages
- `CannotCallSubexpressionError.__init__(open_frames:
tuple[OpenFrameInfo, ...])` — lists every open frame innermost-first,
one line per frame, with its opener call site (`filename:line:col`).
Named captures reproduce the declared name
(`.named_capture("username")`).
- `DanglingQuantifierError.__init__(pending_quantifier_name,
pending_quantifier_call_site)` — names the specific queued quantifier
(`.exactly(3)`) and points at its call site.
- `edify.builder.diagnose.describe_open_frames(state)` — the shared
helper both terminals and subexpression use.
## Breaking changes
- `any_of_chars` and `anything_but_chars` emit the minimal-correct form,
not the over-escaped form. Match behavior is unchanged (equivalence
corpus test verifies), but any caller comparing the emitted string
against a hard-coded expected value with `\.`/`\-` etc. needs to drop
the redundant backslashes.
- `CannotCallSubexpressionError.__init__` now takes
`tuple[OpenFrameInfo, ...]` instead of a bare string. Direct
constructors need updating.
- `DanglingQuantifierError.__init__` accepts optional
`pending_quantifier_name` / `pending_quantifier_call_site` kwargs; the
bare `DanglingQuantifierError()` call still works.
Closes #72, closes #98, closes #99, closes #100, closes #101, closes
#102, closes #103, closes #104, closes #105, closes #106, closes #73,
closes #107, closes #114, closes #192.
|
|
disassembler bug
|
|
ReDoSWarning at build time
|
|
DanglingQuantifierError names the pending quantifier
|
|
|
|
named-backref, and ReDoS edge fixtures
|
|
regex drift
|
|
|
|
equivalence corpus
|
|
to_regex().pattern invariants
|
|
regenerate mode
|
|
serialize/load
|
|
and reverse parser (#280)
Ships the opt-in regex-engine backend and the builder-ergonomics polish
end to end.
## Optional regex-engine backend
- Introduces `edify/compile/backend.py` — a small backend layer that
owns the "compile a rendered pattern string with these flags" operation
per engine.
- `edify.RegexBuilder().*.to_regex(engine="re" | "regex")` now actually
routes: `"re"` uses the stdlib :mod:`re` module (default, unchanged);
`"regex"` uses the third-party `regex` module.
- The `regex` module is a **deferred import** — `import edify` never
imports it. When the caller selects `engine="regex"` without the extra
installed, they get a clean, annotated `MissingRegexBackendError`
telling them to `pip install edify[regex]`, not a `ModuleNotFoundError`
deep in a traceback.
- Under `engine="regex"`, **variable-width lookbehind** compiles cleanly
(`assert_behind` bodies with quantifiers like `.between(1,
3).string("foo")` no longer fail). Under `engine="re"`, the compile path
catches stdlib re's cryptic "look-behind requires fixed-width pattern"
and re-raises `VariableWidthLookbehindNotSupportedError` — an annotated
message that names the construct and points the caller at
`engine="regex"`.
- Every match method on `Regex` (`match`, `search`, `fullmatch`,
`findall`, `finditer`, `sub`, `subn`, `split`) accepts a per-call
`timeout=` kwarg. Under `engine="regex"` the value flows through to the
underlying pattern; under `engine="re"` it raises
`TimeoutNotSupportedByEngineError`.
- Two new CI jobs — one installs edify without the extra and verifies
both the friendly ImportError path and the re engine's happy path; the
other installs `edify[regex]` and verifies both engines compile plus the
variable-width-lookbehind fixture only compiles under `engine="regex"`.
## Builder ergonomics
- **Per-instance lazy compile cache.** The first no-kwargs `.to_regex()`
(or match verb) compiles once and caches the resulting `Regex`; every
subsequent no-kwargs call returns the same object (`builder.to_regex()
is builder.to_regex()`). A chain step or `fork()` produces a fresh
builder with its own empty cache. Kwarg calls bypass the cache and
always compile fresh.
- **Match-verb surface closed at five.** `MatcherMixin` now exposes
exactly `test` / `match` / `search` / `findall` / `sub` on the builder.
The four less-common verbs (`fullmatch`, `finditer`, `subn`, `split`)
are reached through `.to_regex()` on the `Regex` wrapper. A discipline
test asserts the closed surface and the absence of every other
`re.Pattern` attribute (`groups`, `groupindex`, `pattern`, `flags`).
- **`Pattern.__call__` delegates to `.test`** — so a `Pattern` doubles
as a validator callable (`email(value) -> bool`) without an intermediate
`.test` step.
- **Testing helpers.** `.assert_matches([...])` and
`.assert_rejects([...])` raise annotated `PatternDidNotMatchInputsError`
/ `PatternMatchedRejectedInputsError` (both subclasses of
`AssertionError` so pytest introspects them exactly like a bare
`assert`).
- **Match wrapper.** Every match verb now returns an edify `Match` that
exposes named captures as attributes — `m.username` and
`m.captures.username` — while delegating everything else (`group()`,
`groupdict()`, `span()`, ...) to the underlying `re.Match` via
`__getattr__`. `re.Pattern.sub`/`subn` callables receive the wrapped
`Match`.
- **`RegexBuilder.from_regex(pattern)` reverse parser.** Converts a raw
regex string back into a builder chain via `re._parser.parse`, covering
the common construct set (literals, character classes with
ranges/categories, quantifiers greedy + lazy including
`?`/`*`/`+`/`{n}`/`{m,n}`, capture / named-capture / non-capturing
groups, all four lookarounds, alternation over literals, anchors,
categories). Anything unrecognized raises the annotated
`UnsupportedReverseParseError`.
- **Per-call allocation win.** Cached `os.path.abspath` for
caller-context filenames, cached `co_positions()` per code object, and
moved `source_line` reading behind a lazy property on `CallerContext`.
Chain-step allocation is ~4.6× faster on a 300-step build/compile
microbenchmark (9.4s → 2.0s / 1000 iterations).
- **Immutability contract documented.** Dedicated section in the API
reference + one-liner in the README explaining that every chain method
returns a new builder, that branching (`base.exactly(2).digit()` vs
`base.end_of_input()`) is always safe, and that repeat `.to_regex()`
returns the cached instance.
## Breaking changes
- `MatcherMixin` no longer exposes `fullmatch`, `finditer`, `subn`,
`split` on the builder — call `.to_regex()` first and use them on the
returned `Regex`.
- `Regex.match` / `.search` / `.fullmatch` and `Regex.finditer` now
return the edify `Match` wrapper (or an iterator of it) rather than a
bare `re.Match`. Existing code that calls `.group(...)`, `.groupdict()`,
`.span(...)`, etc. keeps working via `__getattr__` delegation.
- `EngineNotWiredError` is deleted; both engines are wired now, so the
class had no live callers.
Closes #74, closes #115, closes #116, closes #117, closes #118, closes
#119, closes #120, closes #121, closes #129, closes #138, closes #140,
closes #141, closes #142, closes #143, closes #144, closes #149, closes
#150, closes #151, closes #175.
|
|
|
|
in the API reference and README
|
|
construct set
|
|
(m.username, m.captures.username)
|
|
pattern authors
|
|
callables
|
|
chain-step allocation by ~4.6x
|
|
test/match/search/findall/sub; reach fullmatch/finditer/subn/split via .to_regex()
|
|
the matcher methods
|
|
engine=regex
|
|
the re-engine failure with an actionable error
|
|
is a deferred-import extra
|
|
|
|
annotations (#279)
Ships the static-typing surface end to end.
- Installs the `edify/py.typed` marker so downstream code sees `edify`
as a typed distribution, and force-includes it in the wheel.
- Adds a `[tool.mypy]` block that runs `mypy --strict` against `edify.*`
(with `ignore_missing_imports` scoped to `regex` and `graphviz`), and a
`[tool.pyright]` block with `typeCheckingMode = "strict"` bound to
`include = ["edify"]`.
- Pins `mypy==1.13.0` and `pyright==1.1.389` in the dev group and
refreshes `uv.lock`.
- Annotates the full public surface: `Self` on every fluent chain
method, `TypeVar`-generic private helpers across the mixins, precise
element unions on `render_element` / `merge` / `fuse`, `FrameType` on
the caller-context walker, a `JSONValue` recursive type over the
serialize layer (no `Any`, no `object`), and precise dict types on the
`class_for` / `kind_for` registry.
- Introduces a typed `engine: Literal["re", "regex"]` kwarg on
`to_regex`, with `EngineNotWiredError` (annotated summary + pointer
block + `= note:` + `help:`) surfacing when a caller asks for an engine
that is not wired in this build.
- Introduces `edify/builder/types/protocol.py::BuilderProtocol` — a
`runtime_checkable` `Protocol` that declares the shared attribute +
method surface every mixin assumes about `self`, so mixins in isolation
see the full chain surface.
- Introduces `edify/serialize/types.py::JSONValue` (recursive: `str |
int | float | bool | None | list[JSONValue] | dict[str, JSONValue]`) and
threads it through `element_to_dict`, `state_to_dict`, and the load path
(now returning `BuilderState` to break a circular import with
`Pattern`).
- Adds a discipline test that grep-fails on any bare `# type: ignore` or
`# pyright: ignore` in `edify/`, so every suppression must carry a code
and a reason (there are none in this branch).
- Wires `mypy` and `pyright` as required CI matrix entries in
`.github/workflows/github-actions.yml`, both running `--strict` against
`edify/`.
Closes #71, closes #84, closes #85, closes #86, closes #87, closes #88,
closes #89, closes #90, closes #91, closes #92, closes #93, closes #94,
closes #95.
|
|
overrides to top level
|
|
|