Repository navigation
Remove empty_attribute_default; empty attributes always return '' #255
Copy link
Copy link
Closed
Labels
Milestone
Description
Activity
added on Jul 6, 2026
breaking-changeBackwards-incompatible API changeBackwards-incompatible API change
Note from the review of #277 (the 1.3.1 fix for #254): the fix changed str() behavior for custom truthy values of empty_attribute_default, an undocumented configuration (only '' and None are documented or tested).
With empty_attribute_default = 'N/A':
- before Fix str() scrubbing literal "None" from names when empty_attribute_default is None #277:
str(HumanName('John Smith'))→'John Smith'— the post-format scrub deleted the placeholder from empty slots, but also corrupted real name text containing it ('N/A Smith'→'Smith', the same bug class as str() strips literal "None" from real names when empty_attribute_default is None #254) - after Fix str() scrubbing literal "None" from names when empty_attribute_default is None #277: →
'N/A John N/A Smith N/A (N/A)'— thev or ''substitution only blanks falsy values, so a truthy default now renders literally in formatted output
Decision was to leave this as-is rather than complicate the deprecation-window code, since this option is removed entirely here in 2.0. If anyone reports depending on a truthy default before then, the fix would be to key the __str__ substitution off actual field emptiness (the _list attributes) instead of value falsiness.
🤖 Generated with Claude Code
added 10 commits that reference this issue on Jul 12, 2026
added a commit that references this issue on Jul 26, 2026
Shipped in 2.0.0. Delivery and release-log coverage were verified issue-by-issue in the pre-release milestone audit.
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