Release Log¶
2.1.0 - Unreleased
East Asian name support
Add the Chinese locale pack
locales.ZH— opt-in Han segmentation for unspaced names like毛泽东, with the surname vocabulary it needs. A pack rather than a default because a Chinese surname list corrupts the Japanese names written in the same characters (高橋一郎would split高+橋一郎); Japanese data goes throughlocales.JAand its segmenter instead. It sets no name order, since native-script Han already reads family-first without it (#271)Add
Lexicon.surnames,Policy.script_orders,Policy.segment_scripts, theScriptenum and theDEFAULT_SCRIPT_ORDERSconstant to the public API. This is the first behavior nameparser keys on the script a name is written in; it is allowed only where the script itself settles a convention, never as a proxy for guessing the language (#271)Add
Lexicon.honorific_tails, the vocabulary the glued-honorific peel matches: entries that may be split off the END of a name token, matched longest-first. Deliberately a separate, narrower set than the spaced honorific vocabulary — a glued tail has no token boundary to lean on — and every entry is also asuffix_wordsentry, since the peeled piece is claimed by ordinary suffix classification. That subset rule is enforced, so extend both fields in the one call —Lexicon.default().add(suffix_words={"ちゃま"}, honorific_tails={"ちゃま"}); adding tohonorific_tailsalone raisesValueErrornaming the orphan (#308)Add
AmbiguityKind.SEGMENTATION, reported when a surname split had a vocabulary-supported alternative:"남궁민수"is 남궁 + 민수 by the compound surname but 남 + 궁민수 by the single-syllable one, and longest-match had to pick. A name with only one possible split decided nothing and reports nothing (#271)Add the Japanese locale pack
locales.JAand the segmenter factorylocales.ja_segmenter(), which together divide an unspaced Japanese name:parser_for(locales.JA, segmenter=locales.ja_segmenter())reads山田太郎as family山田, given太郎. The two halves are separate because no surname list can do this job — family and given names draw on the same kanji and the reading, not the spelling, decides most divisions — so the pack activates the stage and a third-party divider performs it.ja_segmenter()wraps namedivider-python, installed with the newnameparser[ja]extra; the core stays dependency-free, andja_segmenter(gbdt=True)selects namedivider’s more accurate gradient-boosted model, which downloads its data on first use. With both packs registered,locales.available()is now('ja', 'ru', 'tr_az', 'zh')(closes #272)Add
Segmentationand theSegmentertype alias to the public API, plus the keyword-onlyParser(segmenter=...)hook they describe: any callable from a token’s text to aSegmentation(the interior offsets to cut at, and a confidence) orNoneto decline. It is consulted only for scripts listed inPolicy.segment_scripts, and only where the surname vocabulary declined first, soparser_for(locales.ZH, locales.JA, segmenter=...)composes — a listed Chinese surname wins, the segmenter takes the rest. Read that composition with the zh pack’s own warning still attached: vocabulary-first means a Japanese kanji name opening on a listed Chinese surname never reaches the segmenter, so高橋一郎still splits高+橋一郎under the stack exactly as it does underlocales.ZHalone. The two packs are alternatives, one per corpus; stack them only for genuinely mixed data that accepts that trade (#272)Add the
ScriptmembersHIRAGANAandKATAKANA. Two members rather than oneKANAbecause the parser treats them differently: hiragana never transcribes a foreign name, while a wholly-katakana name usually is one (#272)
Breaking Changes
Change the pickle compatibility of the 2.0 API’s
PolicyandLexicon: the new fields change the field layout their guarded__setstate__checks, so a pickle written by 2.0.0 raisesValueErrornaming the missing fields instead of loading. Re-pickle after upgrading.HumanNamepickles are unaffected — the facade pickles v1-shaped component state, not these objectsChange the pickle compatibility of
Parserin the same way and for the same reason: the newsegmenterfield changes the layout its guarded__setstate__checks, so aParserpickled by 2.0.x raisesValueErrornaming the missing field rather than loading silently with the field absent. That guard is the design, not a regression — re-pickle after upgrading. AParsercarrying a segmenter pickles only if that segmenter does, which a module-level function does and a closure or lambda does not (#272)Change one thing about parse totality:
parse()still never raises on any input, but a user-suppliedParser(segmenter=...)runs inside the parse and its own exceptions propagate rather than being absorbed. A failure there is a bug in your callable, not a fact about the name, and hiding it would only make it harder to find (#272)
Behavior Changes
Fix names written wholly in Han or Hangul parsing given-first: native-script CJK now reads family-first by default, through the new
Policy.script_orderstable, so"毛 泽东"gives family毛where 1.x gave family泽东. A name that is a single unspaced token moves the same way —"毛泽东"and"山田太郎"now land infamily/lastwhere 1.x put them ingiven/first, with the string itself untouched, which makes it the easiest form of this change to miss — a lone token renders the same whichever field holds it, whereas the spaced form does show up in the output (str(HumanName("毛 泽东"))is now"泽东 毛"). No language detection is involved: Chinese and Japanese both write the family name first in native script, so the script settles the order without anyone having to know which language it is. Latin-script and mixed-script names are never affected, and an explicit comma still wins. Default-on: changes parse output for wholly-CJK names, throughHumanNameas well as the 2.0 API (closes #271)Fix unspaced Korean names not splitting: the census surname list now ships as default vocabulary (
Lexicon.surnames) with hangul segmentation on by default (Policy.segment_scripts), so"김민준"parses family김, given민준where 1.x returned the whole string asfirst. Rendering follows the split, sostr(HumanName("김민준"))is now"민준 김"where 1.x echoed the input back unchanged. Nothing but Korean is written in hangul and its surnames are a closed census set, which is what makes the split safe as a default rather than a pack. Default-on, and it reachesHumanNametoo.Policy(segment_scripts=())turns the split off;Policy(script_orders={})separately restores the positional reading; clearing both restores 2.0 behavior exactly (#271)Fix Japanese names carrying kana parsing given-first: the family-first rule above extends to any name whose characters stay within kanji and kana while carrying at least one kana character, so
"高橋 みなみ"gives family高橋and"山田 エミ"family山田where 1.x read both the other way round, and a lone such token ("高橋みなみ","みなみ") lands infamily/lastwhere 1.x put it ingiven/first. The reasoning is the one hangul already uses: hiragana never transcribes a foreign name, and a transcription is kana ALONE, so kanji-plus-kana is a Japanese person’s name written in Japanese order. A name written wholly in katakana is deliberately excluded and stays positional — it is predominantly a transcribed foreign name ("マイケル ジャクソン") already in given-first order. Default-on: changes parse output for kana-bearing Japanese names, throughHumanNameas well as the 2.0 API, and the spaced forms change what the name renders as (str(HumanName("高橋 みなみ"))is now"みなみ 高橋").Policy(script_orders={})clears this entry along with the Han and Hangul ones (#272)Fix names containing 〆 (U+3006, the shime mark that opens Japanese surnames like 〆木 and 〆谷) parsing given-first: the script classifier now counts 〆 as Han, extending the 々 entry the table already carries, and going a step further than it — 々 is Script=Han and merely outside the ideograph blocks, while 〆 is Script=Common — so these names take the East Asian family-first reading like any other wholly-Han name.
Policy(script_orders={})restores the positional reading for these names exactly as for other wholly-Han names (#303)Fix the katakana middle dot
・(U+30FB, and its halfwidth twin U+FF65) being read as part of a name rather than as the divider it is: it now separates tokens exactly as a space does. A transcribed foreign name therefore divides into its parts and, being wholly katakana, keeps its source order —"マイケル・ジャクソン"gives givenマイケル, familyジャクソン, where 1.x left the whole string infirst— while a kanji pair written the same way takes the family-first rule ("高橋・一郎"→ family高橋). Native Japanese names never contain this character and no other script’s names use it, so the separation is unconditional, which also means it is not covered by the two policy opt-outs. Rendering follows, the way the Korean split’s does: the dot comes back as a space, sostr(HumanName("マイケル・ジャクソン"))is now"マイケル ジャクソン", and that reaches delimited content too — the nickname in"山田 太郎 (マイケル・ジャクソン)"renders"マイケル ジャクソン". The Chinese interpunct·is not an unconditional separator like these two: U+00B7 is also the Catalan punt volat and appears inside legitimate names (Gal·la), so it divides only between classified-script characters — the transcription treatment it marks is #298’s (#272)Fix 间隔号-divided transcriptions parsing as one unsplit token: U+00B7 — the interpunct Chinese text divides a transcribed foreign name with,
威廉·莎士比亚for William Shakespeare — is now a token separator between characters of a classified script, and a name it divides keeps its source order and is never segmented: the dot is the transcription marker, playing the role pure katakana plays in the kana license. It divides ONLY between classified-script characters, so the Catalan punt volat interior to names likeGal·lais untouched. The Japanese nakaguro is deliberately not a transcription marker —高橋・一郎is roster formatting, 姓・名, and keeps its family-first reading — so a transcription typed with the wrong dot (威廉・莎士比亚) reads by the convention of the codepoint it was typed with; like the spaced form, only the Chinese dot rescues it. Rendering stays space-joined (str(HumanName("威廉·莎士比亚"))is"威廉 莎士比亚"); a customstring_formatsuch as"{first}·{last}"reinstates the dot (#298)Fix spaced CJK postnominal honorifics parsing as name parts: 씨, 박사, 선생님, 교수님, 군, 양 (Korean — standardly written as their own token), 先生, 女士, 小姐, 博士, 教授 (Chinese, with 先生/博士/教授 shared with Japanese), and 様, 氏 (Japanese) now route to
suffix, so王小明 先生reads family王小明where the family-first default had confidently made 先生 the given name. Whole-token matching, which also reaches a glued surname+honorific token, since segmentation splits off the surname first (김씨reads family 김, suffix 씨). The glued forms whole-token matching cannot reach are handled by the peel described below (closes #307)Fix glued CJK honorifics parsing as part of the name: an honorific written against the name —
田中さん,山田太郎様,김민준씨,김민준님,王小明先生— is now split off the end of the last name token and routed tosuffix, where it had been swallowed by the name (the whole of田中さんwas the family name;김민준씨gave given 민준씨). The peeled name then goes through the ordinary machinery, so the Korean split still happens (김민준씨→ family 김, given 민준, suffix 씨) and the田中さんcase stops misreading as a kana-licensed composite. Only entries that can never END a name peel — 씨, 님, 박사, 박사님, 선생님, 교수님, さん, さま, くん, ちゃん, 様, 先生, 教授, 女士, 小姐 — while 양, 군, 氏, 博士 and 殿 are recognized in their spaced form only, because 김지양 is a given name, 田中博士 is Tanaka Hiroshi as readily as Doctor Tanaka, and some ninety Japanese surnames end in 殿 (鵜殿, 真殿); 君 is recognized in neither form, since 王君 is a complete Chinese name, though its kana spelling くん peels. Seven entries are new vocabulary in this change (さん, さま, くん, ちゃん, 殿, 님, 박사님), so their spaced forms route tosuffixtoo (田中 さん,田中 殿,김민준 님,김민준 박사님). 박사님 closes a gap in the shipped set rather than opening new ground: 선생님 and 교수님 shipped in 2.1 without it, so김민준박사님stranded 박사 in the given name and the spaced김민준 박사님came back as two suffixes for one honorific. Exactly one honorific peels off a token, and every entry is a whole honorific rather than a part of one. A token that is nothing but an honorific is no longer taken apart either —선생님and박사now stay whole where hangul segmentation had split them 선 + 생님 and 박 + 사, since 선 and 박 are listed surnames — which is default-on in its own right, hangul segmentation being a default. A configured segmenter benefits twice over: it is handed the name without the honorific, and the honorific the peel just cut off does not then look to it like a boundary the writer drew — soparser_for(locales.JA, segmenter=ja_segmenter())reads山田太郎様as family 山田, given 太郎, suffix 様. Worth knowing before you upgrade: that exemption is what makes a GLUED honorific stop protecting a name from division, so a family name written alone with one —田中さん— now divides the way bare田中already did (family 田, given 中, suffix さん). It is exactly and only the peeled tail that is exempt. A SPACED honorific is a token boundary its writer typed, and anything standing beside a name calls the segmenter off, so the name is left as written:田中 さんand佐藤 氏keep family 田中 and 佐藤 under the pack — the division the pack gives them without this change, whichever field the honorific itself lands in. That is the conservative reading rather than a claim about intent: a spaced honorific cannot be told apart from a spaced given name by position, and counting it as one keeps four real surnames whole (佐藤 氏,田中 様,鈴木 先生,中村 教授) at the price of the one division it then declines to make (山田太郎 様). Writing the honorific spaced is therefore an opt-out in its own right on the SEGMENTER path, alongside declining the pack or the segmenter. It is no lever where the VOCABULARY divides the name, the two spellings agreeing exactly there —김민준 씨and김민준씨both give family 김, given 민준, suffix 씨, as do王小明 先生and王小明先生under the Chinese pack — and Korean data has no pack to decline either, hangul segmentation being on by default. Default-on: changes parse output for glued CJK honorific forms, throughHumanNameas well as the 2.0 API (closes #308)Fix a comma or a 间隔号 stopping the glued-honorific peel: an honorific written against the name is now split off and routed to
suffix, so김, 민준씨gives family 김, given 민준, suffix 씨 — the same as the spaced김 민준씨— and likewise田中, 太郎さん, which also matches its spaced form.田中さん, PhDand威廉·莎士比亚さんpeel too; the first of those does not otherwise match its spaced form, since田中さん PhDleaves PhD insuffixbeside さん while after a comma it reads as atitle— where the credential lands is the comma’s business, not the peel’s. Previously each of these left the honorific inside the name. A comma no longer switches the peel off; what it does now is say which runs of the name to look in, and those are the two around a family comma, an honorific being as often glued to the given name as to the family. Anything past those two runs is out of reach, which is the one limit worth knowing:김, 민준 지훈씨peels, while김, 민준, 지훈씨(a third run) and김,, 민준씨(a doubled comma, which puts the name in a later run) do not. The second of those two runs counts only where it is name text, which is a question the parser asks rather than reads off the comma — see the next entry. The 间隔号 does not switch the peel off either. Both marks say where a name divides into surname and given, and an honorific is not part of the name in either reading. The surname split still stands down for both, unchanged — a comma still means the writer said where the family name ends, and the 间隔号 still marks a transcription.田中さん, 太郎is unaffected, because the honorific there is not at the end of the name: 太郎 is. Default-on, and it reachesHumanNametoo (closes #312)Fix a glued honorific staying inside the name when the whole post-comma remainder is a credential:
田中さん, V.and田中さん, Ph. D.now give up さん tosuffixthe way田中さん, PhDalready did, and so do김민준씨, V.,王小明先生, Ph. D.and山田太郎様, Jr. V.. The peel looks in the two runs around a family comma (the entry above), and it had been taking the second of those for name text on the strength of the comma alone. A comma does not say that much: the suffix-comma reading also wants more than one word ahead of the comma, so a one-word name followed by nothing but credentials reads as a family comma anyway — which is exactly the shape田中さん, V.has. The scan then walked into the credentials, tookV.for the honorific’s site, found no honorific on the end of it and abandoned the peel without a word. Whether a post-comma run is credentials is now asked with the same test that decides the comma structure itself, and where the answer is yes the run is declined and the scan stays with the name. Declining takes a second condition, because that test is not independent of the peel: every honorific tail is also a suffix word, so a glued honorific is itself part of the evidence that the run it sits in is suffix-shaped. The run is therefore declined only where the part before the comma offers a peel site of its own — otherwise declining would throw away the only site there is, and이, J.씨would lose its given name, where it in fact still reads givenJ., family이, suffix씨. Where both parts offer a site the decline stands and the person’s own honorific is the one peeled:김민준씨, J.씨now gives family 김민준 with씨, J.씨insuffix, where before it peeled the 씨 off the junkJ.씨behind the comma and left 김민준씨 whole. One credential in three spellings, one answer — from the peel: where the credential itself lands is the comma’s business as before and still differs by spelling,PhDreading as atitle,V.as agiven, andPh. D.joining さん insuffix.Policy(lenient_comma_suffixes=False)keeps the old reading for the initial-shaped credentials the knob is about — with it set,V.,VandIare a given name again and the family name is the whole田中さん— but it is no blanket freeze of the old behavior:田中さん, Ph. D.peels under the knob too, the credential pair merging to a form the strict test accepts either way. Nothing Latin-script moves, since nothing there had an honorific to peel, and no name that peeled before stops peeling — that second one is what the site condition above buys, and it is measured rather than reasoned. What did change wherever the run is declined, answer or no answer, is which runs the peel reads:田中さん, PhDand田中さん, 様.reached さん before by stepping over the post-comma token the scan had walked onto, and reach it now by never looking at that run —様.being a listed honorific rather than a credential, and the second one this name has no use for. Default-on, and it reachesHumanNametoo (closes #319)Fix an ASCII period after a CJK honorific stopping it being recognized:
씨.,様.,氏.,님.,군.,양.and殿.now route tosuffixlike their periodless spellings, where the trailing period had left them inside the name — the family name in"김민준 씨.", the given name in"김민준, 씨.". The cause was v1’s initial regex,^(\w\.|[A-Z])?$(REGEXES["initial"], still public v1 API), whose\wis Unicode-aware and so matched a hangul syllable or a Han ideograph as readily as a letter; the strict suffix test applies that as a veto (V.in"John V. Smith"is a middle initial, not roman five), and a veto written for Latin was being asked of scripts it was never about. The cost ran past the honorific itself: because the vetoed token read as name text, the glued-honorific peel’s scan back for its site stopped at it instead of stepping over it, took it as the site, found no honorific at the end of it and gave up — so"田中さん 様."keptさんinside the name, reading given田中さんand family様., while"田中さん 様"peeled it. The comma form"田中さん, 様."reached the same scan when this was written; since the #319 entry above it no longer does — its post-comma run is now declined as suffix-shaped before the scan begins, so様.gets tosuffixthrough classification rather than by being stepped over. Measured against 1.4.0,"김민준, 씨."and"田中さん, 様."were returning exactly what 1.x returns, so the honorific work earlier in this release had a hole in it wherever the honorific was written with a period. An initial is a single LETTER standing in for a name, and Han ideographs, hangul syllables and kana are morphemes and syllables rather than letters, so the veto now asks its question only of the scripts where it means something. Alphabets keep their initials untouched —"А. С. Пушкин","م. الفارسي"and"Ա. Խաչատրյան"are unaffected, and so is the Ukrainian conjunction entry below, where a punctuatedЙ.still outranks the conjunctionй. The publicinitialtag follows the same line:씨.no longer carries it. Readperiodstrictly here: the fix is scoped to the ASCII full stop U+002E, because that is the only period_normalizestrips. The fullwidth U+FF0E and the ideographic U+3002 (with its halfwidth twin U+FF61) — the stops a CJK writer is likelier to type — leave the honorific unmatchable by the vocabulary lookup, which runs before the veto is ever consulted, so"김민준 씨."still reads the honorific as the family name. That is a separate, still-open gap in_normalizerather than in the veto: those spellings parse identically before and after this change, and widening the strip is follow-up work. Default-on, and it reachesHumanNametoo (#320)Fix NFD-decomposed input missing the East Asian defaults entirely: script classification now normalizes to NFC before deciding, so a Korean or Japanese name typed on macOS — where decomposed text is routine — gets the same order rule as its composed twin, which it silently did not before. Segmentation MATCHING deliberately stays raw, so an unspaced NFD hangul name is ordered correctly but not split, rather than being split in the wrong place. One gotcha worth stating plainly: parse output preserves the encoding it was given, so for NFD input
name.family == "김"isFalseeven though it is the same name — compare NFC-normalized text when comparing across encodings (#272)Fix the Ukrainian conjunction
йnot joining the pieces around it: it is the euphonic alternate ofі, the two chosen by the surrounding vowel and consonant rather than by meaning ("Олесь і Олена"but"Марія й Петро"), so real Ukrainian data carries both spellings and shipping onlyіrecognized just one of them."Олесь й Олена Коваленки"now gives given"Олесь й Олена"where theйpreviously landed inmiddle. Same treatment as theи/іentries added in 2.0.0, single-letter carve-out included: the conjunction joins only once the name has enough pieces, and a punctuated initial still wins, so"Й. Сліпий"is unaffected. Raised in a comment on #267Add the Japanese maiden-name marker
旧姓to the default vocabulary (#309):"山田花子 旧姓 佐藤"now gives family山田花子and maiden佐藤, where 1.4.0 left the marker in the name (first山田花子, middle旧姓, last佐藤). It sits beside the Cyrillicурожд.and Germangeb.entries rather than inlocales.JA, on the rule that admitted those: a native-script marker cannot collide with a Latin-script name and matching is whole-token, so it is safe as a default and needs no my-data-is-Japanese declaration – it can only ever match Han text. Scope worth knowing before you rely on it: matching is whole-token, so the marker has to be a token — which for Japanese, written without spaces between words, means something has to divide it from the name it marks. A space does, and so does a configured delimiter, the brackets being masked out of the text before it is tokenized:"山田(旧姓 佐藤)"needs no space in front of旧姓. So the bare"山田花子 旧姓 佐藤"qualifies, and so does the bracketed"山田 花子(旧姓 佐藤)"underPolicy(maiden_delimiters=...), once the entry below closed #329. What divides nothing is the fullwidth colon that the spelling Japanese more often uses puts after the marker:"山田(旧姓:佐藤)"still returns maiden"旧姓:佐藤"with the marker and its colon attached. Not because delimited content escapes classification — a marker is tagged wherever it is a token — but because that colon is no separator, so marker and name arrive as a single token with nothing to divide; the wholly unspaced"山田花子(旧姓佐藤)"reads as one token for the same reason. Peeling a marker off the head of a token is #317’s job. Default-on, and it reachesHumanNametoo (#309)Fix a maiden marker inside bracketed content staying in the
maidenvalue: where a delimiter pair is routed tomaidenbyPolicy(maiden_delimiters=...), a marker word at the head of the bracketed clause is now dropped the way it has always been dropped in the bare form, so"Jane Smith (née Jones)"gives maidenJoneswhere it gavenée Jonesbefore — the same answer as the unbracketed"Jane Smith née Jones". The Japanese spelling moves with it:"山田 花子(旧姓 佐藤)"gives maiden佐藤. Nothing else about either name changes — the marker leaves themaidenvalue without turning up anywhere else, the way it already vanished from the bare form, and every other field reads exactly as before.maidenis in fact the only field that ever differs, which is measured rather than reasoned. The cause was never that the marker went unrecognized — a maiden marker is classified wherever it stands as a token, inside brackets as anywhere else. What was missing was the consuming: bracketed content is claimed as a region before the name is tokenized at all, and the tokens cut inside it are born already holding the maiden role, which keeps them out of the grouped runs the marker rule walks, so that rule could not reach a marker sitting in one. The new pass is scoped to the delimited clause, keyed on the extraction spans rather than on a maiden token’s neighbours, and both halves of that are load-bearing. A maiden role is not proof of extraction — the bare rule sets it too — so a neighbour test would fire on the bare path and eat the surname out of"Jane Smith nee Nee Jones", which still gives maidenNee Jones. And separate clauses are separate content:"Jane Smith (Nee) (Jones)"also still gives maidenNee Jones, because the drop takes a clause’s first token only where the clause holds more than one —Neeis a real surname (Irish Ní/Nee, and a Chinese romanization), so a one-token clause is a name rather than a marker, and"Jane Smith (née)"keeps itsnéefor the same reason. Each clause loses its own leading marker, so"Jane Smith (née Jones) (geb Braun)"gives maidenJones Braun. One edge deserves naming because what it changes is a name’s truthiness rather than a field: where a maiden clause is the entire input and everything in it besides the marker is punctuation, dropping the marker leaves nothing carrying an alphanumeric character, and 2.0’s standing rule that such input is not a name empties the parse —"(née —)"returned maiden"née —"and is now falsy throughout. That follows from treating the marker as structural, the same reading under which"(-)"has always come back empty, and it takes a name that is nothing but the clause:"Jane Smith (née —)"still gives givenJane, familySmith, maiden—. Scope before you count on it:Policy.maiden_delimitersis empty by default, so this reaches only callers who have opted a pair into it — under the default policy brackets route tonicknameand nothing here applies.HumanNamereaches it only through the v1 bucket move —maiden_delimiters['parenthesis'] = nickname_delimiters.pop('parenthesis')— since v1 precedence gives a pair held in both buckets tonickname, and the facade preserves that; with the move,HumanName("Jane Smith (née Jones)")gives maidenJonesas well. And the agreement with the bare form is not total: the Japanese form written with a fullwidth colon,"山田(旧姓:佐藤)", still returns maiden"旧姓:佐藤", since the colon leaves marker and name a single token with nothing to separate — that one wants the head-peel #317 tracks, and the entry above says the rest of it (closes #329)
Documentation
Correct the documented scope of the period-abbreviation title rule. It was described as applying to “a leading word”, which was never true of any comma form: the rule runs at the front of the part that carries the given name, which after a family comma is the part after the comma — so
"Morse, Det. Insp. Jane"gives titleDet. Insp.. Behavior is unchanged and matches 1.4.0; only the description was wrong. The examples now use real abbreviations absent from the shipped vocabulary (Det. Insp.) rather than whole words carrying a stray period, so they demonstrate the structural inference instead of merely surviving it
2.0.0 - July 27, 2026
Two release candidates preceded this release (rc1 on 2026-07-23, rc2 on 2026-07-26). Please report anything the migration missed on issue #284. The notes below describe 2.0.0 as a whole.
nameparser 2.0 adds a new parsing API alongside
HumanName.parse()returns an immutableParsedNamewhose seven fields are named for what they are (given,family) rather than where they sit in a Western name, configured by two frozen value objects – aLexiconof vocabulary and aPolicyof behavior – instead of a mutable global.HumanNamekeeps working: it is now a compatibility facade over the same pipeline, and it stays through 2.x. The removals below are the deprecations announced in 1.3.0 and 1.4.0 coming due; if your code runs warning-free on 1.4.0, most of them will not affect you. See Migrating from HumanName for the field-by-field map.The 2.0 API
Add
parse(text), returning an immutableParsedNamewith seven fields –title,given,middle,family,suffix,nickname,maiden– plus the derived viewsgiven_names,surnames,family_baseandfamily_particles. Parsing is a pure function of the text, aLexiconand aPolicy: nothing global is consulted and nothing is mutatedAdd
Lexicon, the frozen vocabulary object.Lexicon.default()is the shipped vocabulary andLexicon.empty()is a blank one;add(**entries),remove(**entries)and field-wise|all return new instances. Its fields aretitles,given_name_titles,suffix_acronyms,suffix_words,suffix_acronyms_ambiguous,particles,particles_ambiguous,conjunctions,bound_given_names,maiden_markersandcapitalization_exceptionsAdd
Policy, the frozen behavior object:name_order,patronymic_rules,middle_as_family,nickname_delimiters,maiden_delimiters,extra_suffix_delimiters,lenient_comma_suffixes,strip_emojiandstrip_bidiAdd
Parser, a reusable parser bound to a lexicon and policy (Parser(lexicon=..., policy=...).parse(text)), andparser_for(*locales, base=None), which folds locale packs onto a base parserAdd
name_orderand the constantsGIVEN_FIRST,FAMILY_FIRSTandFAMILY_FIRST_GIVEN_LAST, so a family-first name can be parsed as written rather than reordered by hand (the configuration half of #270; the locale packs below complete it)Add
PatronymicRulewith the membersEAST_SLAVICandTURKIC. v1’s singlepatronymic_name_orderflag enabled both detectors at once;Policy(patronymic_rules=...)lets you enable either one aloneAdd
PolicyPatchand theUNSETsentinel for partial policy deltas that compose – set-valued fields union, scalar fields override with later winning. This is the mechanism locale packs are built from, andUNSETis only needed when you must distinguish “not set” from a realFalseorNoneAdd
Token,SpanandRole: every field is backed by tokens carrying exact(start, end)offsets into the original string, reachable withtokens_for(Role.GIVEN). This replaces v1’s*_listattributes and makes it possible to highlight or re-slice the input the parse came from.Roleis aStrEnum, so members compare and stringify as their field names (Role.GIVEN == "given"), matchingAmbiguityKind;tokens_for()accepts a role’s string name too, and raisesValueErrorfor an unknown roleAdd
STABLE_TAGSto the public API: the four documentedToken.tagsvalues (particle,conjunction,initial,joined)Add
Policy.patched(patch), applying aPolicyPatchdirectly without wrapping it in a locale packAdd
Parser.matches(a, b)andParser.capitalized(name): theParsedNamemethods of the same names fall back to the default configuration for str/omitted arguments, which is silently wrong for names parsed with a customParserAdd
Parser.revise(name, **fields):ParsedName.replace()with the replacement text classified by the parser’s vocabulary, so particle/initial/suffix-join behavior survives the editAdd
Ambiguityand theAmbiguityKindenum, so a parse reports what it had to guess at instead of guessing silently. The kinds emitted today areparticle-or-given,suffix-or-name,suffix-or-nickname,unbalanced-delimiterandcomma-structure;orderis reserved and not yet emitted. The two suffix kinds cover the post-nominals that are also ordinary words:"John Smith MA"reports thatMAwas read as a credential rather than a surname, and"JEFFREY (JD) BRICKEN"that the delimitedJDwas read as a nickname rather than a suffix. A reading the vocabulary settles on its own —"John Smith M.A.","Andrew Perkins (MBA)"— is not a guess and reports nothingAdd
ParsedNameoutput and comparison methods:render(spec),initials(),capitalized()(which returns a new value rather than mutating in place),as_dict()(whoseinclude_emptyflag is keyword-only, unlikeHumanName.as_dict()’s),replace(**fields),matches()andcomparison_key()Add
Locale, the public pack type. Writing your own needs no registration – construct aLocaleand pass it toparser_for()Ship a fully typed public API (PEP 561): the core modules are checked under strict mypy settings, and nameparser 2.0 has no runtime dependencies
Breaking Changes
Raise the minimum Python to 3.11 and drop the last runtime dependency,
typing_extensions(#257). Python 3.10 is no longer supportedRemove
HumanName.__eq__and__hash__(deprecated in 1.3.0, #223): instances now compare and hash by identity, soHumanName("John Smith") == "John Smith"isFalsewhere 1.x returnedTrue. This changes result silently rather than raising – it is the one removal that can pass unnoticed into production. Usematches()to ask whether two names are the same, andcomparison_key()as a dict key or sort keyRemove every v1 parsing hook and subclass extension point:
pre_process,post_process,parse_pieces,parse_nicknames,join_on_conjunctions, theis_*predicates,cap_word/cap_piece,handle_firstnames,fix_phdand the rest. A subclass that overrides one gets aDeprecationWarningat construction naming the hooks, because the facade delegates to the coreParserand never calls them (closes #280). Customize throughLexicon/PolicyinsteadRemove regex configuration:
CONSTANTS.regexesis now a read-only proxy. Reads still work, butCONSTANTS.regexes.bidi = False, item assignment andConstants(regexes=...)all raiseTypeError. If you followed 1.3.1’s advice to keep bidi marks withCONSTANTS.regexes.bidi = False, that opt-out is nowPolicy(strip_bidi=False)on the 2.0 API; the same applies toregexes.emojiandPolicy(strip_emoji=False)Remove
bytesinput and theencodingargument (#245): passingbytestoHumanNameor to a set manager raisesTypeErrorwith a decode hint, andSetManager.add_with_encoding()andDEFAULT_ENCODINGare gone. Decode first, then useadd()Remove
SetManager.__call__(#243); iterate the manager or callset(manager).remove()of a missing member now raisesKeyErrorlikeset.remove–discard()is the ignore-missing form – and the set operators|,&,-and^return a plainsetRemove
HumanNameslice access and item assignment (#258):name[1:-3]andname['first'] = valueraiseTypeError. String-key reads (name['first']) and iteration are unchanged; assign fields as plain attributesRemove
Constants.empty_attribute_default(#255): empty fields are always''. Assigning it raisesAttributeError; a pickle carrying the key still loads, with the value ignoredRemove
constants=None(#261): bothHumanName(..., constants=None)andhn.C = NoneraiseTypeError. UseConstants()for library defaults orCONSTANTS.copy()for a private snapshotRemove silent unknown-key access on the mapping managers (#256):
CONSTANTS.capitalization_exceptions.typoandCONSTANTS.regexes.typoraiseAttributeErrornaming the miss, instead of returningNone/EMPTY_REGEX(capitalization_exceptionsalso lists the known keys)..get()remains available on both for intentional soft accessRemove support for
Constantspickles written by nameparser 1.2.x or earlier (#279): loading one raisesValueErrortelling you to re-pickle under 1.3/1.4 firstRemove the dead
regexes.no_vowelspattern (#268) and theConstants.suffixes_prefixes_titlescached union; neither was read by the parserChange
HumanName’s*_listattributes to read-only properties: they remain readable snapshots, butname.first_list = [...]now raisesAttributeErrorChange the
Constantsconstructor to keyword-only; positional construction no longer works
Behavior Changes
Recognize maiden-name markers –
née,nee,geb.,roz.and the Scandinavian participle forms – and route the following name to the newmaidenfield. 1.x folded them intomiddle/last, so"Jane Smith née Jones"parsed asmiddle="Smith née"(closes #274)Recognize typographic nickname delimiters by default in both APIs (closes #273): smart quotes (
“Jack”), German and Polish low-high quotes („Hansi“), Swedish right-right quotes (”Ann”), guillemets in either direction («Petit»,»Hansi«), CJK corner brackets (「タロ」,『ハナ』) and fullwidth parentheses. In 1.x these leaked intomiddleas literal text. Curly single quotes stay excluded, because U+2019 is the apostrophe in “O’Connor”Fix the pre-comma piece being routed to
firstwhen everything after the comma is a suffix or title:"Andrews, M.D."now reads familyAndrews/ suffixM.D.where 1.x read givenM.D./ familyAndrews, and"Smith, Dr."movesSmithfromfirsttofamily(the title was already correct in 1.x). The piece before a comma is definitionally the family nameFix a lone recognized trailing suffix with no comma being routed to
first/last:"Johnson PhD"and"Mr. Johnson PhD"now keep the suffix insuffixFix a split
"Ph. D."credential being read as two tokens; it now classifies as one suffix, replacing v1’sfix_phdhook. This now holds wherever the credential sits: 1.x healed the pair only when it trailed, so"Ph. D. John Smith"parsed as titlePh./ givenD.with the real given name pushed tomiddle; it now reads givenJohn, familySmith, suffixPh. D.Fix
chargé d'affaires: shipped as one unmatchableTITLESentry since it was added, it is now two chainable entries (chargé,d'affaires), so the title is recognized; the unaccented spellingchargeships too, likeattaché/attacheRemove seven multi-word
SUFFIX_ACRONYMSentries (leed ap,nicet i–nicet iv,psm i,psm ii) that could never match in any release; splitting them would swallow real names (“John Leed”, “Smith, A.P.”), so they are dropped insteadAdd a
UserWarningwhen a multi-word entry is stored in a per-wordLexiconfield or as acapitalization_exceptionskey – such entries can never matchParse an input with no alphanumeric character to an empty name in both APIs. 1.x kept pure punctuation as a name part, so
"."gavefirst="."andbool()wasTrue; 2.0 empties it, keepingbool(parse(x))an honest “did I get a name?” test. The check is Unicode-aware, so names in any script are unaffected; only inputs that are entirely punctuation or symbols (".","- -") change. Junk embedded in a name with real content – the stray dot in"John . Smith"– is still kept, since that parse is already truthyFold a leading never-given particle into the family name. Note that
Lexicon.particles_ambiguousis the complement of v1’snon_first_name_prefixes, not a rename – it lists the particles that may double as a given name, where v1 listed the ones that may not. Copying a v1 customization across without inverting it silently reverses the behavior; see Migrating from HumanNameAdd
maanddotosuffix_acronyms_ambiguous, the set of post-nominals that are also ordinary surnames. An entry there is read as a credential when the name can spare it — written with periods ("John Smith M.A."), or when removing it still leaves a given and a family name ("John Smith MA"→ suffixMA). With only two pieces to go around, the surname reading wins instead:"Jack Ma"and"Anh Do"keep their family names. As a side effect, a parenthesized or quoted"(MA)"/"(DO)"now falls through to nickname parsing rather than escaping tosuffix, since inside delimiters the nickname reading is the plausible oneChange
comparison_key()andmatches()in both APIs to fold withstr.casefold()where 1.4 usedstr.lower(), so Unicode case pairs compare equal –"STRASSE"matches"Straße", and a Greek final sigma matches its regular form. This is strictly more permissive: anything 1.4 matched still matches. Vocabulary normalization deliberately still useslower(), for v1 parityChange the 2.0 API’s default
render()/str()spec to show every non-empty field:'{title} {given} "{nickname}" {middle} {family} ({maiden}) {suffix}'. The quoted nickname round-trips exactly; the parenthesized maiden re-parses as a nickname, a deliberate choice of presentation over lossless round-trip – usenée {maiden}in a custom spec if you need it to survive a reparse.HumanNamekeeps v1’s ownstring_formatdefault, unchangedChange delimiter-overlap precedence in the 2.0 API: a pair listed in
Policy.maiden_delimitersis dropped from the effective nickname set, soPolicy(maiden_delimiters={("(", ")")})alone routes parenthesized content tomaiden. The default nickname set is exported asDEFAULT_NICKNAME_DELIMITERS.HumanNamekeeps v1’s nickname-wins precedence, so no existing behavior changesChange suffix-delimiter rendering when a custom suffix delimiter is configured: for
suffix_delimiter="/"and"John Smith, RN/CRNA", 1.x split the token and renderedsuffix="RN, CRNA"where 2.0 keeps it whole as"RN/CRNA". Role assignment is unchanged; only rendering differsCorrect a long-standing typo in the shipped vocabulary that 1.x carried:
actorandtelevisionhad trailing spaces inTITLES, so"actor" in TITLESwasFalse. Parse output is unchanged – the parser normalizes entries on ingest, which is exactly what let the typo go unnoticed – but code that tests membership against the exported constants directly will see corrected results. The data modules now assert their invariants at import time, alongside the onesprefixes.pyalready checked; entries must be stored lowercase and whitespace-free, so this class of typo now fails the build
International name support
Add
nameparser.localeswith the first two packs:locales.RU(East Slavic patronymic order) andlocales.TR_AZ(Turkic patronymic markers). Packs are pure data folded in atparser_for(locales.RU), they compose (parser_for(locales.RU, locales.TR_AZ)unions the rules), and they are never auto-detected – there is no reliable way to detect a name’s language from the name alone.locales.available()andlocales.get(code)look packs up by code; loading is lazy, so importing the package imports no pack (completes #270; #271/#272/#146 stay staged for 2.x)Add non-Latin vocabulary to the default lexicon (#269): Cyrillic, Greek, Arabic and Hebrew titles, conjunctions and name particles. Native-script entries cannot collide with Latin-script names, which is what makes them safe to enable by default. Deferred pending vetting: the Cyrillic
мл/стsuffixes and the bare Greekκtitle, which collides with the initial-plus-surname shape. Behavior note:محمد بن سلمانnow chainsبنonto the family name where 1.x read it as a middle nameAdd Arabic-script bound given names (#269):
عبد, the kunya pairأبو/ابو, andأم/امjoin the following word into the given name exactly as their transliterations (abdul,abu,umm) always did, soعبد الرحمن محمدparses givenعبد الرحمن, familyمحمدwhere 1.x split it into given plus middleAdd Arabic honorific titles and the conjunction
و(#269): the doctor, professor, hajj, sheikha and engineer forms (الدكتور/الدكتورة/دكتور/دكتورة,الأستاذ/الأستاذة/أستاذ/أستاذة,الحاج/الحاجة,الشيخة,مهندس) as given-name titles, since Arabic honorifics precede the given name likeالشيخ. Deferred under the collision rule: bareسيد/شيخ/أمير/سلطان(all common given names), theد.abbreviation (bareدwould swallow initials), and the Ottoman post-nominalsباشا/بك/أفندي(which survive as family names)Add Hebrew honorifics and post-nominals, and Devanagari titles (#269): the Israeli honorifics
גברת,פרופ'/פרופ׳,פרופסור,עו"ד/עו״דandהרבas titles;ז"ל/ז״לandשליט"א/שליט״אas suffixes, in both gershayim spellings; and Devanagariश्री,श्रीमतीandडॉ. Latinsri/shriwere deliberately not added, because they collide with real given names where the native script cannot. Deferred: bareרב(an ordinary word meaning “many”) andברas a particle (Bar is a common modern given name)
Compatibility layer
Reimplement
HumanNameas a facade over the 2.0 pipeline, andConstantsas a shim resolving to a(Lexicon, Policy)snapshot with a shared parser cache. Fields, aggregates, mutation throughname.C.titles.add(...), rendering defaults,capitalize(),matches(),comparison_key(), iteration,as_dict()and pickling are all preserved, andnameparser.parserandnameparser.configremain importable. The compatibility layer ships through 2.x and is removed in 3.0Note that
CONSTANTS.capitalize_nameandforce_mixed_case_capitalizationare still honored through the facade, but the 2.0 API never capitalizes duringparse()– callcapitalized()when you want itAdd
Rolemembers (and their string valuesgiven/family) as validHumanNamesubscript keys:hn[Role.GIVEN]returnshn.firstAdd a
UserWarningwhen assigningHumanName.givenor.family: the facade spells those attributesfirst/last, so the assignment creates an inert stray attribute while the parse keeps the old value. The assignment still happens (v1-legal ad-hoc attributes keep working); the warning names the v1 spelling to use
Command line
Rewrite
python -m nameparserover the 2.0 API with a real argument parser. It prints the parse plus its capitalized form and initials by default, takes--jsonto emit the fields as JSON (python -m nameparser --json "Doe, John"), takes--locale CODEto apply a pack, and exits with a usage message on bad arguments
Documentation
Rewrite the documentation new-API-first: a new front page and README,
usage.rstas a tour of the 2.0 API, a principle-firstcustomize.rst, a reference split into the 2.0 API and the compatibility layer, and new pages for How the parser works, Locale packs and Migrating from HumanName – the last carrying full attribute and configuration maps from v1 names to 2.0 names (#262). The 1.x documentation remains online as the readthedocsstablebuild
Parsing changes were checked against a 652-name differential corpus – names harvested from the v1 test banks, plus names reported in the issue tracker – and everything not listed above parses identically between 1.4.0 and 2.0. The harness lives in
tools/differential/in the source repository (it is development tooling, not part of the installed package) – see its README to reproduce the comparison against your own names.Changed since 2.0.0rc1 (for anyone who tested the release candidate)
Rolebecame aStrEnum;str(Role.GIVEN)is now"given"ParsedName.tokens_for()raisesValueErrorfor unknown roles instead of returning no tokens; it also accepts role-name stringsParsedName.as_dict()’sinclude_emptyis keyword-onlyHumanNamesubscripting acceptsRolemembersAssigning
HumanName.given/.familywarns (the facade spells themfirst/last; the assignment was and remains an inert stray attribute)The eight multi-word vocabulary entries that could never match were repaired (
chargé d'affairessplit; seven credential acronyms removed), and storing a new multi-word entry now warns; those eight are dropped silently, not warned about, when a restoredConstantspickle carries all eight of them (the pre-2.0 signature)PolicyPatch’s repr shows only the fields a patch sets, instead of all nine with UNSET sentinelsNew since rc1:
STABLE_TAGS,Policy.patched(),Parser.matches(),Parser.capitalized(), andParser.revise()– see the API section above
1.4.0 - July 12, 2026
Add
Constants.copy(), a detached deep copy that preserves the source instance’s current customizations (unlikeConstants(), which always starts from library defaults) – useful asCONSTANTS.copy()for a private snapshot of the shared config (#260)Deprecate passing
constants=NonetoHumanName(or assigninghn.C = None): it silently builds a freshConstants(), discarding any customizations the caller may have expected to carry over from the sharedCONSTANTS. EmitsDeprecationWarning; will raiseTypeErrorin 2.0. Useconstants=Constants()for fresh library defaults orconstants=CONSTANTS.copy()for a private snapshot instead (closes #260)Deprecate assigning
Constants.empty_attribute_defaultfor removal in 2.0 (#255): onceNonesupport goes, the only legal value left is the default'', so a dial with one position isn’t configuration. EmitsDeprecationWarning; reading the attribute is unaffectedDeprecate unknown-key attribute access on
TupleManager/RegexTupleManager(CONSTANTS.regexes.typo,CONSTANTS.capitalization_exceptions.typo, etc.) for removal in 2.0 (#256): a misspelled or omitted key currently degrades silently (None/EMPTY_REGEX) with no traceback pointing at the typo. EmitsDeprecationWarningnaming the miss and the known keys; will raiseAttributeErrorin 2.0..get()remains available for intentional soft accessDeprecate
HumanNameslice access (name[1:-3]) and item assignment (name['first'] = value) for removal in 2.0 (#258): field access by position has no real use case, and item assignment duplicates plain attribute assignment. Both emitDeprecationWarning; string-key access (name['first']) is unaffectedDeprecate
SetManager.add_with_encoding()itself for removal in 2.0 (#245), regardless of argument type: useadd()instead (decoding bytes first). Previously only thebytespath warned; thestrpath was silent even though the whole method goes awayDeprecate loading a legacy-format
Constantspickle (written by nameparser <= 1.2.x, before the 1.3.0 pickle fix) for removal in 2.0 (#279):__setstate__’s migration shim currently skips the stale computed-property key silently. EmitsDeprecationWarningonce per call telling users to re-pickle; will raiseValueErrorin 2.0Fix the
"Lastname, Firstname"comma format not being recognized when the input uses the Arabic comma،(U+060C, the standard comma in Arabic/Persian/Urdu text) or the fullwidth CJK comma,(U+FF0C) instead of the ASCII comma: both variants now also split the format and no longer leak into the parsed output (closes #265)
1.3.1 - July 11, 2026
Fix invisible Unicode bidirectional control characters (LRM/RLM/ALM, the embedding/override marks, and the isolates U+2066–U+2069) surviving parsing and sticking to
first/last/etc., so a copy-pasted right-to-left name silently failed equality and dedup. They are now stripped in preprocessing like emoji; disable viaCONSTANTS.regexes.bidi = False(closes #266)Fix
str()corrupting name text containing the substring"None"whenempty_attribute_defaultisNone(e.g."Nonez Smith"rendered as"z Smith"): empty attributes are now substituted as''before the format string is applied, instead of scrubbing the interpolated"None"from the output afterward (closes #254)
1.3.0 - July 5, 2026
Breaking Changes & Deprecations
Deprecate
HumanName.__eq__and__hash__for removal in 2.0 (#223): the current design’s three promises — case-insensitive equality, equality with plain strings, and hashability — are mutually inconsistent (equal objects can hash differently), equality depends onstring_format, andmaidenis invisible to it. Both now emitDeprecationWarningnaming the replacement; behavior is otherwise unchanged until 2.0 (closes #224)Deprecate
bytesinput for removal in 2.0 (#245): passingbytestoHumanName/full_nameor toSetManager.add()/add_with_encoding()now emitsDeprecationWarning— decode first, e.g.value.decode('utf-8'). Theencodingconstructor argument is deprecated with itDeprecate
SetManager.__call__for removal in 2.0 (#243): calling a manager returns the raw underlying set, so mutating the result bypasses normalization and cache invalidation; iterate the manager or copy withset(manager)insteadAdd
SetManager.discard(), and deprecateremove()of a missing member (#243): it currently does nothing but will raiseKeyErrorin 2.0, matchingset.remove; usediscard()for intentional ignore-missing removal. Removing present members is unchanged and does not warnFix
HumanNameacting as its own iterator with a stored cursor: breaking out of a loop, iterating in nested loops, or callinglen(name)mid-loop corrupted subsequent iteration;iter(name)now returns a fresh independent iterator each time.next(name)on the instance itself (undocumented) now raisesTypeError— callnext(iter(name))instead (closes #225)Remove the vestigial
unparsableattribute: the guard that was meant to set it has been unreachable since 2013 (v0.2.9), so it has reportedFalsefor every parsed name for over a decade; checklen(name) == 0to detect an empty parseRemove
__ne__; Python 3 derives!=from__eq__automaticallyChange internal initials helper
__process_initial__to_process_initial: double-underscore-both-sides names are reserved for Python special methods; subclasses overriding the old name must rename their overrideChange
REGEXESfrom asetof(name, pattern)tuples to adict, so a duplicate name is a visible overwrite in the source instead of a nondeterministic winner at import time; code iteratingREGEXESdirectly now gets keys instead of pairs — use.items()(#227)Change
CAPITALIZATION_EXCEPTIONSfrom a tuple of(key, value)tuples to adict; code iterating it directly now gets keys instead of pairs — use.items()(#233)
Behavior Changes (affect existing parse output)
Add
bound_first_namesset toConstants; bound Arabic given-name prefixes (abdul,abu, etc.) now join forward to form a single first name (e.g."abdul salam ahmed salem"→first="abdul salam",middle="ahmed",last="salem"). Disable viaCONSTANTS.bound_first_names.clear(). Default-on: changes parsing output for names with these prefixes. (#150)Treat an unrecognized, multi-letter token ending in a period in the leading title run (before the first name is set), e.g.
"Major.", as atitleinstead of afirstname; internal-period abbreviations ("E.T.") and single-letter initials ("J.") are unaffected. Default-on: changes parsing of names with a leading unknown period-abbreviation (closes #109)Fix parsing writing back into the
Constantsit reads (usually the shared module-levelCONSTANTS): pieces derived while parsing a name — period-joined titles/suffixes like"Lt.Gov."and conjunction-joined pieces like"Mr. and Mrs."or"von und zu"— are now tracked per parse instead of being permanentlyadd()-ed to the config, so parse results no longer depend on which names were parsed earlier in the process and parsing no longer mutates shared state across threadsFix
__hash__to lowercase the name like__eq__does, so equalHumanNameinstances hash equal and behave correctly in sets and dicts
New comparison methods
Add
matches()andcomparison_key()for explicit name comparison:matches()compares parsed components case-insensitively (parsingstrarguments first, soname.matches("Smith, John")andname.matches("John Smith")both match) andcomparison_key()returns a hashable tuple of the seven components for dedup, dict keys, and sorting (#224)
New name fields
Add a first-class
maidenfield andmaiden_delimiterstoConstants, so a delimiter (e.g. parenthesis) can be routed tomaideninstead ofnicknamefor alternate/maiden surnames, e.g."Baker (Johnson), Jenny"(closes #22)Add
given_names(andgiven_names_list) attribute as aggregate of first and middle names, mirroringsurnames(closes #157)Add
last_base,last_prefixes(and_listvariants) for splitting last-name prefix particles (tussenvoegsels) from the core surname (#130, #132)
New customization options
Add
initials_separatortoConstantsandHumanNameto control spacing between consecutive initials within a name group (#171)Add
suffix_delimitertoConstantsandHumanNamefor parsing suffixes separated by arbitrary delimiters, e.g."RN - CRNA"(#156)Add
nickname_delimiterstoConstantsfor registering additional nickname-delimiter regex patterns at runtime, without subclassing (closes #110, #112)Add
suffix_acronyms_ambiguoustoConstantsfor acronym suffixes that also read as given-name nicknames (e.g."JD","Ed"), used when disambiguating parenthesized/quoted content (#111)
International name support
Add
patronymic_name_orderflag toConstantsandHumanNamefor opt-in detection and reordering of Russian formal-order names (Surname GivenName Patronymic) (#85)Add Turkic (Azerbaijani/Central-Asian) patronymic detection to
patronymic_name_order, rotating the reversed 4-token formal shape (Surname GivenName PatronymicRoot Marker, e.g.oglu/qizi) into Western order (#185)Add
middle_name_as_lastflag toConstantsandHumanNamefor opt-in folding of middle names into the last name, for naming systems with no middle-name concept (e.g. Arabic patronymic chaining) (#133)Add
non_first_name_prefixestoConstants: a leading particle that is never a first name (e.g."de Mesnil","dos Santos") now parses as a surname with an empty first name, instead of treating the particle as the first name (closes #121)Add international honorifics to
TITLES(#187)Add German/Austrian nobility and ecclesiastical titles to
TITLES(closes #101)Add German/Dutch last-name prefixes and title/degree suffixes; fix
join_on_conjunctions()to register multi-word prefix chains (e.g."von und zu") as prefixes, mirroring existing title handling (closes #18)
Parsing fixes
Fix suffix boundary lookup for prefixed last names with a title before and after (e.g.
"dr Vincent van Gogh dr"producing a corrupted middle name) (closes #100)Fix a repeated prefix word in a prefix chain (e.g.
"Juan de la de la Vega") silently dropping the earlier occurrence injoin_on_conjunctions(): value-basedpieces.index(prefix)lookups re-found the wrong occurrence once the list had already been mutated by prior joins; prefix positions are now tracked positionally instead of re-derived by value (closes #208)Fix a trailing suffix being silently dropped after an empty comma segment, e.g.
"Doe, John,, Jr."losing the"Jr."Fix degenerate comma input (a bare
","or an empty comma segment, e.g."Doe,, Jr.","John Doe, Jr.,,") leaving an empty-string member infirst_list,last_list, orsuffix_list; whitespace-only tokens assigned via the setters are dropped the same wayFix suffix-shaped parenthesized/quoted content (e.g.
"(Ret)","(MBA)") being misclassified as a nickname instead of a suffix (closes #111)Fix single-character symbol conjunctions (e.g.
"&","/") being ignored in short names (#173)Fix recognition of single-letter roman numeral suffixes (e.g.
"I","V") in suffix-comma format (closes #136)Fix recognition of trailing
suffix_not_acronyms(e.g."Jr.") in lastname-comma format (closes #144)Fix missing comma between
'msc'and'mscmsm'insuffix_acronyms, which silently concatenated them into a bogus'mscmscmsm'entry (#111)Fix
'apn aprn'split into separatesuffix_acronymsentries so each is recognized independently (closes #155)
Formatting and output fixes
Fix
IndexErrorininitials()/initials_list()when a*_listattribute was assigned directly with an element containing unnormalized whitespace (e.g.name.middle_list = ['Q R']), bypassing the parser’s whitespace normalization (closes #232)Fix
initials()emitting a stray empty initial (e.g."J. . V.") – or raisingTypeErrorwhenempty_attribute_defaultisNone– for name parts with no initialable words, e.g. a prefix-only middle name like"de la"Fix capitalization of suffix acronyms written with dots, e.g.
"M.D."(closes #141)Fix extra whitespace before punctuation in
str()output when astring_formatfield is empty (closes #139)Fix spurious leading space in surnames and empty token in suffix list after
capitalize()with an empty middle or suffix (#164)
API correctness and cleanup
Fix the five non-cached-union
SetManager-backedConstantsattributes (first_name_titles,conjunctions,bound_first_names,non_first_name_prefixes,suffix_acronyms_ambiguous) accepting non-SetManagerassignment silently (e.g.constants.conjunctions = 'and'), degrading membership checks into substring tests with no error; assignment now raisesTypeErrorlike the four cached-union attributes already did (closes #241)Fix
HumanName.Caccepting an invalidconstantsvalue on post-construction assignment (e.g.hn.C = 'garbage'), bypassing the constructor’s validation and failing later with an unrelatedAttributeError;Cis now a property that validates on assignment too (closes #239)Fix
TupleManager(andRegexTupleManager) accepting a bare string/bytes argument (raising a crypticdict-internalsValueError) or an iterable of 2-character strings (silently shredding each into a key/value pair, e.g.Constants(capitalization_exceptions=['ii'])becoming{'i': 'i'}); both now raiseTypeErrorwith a clear message (closes #242)Fix
SetManager.__contains__being the one operation that didn’t normalize (lowercase, strip leading/trailing periods) its operand, so e.g.'Dr.' in constants.titlescould returnFalseeven though the title was correctly configured; membership checks now normalize likeadd()/remove()/the constructor/the set operators (closes #244)Fix a bare string passed to a set-backed
Constantsargument (e.g.Constants(titles='dr')), toSetManager, or as aSetManagerset-operator operand (e.g.constants.titles |= 'esq') being silently split into single characters, replacing or polluting the set and producing wrong parses with no error; it now raisesTypeErrorwith the suggested fix — wrap strings in a list, decodebytesfirst (closes #238)Fix
SetManagerset operators and the constructor skipping the lowercase/strip-edge-periods normalization thatadd()applies:constants.titles |= ['Esq.']kept a raw'Esq.'the parser’s lookups could never match,titles & ['Dr.']missed'dr', andConstants(titles=[...])stored raw elements that silently never matched; elements and operands are now normalized everywhere, and non-strelements (bytes,None, numbers) raiseTypeErrorinstead of crashing cryptically or being coercedFix the
constantsconstructor argument silently discardingConstantssubclass instances: the exact-type check replaced them with fresh defaults, throwing away the caller’s configuration. Subclass instances are now used as given; anything that is neitherNonenor aConstantsinstance now raisesTypeErrorinstead of being silently swapped for defaults (closes #226)Fix
Constantscustomizations, singleton identity, andTupleManagersubclass being lost acrosspickle/deepcopyround-trips (#167, #168, #169)Fix
is_rootname()returning stale results afteradd()/remove()ontitles,prefixes,suffix_acronyms, orsuffix_not_acronyms(#166)Fix the library logger calling
setLevel(logging.ERROR)on import, which silently discarded log records regardless of an application’s own logging configuration; the logger now leaves its level atNOTSETand lets the application control verbosity (closes #228)Minor internal cleanups: drop a dead length check in the initials helper, simplify double-wrapped
len(list(...))calls, and other small parser tidy-ups with no behavior change (closes #229)Change
Constants.__repr__to report collection sizes and non-default scalar config, replacing the uninformative<Constants() instance>(#221)
- 1.2.1 - June 19, 2026
Fix
initials()interpolating the literalNonefor empty name parts whenempty_attribute_default = None(e.g."J. None D."); empty parts now render as an empty string and a fully-empty result returnsempty_attribute_defaultAdd
python -m nameparser "Name String"command-line helper that prints a parsed nameReorganize the test suite from a single
tests.pyinto atests/pytest package
- 1.2.0 - June 11, 2026
Drop Python 2 and Python < 3.10 support; Python 3.10–3.14 now required
Add type hints and type declarations (PEP 561
py.typedmarker)Migrate build tooling to
pyproject.toml, dropsetup.pyRemove dead Python 2 compatibility shims (
ENCODINGconstant,next()aliases)Modernize CI: uv-based workflow, trusted publishing to PyPI, Dependabot
- 1.1.3 - September 20, 2023
Fix case when we have two same prefixes in the name ()#147)
- 1.1.2 - November 13, 2022
Add support for attributes in constructor (#140)
Make HumanName instances hashable (#138)
Update repr for names with single quotes (#137)
- 1.1.1 - January 28, 2022
Fix bug in is_suffix handling of lists (#129)
- 1.1.0 - January 3, 2022
Add initials support (#128)
Add more titles and prefixes (#120, #127, #128, #119)
- 1.0.6 - February 8, 2020
Fix Python 3.8 syntax error (#104)
- 1.0.5 - Dec 12, 2019
Fix suffix parsing bug in comma parts (#98)
Fix deprecation warning on Python 3.7 (#94)
Improved capitalization support of mixed case names (#90)
Remove “elder” from titles (#96)
Add post-nominal list from Wikipedia to suffixes (#93)
- 1.0.4 - June 26, 2019
Better nickname handling of multiple single quotes (#86)
full_name attribute now returns formatted string output instead of original string (#87)
- 1.0.3 - April 18, 2019
fix sys.stdin usage when stdin doesn’t exist (#82)
support for escaping log entry arguments (#84)
- 1.0.2 - Oct 26, 2018
Fix handling of only nickname and last name (#78)
- 1.0.1 - August 30, 2018
Fix overzealous regex for “Ph. D.” (#43)
Add surnames attribute as aggregate of middle and last names
- 1.0.0 - August 30, 2018
Fix support for nicknames in single quotes (#74)
Change prefix handling to support prefixes on first names (#60)
Fix prefix capitalization when not part of lastname (#70)
Handle erroneous space in “Ph. D.” (#43)
- 0.5.8 - August 19, 2018
Add “Junior” to suffixes (#76)
Add “dra” and “srta” to titles (#77)
- 0.5.7 - June 16, 2018
Fix doc link (#73)
Fix handling of “do” and “dos” Portuguese prefixes (#71, #72)
- 0.5.6 - January 15, 2018
Fix python version check (#64)
- 0.5.5 - January 10, 2018
Support J.D. as suffix and Wm. as title
- 0.5.4 - December 10, 2017
Add Dr to suffixes (#62)
Add the full set of Italian derivatives from “di” (#59)
Add parameter to specify the encoding of strings added to constants, use ‘UTF-8’ as fallback (#67)
Fix handling of names composed entirely of conjunctions (#66)
- 0.5.3 - June 27, 2017
Remove emojis from initial string by default with option to include emojis (#58)
- 0.5.2 - March 19, 2017
Added names scrapped from VIAF data, thanks daryanypl (#57)
- 0.5.1 - August 12, 2016
Fix error for names that end with conjunction (#54)
- 0.5.0 - August 4, 2016
Refactor join_on_conjunctions(), fix #53
- 0.4.1 - July 25, 2016
Remove “bishop” from titles because it also could be a first name
Fix handling of lastname prefixes with periods, e.g. “Jane St. John” (#50)
- 0.4.0 - June 2, 2016
Remove “CONSTANTS.suffixes”, replaced by “suffix_acronyms” and “suffix_not_acronyms” (#49)
Add “du” to prefixes
Add “sheikh” variations to titles
Add parameter to force capitalization of mixed case strings
- 0.3.16 - March 24, 2016
Clarify LGPL licence version (#47)
Skip pickle tests if pickle not installed (#48)
- 0.3.15 - March 21, 2016
Fix string format when empty_attribute_default = None (#45)
Include tests in release source tarball (#46)
- 0.3.14 - March 18, 2016
Add CONSTANTS.empty_attribute_default to customize value returned for empty attributes (#44)
- 0.3.13 - March 14, 2016
Improve string format handling (#41)
- 0.3.12 - March 13, 2016
Fix first name clash with suffixes (#42)
Fix encoding of constants added via the python shell
Add “MSC” to suffixes, fix #41
- 0.3.11 - October 17, 2015
Fix bug capitalization exceptions (#39)
- 0.3.10 - September 19, 2015
Fix encoding of byte strings on python 2.x (#37)
- 0.3.9 - September 5, 2015
Separate suffixes that are acronyms to handle periods differently, fixes #29, #21
Don’t find titles after first name is filled, fixes (#27)
Add “chair” titles (#37)
- 0.3.8 - September 2, 2015
Use regex to check for roman numerals at end of name (#36)
Add DVM to suffixes
- 0.3.7 - August 30, 2015
Speed improvement, 3x faster
Make HumanName instances pickleable
- 0.3.6 - August 6, 2015
Fix strings that start with conjunctions (#20)
handle assigning lists of names to a name attribute
support dictionary-like assignment of name attributes
- 0.3.5 - August 4, 2015
Fix handling of string encoding in python 2.x (#34)
Add support for dictionary key access, e.g. name[‘first’]
add ‘santa’ to prefixes, add ‘cpa’, ‘csm’, ‘phr’, ‘pmp’ to suffixes (#35)
Fix prefixes before multi-part last names (#23)
Fix capitalization bug (#30)
- 0.3.4 - March 1, 2015
Fix #24, handle first name also a prefix
Fix #26, last name comma format when lastname is also a title
- 0.3.3 - Aug 4, 2014
Allow suffixes to be chained (#8)
Handle trailing suffix in last name comma format (#3). Removes support for titles with periods but no spaces in them, e.g. “Lt.Gen.”. (#21)
- 0.3.2 - July 16, 2014
Retain original string in “original” attribute.
Collapse white space when using custom string format.
Fix #19, single comma name format may have trailing suffix
- 0.3.1 - July 5, 2014
Fix Pypi package, include new config module.
- 0.3.0 - July 4, 2014
Refactor configuration to simplify modifications to constants (backwards incompatible)
use unicode_literals to simplify Python 2 & 3 support.
Generate documentation using sphinx and host on readthedocs.
- 0.2.10 - May 6, 2014
If name is only a title and one part, assume it’s a last name instead of a first name, with exceptions for some titles like ‘Sir’. (#7).
Add some judicial and other common titles. (#9)
- 0.2.9 - Apr 1, 2014
Add a new nickname attribute containing anything in parenthesis or double quotes (Issue 33).
- 0.2.8 - Oct 25, 2013
Add support for Python 3.3+. Thanks to @corbinbs.
- 0.2.7 - Feb 13, 2013
Fix bug with multiple conjunctions in title
add legal and crown titles
- 0.2.6 - Feb 12, 2013
Fix python 2.6 import error on logging.NullHandler
- 0.2.5 - Feb 11, 2013
Set logging handler to NullHandler
Remove ‘ben’ from PREFIXES because it’s more common as a name than a prefix.
Deprecate BlankHumanNameError. Do not raise exceptions if full_name is empty string.
- 0.2.4 - Feb 10, 2013
Adjust logging, don’t set basicConfig. Fix Issue 10 and Issue 26.
Fix handling of single lower case initials that are also conjunctions, e.g. “john e smith”. Re Issue 11.
Fix handling of initials with no space separation, e.g. “E.T. Jones”. Fix #11.
Do not remove period from first name, when present.
Remove ‘e’ from PREFIXES because it is handled as a conjunction.
Python 2.7+ required to run the tests. Mark known failures.
tests/test.py can now take an optional name argument that will return repr() for that name.
0.2.3 - Fix overzealous “Mac” regex
0.2.2 - Fix parsing error
- 0.2.0
Significant refactor of parsing logic. Handle conjunctions and prefixes before parsing into attribute buckets.
Support attribute overriding by assignment.
Support multiple titles.
Lowercase titles constants to fix bug with comparison.
Move documentation to README.rst, add release log.
0.1.4 - Use set() in constants for improved speed. setuptools compatibility - sketerpot
0.1.3 - Add capitalization feature - twotwo
0.1.2 - Add slice support