aboutsummaryrefslogtreecommitdiff
path: root/tests/pattern
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-07-11 17:44:20 +0530
committerGitHub <[email protected]>2026-07-11 17:44:20 +0530
commitabdaeb4aad4284986f012efcce35a6e4189f2929 (patch)
tree9eaff3968954dc8f5bf079684dba46b6026e2221 /tests/pattern
parent2038ee5c4a5e473c20cf1e38af5c32ab450a2aea (diff)
parentc7a8a902eb86dfec5dd5c4ec77c532f5b920d3ac (diff)
downloadedify-abdaeb4aad4284986f012efcce35a6e4189f2929.tar.xz
edify-abdaeb4aad4284986f012efcce35a6e4189f2929.zip
feat!: callable-Pattern validator API + 223-pattern library across 22 category folders (#274)
Ships the callable-`Pattern` validator API and expands the built-in library from 14 validators to **223**, organized into 22 category folders. ## Callable-`Pattern` contract Every built-in validator is now a `Pattern` instance whose `__call__` returns `bool`. The old function-style `validator(value)` call form still works; every `Pattern` method is now also available on each validator (`match`, `to_regex_string`, `.explain()`, `.visualize()`, etc.). ```python from edify import Pattern from edify.library import uuid, email isinstance(uuid, Pattern) # True uuid("550e8400-e29b-11d4-a716-446655440000") # True uuid.match("550e8400-e29b-11d4-a716-446655440000") # <re.Match ...> email("[email protected]", strict=True) # available on parameterized validators ``` Implementation: - `Pattern.__call__(value) -> bool` on the base class (non-string returns `False`, no crash on user input). - **Every validator is built via the fluent chain** — `Pattern().start_of_input().exactly(8).any_of()....end_of_input()`. No raw-regex escape hatch anywhere in the library. - Composable atoms in `edify/library/_support/atoms.py` (`hex_any`, `octet`, etc.) keep the chains readable across categories. ## The 223 patterns across 22 categories | Category | Count | Patterns | |---|---|---| | `identifier/` | 26 | uuid, guid, mac, ssn, ein, tin, itin, iban, bic, lei, isin, cusip, sedol, sku, asin, imei, meid, iccid, vin, imo, mmsi, iata, icao, arn, orcid, did | | `data/` | 14 | xml, json, yaml, toml, csv, tsv, ini, avro, protobuf, msgpack, html, hdf5, parquet, orc | | `address/` | 13 | ip, cidr, subnet, hostname, domain, subdomain, tld, url, uri, path, port, socket, ptr | | `software/` | 13 | semver, version, package, docker, image, digest, git, ref, checksum, component, makefile, cargo, bump | | `api/` | 12 | oauth, openid, saml, openapi, swagger, jsonapi, hal, graphql, soap, webhook, rss, atom | | `auth/` | 19 | password, pin, token, jwt, otp, apikey, bearer, session, csrf, mnemonic, hmac, passkey, mfa, webauthn, secret, sso, refresh, signing, challenge | | `temporal/` | 11 | date, time, datetime, duration, timezone, offset, epoch, timestamp, year, cron, interval | | `text/` | 11 | slug, base, script, ascii, printable, alphanumeric, alpha, numeric, word, unicode, emoji | | `media/` | 11 | mimetype, extension, filename, glob, regex, shebang, encoding, charset, locale, favicon, codec | | `document/` | 11 | svg, pdf, epub, mobi, docx, xlsx, pptx, odt, readme, rtf, tex | | `security/` | 11 | pgp, pem, der, csr, x509, certificate, ssh, age, keyring, nonce, signature | | `web/` | 11 | sitemap, captcha, robots, manifest, htaccess, humans, nginx, apache, cookie, csp, useragent | | `numeric/` | 10 | number, integer, hash, percentage, ratio, fraction, scientific, roman, ordinal, natural | | `geo/` | 8 | coordinate, geohash, plus, postal, place, altitude, mgrs, bearing | | `contact/` | 7 | email, phone, fax, pager, address, username, handle | | `financial/` | 7 | currency, card, wallet, crypto, routing, sortcode, vat | | `publishing/` | 6 | isbn, issn, doi, arxiv, pmid, pmc | | `grammar/` | 6 | bnf, ebnf, abnf, peg, pest, antlr | | `color/` | 5 | color, palette, swatch, gradient, filter | | `transport/` | 4 | vehicle, aircraft, flight, plate | | `medical/` | 4 | medical, blood, dosage, dicom | | `product/` | 3 | gtin, mpn, barcode | | **Total** | **223** | | Each pattern is importable both from its category submodule and from the flat top-level namespace: ```python from edify.library import uuid # flat from edify.library.identifier import uuid # category ``` ## Merged and renamed 0.3 validators Per the \"each pattern is unique / everything doable via a form must be merged\" rule: - `iso_date` → `date` (accepts every recognised date form) - `email_rfc_5322` → `email` (accepts basic or RFC 5322) - `ipv4` + `ipv6` → `ip` - `phone_number` → `phone` - `zip` → `postal` Documented in \`docs/upgrading/0.3-to-1.0.rst\` under the \`.. _validators-callable:\` anchor. Closes #176 Closes #177 Closes #178 Closes #179 Closes #180 Closes #181 Closes #182 Closes #183 Closes #184 Closes #185 Closes #186 Closes #187 Closes #188 Closes #189 Closes #190
Diffstat (limited to 'tests/pattern')
-rw-r--r--tests/pattern/anchors.test.py4
-rw-r--r--tests/pattern/boundaries.test.py4
-rw-r--r--tests/pattern/composition.test.py2
3 files changed, 5 insertions, 5 deletions
diff --git a/tests/pattern/anchors.test.py b/tests/pattern/anchors.test.py
index a42aca0..225e8e9 100644
--- a/tests/pattern/anchors.test.py
+++ b/tests/pattern/anchors.test.py
@@ -20,8 +20,8 @@ def test_end_compiles_to_dollar_sign():
def test_start_matches_start_of_input_element_from_the_fluent_chain():
- assert START._state == Pattern().start_of_input()._state
+ assert Pattern().start_of_input() == START
def test_end_matches_end_of_input_element_from_the_fluent_chain():
- assert END._state == Pattern().end_of_input()._state
+ assert Pattern().end_of_input() == END
diff --git a/tests/pattern/boundaries.test.py b/tests/pattern/boundaries.test.py
index b391733..2892866 100644
--- a/tests/pattern/boundaries.test.py
+++ b/tests/pattern/boundaries.test.py
@@ -20,8 +20,8 @@ def test_non_word_boundary_compiles_to_backslash_capital_b():
def test_word_boundary_matches_fluent_chain_output():
- assert WORD_BOUNDARY._state == Pattern().word_boundary()._state
+ assert Pattern().word_boundary() == WORD_BOUNDARY
def test_non_word_boundary_matches_fluent_chain_output():
- assert NON_WORD_BOUNDARY._state == Pattern().non_word_boundary()._state
+ assert Pattern().non_word_boundary() == NON_WORD_BOUNDARY
diff --git a/tests/pattern/composition.test.py b/tests/pattern/composition.test.py
index 18a6bc2..34d6f89 100644
--- a/tests/pattern/composition.test.py
+++ b/tests/pattern/composition.test.py
@@ -9,7 +9,7 @@ from edify.errors.input import MustBeInstanceError
def test_pattern_builds_the_same_element_tree_as_a_builder():
pattern = Pattern().between(3, 20).word()
builder = RegexBuilder().between(3, 20).word()
- assert pattern._state == builder._state
+ assert pattern == builder
def test_pattern_exposes_to_regex_string_terminal():