Migrating from HumanName

HumanName and CONSTANTS keep working in 2.0 — same imports, same attributes, same mutation API (name.C.titles.add(...), name.first = "...", and so on). Upgrading to 2.0 and migrating your code to the new Parser/Lexicon/ Policy API are two separate decisions — you can do the former today and the latter whenever it’s convenient. The compatibility layer (HumanName and nameparser.config) is removed in 3.0; that release is not scheduled.

The full 1.x documentation remains the canonical reference for HumanName and stays online at the readthedocs stable build, currently the 1.4.0 release: https://nameparser.readthedocs.io/en/stable/

This page exists for the other direction: translating a v1 customization or a v1-shaped comparison into the 2.0 API, one row per old name.

Before you upgrade

What 2.0 removes is the batch of deprecations 1.3 and 1.4 announced. If your test suite runs clean on 1.4 under python -W error::DeprecationWarning, it will run on 2.0 — with four exceptions that 1.4 never warned about. The first three raise the first time you hit them; the fourth only warns, so read it carefully:

  • CONSTANTS.regexes.<name> = ... raises TypeError. This includes CONSTANTS.regexes.bidi = False, the opt-out 1.3.1 recommended for keeping bidirectional marks; the 2.0 spellings are Policy(strip_bidi=False) and Policy(strip_emoji=False)

  • assigning a *_list attribute (name.first_list = [...]) raises AttributeError — the lists are read-only snapshots in 2.0

  • positional Constants(...) construction raises TypeError; the constructor is keyword-only

  • a subclass overriding a v1 parsing hook (pre_process, parse_pieces, is_title, and the rest) gets a DeprecationWarning at construction naming the hooks it overrode, because 2.0 delegates parsing to the core parser and never calls them

One removal changes results without saying anything. HumanName no longer defines __eq__, so name == "John Smith" is now False where 1.x returned True. That one did warn on 1.4, but nothing will tell you on 2.0. If you compare names anywhere, grep for == before upgrading and move to matches() — see Comparison.

One step has to happen before you upgrade, because the fix is only available on the version you’re leaving: a Constants pickle written by nameparser 1.2.x or earlier must be re-pickled under 1.3 or 1.4. 2.0 refuses to load one, and by then the code that could rewrite it is gone.

If your suite did not run clean, these are the replacements for the warned removals:

1.4 spelling

2.0 replacement

HumanName(b"..."), manager.add(b"...")

Decode first: HumanName(raw.decode("utf-8"))

SetManager.add_with_encoding(b"...")

add(), on already-decoded str

manager() (calling a set manager)

set(manager), or iterate it directly

manager.remove(missing) (was tolerant)

discard() to ignore missing; remove() now raises KeyError like set.remove

HumanName(..., constants=None)

Constants() for library defaults, or CONSTANTS.copy() for a private snapshot of the current shared config

name['first'] = value

name.first = value

CONSTANTS.regexes.typo (returned EMPTY_REGEX)

.get("typo") for intentional soft access; attribute access now raises AttributeError. The same applies to capitalization_exceptions, whose error also lists the known keys

CONSTANTS.empty_attribute_default

Gone; empty fields are always ''

Finally, if you want to see the difference on your own data rather than ours, tools/differential/ in the source repository diffs 1.4 and 2.0 parses over a corpus of names you supply. It is development tooling, not part of the installed package — see its README.

Attribute map

HumanName’s seven fields and their aggregates map onto ParsedName like this:

HumanName

ParsedName

Note

title

title

first

given

middle

middle

last

family

suffix

suffix

nickname

nickname

maiden

maiden

New field (added 1.3)

title_list, first_list, middle_list, last_list, suffix_list, nickname_list, maiden_list

tokens_for(Role.TITLE), tokens_for(Role.GIVEN), …

Returns the raw Token tuple for that role; read .text off each token for the string a _list attribute gave you

given_names / given_names_list

given_names

Unchanged name; middle folded into first

surnames / surnames_list

surnames

Unchanged name; middle folded into last

last_base / last_base_list

family_base

The surname with leading particles split off

last_prefixes / last_prefixes_list

family_particles

The particles family_base was split from (e.g. "de la")

string_format

render(spec)

A per-call argument now, not stored config — see Customizing the parser

initials_format, initials_delimiter, initials_separator

initials(spec, delimiter, separator)

Same three knobs, now call-site arguments to initials()

suffix_delimiter

Policy(extra_suffix_delimiters={...})

Moves from a HumanName/Constants scalar to a Policy set field, so more than one custom delimiter can be active at once. It is the set that moved, not just the name: passing the old scalar through (extra_suffix_delimiters=" - ") raises, rather than silently registering three one-character delimiters

capitalize(force=...)

capitalized(force=...)

capitalized() returns a new value rather than mutating in place. Its optional first argument takes a Lexicon, if you need custom capitalization exceptions

Side by side:

