Skip to content

Commit d4852e4

Browse files
committed
Improve packstream Structure class
These changes have no affect on the driver's public API. They're targeted at improving the development and debugging experience. * Adjust `repr` to follow Python's recommendations * Fix `__eq__` returning `NotImplementedError` instead of `NotImplemented` * Add type hints * Add tests * Remove unused `__getitem__` and `__setitem__` methods
1 parent 9816ca0 commit d4852e4

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-10
lines changed

src/neo4j/_codec/packstream/_python/_common.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,32 @@
1414
# limitations under the License.
1515

1616

17+
from .... import _typing as t
18+
19+
1720
class Structure:
18-
def __init__(self, tag, *fields):
21+
tag: bytes
22+
fields: list[t.Any]
23+
24+
def __init__(self, tag: bytes, *fields: t.Any):
1925
self.tag = tag
2026
self.fields = list(fields)
2127

22-
def __repr__(self):
23-
fields = ", ".join(map(repr, self.fields))
24-
tag_int = ord(self.tag)
25-
return f"Structure[0x{tag_int:02X}]({fields})"
28+
def __repr__(self) -> str:
29+
args = ", ".join(map(repr, (self.tag, *self.fields)))
30+
return f"Structure({args})"
2631

27-
def __eq__(self, other):
32+
def __eq__(self, other) -> bool:
2833
try:
2934
return self.tag == other.tag and self.fields == other.fields
3035
except AttributeError:
31-
return NotImplementedError
36+
return NotImplemented
3237

33-
def __len__(self):
38+
def __len__(self) -> int:
3439
return len(self.fields)
3540

36-
def __getitem__(self, key):
41+
def __getitem__(self, key: int) -> t.Any:
3742
return self.fields[key]
3843

39-
def __setitem__(self, key, value):
44+
def __setitem__(self, key: int, value: t.Any) -> None:
4045
self.fields[key] = value
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
import pytest
18+
19+
from neo4j._codec.packstream import Structure
20+
21+
22+
@pytest.mark.parametrize(
23+
"args",
24+
(
25+
(b"T", 1, 2, 3, "abc", 1.2, None, False),
26+
(b"F",),
27+
),
28+
)
29+
def test_structure_accessors(args):
30+
tag = args[0]
31+
fields = list(args[1:])
32+
s1 = Structure(*args)
33+
assert s1.tag == tag
34+
assert s1.fields == fields
35+
36+
37+
@pytest.mark.parametrize(
38+
("other", "expected"),
39+
(
40+
(Structure(b"T", 1, 2, 3, "abc", 1.2, [{"a": "b"}, None]), True),
41+
(Structure(b"T", 1, 2, 3, "abc", 1.2, [{"a": "b"}, 0]), False),
42+
(Structure(b"T", 1, 2, 3, "abc", 1.2, [{"a": "B"}, None]), False),
43+
(Structure(b"T", 1, 2, 3, "abc", 1.2, [{"A": "b"}, None]), False),
44+
(Structure(b"T", 1, 2, 3, "abc", 1.3, [{"a": "b"}, None]), False),
45+
(
46+
Structure(b"T", 1, 2, 3, "aBc", float("Nan"), [{"a": "b"}, None]),
47+
False,
48+
),
49+
(Structure(b"T", 2, 2, 3, "abc", 1.2, [{"a": "b"}, None]), False),
50+
(Structure(b"T", 2, 3, "abc", 1.2, [{"a": "b"}, None]), False),
51+
(Structure(b"T", [1, 2, 3, "abc", 1.2, [{"a": "b"}, None]]), False),
52+
(object(), NotImplemented),
53+
),
54+
)
55+
def test_structure_equality(other, expected):
56+
s1 = Structure(b"T", 1, 2, 3, "abc", 1.2, [{"a": "b"}, None])
57+
assert s1.__eq__(other) is expected # noqa: PLC2801
58+
if expected is NotImplemented:
59+
assert s1.__ne__(other) is NotImplemented # noqa: PLC2801
60+
else:
61+
assert s1.__ne__(other) is not expected # noqa: PLC2801
62+
63+
64+
@pytest.mark.parametrize(
65+
("args", "expected"),
66+
(
67+
((b"F", 1, 2), "Structure(b'F', 1, 2)"),
68+
((b"f", [1, 2]), "Structure(b'f', [1, 2])"),
69+
(
70+
(b"T", 1.3, None, {"a": "b"}),
71+
"Structure(b'T', 1.3, None, {'a': 'b'})",
72+
),
73+
),
74+
)
75+
def test_structure_repr(args, expected):
76+
s1 = Structure(*args)
77+
assert repr(s1) == expected
78+
assert str(s1) == expected
79+
80+
# Ensure that the repr is consistent with the constructor
81+
assert eval(repr(s1)) == s1
82+
assert eval(str(s1)) == s1

0 commit comments

Comments
 (0)