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.
- {nameparser-1.2.0 → nameparser-1.2.1}/MANIFEST.in +1 -1
- {nameparser-1.2.0/nameparser.egg-info → nameparser-1.2.1}/PKG-INFO +1 -1
- nameparser-1.2.1/nameparser/__main__.py +31 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/_version.py +1 -1
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/parser.py +8 -4
- {nameparser-1.2.0 → nameparser-1.2.1/nameparser.egg-info}/PKG-INFO +1 -1
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/SOURCES.txt +18 -2
- {nameparser-1.2.0 → nameparser-1.2.1}/pyproject.toml +8 -0
- nameparser-1.2.1/tests/__init__.py +0 -0
- nameparser-1.2.1/tests/base.py +40 -0
- nameparser-1.2.1/tests/conftest.py +80 -0
- nameparser-1.2.1/tests/test_brute_force.py +808 -0
- nameparser-1.2.1/tests/test_capitalization.py +84 -0
- nameparser-1.2.1/tests/test_conjunctions.py +203 -0
- nameparser-1.2.1/tests/test_constants.py +106 -0
- nameparser-1.2.1/tests/test_first_name.py +69 -0
- nameparser-1.2.1/tests/test_initials.py +128 -0
- nameparser-1.2.1/tests/test_nicknames.py +176 -0
- nameparser-1.2.1/tests/test_output_format.py +126 -0
- nameparser-1.2.1/tests/test_prefixes.py +118 -0
- nameparser-1.2.1/tests/test_python_api.py +226 -0
- nameparser-1.2.1/tests/test_suffixes.py +138 -0
- nameparser-1.2.1/tests/test_titles.py +241 -0
- nameparser-1.2.1/tests/test_variations.py +211 -0
- nameparser-1.2.0/tests.py +0 -2630
- {nameparser-1.2.0 → nameparser-1.2.1}/AUTHORS +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/LICENSE +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/README.rst +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/__init__.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/__init__.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/capitalization.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/conjunctions.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/prefixes.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/regexes.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/suffixes.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/config/titles.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/py.typed +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser/util.py +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-1.2.0 → nameparser-1.2.1}/setup.cfg +0 -0
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
@@ -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
|