Skip to content

Commit 016873c

Browse files
authored
Merge pull request #221 from derek73/feature/constants-repr
Give Constants a useful __repr__
2 parents e518479 + 173e951 commit 016873c

4 files changed

Lines changed: 79 additions & 5 deletions

File tree

docs/customize.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ The first is via ``from nameparser.config import CONSTANTS``.
1717
.. doctest::
1818

1919
>>> from nameparser.config import CONSTANTS
20-
>>> CONSTANTS
21-
<Constants() instance>
20+
>>> CONSTANTS # doctest: +ELLIPSIS
21+
<Constants : [
22+
prefixes: ...
23+
]>
2224

2325
The other is the ``C`` attribute of a ``HumanName`` instance, e.g.
2426
``hn.C``.
@@ -27,8 +29,10 @@ The other is the ``C`` attribute of a ``HumanName`` instance, e.g.
2729

2830
>>> from nameparser import HumanName
2931
>>> hn = HumanName("Dean Robert Johns")
30-
>>> hn.C
31-
<Constants() instance>
32+
>>> hn.C # doctest: +ELLIPSIS
33+
<Constants : [
34+
prefixes: ...
35+
]>
3236

3337
Both places are usually a reference to the same shared module-level
3438
:py:class:`~nameparser.config.CONSTANTS` instance, depending on how you

docs/release_log.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Release Log
4141
- Add international honorifics to ``TITLES`` (#187)
4242
- Add German/Austrian nobility and ecclesiastical titles to ``TITLES`` (closes #101)
4343
- 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)
44+
- Change ``Constants.__repr__`` to report collection sizes and non-default scalar config, replacing the uninformative ``<Constants() instance>`` (#221)
4445
* 1.2.1 - June 19, 2026
4546
- Fix ``initials()`` interpolating the literal ``None`` for empty name parts when ``empty_attribute_default = None`` (e.g. ``"J. None D."``); empty parts now render as an empty string and a fully-empty result returns ``empty_attribute_default``
4647
- Add ``python -m nameparser "Name String"`` command-line helper that prints a parsed name

nameparser/config/__init__.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,8 +531,29 @@ def suffixes_prefixes_titles(self) -> Set[str]:
531531
self._pst = self.prefixes | self.suffix_acronyms | self.suffix_not_acronyms | self.titles
532532
return self._pst
533533

534+
_repr_collection_attrs = (
535+
'prefixes', 'suffix_acronyms', 'suffix_not_acronyms', 'titles',
536+
'first_name_titles', 'conjunctions', 'bound_first_names',
537+
'non_first_name_prefixes', 'suffix_acronyms_ambiguous',
538+
)
539+
_repr_scalar_attrs = (
540+
'string_format', 'initials_format', 'initials_delimiter',
541+
'initials_separator', 'suffix_delimiter', 'empty_attribute_default',
542+
'capitalize_name', 'force_mixed_case_capitalization',
543+
'patronymic_name_order', 'middle_name_as_last',
544+
)
545+
534546
def __repr__(self) -> str:
535-
return "<Constants() instance>"
547+
# Collections (some with hundreds of entries, e.g. titles/prefixes)
548+
# are summarized as counts rather than dumped in full. Scalars are
549+
# only shown when they differ from the class default, so a plain
550+
# Constants() reads as just the collection sizes.
551+
lines = [f" {name}: {len(getattr(self, name))}" for name in self._repr_collection_attrs]
552+
lines += [
553+
f" {name}: {value!r}" for name in self._repr_scalar_attrs
554+
if (value := getattr(self, name)) != getattr(type(self), name)
555+
]
556+
return "<Constants : [\n" + "\n".join(lines) + "\n]>"
536557

537558
def __setstate__(self, state: Mapping[str, Any]) -> None:
538559
# Restore each saved attribute directly. The previous implementation

tests/test_constants.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,51 @@ def test_repeated_access_is_cached(self) -> None:
522522
f"{cached_per_call * 1e6:.1f} us/call vs an uncached build cost of "
523523
f"{uncached_per_call * 1e6:.1f} us/call. Was _pst caching removed?"
524524
)
525+
526+
527+
class ConstantsReprTests(HumanNameTestBase):
528+
529+
def test_repr_reports_actual_collection_sizes(self) -> None:
530+
c = Constants()
531+
repr_str = repr(c)
532+
for name in Constants._repr_collection_attrs:
533+
self.assertIn(f"{name}: {len(getattr(c, name))}", repr_str)
534+
535+
def test_repr_omits_scalars_at_default_value(self) -> None:
536+
c = Constants()
537+
repr_str = repr(c)
538+
for name in Constants._repr_scalar_attrs:
539+
self.assertNotIn(name, repr_str)
540+
541+
def test_repr_shows_scalar_override_via_constructor(self) -> None:
542+
c = Constants(middle_name_as_last=True)
543+
self.assertIn("middle_name_as_last: True", repr(c))
544+
545+
def test_repr_shows_scalar_override_via_assignment(self) -> None:
546+
# Most _repr_scalar_attrs (e.g. capitalize_name) aren't __init__ kwargs
547+
# at all -- they're only ever overridden by direct assignment.
548+
c = Constants()
549+
c.capitalize_name = True
550+
self.assertIn("capitalize_name: True", repr(c))
551+
552+
def test_repr_shows_multiple_simultaneous_scalar_overrides(self) -> None:
553+
c = Constants(patronymic_name_order=True)
554+
c.capitalize_name = True
555+
repr_str = repr(c)
556+
self.assertIn("patronymic_name_order: True", repr_str)
557+
self.assertIn("capitalize_name: True", repr_str)
558+
559+
def test_repr_reflects_mutated_collection_size(self) -> None:
560+
c = Constants()
561+
before = len(c.titles)
562+
c.titles.add('a-brand-new-title-for-repr-test')
563+
self.assertIn(f"titles: {before + 1}", repr(c))
564+
565+
def test_repr_reports_empty_collection(self) -> None:
566+
c = Constants(titles=[])
567+
self.assertIn("titles: 0", repr(c))
568+
569+
def test_repr_is_bracketed_multiline(self) -> None:
570+
repr_str = repr(Constants())
571+
self.assertTrue(repr_str.startswith("<Constants : [\n"))
572+
self.assertTrue(repr_str.endswith("\n]>"))

0 commit comments

Comments
 (0)