Summary
2.0 removes the empty_attribute_default config option entirely. Empty name attributes always return ''. Once None support goes (see below for why it must), the only legal value left is the default — a dial with one position isn't configuration.
History: why it exists
Added in v0.3.15 (2016, pre-1.0) for #44: a user wanted None instead of '' to store name components as database NULLs. The maintainer's first response was the one-liner that is now the migration path — "you could do it easily too with something like name.title or None" — and "I don't have a strong preference." Making None the universal return broke the formatting tests, so a config option was added as a compromise. The first consequence arrived in the same thread: str() produced 'None John None Doe None (None)', patched in PR #45 with a .replace(str(empty_attribute_default), '') scrub that is still in __str__ today.
What it has cost
What removal deletes
The or self.C.empty_attribute_default tail on all seven attribute properties plus surnames/given_names/last_base/last_prefixes and initials(); the __str__ scrub hack; the docs section; the test type: ignores.
Removal mechanics (both learned from the 1.3.0 guard work)
Migration
hn.first or None at the database boundary, or {k: v or None for k, v in name.as_dict().items()} for a whole record.
Bridge (1.4) — tracked in #263
Summary
2.0 removes the
empty_attribute_defaultconfig option entirely. Empty name attributes always return''. OnceNonesupport goes (see below for why it must), the only legal value left is the default — a dial with one position isn't configuration.History: why it exists
Added in v0.3.15 (2016, pre-1.0) for #44: a user wanted
Noneinstead of''to store name components as databaseNULLs. The maintainer's first response was the one-liner that is now the migration path — "you could do it easily too with something likename.title or None" — and "I don't have a strong preference." MakingNonethe universal return broke the formatting tests, so a config option was added as a compromise. The first consequence arrived in the same thread:str()produced'None John None Doe None (None)', patched in PR #45 with a.replace(str(empty_attribute_default), '')scrub that is still in__str__today.What it has cost
-> strannotation on the public API is false when the option is set toNone; the attribute was deliberately left untyped in the mypy work (PR Add tests/ to mypy scope; fix real type gaps it surfaces #250) because typing it honestly cascades| Nonethrough everything"J. None D."initials bug, fixed in 1.3.0 — same laundering classNonefrom name text, so in None-modestr(HumanName("Nonez Smith"))returns'z Smith'— the parse is correct and__str__strips the literalNoneout of the person's nameWhat removal deletes
The
or self.C.empty_attribute_defaulttail on all seven attribute properties plussurnames/given_names/last_base/last_prefixesandinitials(); the__str__scrub hack; the docs section; the testtype: ignores.Removal mechanics (both learned from the 1.3.0 guard work)
empty_attribute_defaultis a plain attribute, so naive removal makesCONSTANTS.empty_attribute_default = Nonea silent no-op — the accept-and-ignore failure family Non-descriptor Constants set attributes accept bare-string assignment; substring 'in' silently corrupts parsing #241 exterminated. 2.0 keeps a property whose setter raises with the migration hint.hn.C.empty_attribute_defaultin downstream tests. Reads break loudly with the tombstone; migration is "compare with''or truthiness."Migration
hn.first or Noneat the database boundary, or{k: v or None for k, v in name.as_dict().items()}for a whole record.Bridge (1.4) — tracked in #263
DeprecationWarningon any assignment toempty_attribute_default__str__mangling fix (str() strips literal "None" from real names when empty_attribute_default is None #254) so None-mode users get correct output during their migration window