aboutsummaryrefslogtreecommitdiff
AgeCommit message (Collapse)AuthorFilesLines
12 dayschore(typing): pyright per-directory overrides for graphviz/regex missing stubsnatsuoto1-2/+13
12 daysrefactor(typing): make edify pass mypy --strict and pyright --strict end to endnatsuoto15-46/+101
12 daystest(discipline): every type-checker suppression carries a code and inline ↵natsuoto1-0/+35
reason
12 daysfeat(builder): typed engine=Literal kwarg on to_regex with EngineNotWiredErrornatsuoto4-0/+67
12 daysrefactor(builder): TypeVar-generic helpers and cross-mixin surface on ↵natsuoto3-20/+37
BuilderProtocol
12 daysrefactor(result): keep _RePatternAttribute union on Regex.__getattr__; drop ↵natsuoto1-1/+2
Any escape
12 daysrefactor(atoms): drop docstring from __init__ per exports-only rulenatsuoto1-7/+0
12 daysrefactor(serialize): precise JSONValue types replace Any; hoist imports; ↵natsuoto7-170/+127
drop docstring from __init__
12 dayschore: refresh uv.lock for mypy + pyright pinsnatsuoto1-0/+72
12 dayschore(typing): ship py.typed marker and library-scope mypy/pyright confignatsuoto2-0/+33
13 daysfeat(pattern): canonical dict/JSON serialization for Pattern (#277)Bobby9-0/+792
Adds canonical serialization to `Pattern` — round-trip-safe dict and JSON, with a schema-version header and a public `kind`-based AST vocabulary that shields the wire format from internal class-name churn. ## Usage ```python from edify import Pattern p = Pattern().start_of_input().named_capture("year").exactly(4).digit().end().ignore_case() blob = p.to_json() # {"edify":0,"flags":{"ignore_case":true},"pattern":{"children":[...],"kind":"root"}} restored = Pattern.from_json(blob) assert restored == p ``` ## Schema shape ```json { "edify": 0, "pattern": { "kind": "root", "children": [ { "kind": "start" }, { "kind": "named-capture", "name": "year", "children": [ { "kind": "exactly", "times": 4, "child": { "kind": "digit" } } ] } ] }, "flags": { "ignore_case": true } } ``` Every element carries a public `kind` string (registered in `edify.serialize.kinds`). Internal class names (`ExactlyElement`, etc.) never appear in the payload, so later privatisation and refactors don't leak into serialized output. The `flags` key is omitted when no flags are set. ## Schema version `SCHEMA_VERSION = 0` — **experimental**. The concrete AST shape may change without a deprecation cycle until it is promoted to `1` in a later release. Consumers should not commit to the on-wire shape yet. `from_dict` / `from_json` reject any payload whose `edify` key does not match this build's `SCHEMA_VERSION`, and reject unknown `kind` values with `UnknownElementKindError` so payloads from a newer build fail loudly on an older loader. ## Forward-compatibility Unknown fields on individual element dicts are silently ignored, so a newer edify that adds `{"kind": "digit", "future_marker": true}` will still load on this build — the extra field just drops. Closes #193 Closes #194 Closes #195 Closes #196 Closes #197
13 daysfeat(pattern): canonical dict/JSON serialization on Pattern (to_dict, ↵natsuoto9-0/+792
from_dict, to_json, from_json, schema v0)
13 daysfeat(atoms): public edify.atoms — 83 composable Pattern fragments (#276)Bobby171-38/+3626
Adds `edify.atoms`, a public module of unanchored `Pattern` fragments that snap into any builder via `.use()`. ## Usage ```python from edify import Pattern from edify.atoms import hostname, port, protocol url_shape = ( Pattern() .start_of_input() .use(protocol).string("://").use(hostname).char(":").use(port) .end_of_input() ) url_shape("https://example.com:8080") # True ``` ## The 83 atoms | Group | Count | Names | |---|---|---| | Character | 7 | `letter`, `lower`, `upper`, `alnum`, `printable`, `ascii`, `space` | | Numeric | 12 | `natural`, `integer`, `signed`, `unsigned`, `decimal`, `floatnum`, `scientific`, `percent`, `ratio`, `hexnum`, `binnum`, `octnum` | | Network | 12 | `octet`, `port`, `hostname`, `label`, `ipv4`, `ipv6`, `cidr`, `mac`, `protocol`, `scheme`, `tld`, `nibble` | | Temporal | 11 | `clock`, `clock12`, `isodate`, `isodatetime`, `epoch`, `year`, `month`, `day`, `weekday`, `timezone`, `duration` | | Identifier | 7 | `uuid`, `guid`, `ulid`, `oid`, `objectid`, `semver`, `slug` | | Text | 9 | `word`, `line`, `quoted`, `parens`, `brackets`, `braces`, `username`, `localpart`, `hexcolor` | | Path | 3 | `filename`, `filepath`, `extension` | | Crypto | 7 | `hexstring`, `base64`, `base64url`, `base32`, `base58`, `sha256`, `md5` | | Web | 7 | `url`, `uri`, `email`, `rgbcolor`, `httpmethod`, `httpstatus`, `mimetype` | | Financial | 5 | `iban`, `bic`, `currency`, `money`, `creditcard` | | Boolean | 3 | `yesno`, `truefalse`, `boolean` | Every atom is a `Pattern` instance built via the fluent chain (no raw regex). Every atom has a corpus test at `tests/atoms/<name>.test.py` covering: accepts a shape-conforming sample, rejects an off-shape input, composes cleanly under `Pattern().use(...)`, emits a non-empty regex fragment. Every filename is single-word (no underscores). Every attribute name avoids Python builtins. `edify/library/_support/atoms.py` (the previous private home of `octet` / `hex_any`) is deleted; the three library validators that used it (`address/ip`, `address/ptr`, `address/socket`) now import from `edify.atoms`. Closes #170 Closes #171 Closes #172 Closes #173 Closes #174
13 daysfeat(atoms): expand from 13 to 83 general-purpose composable fragmentsnatsuoto141-0/+2991
13 daysrename(atoms): single-word filenames ↵natsuoto25-112/+106
(octet/port/label/uuid/hexcolor/nibble/isodate/clock/localpart)
13 daysfeat(atoms): public edify.atoms with 13 composable Pattern fragmentsnatsuoto31-40/+643
2026-07-11feat!: callable-Pattern validator API + 223-pattern library across 22 ↵Bobby302-1599/+8100
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
2026-07-11feat(library): add 23 patterns across 12 categories (medical, transport, ↵natsuoto36-10/+601
financial, geo, security, numeric, web, grammar, document, data, contact, software)
2026-07-11refactor: eliminate suppression comments and internal-import tests, delete ↵natsuoto24-503/+68
now-dead defensive code
2026-07-11refactor(library): rewrite geo/postal to inline patterns; delete ↵natsuoto44-719/+451
RegexBackedPattern escape hatch
2026-07-11refactor(library): rewrite software/ validators to fluent Pattern() chainnatsuoto12-57/+562
2026-07-11refactor(library): rewrite temporal/, text/ validators to fluent Pattern() chainnatsuoto21-107/+514
2026-07-11refactor(library): rewrite web/, security/, media/ validators to fluent ↵natsuoto27-62/+632
Pattern() chain
2026-07-11refactor(library): rewrite numeric/ validators to fluent Pattern() chainnatsuoto8-27/+201
2026-07-11refactor(library): rewrite validators to fluent Pattern() chainnatsuoto77-267/+2365
2026-07-11chore: ruff format + lint fixes (en-dash → hyphen, line-length, script.py ↵natsuoto186-317/+1308
noqa)
2026-07-11feat(library): reorganize into 22 category folders + flat 200-name re-export ↵natsuoto29-837/+126
at edify.library
2026-07-11feat(library): 22 patterns across security/grammar/web categoriesnatsuoto25-0/+140
2026-07-11feat(library): 33 patterns across api/data/document categoriesnatsuoto36-0/+209
2026-07-11feat(library): 12 patterns across transport/publishing/product/medical ↵natsuoto16-0/+85
categories
2026-07-11feat(library/software): 12 software patterns (semver, version, package, ↵natsuoto13-0/+102
docker, image, digest, git, ref, checksum, component, makefile, cargo)
2026-07-11feat(library/media): 11 media patterns (mimetype, extension, filename, glob, ↵natsuoto12-0/+84
regex, shebang, encoding, charset, locale, favicon, codec)
2026-07-11feat(library/color): 5 color patterns (color, palette, swatch, gradient, filter)natsuoto6-0/+46
2026-07-11feat(library/text): 11 text patterns (slug, base, script, ascii, printable, ↵natsuoto12-0/+137
alphanumeric, alpha, numeric, word, unicode, emoji)
2026-07-11feat(library/numeric): 9 numeric patterns (number, integer, hash, ↵natsuoto10-0/+109
percentage, ratio, fraction, scientific, roman, ordinal)
2026-07-11feat(library/geo): 6 geo patterns (coordinate, geohash, plus, postal, place, ↵natsuoto7-0/+80
altitude)
2026-07-11feat(library/temporal): 11 temporal patterns (date, time, datetime, ↵natsuoto12-0/+178
duration, timezone, offset, epoch, timestamp, year, cron, interval)
2026-07-11feat(library/financial): 4 financial patterns (currency, card, wallet, crypto)natsuoto5-0/+65
2026-07-11feat(library/auth): 19 auth patterns (password, pin, token, jwt, otp, ↵natsuoto20-0/+371
apikey, bearer, session, csrf, mnemonic, hmac, passkey, mfa, webauthn, secret, sso, refresh, signing, challenge)
2026-07-11feat(library/contact): 6 contact patterns (email, phone, fax, pager, ↵natsuoto7-0/+103
address, username)
2026-07-11feat(library/address): 13 address patterns (ip, cidr, subnet, hostname, ↵natsuoto14-0/+242
domain, subdomain, tld, url, uri, path, port, socket, ptr)
2026-07-11chore: drop as_pattern helper (obsoleted by Pattern()-only construction)natsuoto1-20/+0
2026-07-11feat(library/identifier): all 26 identifier patterns as callable Pattern chainsnatsuoto28-118/+525
2026-07-11feat(library): infrastructure and first three identifier migrations (uuid, ↵natsuoto6-0/+181
guid, mac)
2026-07-10feat: Pattern.__call__(value) -> bool and RegexBackedPattern base for ↵natsuoto2-0/+60
validator migration
2026-07-10chore!: relicense edify from Apache 2.0 to MIT (#273)Bobby4-203/+39
Relicenses edify from Apache 2.0 to the MIT License across every artifact that names or ships it. Sole-copyright-holder relicense — no CLA sweep needed. ## Landings - **`LICENSE`** — the Apache 2.0 text is replaced with the standard MIT text, copyright line `Copyright (c) 2022-2026 Bobby` frozen as a static literal through the first MIT release. - **`pyproject.toml`** — the SPDX `license = "…"` field switches from `Apache-2.0` to `MIT`. `license-files = ["LICENSE"]` continues to attach the license file to the wheel and sdist via hatchling's Core Metadata 2.4 emission. - **`README.rst`** — the license section reads "MIT License" and continues to link the repo's `LICENSE`. The shields.io license badge reads its label from PyPI metadata, so no manual badge edit is needed. - **`tests/license.test.py`** — two acceptance tests that read the installed edify metadata via `importlib.metadata`: `License-Expression == "MIT"` and `Apache` does not appear in any classifier. Guards against silent backslide in a future dependency update. The `License :: OSI Approved :: Apache Software License` classifier has already been absent from `pyproject.toml`'s `classifiers` list since the toolchain migration; the second acceptance test above pins that state going forward. Closes #231 Closes #232 Closes #233 Closes #234 Closes #237
2026-07-10test: license.test.py asserts installed edify carries MIT license expression ↵natsuoto1-0/+16
(#237)
2026-07-10docs: README license section reads MIT (#234)natsuoto1-1/+1
2026-07-10chore!: pyproject.toml license field = MIT SPDX (#232)natsuoto1-1/+1
2026-07-10chore!: replace LICENSE with MIT text (#231)natsuoto1-201/+21