Skip to content

Commit 219368f

Browse files
derek73claude
andcommitted
Add tests/ to mypy scope and fix the type gaps it surfaces
Widens a handful of source annotations that were too narrow for already-supported behavior (str|None config scalars, list[str] accepted by is_prefix/is_conjunction/is_suffix and __setitem__, bytes accepted by add_with_encoding), and adds targeted type: ignore comments on the lines that deliberately violate the type contract to assert a TypeError/behavior, matching the existing convention in test_constants.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 94b253c commit 219368f

7 files changed

Lines changed: 25 additions & 24 deletions

File tree

nameparser/config/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def __xor__(self, other: Iterable[str]) -> 'SetManager': # type: ignore[overrid
193193

194194
__rxor__ = __xor__
195195

196-
def add_with_encoding(self, s: str, encoding: str | None = None) -> None:
196+
def add_with_encoding(self, s: str | bytes, encoding: str | None = None) -> None:
197197
"""
198198
Add the lowercased, leading/trailing-periods-stripped version of the string to the set. Pass an
199199
explicit `encoding` parameter to specify the encoding of binary strings that
@@ -486,7 +486,7 @@ class Constants:
486486
maiden_delimiters: TupleManager[re.Pattern[str] | str]
487487
_pst: Set[str] | None
488488

489-
string_format = "{title} {first} {middle} {last} {suffix} ({nickname})"
489+
string_format: str | None = "{title} {first} {middle} {last} {suffix} ({nickname})"
490490
"""
491491
The default string format use for all new `HumanName` instances.
492492
"""
@@ -515,7 +515,7 @@ class Constants:
515515
spacing from the template is still applied.
516516
"""
517517

518-
suffix_delimiter = None
518+
suffix_delimiter: str | None = None
519519
"""
520520
If set, an additional delimiter used to split suffix groups after
521521
comma-splitting. For example, setting ``suffix_delimiter=" - "`` allows

nameparser/parser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def __getitem__(self, key: slice | str) -> str | list[str]:
228228
else:
229229
return getattr(self, key)
230230

231-
def __setitem__(self, key: str, value: str) -> None:
231+
def __setitem__(self, key: str, value: str | list[str] | None) -> None:
232232
if key in self._members:
233233
self._set_list(key, value)
234234
else:
@@ -696,7 +696,7 @@ def is_leading_title(self, piece: str) -> bool:
696696
"""
697697
return self.is_title(piece) or bool(self.C.regexes.period_abbreviation.match(piece))
698698

699-
def is_conjunction(self, piece: str) -> bool:
699+
def is_conjunction(self, piece: str | list[str]) -> bool:
700700
"""Is in the conjunctions set — config or derived earlier in this
701701
parse (e.g. ``"of the"``) — and not :py:func:`is_an_initial()`."""
702702
if isinstance(piece, list):
@@ -708,7 +708,7 @@ def is_conjunction(self, piece: str) -> bool:
708708
or piece.lower() in self._derived_conjunctions) \
709709
and not self.is_an_initial(piece)
710710

711-
def is_prefix(self, piece: str) -> bool:
711+
def is_prefix(self, piece: str | list[str]) -> bool:
712712
"""
713713
Lowercased, leading/trailing-periods-stripped version of piece is in the
714714
:py:data:`~nameparser.config.prefixes.PREFIXES` set, or was derived as
@@ -765,7 +765,7 @@ def is_roman_numeral(self, value: str) -> bool:
765765
"""
766766
return bool(self.C.regexes.roman_numeral.match(value))
767767

