Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
3c35b85
Add v2 Span, Role, and Token with construction validation
derek73 Jul 12, 2026
2f70ac7
Raise the Python floor to 3.11 on the 2.0 branch (#257, partial)
derek73 Jul 12, 2026
0a416ed
Fail loud on malformed Token spans and non-string text
derek73 Jul 12, 2026
3f3d448
Add v2 AmbiguityKind StrEnum and Ambiguity type
derek73 Jul 12, 2026
de78ebf
Hoist v2 test imports to the top-of-file block
derek73 Jul 12, 2026
072e2da
Validate Ambiguity detail and token element types
derek73 Jul 12, 2026
ef6d2b8
Add v2 ParsedName with constructor-enforced span invariants
derek73 Jul 12, 2026
4a79080
Validate ParsedName element types and original
derek73 Jul 12, 2026
8e8160b
Add v2 ParsedName string properties, derived views, and as_dict
derek73 Jul 12, 2026
3190abd
Add v2 ParsedName.replace() with positional synthetic tokens
derek73 Jul 12, 2026
06a7e22
Fail loud on non-str replace values; canonical append order
derek73 Jul 12, 2026
aeefe51
Add v2 ParsedName.comparison_key()
derek73 Jul 12, 2026
9d781c7
Add v2 Lexicon with canonical storage and v1-sourced default
derek73 Jul 12, 2026
61362bd
Fail loud on malformed Lexicon input; dedupe exception keys
derek73 Jul 12, 2026
f216e5f
Add v2 Lexicon composition: add/remove and field-wise union
derek73 Jul 12, 2026
0be9e53
Add v2 Policy with order-spec validation and PatronymicRule
derek73 Jul 12, 2026
21c817b
Reject malformed Policy delimiter pairs and patronymic rules
derek73 Jul 12, 2026
719fee6
Coerce and validate all Policy collection fields
derek73 Jul 12, 2026
0a47671
Add v2 PolicyPatch with UNSET sentinel and declared composition
derek73 Jul 12, 2026
cd46673
Document the replace type-ignore; rename parity test
derek73 Jul 12, 2026
29f603f
Canonicalize PolicyPatch union fields; lock field types in parity test
derek73 Jul 12, 2026
2d3dd04
Add v2 Locale type
derek73 Jul 12, 2026
91845fd
Reject whitespace in Locale codes
derek73 Jul 12, 2026
b88d3ac
Drop exception keys that normalize to empty
derek73 Jul 12, 2026
0838e98
Add bounded deviation-only reprs to all v2 types
derek73 Jul 12, 2026
af6a01a
Export v2 core types; enforce layering and mypy strictness
derek73 Jul 12, 2026
30ef763
Make Lexicon picklable despite its mappingproxy slot
derek73 Jul 12, 2026
401535c
Drop Python 3.10 from the CI matrix (#257, partial)
derek73 Jul 12, 2026
f2b42c8
Drop the pre-3.11 typing_extensions shim (#257)
derek73 Jul 12, 2026
4c171fc
Annotate v2 test returns for ruff ANN compliance
derek73 Jul 12, 2026
353d10a
Tighten the layering test to exact-module matching
derek73 Jul 12, 2026
09cc85d
Pin the derived views' particle tag to STABLE_TAGS
derek73 Jul 12, 2026
473c0b9
Reject mappings passed to plain Lexicon vocab fields
derek73 Jul 12, 2026
ce7b36b
Diff Lexicon reprs against the nearer named baseline
derek73 Jul 12, 2026
334c617
Order Lexicon sections per conventions doc §4
derek73 Jul 12, 2026
5a5bf80
Adopt the stdlib exception taxonomy across v2 validation
derek73 Jul 12, 2026
974aec2
Source maiden markers from a config data module (#274)
derek73 Jul 12, 2026
5d78933
Add Scandinavian participle maiden markers (#274)
derek73 Jul 12, 2026
a16364e
Distill 2.0 implementation conventions into AGENTS.md
derek73 Jul 12, 2026
339a3ed
Guard Token.tags and coerce Token.role
derek73 Jul 12, 2026
9d7493c
Close the fail-loud gaps in Policy.__post_init__
derek73 Jul 12, 2026
cff23ff
Canonicalize PolicyPatch's scalar name_order to a tuple
derek73 Jul 12, 2026
d4cb1b2
Validate capitalization_exceptions entry shapes
derek73 Jul 12, 2026
af3aad0
Fail at unpickle time on Lexicon field-layout skew
derek73 Jul 12, 2026
c5090d1
Fix vacuous removal assertion in the add/remove test
derek73 Jul 12, 2026
2310fb1
Pin four stated-but-untested v2 contracts
derek73 Jul 12, 2026
cbd8cdf
Fix stale and inaccurate comments found in review
derek73 Jul 12, 2026
358ce80
Raise on entries that normalize to empty
derek73 Jul 12, 2026
22fe2ea
Block Span concatenation and bool span coordinates
derek73 Jul 12, 2026
f07082b
Pin Locale.code to the registry-key charset [a-z0-9_]+
derek73 Jul 12, 2026
22ea503
Extract _coerce_enum for the duplicated enum coercion blocks
derek73 Jul 12, 2026
eaba92f
Extract _normpairs as _normset's sibling
derek73 Jul 12, 2026
cac4231
Drop the whitespace check the charset pin subsumed
derek73 Jul 12, 2026
a2d88ea
Test the real matrix interpreter, and add 3.15 pre-release (#259)
derek73 Jul 12, 2026
73e816e
Add _render with the #254-normative collapse; extend layering for it
derek73 Jul 12, 2026
a6750dc
Add render(): spec formatting over role fields and derived views
derek73 Jul 12, 2026
bbca365
Add ParsedName.render() and __str__ via call-time _render delegation
derek73 Jul 12, 2026
445571d
Add initials() with call-site delimiter/separator arguments
derek73 Jul 12, 2026
da94eb5
Add capitalized(): lexicon-driven case fixing, same spans
derek73 Jul 12, 2026
936d347
True up module docstrings to the rendering delegation
derek73 Jul 12, 2026
27616dc
Guard all frozen types against pickle layout skew
derek73 Jul 12, 2026
35e1836
Validate rendering arguments eagerly
derek73 Jul 13, 2026
7e8b6fb
Reject bare-string name_order with the taxonomy TypeError
derek73 Jul 13, 2026
4db87b7
Pin two documented rendering contracts
derek73 Jul 13, 2026
96b9166
Fix three stale doc claims from the PR review
derek73 Jul 13, 2026
79277de
Simplify the rendering module
derek73 Jul 13, 2026
60802f1
Single-source the bare-string name_order guard
derek73 Jul 13, 2026
bc56bd2
Enforce the pickle guards mechanically; flatten test imports
derek73 Jul 13, 2026
bf68147
Add the pipeline package skeleton: ParseState and layering wiring
derek73 Jul 13, 2026
24c5191
Add extract_delimited: delimiter pairs to nickname/maiden spans
derek73 Jul 13, 2026
7e341a9
Add tokenize: char-classification tokenizer with exact spans
derek73 Jul 13, 2026
29ceb18
Add segment: comma-structure decision over token index runs
derek73 Jul 13, 2026
a91e465
Add classify: vocabulary tags from the lexicon
derek73 Jul 13, 2026
20e6bf6
Add group: piece boundaries by index; joins ported from v1
derek73 Jul 13, 2026
5e22e78
Add assign: positional roles per name_order over pieces
derek73 Jul 13, 2026
4f3a707
Add post_rules: firstname swap and opt-in patronymic rotations
derek73 Jul 13, 2026
bb80c8d
Add assemble: final ParseState to validated ParsedName
derek73 Jul 13, 2026
9bcd007
Add Parser and parse(): the 2.0 pipeline goes end to end
derek73 Jul 13, 2026
8ad0247
Add parser_for() with locale-identified errors and matches()
derek73 Jul 13, 2026
c7e3fb0
Fix the shared case-table format and seed it with the pinned corpus
derek73 Jul 13, 2026
9347848
Add property, contract, and benchmark test layers
derek73 Jul 13, 2026
bacb42b
Apply type-design review fixes to the pipeline state contract
derek73 Jul 13, 2026
f65bd7c
Exclude the ambiguous subset from the plain suffix-acronym test
derek73 Jul 13, 2026
c66be14
Heal the split Ph. D. credential across the pipeline (I2/I3)
derek73 Jul 13, 2026
ec9e3a2
Fold a leading never-given particle into the family name (I4)
derek73 Jul 13, 2026
481f27a
Wire the three dead Policy fields (I5)
derek73 Jul 13, 2026
e7ccd67
Restrict name_order to the exported orders; drop dangled ambiguities
derek73 Jul 13, 2026
ddd0416
Add the review's test-coverage batch (T1-T4, T6)
derek73 Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,22 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
# 3.15 is pre-release until 2026-10-01 (#259): surface breakage
# without blocking the branch; flip to blocking when it goes final.
continue-on-error: ${{ matrix.python-version == '3.15' }}
env:
# Without this, uv sync honors .python-version (3.11) over the
# matrix interpreter and every job silently tests 3.11.
UV_PYTHON: ${{ matrix.python-version }}

steps:
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10
3.11
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ Parse flow:

Each named attribute (`title`, `first`, etc.) is a `@property` that joins its corresponding `_list`. Setters call `_set_list()` which runs the value through `parse_pieces()`, so assigning `hn.last = "de la Vega"` correctly re-parses prefix tokens.

## 2.0 API modules (`nameparser/_*.py`, `tests/v2/`)

The 2.0 rewrite lands as underscore-private modules alongside the v1 code. These conventions apply to all new-API code and are stricter than the v1 sections above. The full design record (rationale, settled-decision logs, dated amendments) lives in untracked `docs/superpowers/specs/`; this section is the enforceable subset. **A commit that establishes or amends one of these conventions must update this section in the same commit** — grep-driven staleness sweeps miss paraphrased prose (see Workflow above), so write-time maintenance is the mechanism, audits are the backstop.

- **Module layout**: every new module is underscore-private (`_types.py`, `_lexicon.py`, `_policy.py`, `_locale.py`, `_render.py`, `_pipeline/`, `_parser.py`). The public import surface is exactly `nameparser` (and later `nameparser.locales`); `nameparser/__init__.py` holds re-exports and `__all__` only — no logic. Old paths (`nameparser.parser`, `nameparser.config`) stay v1-shaped.
- **Layering is enforced by `tests/v2/test_layering.py`** (exact-module matching; `if TYPE_CHECKING:` imports don't count): `_types` imports nothing internal at module level — its rendering delegates import `_render` at call time; `_lexicon` and `_policy` sit above `_types` independently (`_lexicon` may import `nameparser.config.*` DATA modules only — vocabulary is single-sourced from the v1 data modules through 2.x, e.g. `config/maiden_markers.py`); `_locale` sits on `_lexicon`+`_policy` (plus `_types` for the shared pickle mixin); `_render` imports `_types` and `_lexicon` (for `Lexicon.default()` and `_normalize`); `_pipeline/*` imports `_types`+`_lexicon`+`_policy` plus in-package `_pipeline` helpers; `_parser` sits on everything except `_render`. Extend the test's `ALLOWED` table when adding a module.
- **Canonical field order** — the seven roles in `Role` enum declaration order, defined once and derived everywhere (properties, `as_dict`, reprs, `comparison_key`). Never restate the order literally.
- **Method organization**, fixed section order in every class: fields + `__post_init__` validation → alternative constructors → dunders (construction/equality → protocol → operators) → properties → public methods by concern (access → editing → comparison → rendering delegates) → private helpers last, except a helper serving exactly one section may sit at that section's head.
- **Validation is eager and fail-loud**: every `raise` states the offending value, the expected form, and the fix. Exception taxonomy: wrong type — including wrong element type inside a collection, bare `str` where an iterable of strings is expected, or a `Mapping` where a plain iterable is expected — raises `TypeError`; well-typed but unacceptable values raise `ValueError`; failed enum lookups stay `ValueError` for any input (stdlib `EnumType` precedent).
- **Reprs are bounded**: render which fields deviate from a named baseline and by how much, never contents (`Lexicon(default + titles: +2)`).
- **Typing/docs**: `from __future__ import annotations`; `frozen=True, slots=True` on every public dataclass; strict-profile mypy flags via per-module overrides in pyproject (`strict = true` itself is not valid per-module). Docstrings state contracts in prose with **no doctest blocks** — `--doctest-modules` makes every example a test; behavior examples go to unit tests per the lean-docs rule.
- **Pickling**: v2 types must round-trip (`Parser` will be picklable by construction, and it holds a `Lexicon`). Every frozen type assigns `_guarded_getstate`/`_guarded_setstate` (`_types.py`) in its class body (`@dataclass(slots=True)` would override inherited pickle methods) — unpickling fails at the LOAD site on field-layout skew, and values are deliberately NOT re-validated (pickle is not a security boundary; canonical state comes from a validated instance). `Lexicon` keeps its own copy of the guard (layering) plus the `mappingproxy` slot rebuild; a new unpicklable slot type needs the same treatment plus a round-trip test.
- **One sanctioned global**: the (future) cached default `Parser`. Lazily cached FROZEN singletons (`Lexicon.default()`'s `functools.cache`, the future default parser) are constants, not state; any second piece of module-level MUTABLE state requires amending the conventions doc, on purpose, in review.
- **Tests**: all v2 tests live in the `tests/v2/` package (its `conftest.py` overrides the v1 dual-run fixture — v2 code never reads shared `CONSTANTS`), one test module per source module plus cross-cutting `test_reprs.py` and `test_layering.py`, names stating behavior. Never assert `Lexicon.default()` contents; the narrow sourcing spot-checks in `test_default_sources_v1_vocabulary` that pin the v1→v2 migration contract (e.g. the flipped `particles_ambiguous` model) are the sanctioned exception.

## Extension Patterns

**Adding a scalar `Constants` attribute + `HumanName` kwarg** (e.g. `initials_separator`, `suffix_delimiter`):
Expand Down
2 changes: 2 additions & 0 deletions docs/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ HumanName.config Defaults
:members:
.. automodule:: nameparser.config.conjunctions
:members:
.. automodule:: nameparser.config.maiden_markers
:members:
.. automodule:: nameparser.config.capitalization
:members:
.. automodule:: nameparser.config.regexes
Expand Down
31 changes: 31 additions & 0 deletions nameparser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,34 @@
__author_email__ = 'derek73@gmail.com'
__license__ = "LGPL"
__url__ = "https://github.com/derek73/python-nameparser"

from nameparser._lexicon import Lexicon
from nameparser._locale import Locale
from nameparser._parser import Parser, parse, parser_for
from nameparser._policy import (
FAMILY_FIRST,
FAMILY_FIRST_GIVEN_LAST,
GIVEN_FIRST,
UNSET,
PatronymicRule,
Policy,
PolicyPatch,
)
from nameparser._types import (
Ambiguity,
AmbiguityKind,
ParsedName,
Role,
Span,
Token,
)

__all__ = [
# v1 (compatibility layer)
"HumanName",
# v2 core
"Span", "Role", "Token", "Ambiguity", "AmbiguityKind", "ParsedName",
"Lexicon", "Policy", "PolicyPatch", "PatronymicRule", "UNSET",
"GIVEN_FIRST", "FAMILY_FIRST", "FAMILY_FIRST_GIVEN_LAST", "Locale",
"Parser", "parse", "parser_for",
]
Loading
Loading