Skip to content

Commit bc56bd2

Browse files
derek73claude
andcommitted
Enforce the pickle guards mechanically; flatten test imports
Forgetting the two class-body guard assignments on a future frozen type is silent -- @DataClass(slots=True) installs its own working pickle methods without skew detection. A conventions test now enumerates every frozen dataclass in the package and asserts the guards are present (Lexicon allow-listed for its private copy), turning a per-class convention into a CI gate. Also folds five vestigial function-local render imports into the top import block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 60802f1 commit bc56bd2

2 files changed

Lines changed: 33 additions & 7 deletions

File tree

tests/v2/test_layering.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
"""Enforce the conventions doc's import layering mechanically."""
1+
"""Enforce the conventions doc's contracts mechanically: import
2+
layering, public exports, and the pickle layout guards."""
23
import ast
34
import pathlib
45

@@ -108,3 +109,33 @@ def test_type_checking_imports_do_not_count(tmp_path: pathlib.Path) -> None:
108109
" from nameparser import _render\n"
109110
)
110111
assert _nameparser_imports(mod) == ["nameparser.util", "nameparser"]
112+
113+
114+
def test_every_frozen_dataclass_carries_the_pickle_guards() -> None:
115+
# Forgetting the two class-body assignments is SILENT --
116+
# @dataclass(slots=True) installs its own working pickle methods
117+
# without skew detection. Lexicon keeps a private copy of the guard
118+
# (layering keeps _lexicon import-free of _types).
119+
import dataclasses
120+
import inspect
121+
122+
import nameparser._lexicon
123+
import nameparser._locale
124+
import nameparser._policy
125+
import nameparser._types
126+
from nameparser._types import _guarded_getstate, _guarded_setstate
127+
128+
modules = (nameparser._types, nameparser._lexicon,
129+
nameparser._policy, nameparser._locale)
130+
for module in modules:
131+
for _, cls in inspect.getmembers(module, inspect.isclass):
132+
if cls.__module__ != module.__name__:
133+
continue
134+
if not dataclasses.is_dataclass(cls):
135+
continue
136+
if cls.__name__ == "Lexicon":
137+
assert "__getstate__" in cls.__dict__
138+
assert "__setstate__" in cls.__dict__
139+
continue
140+
assert cls.__dict__.get("__getstate__") is _guarded_getstate, cls
141+
assert cls.__dict__.get("__setstate__") is _guarded_setstate, cls

tests/v2/test_render.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from nameparser._lexicon import Lexicon
4-
from nameparser._render import _collapse
4+
from nameparser._render import _collapse, render
55
from nameparser._types import Ambiguity, AmbiguityKind, ParsedName, Role, Span, Token
66

77

@@ -40,7 +40,6 @@ def _delavega() -> ParsedName:
4040

4141

4242
def test_render_fills_fields_and_collapses() -> None:
43-
from nameparser._render import render
4443
pn = _delavega()
4544
assert render(pn, "{title} {given} {middle} {family} {suffix}") \
4645
== "Dr. Juan de la Vega III"
@@ -49,28 +48,24 @@ def test_render_fills_fields_and_collapses() -> None:
4948

5049

5150
def test_render_accepts_derived_view_keys() -> None:
52-
from nameparser._render import render
5351
assert render(_delavega(), "{family_base}, {given} {family_particles}") \
5452
== "Vega, Juan de la"
5553
assert render(_delavega(), "{surnames}") == "de la Vega"
5654
assert render(_delavega(), "{given_names}") == "Juan"
5755

5856

5957
def test_render_every_role_key_is_valid() -> None:
60-
from nameparser._render import render
6158
pn = _delavega()
6259
for role in Role:
6360
render(pn, f"{{{role.value}}}") # must not raise
6461

6562

6663
def test_render_unknown_key_raises_enriched_keyerror() -> None:
67-
from nameparser._render import render
6864
with pytest.raises(KeyError, match="valid fields"):
6965
render(_delavega(), "{first}") # v1 spelling: redirected loudly
7066

7167

7268
def test_render_empty_parse_is_empty_string() -> None:
73-
from nameparser._render import render
7469
assert render(_pn("", []), "{title} {given} {middle} {family} {suffix}") == ""
7570

7671

0 commit comments

Comments
 (0)