nameparser 1.2.0__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {nameparser-1.2.0 → nameparser-1.2.1}/MANIFEST.in +1 -1
  2. {nameparser-1.2.0/nameparser.egg-info → nameparser-1.2.1}/PKG-INFO +1 -1
  3. nameparser-1.2.1/nameparser/__main__.py +31 -0
  4. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/_version.py +1 -1
  5. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/parser.py +8 -4
  6. {nameparser-1.2.0 → nameparser-1.2.1/nameparser.egg-info}/PKG-INFO +1 -1
  7. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/SOURCES.txt +18 -2
  8. {nameparser-1.2.0 → nameparser-1.2.1}/pyproject.toml +8 -0
  9. nameparser-1.2.1/tests/__init__.py +0 -0
  10. nameparser-1.2.1/tests/base.py +40 -0
  11. nameparser-1.2.1/tests/conftest.py +80 -0
  12. nameparser-1.2.1/tests/test_brute_force.py +808 -0
  13. nameparser-1.2.1/tests/test_capitalization.py +84 -0
  14. nameparser-1.2.1/tests/test_conjunctions.py +203 -0
  15. nameparser-1.2.1/tests/test_constants.py +106 -0
  16. nameparser-1.2.1/tests/test_first_name.py +69 -0
  17. nameparser-1.2.1/tests/test_initials.py +128 -0
  18. nameparser-1.2.1/tests/test_nicknames.py +176 -0
  19. nameparser-1.2.1/tests/test_output_format.py +126 -0
  20. nameparser-1.2.1/tests/test_prefixes.py +118 -0
  21. nameparser-1.2.1/tests/test_python_api.py +226 -0
  22. nameparser-1.2.1/tests/test_suffixes.py +138 -0
  23. nameparser-1.2.1/tests/test_titles.py +241 -0
  24. nameparser-1.2.1/tests/test_variations.py +211 -0
  25. nameparser-1.2.0/tests.py +0 -2630
  26. {nameparser-1.2.0 → nameparser-1.2.1}/AUTHORS +0 -0
  27. {nameparser-1.2.0 → nameparser-1.2.1}/LICENSE +0 -0
  28. {nameparser-1.2.0 → nameparser-1.2.1}/README.rst +0 -0
  29. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/__init__.py +0 -0
  30. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/__init__.py +0 -0
  31. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/capitalization.py +0 -0
  32. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/conjunctions.py +0 -0
  33. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/prefixes.py +0 -0
  34. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/regexes.py +0 -0
  35. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/suffixes.py +0 -0
  36. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/titles.py +0 -0
  37. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/py.typed +0 -0
  38. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/util.py +0 -0
  39. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/dependency_links.txt +0 -0
  40. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/requires.txt +0 -0
  41. {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/top_level.txt +0 -0
  42. {nameparser-1.2.0 → nameparser-1.2.1}/setup.cfg +0 -0
@@ -1,4 +1,4 @@
1
1
  include AUTHORS
2
2
  include LICENSE
3
3
  include README.rst
4
- include tests.py
4
+ recursive-include tests *.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nameparser
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: A simple Python module for parsing human names into their individual components.
5
5
  Author-email: Derek Gulbranson <derek73@gmail.com>
6
6
  License: LGPL
@@ -0,0 +1,31 @@
1
+ """Command-line debug helper: parse a name and print the result.
2
+
3
+ Usage:
4
+
5
+ python -m nameparser "Dr. Juan Q. Xavier de la Vega III"
6
+ """
7
+ import logging
8
+ import sys
9
+
10
+ from nameparser import HumanName
11
+
12
+
13
+ def main() -> None:
14
+ if len(sys.argv) <= 1:
15
+ print('Usage: python -m nameparser "Name String"')
16
+ raise SystemExit(1)
17
+ log = logging.getLogger('HumanName')
18
+ log.setLevel(logging.ERROR)
19
+ log.addHandler(logging.StreamHandler())
20
+ name_string = sys.argv[1]
21
+ hn = HumanName(name_string)
22
+ print(repr(hn))
23
+ hn.capitalize()
24
+ print(repr(hn))
25
+ # Use comma rather than concatenation: initials() returns
26
+ # empty_attribute_default (possibly None) when there are no initials.
27
+ print("Initials:", hn.initials())
28
+
29
+
30
+ if __name__ == '__main__':
31
+ main()
@@ -1,2 +1,2 @@
1
- VERSION = (1, 2, 0)
1
+ VERSION = (1, 2, 1)
2
2
  __version__ = '.'.join(map(str, VERSION))
@@ -273,17 +273,21 @@ class HumanName:
273
273
  middle_initials_list = [self.__process_initial__(name) for name in self.middle_list if name]
274
274
  last_initials_list = [self.__process_initial__(name) for name in self.last_list if name]
275
275
 
276
+ # Empty parts must render as '' (not empty_attribute_default, which may be
277
+ # None) so str.format does not interpolate the literal "None" into the
278
+ # output. A fully-empty result falls back to empty_attribute_default,
279
+ # matching the other attribute accessors (e.g. ``first``).
276
280
  initials_dict = {
277
281
  "first": (self.initials_delimiter + " ").join(first_initials_list) + self.initials_delimiter
278
- if len(first_initials_list) else self.C.empty_attribute_default,
282
+ if len(first_initials_list) else "",
279
283
  "middle": (self.initials_delimiter + " ").join(middle_initials_list) + self.initials_delimiter
280
- if len(middle_initials_list) else self.C.empty_attribute_default,
284
+ if len(middle_initials_list) else "",
281
285
  "last": (self.initials_delimiter + " ").join(last_initials_list) + self.initials_delimiter
282
- if len(last_initials_list) else self.C.empty_attribute_default
286
+ if len(last_initials_list) else ""
283
287
  }
284
288
 
285
289
  _s = self.initials_format.format(**initials_dict)
286
- return self.collapse_whitespace(_s)
290
+ return self.collapse_whitespace(_s) or self.C.empty_attribute_default
287
291
 
288
292
  @property
289
293
  def has_own_config(self) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nameparser
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: A simple Python module for parsing human names into their individual components.
5
5
  Author-email: Derek Gulbranson <derek73@gmail.com>
6
6
  License: LGPL
@@ -3,8 +3,8 @@ LICENSE
3
3
  MANIFEST.in
4
4
  README.rst
5
5
  pyproject.toml
6
- tests.py
7
6
  nameparser/__init__.py
7
+ nameparser/__main__.py
8
8
  nameparser/_version.py
9
9
  nameparser/parser.py
10
10
  nameparser/py.typed
@@ -20,4 +20,20 @@ nameparser/config/conjunctions.py
20
20
  nameparser/config/prefixes.py
21
21
  nameparser/config/regexes.py
22
22
  nameparser/config/suffixes.py
23
- nameparser/config/titles.py
23
+ nameparser/config/titles.py
24
+ tests/__init__.py
25
+ tests/base.py
26
+ tests/conftest.py
27
+ tests/test_brute_force.py
28
+ tests/test_capitalization.py
29
+ tests/test_conjunctions.py
30
+ tests/test_constants.py
31
+ tests/test_first_name.py
32
+ tests/test_initials.py
33
+ tests/test_nicknames.py
34
+ tests/test_output_format.py
35
+ tests/test_prefixes.py
36
+ tests/test_python_api.py
37
+ tests/test_suffixes.py
38
+ tests/test_titles.py
39
+ tests/test_variations.py
@@ -39,6 +39,7 @@ nameparser = ["py.typed"]
39
39
 
40
40
  [dependency-groups]
41
41
  dev = [
42
+ "pytest (>=8)",
42
43
  "dill (>=0.2.5)",
43
44
  "sphinx (>=8)",
44
45
  "mypy (>=2.1)",
@@ -54,6 +55,13 @@ module = [
54
55
  ]
55
56
  ignore_missing_imports = true
56
57
 
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ python_classes = ["*Tests", "*TestCase"]
61
+ # Fail if an xfail-marked test unexpectedly passes, so a fixed/regressed
62
+ # expectation surfaces instead of silently turning into an xpass.
63
+ xfail_strict = true
64
+
57
65
  [tool.ruff.lint]
58
66
  extend-select = [
59
67
  "ANN", # flake8-annotations
File without changes
@@ -0,0 +1,40 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from nameparser import HumanName
4
+
5
+ T = TypeVar('T')
6
+
7
+
8
+ class HumanNameTestBase(Generic[T]):
9
+ """Shared assert helpers for the parsing tests.
10
+
11
+ Formerly subclassed unittest.TestCase. It is now a plain class so pytest can
12
+ apply the parametrized dual-run fixture in conftest.py — parametrized
13
+ fixtures do not apply to unittest.TestCase subclasses. The assert* methods
14
+ are thin shims so existing test bodies move over unchanged.
15
+ """
16
+
17
+ def m(self, actual: T, expected: T, hn: HumanName) -> None:
18
+ """assertEqual with a better message and awareness of hn.C.empty_attribute_default"""
19
+ expected_ = expected or hn.C.empty_attribute_default
20
+ try:
21
+ assert actual == expected_, "'%s' != '%s' for '%s'\n%r" % (
22
+ actual,
23
+ expected,
24
+ hn.original,
25
+ hn,
26
+ )
27
+ except UnicodeDecodeError:
28
+ assert actual == expected_
29
+
30
+ def assertEqual(self, first: object, second: object, msg: object = None) -> None:
31
+ assert first == second, msg
32
+
33
+ def assertTrue(self, expr: object, msg: object = None) -> None:
34
+ assert expr, msg
35
+
36
+ def assertFalse(self, expr: object, msg: object = None) -> None:
37
+ assert not expr, msg
38
+
39
+ def assertIn(self, member: object, container: object, msg: object = None) -> None:
40
+ assert member in container, msg # type: ignore[operator]
@@ -0,0 +1,80 @@
1
+ from collections.abc import Iterator
2
+
3
+ import pytest
4
+
5
+ from nameparser.config import CONSTANTS, SetManager, TupleManager
6
+
7
+ ConfigCollection = SetManager | TupleManager
8
+
9
+ # Scalar (non-collection) config attributes that individual tests mutate on the
10
+ # global CONSTANTS singleton. Several tests change these without restoring them;
11
+ # the original suite only survived because unittest happens to run methods in
12
+ # alphabetical order, so a later test reset the value. pytest runs in definition
13
+ # order, so we snapshot and restore these around every test to keep tests
14
+ # isolated regardless of order.
15
+ _SCALAR_CONFIG_ATTRS = (
16
+ "empty_attribute_default",
17
+ "string_format",
18
+ "initials_format",
19
+ "initials_delimiter",
20
+ "capitalize_name",
21
+ "force_mixed_case_capitalization",
22
+ )
23
+
24
+ # Collection config attributes (the SetManager / TupleManager constants). Tests
25
+ # that customize the global CONSTANTS — e.g. adding or removing a title — mutate
26
+ # these in place, so a shallow snapshot of the reference would not protect later
27
+ # tests. We snapshot independent copies and restore them, making collection
28
+ # mutations order-independent too.
29
+ _COLLECTION_CONFIG_ATTRS = (
30
+ "prefixes",
31
+ "suffix_acronyms",
32
+ "suffix_not_acronyms",
33
+ "titles",
34
+ "first_name_titles",
35
+ "conjunctions",
36
+ "capitalization_exceptions",
37
+ "regexes",
38
+ )
39
+
40
+
41
+ def _clone_config_collection(value: ConfigCollection) -> ConfigCollection:
42
+ """Return an independent copy of a config collection manager.
43
+
44
+ ``copy.deepcopy`` is not used because ``RegexTupleManager`` carries compiled
45
+ patterns that its ``__reduce__`` cannot round-trip. Rebuilding from the
46
+ manager's own contents copies the container while sharing the (immutable)
47
+ elements, which is all the snapshot needs.
48
+ """
49
+ if isinstance(value, SetManager):
50
+ return SetManager(set(value))
51
+ # TupleManager / RegexTupleManager are dict subclasses.
52
+ return type(value)(dict(value))
53
+
54
+
55
+ @pytest.fixture(autouse=True, params=['', None], ids=['default', 'none'])
56
+ def empty_attribute_default(request: pytest.FixtureRequest) -> Iterator[str | None]:
57
+ """Run every test under both empty_attribute_default settings, isolating global config.
58
+
59
+ Reproduces the original tests.py __main__ block, which ran the whole suite
60
+ twice — once with the default ('') and once with None — as a regression
61
+ check that the three parsing code paths agree. The surrounding snapshot of
62
+ both the scalar and the collection CONSTANTS attributes restores any global
63
+ config a test mutates, so tests do not leak state into one another (the
64
+ original relied on unittest's alphabetical method ordering to mask such
65
+ leaks).
66
+ """
67
+ scalar_snapshot = {attr: getattr(CONSTANTS, attr) for attr in _SCALAR_CONFIG_ATTRS}
68
+ collection_snapshot = {
69
+ attr: _clone_config_collection(getattr(CONSTANTS, attr))
70
+ for attr in _COLLECTION_CONFIG_ATTRS
71
+ }
72
+ CONSTANTS.empty_attribute_default = request.param
73
+ yield request.param
74
+ for attr, value in scalar_snapshot.items():
75
+ setattr(CONSTANTS, attr, value)
76
+ for attr, value in collection_snapshot.items():
77
+ setattr(CONSTANTS, attr, value)
78
+ # Invalidate the lazily-built suffixes/prefixes/titles cache so it is
79
+ # recomputed from the restored collections rather than a mutated one.
80
+ CONSTANTS._pst = None