aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-07-16 18:03:04 +0530
committerGitHub <[email protected]>2026-07-16 18:03:04 +0530
commit99b159c11ccc3f2dacea64416047b1778e346b81 (patch)
tree6b5f87a9949660f9042a506b592b64aa634cfeb4 /docs
parentb9914de793844d3ad6e2d7503209ac8fd339d6de (diff)
parent188c17c19a0c32e944d9a4234d33702d3adfe143 (diff)
downloadedify-99b159c11ccc3f2dacea64416047b1778e346b81.tar.xz
edify-99b159c11ccc3f2dacea64416047b1778e346b81.zip
Deprecation policy, 0.3 → 1.0 upgrade guide, MIT relicense, and 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
Diffstat (limited to 'docs')
-rw-r--r--docs/deprecation-policy.rst73
-rw-r--r--docs/index.rst2
-rw-r--r--docs/upgrading/0.3-to-1.0.rst202
-rw-r--r--docs/upgrading/index.rst14
4 files changed, 250 insertions, 41 deletions
diff --git a/docs/deprecation-policy.rst b/docs/deprecation-policy.rst
new file mode 100644
index 0000000..0879406
--- /dev/null
+++ b/docs/deprecation-policy.rst
@@ -0,0 +1,73 @@
+.. _deprecation-policy:
+
+Deprecation Policy
+==================
+
+This policy governs how every removed, renamed, or behavior-changed public API
+in Edify **1.0 and later** communicates the change to existing users. It does
+not apply to the 0.3 → 1.0 transition, which is a clean redesign with no
+deprecation stubs — see :ref:`upgrading-0-3-to-1-0` for that move.
+
+The stub pattern
+----------------
+
+When a public symbol is renamed or removed, the old name stays importable for one
+minor-release cycle as a **deprecation stub**. The stub forwards to the new
+implementation (for a rename) or raises on use (for a removal), and always emits
+a :class:`DeprecationWarning` on first use.
+
+.. code-block:: python
+
+ import warnings
+
+ def old_name(*args, **kwargs):
+ warnings.warn(
+ "old_name is deprecated since 1.1; use new_name. "
+ "See https://edify.readthedocs.io/en/latest/upgrading/1.0-to-1.1.html#new-name",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return new_name(*args, **kwargs)
+
+Message format
+--------------
+
+Every deprecation warning message follows this exact shape::
+
+ <name> is deprecated since <version>; use <replacement>. See <docs-url>
+
+* ``<name>`` — the deprecated symbol, spelled exactly as the user typed it.
+* ``<version>`` — the release that introduced the deprecation.
+* ``<replacement>`` — the symbol to use instead, or a one-clause instruction
+ when there is no drop-in replacement.
+* ``<docs-url>`` — an absolute URL into the upgrade guide, ending in the frozen
+ ``#anchor`` for the relevant section. The anchor is a
+ ``.. _label:`` target under ``docs/upgrading/``, decoupled from the heading
+ text so rewording a heading never breaks the link.
+
+Warning category and stacklevel
+-------------------------------
+
+* The category is always :class:`DeprecationWarning`.
+* ``stacklevel=2`` so the warning points at the caller's line, not at the stub.
+* Each stub fires its warning **exactly once per call site** — it never suppresses
+ or batches, and it never fires at import time.
+
+Removal cadence
+---------------
+
+* A symbol deprecated in release ``X.Y`` remains importable through the end of the
+ ``X.(Y+1)`` line and is removed no earlier than ``X.(Y+2)``.
+* Removals only land in a minor or major release, never a patch release.
+* The removal is recorded as a breaking change in the CHANGELOG and gets its own
+ section (with a frozen anchor) in the matching upgrade guide.
+
+Behavior-change rule
+--------------------
+
+A change to what an existing, unchanged call *returns* or *raises* — with no
+change to its name or signature — is a breaking change even though nothing looks
+different at the call site. It is documented in the upgrade guide with a
+before/after, cross-linked from the CHANGELOG, and (where technically possible)
+announced with a :class:`DeprecationWarning` for one cycle before the new
+behavior becomes the default.
diff --git a/docs/index.rst b/docs/index.rst
index 8799638..94ce6cc 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -9,6 +9,8 @@ Contents
built-in/index
regex-builder/index
integrations/index
+ upgrading/index
+ deprecation-policy
contributing
authors
changelog
diff --git a/docs/upgrading/0.3-to-1.0.rst b/docs/upgrading/0.3-to-1.0.rst
index 900fc25..2c7c1b3 100644
--- a/docs/upgrading/0.3-to-1.0.rst
+++ b/docs/upgrading/0.3-to-1.0.rst
@@ -1,18 +1,46 @@
.. _upgrading-0-3-to-1-0:
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-mit:
+
+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:
+
+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-callable:
Validators are callable ``Pattern`` instances
---------------------------------------------
-In 0.3 the built-in validators under ``edify.library`` were plain functions.
-In 1.0 every validator exposes itself as a :class:`edify.Pattern` instance
-that is *callable* — ``email(value)`` still returns a ``bool``, and the
-underlying pattern is available via ``email.match(value)``,
-``email.to_regex_string()``, and every other :class:`Pattern` method:
+In 0.3 the built-in validators under ``edify.library`` were plain functions. In
+1.0 every validator is a callable :class:`edify.Pattern` instance:
.. code-block:: python
@@ -25,41 +53,133 @@ underlying pattern is available via ``email.match(value)``,
email("[email protected]") # True (call form unchanged)
email.match("[email protected]") # <re.Match object>
-The library is also reorganized into 22 category folders (``identifier/``,
-``address/``, ``contact/``, ``auth/``, ``financial/``, ``temporal/``,
-``geo/``, ``numeric/``, ``text/``, ``color/``, ``media/``, ``software/``,
-``transport/``, ``publishing/``, ``product/``, ``medical/``, ``api/``,
-``data/``, ``document/``, ``security/``, ``grammar/``, ``web/``). Every
-validator is importable both from its category submodule and from the
-flat top-level ``edify.library`` namespace:
+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 :class:`Pattern` method on top.
+
+.. _library-reorg:
+
+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:
.. code-block:: python
- from edify.library import uuid # flat top-level
- from edify.library.identifier import uuid # category submodule
-
-The library grew from 14 validators to **200 exported validators** across
-these categories.
-
-Renamed and merged validators
------------------------------
-
-Several 0.3 names have been merged into more general patterns in 1.0:
-
-+---------------------------------+-------------------------------+
-| 0.3 name | 1.0 replacement |
-+=================================+===============================+
-| ``iso_date`` | ``date`` |
-+---------------------------------+-------------------------------+
-| ``email_rfc_5322`` | ``email`` |
-+---------------------------------+-------------------------------+
-| ``ipv4``, ``ipv6`` | ``ip`` |
-+---------------------------------+-------------------------------+
-| ``phone_number`` | ``phone`` |
-+---------------------------------+-------------------------------+
-| ``zip`` | ``postal`` |
-+---------------------------------+-------------------------------+
-
-The new names accept any recognised form of their concept, and the old
-name-plus-``form`` variants continue to work via each validator's
-``form`` / ``strict`` / ``version`` keyword arguments where applicable.
+ 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-backref-return:
+
+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:
+
+``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.
+
+.. code-block:: text
+
+ 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.
+
+.. _char-class-escape:
+
+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.
+
+.. _regex-wrapper-return:
+
+Match methods return an edify ``Match`` wrapper
+-----------------------------------------------
+
+``.match``, ``.search``, and ``.fullmatch`` on :class:`Regex` (and ``.finditer``)
+now return an edify :class:`edify.result.Match` rather than a bare
+:class:`re.Match`. Named captures are reachable as attributes
+(``m.username`` / ``m.captures.username``), and every :class:`re.Match` method
+(``.group()``, ``.groupdict()``, ``.span()``, …) is forwarded, so existing call
+sites keep working.
+
+.. _closed-match-verbs:
+
+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 :class:`Regex`:
+
+.. code-block:: python
+
+ # 0.3
+ builder.finditer(text)
+
+ # 1.0
+ builder.to_regex().finditer(text)
+
+.. _silent-failure-raises:
+
+Silent failures now raise typed errors
+---------------------------------------
+
+Constructs that failed silently or produced a cryptic stdlib error in 0.3 now
+raise a specific :class:`edify.errors.EdifySyntaxError` subclass with an
+annotated message. The most common case: a variable-width ``assert_behind`` under
+the default ``re`` engine raises
+:class:`edify.errors.backend.VariableWidthLookbehindNotSupportedError` instead of
+surfacing ``re``'s "look-behind requires fixed-width pattern".
+
+.. _engine-kwarg:
+
+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:
+
+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-import-paths:
+
+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.
diff --git a/docs/upgrading/index.rst b/docs/upgrading/index.rst
new file mode 100644
index 0000000..ba2e83a
--- /dev/null
+++ b/docs/upgrading/index.rst
@@ -0,0 +1,14 @@
+.. _upgrading:
+
+Upgrading
+=========
+
+Every breaking transition between released versions of Edify has a dedicated
+guide below. Each guide freezes an explicit ``.. _label:`` anchor on every
+section so the deprecation-warning URLs stay valid even when a heading is
+reworded.
+
+.. toctree::
+ :maxdepth: 2
+
+ 0.3-to-1.0