Skip to content

Commit 1a1d1c8

Browse files
committed
Give Constants a useful __repr__
The previous __repr__ ("<Constants() instance>") didn't identify which instance you were looking at or how it differed from the default, making it useless for debugging config state. Collections (some with hundreds of entries, e.g. titles/prefixes) are summarized as counts rather than dumped in full. Scalar config flags are only shown when they differ from the class default, so a plain Constants() reads as just the collection sizes. Formatted as a bracketed multi-line block to match HumanName's __repr__ style. Updates the two doctests in docs/customize.rst that asserted the old repr string, using ELLIPSIS so they don't break every time a config set's size changes.
1 parent e518479 commit 1a1d1c8

3 files changed

Lines changed: 60 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

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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,33 @@ 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(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_reflects_mutated_collection_size(self) -> None:
546+
c = Constants()
547+
before = len(c.titles)
548+
c.titles.add('a-brand-new-title-for-repr-test')
549+
self.assertIn(f"titles: {before + 1}", repr(c))
550+
551+
def test_repr_is_bracketed_multiline(self) -> None:
552+
repr_str = repr(Constants())
553+
self.assertTrue(repr_str.startswith("<Constants : [\n"))
554+
self.assertTrue(repr_str.endswith("\n]>"))

0 commit comments

Comments
 (0)