>>> from nameparser import HumanName, parse, Role
>>> hn = HumanName("Dr. Juan Q. Xavier de la Vega III")
>>> n = parse("Dr. Juan Q. Xavier de la Vega III")
>>> hn.title == n.title, hn.first == n.given, hn.last == n.family
(True, True, True)
>>> hn.first_list == [t.text for t in n.tokens_for(Role.GIVEN)]
True
>>> hn.given_names == n.given_names, hn.surnames == n.surnames
(True, True)
>>> hn.last_base == n.family_base, hn.last_prefixes == n.family_particles
(True, True)

Config map

CONSTANTS’ vocabulary sets map onto Lexicon fields:

CONSTANTS

Lexicon

Note

titles

titles

first_name_titles

given_name_titles

suffix_acronyms

suffix_acronyms

suffix_not_acronyms

suffix_words

suffix_acronyms_ambiguous

suffix_acronyms_ambiguous

prefixes

particles

non_first_name_prefixes

particles_ambiguous

Flipped — see the warning below

conjunctions

conjunctions

bound_first_names

bound_given_names

capitalization_exceptions

capitalization_exceptions

Pair-valued; set it via dataclasses.replace(lexicon, capitalization_exceptions={...}), not add()/remove()

And behavior/render scalars map onto Policy (or a rendering argument, where the 2.0 equivalent isn’t config at all):

CONSTANTS

2.0 equivalent

Note

patronymic_name_order

Policy(patronymic_rules={PatronymicRule.EAST_SLAVIC, PatronymicRule.TURKIC})

v1’s single flag enabled both detectors at once; pick one rule (or a locale pack, see Locale packs) if you only want one tradition

middle_name_as_last

Policy.middle_as_family

nickname_delimiters

Policy.nickname_delimiters

Was a dict of named sentinels; now a plain frozenset of (open, close) pairs. Both APIs gained the #273 typographic defaults (smart quotes, guillemets, CJK brackets, …) in 2.0

maiden_delimiters

Policy.maiden_delimiters

Same shape change as nickname_delimiters. Precedence differs: in the 2.0 API a pair listed here wins over nickname_delimiters; through the 1.x facade a pair in both buckets keeps parsing as nickname (v1 behavior)

regexes.bidi

Policy.strip_bidi

regexes.bidi = False becomes Policy(strip_bidi=False)

regexes.emoji

Policy.strip_emoji

regexes.emoji = False becomes Policy(strip_emoji=False)

force_mixed_case_capitalization

capitalized(force=True)

The stored default is gone; pass force at the call site. v1’s capitalize(force=True) already worked that way — this constant only supplied the default for calls that omitted it

capitalize_name

(no equivalent)

2.0 never capitalizes automatically during parse(); call .capitalized() explicitly on the result instead

Every other regexes.* entry (word, spaces, and the rest of the compiled-pattern proxy) has no 2.0 replacement — parsing behavior is configured entirely through named Policy fields now, not by handing the parser a regex.

Warning

non_first_name_prefixes and particles_ambiguous mark complementary sets, not the same set under a new name. non_first_name_prefixes lists particles that are never read as a given name; particles_ambiguous lists the particles that may be read as one. Translating a customization means flipping the set: particles_ambiguous = lexicon.particles - constants.non_first_name_prefixes. Copying non_first_name_prefixes straight into particles_ambiguous silently inverts which particles are allowed to double as a given name.

Comparison

HumanName.__eq__/__hash__ were deprecated in 1.3.0 and are gone in 2.0’s core API; use matches() for “is this the same name?” and comparison_key() for dedup, dict keys, and sorting — both exist on HumanName and on ParsedName with the same behavior:

>>> from nameparser import HumanName, parse
>>> hn = HumanName("de la Vega, Juan")
>>> n = parse("de la Vega, Juan")
>>> hn.matches("Juan de la Vega"), n.matches("Juan de la Vega")
(True, True)
>>> hn.comparison_key() == n.comparison_key()
True

One behavior changed underneath both methods: components now fold with str.casefold() instead of str.lower(), so more Unicode case-pairs compare equal than did under 1.4. The change is strictly more permissive — anything 1.4 matched still matches:

>>> parse("Anna STRASSE").matches("Anna Straße")
True

Behavior changes

Beyond the API surface mapped above, a handful of parse outputs differ between 1.4 and 2.0 for specific input shapes. The full list, with reasoning, is in the 2.0.0 section of Release Log. These are the shapes worth grepping your own fixtures for, because a recognized suffix or title now stays in its own field instead of landing in first/last:

>>> HumanName("Andrews, M.D.").last, HumanName("Andrews, M.D.").suffix
('Andrews', 'M.D.')
>>> HumanName("Johnson PhD").first, HumanName("Johnson PhD").suffix
('Johnson', 'PhD')

Under 1.4 those read first="M.D."/last="Andrews" and first="Johnson"/last="PhD" respectively. Two more to check for: a maiden marker now fills the maiden field rather than being folded into middle/last ("Jane Smith née Jones"), and with a custom suffix delimiter configured, a no-space delimiter group renders whole ("RN/CRNA") where 1.x split it ("RN, CRNA") — the role assignment is identical, only the rendered string differs.