Upgrading from 0.3 to 1.0

The 1.0 release is a ground-up redesign. This guide covers only the breaking surface — the changes that require a code edit or a behavior review when moving a 0.3 codebase to 1.0. Every section below carries an explicit .. _label: anchor; deprecation-warning URLs point at these anchors, so they stay valid even if a heading is later reworded.

License changed to MIT

Edify 1.0 is released under the MIT License. Releases 0.3.0 and earlier were Apache-2.0.

Two user-facing facts:

  • MIT drops Apache-2.0’s explicit patent grant. If your legal review specifically relied on that grant, pin edify<1.0.

  • Every git tag v0.3.0 and earlier remains Apache-2.0 permanently — the relicense applies going forward only. Installs pinned to a 0.3.x tag are unaffected.

Python floor raised to 3.11

Edify 1.0 requires Python 3.11 or newer. The 0.3 line supported 3.8+. Upgrade your interpreter before installing 1.0; pip install edify on 3.10 or older resolves to the last 0.3 release.

Validators are callable Pattern instances

In 0.3 the built-in validators under edify.library were plain functions. In 1.0 every validator is a callable edify.Pattern instance:

import inspect
from edify import Pattern
from edify.library import email

inspect.isfunction(email)        # False   (was True in 0.3)
isinstance(email, Pattern)       # True    (was False in 0.3)
email("user@example.com")        # True    (call form unchanged)
email.match("user@example.com")  # <re.Match object>

The validator(value) -> bool call form is unchanged, so most call sites need no edit. The new surface adds .match, .search, .to_regex_string, and every other Pattern method on top.

Library reorganized into category submodules

The library moved from a flat module into category folders (identifier/, address/, contact/, auth/, financial/, temporal/, and more). Every validator remains importable from the flat top-level namespace, and is now also importable from its category submodule:

from edify.library import uuid                # flat top-level (unchanged)
from edify.library.identifier import uuid     # category submodule (new)

If you imported validators from edify.library in 0.3, those imports keep working.

Named back-references no longer raise

In 0.3, .named_back_reference(name) raised when the named group had not been declared earlier in the chain. In 1.0 it emits the back-reference and defers validation to compile time, matching the behavior of the numeric .back_reference(index).

to_regex_string() output normalized

The emitted pattern string is now the minimal correct form. The most visible change is character-class escaping: inside [...] only \, ], first-position ^, and interior - are escaped; every other metacharacter is a literal.

0.3:  any_of_chars('#?!@$%^&*-')  ->  [\#\?!@$%\^\&\*\-]
1.0:  any_of_chars('#?!@$%^&*-')  ->  [#?!@$%^&*-]

The two forms match identically — only the string representation changed. Code that compared to_regex_string() against a hard-coded expected string with the old redundant backslashes must drop them.

Character-class escaping is the minimal correct form

This is the rule behind the to_regex_string() change above, stated directly for callers who construct char classes: any_of_chars and anything_but_chars escape only the characters that carry syntactic meaning inside [...]. Match behavior is identical to 0.3’s over-escaped output.

Match methods return an edify Match wrapper

.match, .search, and .fullmatch on Regex (and .finditer) now return an edify edify.result.Match rather than a bare re.Match. Named captures are reachable as attributes (m.username / m.captures.username), and every re.Match method (.group(), .groupdict(), .span(), …) is forwarded, so existing call sites keep working.

Builder match-verb surface closed at five

The builder exposes exactly test / match / search / findall / sub. The less-common fullmatch / finditer / subn / split verbs moved off the builder — reach them through .to_regex() and call them on the returned Regex:

# 0.3
builder.finditer(text)

# 1.0
builder.to_regex().finditer(text)

Silent failures now raise typed errors

Constructs that failed silently or produced a cryptic stdlib error in 0.3 now raise a specific edify.errors.EdifySyntaxError subclass with an annotated message. The most common case: a variable-width assert_behind under the default re engine raises edify.errors.backend.VariableWidthLookbehindNotSupportedError instead of surfacing re’s “look-behind requires fixed-width pattern”.

Optional regex engine via engine= kwarg

to_regex(engine="regex") selects the third-party regex backend, which unlocks variable-width lookbehind and per-call timeouts. It requires the extra: pip install edify[regex]. The default engine="re" is unchanged.

Builder equality compares emitted patterns

Two builders compare equal when they emit the same pattern string and carry the same flags, rather than by object identity. This makes builders usable as dict keys and in set membership.

Moved internal import paths

Internal machinery that leaked into 0.3’s import surface has been privatized or relocated. If you imported from an undocumented internal path in 0.3, import the symbol from its documented location instead — every public symbol is reachable from edify or a documented submodule.