768-
def is_suffix(self, piece: str) -> bool:
768+
def is_suffix(self, piece: str | list[str]) -> bool:
769769
"""
770770
Is in the suffixes set — or was derived as a period-joined suffix
771771
earlier in this parse (e.g. ``"JD.CPA"``) — and not

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ dev = [
4949
]
5050

5151
[tool.mypy]
52-
packages = ["nameparser"]
52+
packages = ["nameparser", "tests"]
5353

5454
[[tool.mypy.overrides]]
5555
module = [

tests/test_constants.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pickle
33
import re
44
import timeit
5+
from typing import Any
56

67
import pytest
78

@@ -201,7 +202,7 @@ def test_tuplemanager_bare_string_raises_typeerror(self) -> None:
201202
# sequence element #0 has length 1; 2 is required" naming no argument
202203
# and suggesting no fix (#242)
203204
with pytest.raises(TypeError, match=r"wrap it in a list"):
204-
TupleManager('ab')
205+
TupleManager('ab') # type: ignore[arg-type]
205206

206207
def test_tuplemanager_bytes_raises_with_decode_hint(self) -> None:
207208
with pytest.raises(TypeError, match=r"decode it first"):
@@ -211,11 +212,11 @@ def test_tuplemanager_string_element_raises_typeerror(self) -> None:
211212
# the silent variant: an iterable of 2-character strings is a valid
212213
# dict-update sequence, so each one shreds into a key/value pair
213214
with pytest.raises(TypeError, match=r"key and a value"):
214-
TupleManager(['ab', 'cd'])
215+
TupleManager(['ab', 'cd']) # type: ignore[arg-type]
215216

216217
def test_constants_capitalization_exceptions_string_elements_raise(self) -> None:
217218
with pytest.raises(TypeError, match=r"key and a value"):
218-
Constants(capitalization_exceptions=['ii'])
219+
Constants(capitalization_exceptions=['ii']) # type: ignore[list-item]
219220

220221
def test_tuplemanager_accepts_mapping_and_pairs(self) -> None:
221222
# the guard must not reject the two legitimate constructor shapes
@@ -300,7 +301,7 @@ def test_clear_removes_all_entries(self) -> None:
300301

301302
def test_empty_attribute_default(self) -> None:
302303
from nameparser.config import CONSTANTS
303-
CONSTANTS.empty_attribute_default = None
304+
CONSTANTS.empty_attribute_default = None # type: ignore[assignment]
304305
hn = HumanName("")
305306
self.m(hn.title, None, hn)
306307
self.m(hn.first, None, hn)
@@ -311,7 +312,7 @@ def test_empty_attribute_default(self) -> None:
311312

312313
def test_empty_attribute_on_instance(self) -> None:
313314
hn = HumanName("", None)
314-
hn.C.empty_attribute_default = None
315+
hn.C.empty_attribute_default = None # type: ignore[assignment]
315316
self.m(hn.title, None, hn)
316317
self.m(hn.first, None, hn)
317318
self.m(hn.middle, None, hn)
@@ -321,7 +322,7 @@ def test_empty_attribute_on_instance(self) -> None:
321322

322323
def test_none_empty_attribute_string_formatting(self) -> None:
323324
hn = HumanName("", None)
324-
hn.C.empty_attribute_default = None
325+
hn.C.empty_attribute_default = None # type: ignore[assignment]
325326
self.assertEqual('', str(hn), hn)
326327

327328
def test_add_constant_with_explicit_encoding(self) -> None:
@@ -360,7 +361,7 @@ def test_pickle_roundtrip_preserves_customizations(self) -> None:
360361
def test_pickle_roundtrip_preserves_instance_scalar_override(self) -> None:
361362
"""An instance-level scalar override must survive a pickle round-trip."""
362363
c = Constants()
363-
c.empty_attribute_default = None
364+
c.empty_attribute_default = None # type: ignore[assignment]
364365

365366
# Safe: round-tripping a Constants the test just built, not untrusted data.
366367
restored = pickle.loads(pickle.dumps(c))
@@ -729,7 +730,7 @@ def _config_snapshot(constants: Constants) -> dict:
729730
added to ``Constants`` later is watched automatically, with no
730731
attribute list to keep in sync.
731732
"""
732-
snap = {}
733+
snap: dict[str, Any] = {}
733734
for attr, value in constants.__getstate__().items():
734735
if isinstance(value, SetManager):
735736
snap[attr] = set(value)

tests/test_initials.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_initials_empty_part_with_none_default_not_literal_none(self) -> None:
2525
# used to be interpolated by str.format as the literal "None" (e.g.
2626
# "John Doe" -> "J. None D."). Empty parts must render as ''.
2727
hn = HumanName("John Doe", constants=None)
28-
hn.C.empty_attribute_default = None
28+
hn.C.empty_attribute_default = None # type: ignore[assignment]
2929
self.assertEqual(hn.initials(), "J. D.")
3030
self.assertTrue("None" not in hn.initials())
3131

@@ -34,7 +34,7 @@ def test_initials_all_empty_returns_empty_attribute_default(self) -> None:
3434
# empty_attribute_default (here None), matching the first/last accessors,
3535
# rather than rendering the literal "None None None".
3636
hn = HumanName("", constants=None)
37-
hn.C.empty_attribute_default = None
37+
hn.C.empty_attribute_default = None # type: ignore[assignment]
3838
self.assertEqual(hn.initials(), None)
3939

4040
def test_initials_middle_name_all_prefixes(self) -> None:

tests/test_output_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def test_keep_non_emojis(self) -> None:
130130
def test_keep_emojis(self) -> None:
131131
from nameparser.config import Constants
132132
constants = Constants()
133-
constants.regexes.emoji = False
133+
constants.regexes.emoji = False # type: ignore[assignment]
134134
hn = HumanName("∫≜⩕ Smith😊", constants)
135135
self.m(hn.first, "∫≜⩕", hn)
136136
self.m(hn.last, "Smith😊", hn)

tests/test_python_api.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,9 @@ def test_assignment_to_attribute(self) -> None:
262262
hn.suffix = "test"
263263
self.m(hn.suffix, "test", hn)
264264
with pytest.raises(TypeError):
265-
hn.suffix = [['test']]
265+
hn.suffix = [['test']] # type: ignore[list-item]
266266
with pytest.raises(TypeError):
267-
hn.suffix = {"test": "test"}
267+
hn.suffix = {"test": "test"} # type: ignore[assignment]
268268

269269
def test_assign_list_to_attribute(self) -> None:
270270
hn = HumanName("John A. Kenneth Doe, Jr.")
@@ -460,9 +460,9 @@ def test_setitem(self) -> None:
460460
hn['last'] = ['test', 'test2']
461461
self.m(hn['last'], "test test2", hn)
462462
with pytest.raises(TypeError):
463-
hn["suffix"] = [['test']]
463+
hn["suffix"] = [['test']] # type: ignore[list-item]
464464
with pytest.raises(TypeError):
465-
hn["suffix"] = {"test": "test"}
465+
hn["suffix"] = {"test": "test"} # type: ignore[assignment]
466466

467467
def test_setitem_invalid_key_raises_keyerror(self) -> None:
468468
hn = HumanName("Dr. John A. Kenneth Doe, Jr.")
@@ -622,7 +622,7 @@ def test_override_conjunctions(self) -> None:
622622
self.assertTrue(sorted(hn.C.conjunctions) == sorted(var))
623623

624624
def test_override_capitalization_exceptions(self) -> None:
625-
var = TupleManager([("spaces", re.compile(r"\s+")),])
625+
var = TupleManager([("abc", "ABC")])
626626
C = Constants(capitalization_exceptions=var)
627627
hn = HumanName(constants=C)
628628
self.assertTrue(hn.C.capitalization_exceptions == var)

0 commit comments

Comments
 (0)