Skip to content

Add type hints#159

Merged
derek73 merged 10 commits into
derek73:masterfrom
v--:master
Jun 8, 2026
Merged

Add type hints#159
derek73 merged 10 commits into
derek73:masterfrom
v--:master

Conversation

@v--
Copy link
Copy Markdown
Contributor

@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
Contributor 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
Comment thread nameparser/parser.py
Comment thread nameparser/config/__init__.py
Comment thread nameparser/parser.py
@v-- v-- changed the title Add type stubs Add type hints Jun 8, 2026
v-- added 4 commits June 8, 2026 04:23
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
Comment thread tests.py
Comment thread nameparser/__init__.py
derek73 and others added 5 commits June 8, 2026 00:45
The test was removed because it had no assertions, but it was covering
string output for UTF-8 names parsed from comma format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves all package metadata into pyproject.toml and extracts VERSION
into a minimal _version.py so setuptools can read it without importing
the full package, removing the self-referential build dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace `python setup.py sdist bdist_wheel` with `python -m build`
and update the editable install URL to use https and modern pip syntax.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dependency-groups (PEP 735) require pip>=24.2; pinning ensures the
upgrade step meets that minimum before the dev install runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Using = [] on class-level attributes creates a single shared list
instance across all HumanName instances. Any direct mutation (e.g.
hn.first_list.append(...)) on an instance created without a full_name
would corrupt the default for all subsequent instances. Both code paths
in __init__ (parse_full_name and the per-attribute setters) always
initialize these as instance-level lists, so the class-level defaults
were never needed — only the type annotations are.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@derek73
Copy link
Copy Markdown
Owner

derek73 commented Jun 8, 2026

In the latest commits I made a few changes on top of this PR:

Restored test_string_output with an assertion — the test was removed because it had no assertions, but it was the only coverage for str(hn) on a UTF-8 name in comma format. Added self.m(str(hn), "Jüan de la Véña", hn) to make it meaningful.

Dropped setup.py — moved all package metadata into pyproject.toml and extracted VERSION into a minimal nameparser/_version.py. This removes the self-referential build dependency (nameparser listed as a build requirement of itself), which would break builds in clean environments where the package isn't already installed. Setuptools reads the version from _version.py directly without importing the full package.

Pinned pip>=24.2 in CIpip install --group dev (PEP 735 dependency groups) requires pip 24.2+. The workflow already upgrades pip but didn't enforce a minimum version.

Fixed mutable class-level list defaultstitle_list: list[str] = [] etc. declared a single shared list instance at the class level. If someone mutates hn.first_list directly on an instance created without a full_name (before parse_full_name replaces it with an instance-level list), it would corrupt the default for all subsequent instances. Changed to bare annotations (title_list: list[str]) since both __init__ code paths always initialize these as instance-level lists anyway.

@derek73
Copy link
Copy Markdown
Owner

derek73 commented Jun 8, 2026

@v-- please review the changes I made and confirm everything works as expected in your environment.

@v--
Copy link
Copy Markdown
Contributor Author

v-- commented Jun 8, 2026

@derek73 The changes seem reasonable and the tests on my project utilizing nameparser pass.

Add tests for two code paths flagged in review:
- test_phd_extracted_without_comma: covers fix_phd() extracting "Ph. D."
  from names without a comma (e.g. "John Smith Ph. D.")
- test_roman_numeral_suffix_not_in_suffix_list: covers the is_roman_numeral(nxt)
  branch for VI-X, which are not in the suffix word lists and so are not caught
  by are_suffixes() first

Other changes:
- Replace list[T]() constructor style with typed annotations (list: T = [])
  in parser.py — same runtime behavior, idiomatic Python
- Add LGPL license classifier to pyproject.toml (was present in setup.py)
- Note pip >= 24.1 requirement for --group dev in CONTRIBUTING.md and AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@derek73
Copy link
Copy Markdown
Owner

derek73 commented Jun 8, 2026

thanks for the contribution! I will go ahead and merge it. I will try to publish a new version to pypi in the next few days.

@derek73 derek73 merged commit 4f8a5e8 into derek73:master Jun 8, 2026
5 checks passed
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