Skip to content

Commit 23e512a

Browse files
committed
refactor: let the package define what the null module exports
The package __init__ is the public surface, so the helpers the SDK uses to recognize the sentinel and replace it for JSON serialization need no underscore to be internal: not exporting them is enough. Export NULL to pass and Null to annotate, since generated route methods type a nullable param as Union[T, Null]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PpqwDhZmyikGbjmCPqFW2A
1 parent 5bd53ac commit 23e512a

4 files changed

Lines changed: 29 additions & 31 deletions

File tree

seam/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
SeamHttpInvalidInputError,
1414
SeamHttpUnauthorizedError,
1515
)
16-
from .null import _replace_null
16+
from .null import replace_null
1717
from .utils.url_search_params_serializer import serialize_url_search_params
1818

1919
SDK_HEADERS = {
@@ -97,7 +97,7 @@ def request(self, method, url, *args, **kwargs) -> Any:
9797
# Route methods omit params set to None, so any remaining NULL sentinel
9898
# is an explicit null and becomes None for JSON serialization.
9999
if "json" in kwargs:
100-
kwargs["json"] = _replace_null(kwargs["json"])
100+
kwargs["json"] = replace_null(kwargs["json"])
101101

102102
# Search params are serialized to the Seam API standard, which httpx
103103
# does not implement. The NULL sentinel is serialized to an empty value.

seam/null.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
from collections.abc import Mapping
1414
from typing import Any
1515

16-
__all__ = ["NULL", "Null"]
17-
1816

1917
class Null:
2018
"""Type of the :data:`NULL` sentinel."""
@@ -59,7 +57,7 @@ def __bool__(self):
5957
"""
6058

6159

62-
def _is_null(value: Any) -> bool:
60+
def is_null(value: Any) -> bool:
6361
"""Returns whether a value is the :data:`NULL` sentinel.
6462
6563
:param value: The value to check
@@ -70,7 +68,7 @@ def _is_null(value: Any) -> bool:
7068
return isinstance(value, Null)
7169

7270

73-
def _replace_null(value: Any) -> Any:
71+
def replace_null(value: Any) -> Any:
7472
"""Recursively replaces the :data:`NULL` sentinel with ``None``.
7573
7674
Returns a copy, so the given value is never modified.
@@ -82,16 +80,16 @@ def _replace_null(value: Any) -> Any:
8280
8381
:returns: A copy of the value with every ``NULL`` sentinel replaced"""
8482

85-
if _is_null(value):
83+
if is_null(value):
8684
return None
8785

8886
if isinstance(value, Mapping):
89-
return {key: _replace_null(item) for key, item in value.items()}
87+
return {key: replace_null(item) for key, item in value.items()}
9088

9189
if isinstance(value, list):
92-
return [_replace_null(item) for item in value]
90+
return [replace_null(item) for item in value]
9391

9492
if isinstance(value, tuple):
95-
return tuple(_replace_null(item) for item in value)
93+
return tuple(replace_null(item) for item in value)
9694

9795
return value

seam/utils/url_search_params_serializer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union
4141
from urllib.parse import parse_qsl
4242

43-
from ..null import _is_null
43+
from ..null import is_null
4444

4545
Params = Mapping[str, Any]
4646

@@ -307,7 +307,7 @@ def _update_url_search_params_from_array(
307307
"is an array containing the empty string which is unsupported",
308308
)
309309

310-
if any(value is None or _is_null(value) for value in values):
310+
if any(value is None or is_null(value) for value in values):
311311
raise UnserializableParamError(
312312
name,
313313
"is an array containing null or undefined values which is unsupported",
@@ -318,7 +318,7 @@ def _update_url_search_params_from_array(
318318

319319

320320
def _serialize(name: str, value: Any) -> str:
321-
if _is_null(value):
321+
if is_null(value):
322322
return ""
323323

324324
if isinstance(value, str):

test/null_test.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
from collections import OrderedDict
22

33
from seam.client import SeamHttpClient
4-
from seam.null import NULL, Null, _is_null, _replace_null
4+
from seam.null import NULL, Null, is_null, replace_null
55

66

77
def test_null_is_a_singleton():
88
assert Null() is NULL
9-
assert _is_null(NULL)
10-
assert _is_null(Null())
9+
assert is_null(NULL)
10+
assert is_null(Null())
1111

1212

1313
def test_null_is_not_none():
1414
assert NULL is not None
15-
assert not _is_null(None)
16-
assert not _is_null("")
17-
assert not _is_null(0)
15+
assert not is_null(None)
16+
assert not is_null("")
17+
assert not is_null(0)
1818

1919

2020
def test_null_is_falsy():
@@ -26,40 +26,40 @@ def test_null_repr():
2626

2727

2828
def test_replace_null():
29-
assert _replace_null(NULL) is None
30-
assert _replace_null(None) is None
31-
assert _replace_null("a") == "a"
32-
assert _replace_null(0) == 0
33-
assert _replace_null(False) is False
29+
assert replace_null(NULL) is None
30+
assert replace_null(None) is None
31+
assert replace_null("a") == "a"
32+
assert replace_null(0) == 0
33+
assert replace_null(False) is False
3434

3535

3636
def test_replace_null_in_dict():
37-
assert _replace_null({"a": NULL, "b": 1, "c": None}) == {
37+
assert replace_null({"a": NULL, "b": 1, "c": None}) == {
3838
"a": None,
3939
"b": 1,
4040
"c": None,
4141
}
4242

4343

4444
def test_replace_null_in_nested_dict():
45-
assert _replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}}
45+
assert replace_null({"a": {"b": {"c": NULL}}}) == {"a": {"b": {"c": None}}}
4646

4747

4848
def test_replace_null_in_lists_and_tuples():
49-
assert _replace_null(["a", NULL]) == ["a", None]
50-
assert _replace_null(("a", NULL)) == ("a", None)
51-
assert _replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]}
49+
assert replace_null(["a", NULL]) == ["a", None]
50+
assert replace_null(("a", NULL)) == ("a", None)
51+
assert replace_null({"a": [{"b": NULL}]}) == {"a": [{"b": None}]}
5252

5353

5454
def test_replace_null_does_not_modify_the_given_value():
5555
params = {"a": NULL, "b": [NULL]}
56-
_replace_null(params)
56+
replace_null(params)
5757

5858
assert params == {"a": NULL, "b": [NULL]}
5959

6060

6161
def test_replace_null_normalizes_mappings_to_dicts():
62-
result = _replace_null(OrderedDict([("a", NULL)]))
62+
result = replace_null(OrderedDict([("a", NULL)]))
6363

6464
assert result == {"a": None}
6565

0 commit comments

Comments
 (0)