From 2a384a196edd07181eebf78c061e4a81ddeb38d8 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Fri, 3 Jul 2026 23:45:29 -0700 Subject: [PATCH] Modernize string formatting to f-strings and improve readability - Convert all .format() and % formatting to f-strings across codebase - Update ruff config to remove UP031/UP032 ignores (Python 2 legacy) - Add noqa comments to dynamic template .format() calls (can't convert to f-strings) - Use SimpleNamespace for dot notation access in test_variations.py - Refactor __repr__ method for improved readability - All 1182 tests pass, ruff checks pass Co-Authored-By: Claude Haiku 4.5 --- docs/conf.py | 2 +- nameparser/config/__init__.py | 2 +- nameparser/parser.py | 30 +++++++++++++++--------------- pyproject.toml | 2 -- tests/base.py | 7 +------ tests/test_variations.py | 25 ++++++++++++++----------- 6 files changed, 32 insertions(+), 36 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5df5adc..c11428c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # General information about the project. project = 'Nameparser' -copyright = '{:%Y}, Derek Gulbranson'.format(date.today()) +copyright = f'{date.today():%Y}, Derek Gulbranson' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/nameparser/config/__init__.py b/nameparser/config/__init__.py index 560c1bd..f6580b8 100644 --- a/nameparser/config/__init__.py +++ b/nameparser/config/__init__.py @@ -75,7 +75,7 @@ def __call__(self) -> Set[str]: return self.elements def __repr__(self) -> str: - return "SetManager({})".format(self.elements) # used for docs + return f"SetManager({self.elements})" # used for docs def __iter__(self) -> Iterator[str]: return iter(self.elements) diff --git a/nameparser/parser.py b/nameparser/parser.py index 1f277d5..0124ffe 100644 --- a/nameparser/parser.py +++ b/nameparser/parser.py @@ -194,7 +194,7 @@ def __next__(self) -> str: def __str__(self) -> str: if self.string_format is not None: # string_format = "{title} {first} {middle} {last} {suffix} ({nickname})" - _s = self.string_format.format(**self.as_dict()) + _s = self.string_format.format(**self.as_dict()) # noqa: UP032 # remove trailing punctuation from missing nicknames _s = _s.replace(str(self.C.empty_attribute_default), '').replace(" ()", "").replace(" ''", "").replace(' ""', "") _s = self.C.regexes.space_before_comma.sub(',', _s) @@ -206,18 +206,18 @@ def __hash__(self) -> int: def __repr__(self) -> str: if self.unparsable: - _string = "<%(class)s : [ Unparsable ] >" % {'class': self.__class__.__name__, } + _string = f"<{self.__class__.__name__} : [ Unparsable ] >" else: - _string = "<%(class)s : [\n\ttitle: %(title)r \n\tfirst: %(first)r \n\tmiddle: %(middle)r \n\tlast: %(last)r \n\tsuffix: %(suffix)r\n\tnickname: %(nickname)r\n\tmaiden: %(maiden)r\n]>" % { - 'class': self.__class__.__name__, - 'title': self.title or '', - 'first': self.first or '', - 'middle': self.middle or '', - 'last': self.last or '', - 'suffix': self.suffix or '', - 'nickname': self.nickname or '', - 'maiden': self.maiden or '', - } + attrs = ( + f"\ttitle: {self.title or ''!r} \n" + f"\tfirst: {self.first or ''!r} \n" + f"\tmiddle: {self.middle or ''!r} \n" + f"\tlast: {self.last or ''!r} \n" + f"\tsuffix: {self.suffix or ''!r}\n" + f"\tnickname: {self.nickname or ''!r}\n" + f"\tmaiden: {self.maiden or ''!r}" + ) + _string = f"<{self.__class__.__name__} : [\n{attrs}\n]>" return _string def as_dict(self, include_empty: bool = True) -> dict[str, str]: @@ -320,7 +320,7 @@ def initials(self) -> str: if len(last_initials_list) else "" } - _s = self.initials_format.format(**initials_dict) + _s = self.initials_format.format(**initials_dict) # noqa: UP032 return self.collapse_whitespace(_s) or self.C.empty_attribute_default @property @@ -552,7 +552,7 @@ def _set_list(self, attr: str, value: str | list[str] | None) -> None: else: raise TypeError( "Can only assign strings, lists or None to name attributes." - " Got {}".format(type(value))) + f" Got {type(value)}") setattr(self, attr+"_list", self.parse_pieces(val)) # Parse helpers @@ -1163,7 +1163,7 @@ def parse_pieces(self, parts: Iterable[str], additional_parts_count: int = 0) -> for part in parts: if not isinstance(part, (str, bytes)): raise TypeError("Name parts must be strings. " - " Got {}".format(type(part))) + f" Got {type(part)}") output += [x.strip(' ,') for x in part.split(' ')] # If part contains periods, check if it's multiple titles or suffixes diff --git a/pyproject.toml b/pyproject.toml index 86da3e7..472eee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,4 @@ extend-select = [ ] ignore = [ "E74", # ambiguous-{variable,class,function}-name (I have trouble believing that these are real rules) - "UP031", # printf-string-formatting (the code already uses % formatting) - "UP032", # f-string (the code already uses str.format) ] diff --git a/tests/base.py b/tests/base.py index f130857..444c841 100644 --- a/tests/base.py +++ b/tests/base.py @@ -18,12 +18,7 @@ def m(self, actual: T, expected: T, hn: HumanName) -> None: """assertEqual with a better message and awareness of hn.C.empty_attribute_default""" expected_ = expected or hn.C.empty_attribute_default try: - assert actual == expected_, "'%s' != '%s' for '%s'\n%r" % ( - actual, - expected, - hn.original, - hn, - ) + assert actual == expected_, f"{actual!r} != {expected!r} for {hn.original!r}\n{hn!r}" except UnicodeDecodeError: assert actual == expected_, f"actual={actual!r} != expected={expected_!r}" diff --git a/tests/test_variations.py b/tests/test_variations.py index d574573..6ce0036 100644 --- a/tests/test_variations.py +++ b/tests/test_variations.py @@ -2,6 +2,8 @@ from tests.base import HumanNameTestBase +from types import SimpleNamespace + TEST_NAMES = ( "John Doe", @@ -199,18 +201,19 @@ def test_variations_of_TEST_NAMES(self) -> None: for name in self.TEST_NAMES: hn = HumanName(name) if len(hn.suffix_list) > 1: - hn = HumanName("{title} {first} {middle} {last} {suffix}".format(**hn.as_dict()).split(',')[0]) + d = SimpleNamespace(**hn.as_dict()) + hn = HumanName(f"{d.title} {d.first} {d.middle} {d.last} {d.suffix}".split(',')[0]) hn.C.empty_attribute_default = '' # format strings below require empty string - hn_dict = hn.as_dict() - nocomma = HumanName("{title} {first} {middle} {last} {suffix}".format(**hn_dict)) - lastnamecomma = HumanName("{last}, {title} {first} {middle} {suffix}".format(**hn_dict)) - if hn.suffix: - suffixcomma = HumanName("{title} {first} {middle} {last}, {suffix}".format(**hn_dict)) - if hn.nickname: - nocomma = HumanName("{title} {first} {middle} {last} {suffix} ({nickname})".format(**hn_dict)) - lastnamecomma = HumanName("{last}, {title} {first} {middle} {suffix} ({nickname})".format(**hn_dict)) - if hn.suffix: - suffixcomma = HumanName("{title} {first} {middle} {last}, {suffix} ({nickname})".format(**hn_dict)) + d = SimpleNamespace(**hn.as_dict()) + nocomma = HumanName(f"{d.title} {d.first} {d.middle} {d.last} {d.suffix}") + lastnamecomma = HumanName(f"{d.last}, {d.title} {d.first} {d.middle} {d.suffix}") + if d.suffix: + suffixcomma = HumanName(f"{d.title} {d.first} {d.middle} {d.last}, {d.suffix}") + if d.nickname: + nocomma = HumanName(f"{d.title} {d.first} {d.middle} {d.last} {d.suffix} ({d.nickname})") + lastnamecomma = HumanName(f"{d.last}, {d.title} {d.first} {d.middle} {d.suffix} ({d.nickname})") + if d.suffix: + suffixcomma = HumanName(f"{d.title} {d.first} {d.middle} {d.last}, {d.suffix} ({d.nickname})") for attr in hn._members: self.m(getattr(hn, attr), getattr(nocomma, attr), hn) self.m(getattr(hn, attr), getattr(lastnamecomma, attr), hn)