Skip to content

Add type hints#159

Open
v-- wants to merge 3 commits into
derek73:masterfrom
v--:master
Open

Add type hints#159
v-- wants to merge 3 commits into
derek73:masterfrom
v--:master

Conversation

@v--
Copy link
Copy Markdown

@v-- v-- commented Jun 7, 2026

I added type hints (PEP 561) for the project. As far as I understood, the project insists on Python 2 compatibility, so I had to add the hints in stub files (.pyi). These stubs, as well as the required py.typed marker file, get included in setuptools' builds.

I have added a pyproject.toml file with a standardized (PEP 517) build configuration. For this, I have set a low version of setuptools (v39.2.0) --- that last that supports Python 3.2 --- to avoid breaking compatibility.

For details, see the individual commits.

PS: If you decide to drop support for legacy Python versions (i.e. anything below 3.10), the type hints can be simplified (PEP 585 and possibly PEP 612) and merged into the source itself. This will also allow simplifying the project configuration (i.e. drop setup.py/MANIFEST.in and use standardized version/dependency management).

@derek73
Copy link
Copy Markdown
Owner

derek73 commented Jun 7, 2026

Thanks for this contribution — stub coverage is thorough and the approach (.pyi files) is appropriate given the project's current Python 2/3 compatibility goals. I have a few corrections needed before merging:

Bugs: incorrect stub signatures

nameparser/parser.pyi

  • initials() return type is wrong. Stub says -> list[str] but the implementation returns str (see parser.pyreturn self.collapse_whitespace(_s); docstring also says :rtype: str).
  • parse_pieces() and join_on_conjunctions() return types are wrong. Both say -> str but the actual return type is list (:rtype: list in both docstrings).
  • Property setter return types should be None, not str. All setters (title, first, middle, last, suffix, nickname) are annotated -> str, but Python property setters always return None.
  • are_suffixes(self, pieces: str) — wrong parameter type. This takes a list of string pieces, not a single string. Should be Iterable[str] or list[str].

nameparser/__init__.pyi and nameparser/util.pyi

  • VERSION: tuple[int] means exactly one int. Should be tuple[int, ...].
  • text_types: tuple[type] — same issue, should be tuple[type, ...].

Build configuration bug

pyproject.toml — the [build-system] block includes "nameparser" as a build requirement, but that's the package being built. It can't be its own build dependency and should be removed.


Re: your note about dropping legacy Python support — that's on my radar. I'm going to open a separate PR to update the CI matrix (3.7 and 3.8 checks are failing anyway, and we're missing 3.12/3.13). Once that lands, if you'd like to follow up with a simplified version of these stubs using inline annotations and X | Y union syntax, that would be welcome.

@derek73
Copy link
Copy Markdown
Owner

derek73 commented Jun 7, 2026

FYI, I just merged a separate PR (#160) that drops legacy Python support and sets 3.10 as the minimum version. This means you can simplify the stubs if you'd like — inline annotations directly in the source, X | Y union syntax instead of Union[...], and list[]/dict[] instead of List[]/Dict[]. Happy to take a follow-up PR for that, or I can handle it after merging this one.

@derek73
Copy link
Copy Markdown
Owner

derek73 commented Jun 7, 2026

I went ahead and cleaned out the Python 2 cruft in #161

@v--
Copy link
Copy Markdown
Author

v-- commented Jun 8, 2026

@derek73 Thank you for the quick response. Sorry I messed up some hints in the original pull request. I usually rely on mypy for detecting type issues, but it did not help here because the hints were separated from the code.

I have merged the type annotations with the code now. This also required some minor changes. I also removed code aimed at Python 2 compatibility, as well as some code that seemed dead. I added review comments to highlight this. See the descriptions of the individual commits for more information.

Regarding your comment about nameparser as a build requirement - this is needed as long as setup.py imports nameparser. I also added this as comment in pyproject.toml.

Additionally, I removed dev-requirements.txt in favor of a standardized configuration in pyproject.toml. I did this because I wanted to add mypy and ruff (mostly the pyupgrade rules) and thought it was a good opportunity to migrate.

@v-- v-- force-pushed the master branch 3 times, most recently from 0c48fae to 3203d0f Compare June 8, 2026 00:41
Comment thread nameparser/parser.py
Comment thread nameparser/parser.py
_re = self.C.regexes.phd

def parse_nicknames(self):
if match := _re.search(self._full_name):
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code failed when the regex was the default empty one because it had no "group 1". See the test_override_regex test.

Comment thread nameparser/parser.py
re_parenthesis = self.C.regexes.parenthesis or empty_re
re_quoted_word = self.C.regexes.quoted_word
re_double_quotes = self.C.regexes.double_quotes
re_parenthesis = self.C.regexes.parenthesis
Copy link
Copy Markdown
Author

@v-- v-- Jun 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty regex is used as a fallback in the regex tuple manager itself.

class Constants(object):
class RegexTupleManager(TupleManager[re.Pattern[str]]):
def __getattr__(self, attr: str) -> re.Pattern[str]:
return self.get(attr, EMPTY_REGEX)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried several variants here and found it easiest to subclass the TupleManager so that regexes have a default. Otherwise, the parser often uses a regex directly even though is can possibly be None.

Comment thread nameparser/parser.py
@@ -794,26 +821,13 @@ def join_on_conjunctions(self, pieces, additional_parts_count=0):
conj_index = [i for i, piece in enumerate(pieces)
if self.is_conjunction(piece)]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code I removed here seems accidentally committed.

@v-- v-- changed the title Add type stubs Add type hints Jun 8, 2026
v-- added 3 commits June 8, 2026 04:08
This includes a build system specification (PEP 518) and essential
metadata (PEP 621). This configuration is needed for modern tooling.

Migrate dev-dependencies.txt to pyproject.toml and add ruff and mypy.

Also add a PEP 561 py.typed marker and a .python-version file.
The changes are mostly related to unicode and conditional imports.
This also required some refactoring:
* I removed some code that seemed dead.
* I created a subclass of TupleManager for regexes with a fallback.
  When accessing regexes previously, it was possible to get None.
* I had to move all setter next to their getters due to a bug in mypy.
  See python/mypy#1465
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants