Skip to content

Commit d8bba52

Browse files
authored
Merge pull request #86 from fjarri-eth/typed-tests
Add type hints to tests
2 parents a9bdc72 + 32153f4 commit d8bba52

22 files changed

Lines changed: 823 additions & 406 deletions

.github/workflows/lints.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
run: curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3
3232
- name: Install dependencies
3333
run: |
34-
pdm sync -G lint,compiler,local-provider,http-provider-server
34+
pdm sync -G lint,compiler,local-provider,http-provider-server,tests
3535
- name: Run mypy
3636
run: |
37-
pdm run mypy pons
37+
pdm run mypy pons tests

docs/api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Errors
5151
.. autoclass:: pons.ABIDecodingError
5252
:show-inheritance:
5353

54+
.. autoclass:: pons.InvalidResponse
55+
:show-inheritance:
56+
5457
.. autoclass:: pons.Unreachable
5558
:show-inheritance:
5659

docs/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ Added
2121
- A base class for bound calls (``BaseBoundMethodCall``). (PR_84_)
2222
- A helper class for interacting with the Multicall contract (``Multicall``). (PR_84_)
2323
- Exporting ``HTTPError`` and ``BadResponseFormat``. (PR_82_)
24+
- Exporting ``InvalidResponse``. (PR_86_)
25+
- ``LocalProvider.root`` is now of type ``AccountSigner`` instead of ``Signer``. (PR_86_)
2426

2527

2628
.. _PR_81: https://github.com/fjarri-eth/pons/pull/81
2729
.. _PR_82: https://github.com/fjarri-eth/pons/pull/82
2830
.. _PR_84: https://github.com/fjarri-eth/pons/pull/84
31+
.. _PR_86: https://github.com/fjarri-eth/pons/pull/86
2932

3033

3134
0.8.1 (2024-11-12)

pons/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@
4848
from ._http_provider_server import HTTPProviderServer
4949
from ._local_provider import LocalProvider, SnapshotID
5050
from ._multicall import BoundMultiMethodCall, BoundMultiMethodValueCall, Multicall
51-
from ._provider import HTTPError, HTTPProvider, ProtocolError, Provider, Unreachable
51+
from ._provider import (
52+
HTTPError,
53+
HTTPProvider,
54+
InvalidResponse,
55+
ProtocolError,
56+
Provider,
57+
Unreachable,
58+
)
5259
from ._signer import AccountSigner, Signer
5360
from ._utils import get_create2_address, get_create_address
5461

@@ -90,6 +97,7 @@
9097
"HTTPError",
9198
"HTTPProvider",
9299
"HTTPProviderServer",
100+
"InvalidResponse",
93101
"LocalProvider",
94102
"Method",
95103
"MethodCall",

pons/_local_provider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ethereum_rpc import Amount
1212

1313
from ._provider import RPC_JSON, Provider, ProviderSession
14-
from ._signer import AccountSigner, Signer
14+
from ._signer import AccountSigner
1515

1616

1717
class SnapshotID:
@@ -24,7 +24,7 @@ def __init__(self, id_: int):
2424
class LocalProvider(Provider):
2525
"""A provider maintaining its own chain state, useful for tests."""
2626

27-
root: Signer
27+
root: AccountSigner
2828
"""The signer for the pre-created account."""
2929

3030
def __init__(

pons/_provider.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,10 @@ class ProviderSession(ABC):
7777
The base class for provider sessions.
7878
7979
The methods of this class may raise the following exceptions:
80-
- :py:class:`RPCError` signifies an error coming from the backend provider;
81-
- :py:class:`Unreachable` if the provider is unreachable;
82-
- :py:class:`InvalidResponse` if the response was received but could not be parsed;
83-
- :py:class:`ProtocolError` if there was an unrecognized error on the protocol level
84-
(e.g. an HTTP status code that is not 200 or 400).
85-
86-
All other exceptions can be considered implementation bugs.
80+
:py:class:`RPCError`,
81+
:py:class:`Unreachable`,
82+
:py:class:`InvalidResponse`,
83+
a provider-specific derived class of :py:class:`ProtocolError`.
8784
"""
8885

8986
@abstractmethod

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ ignore = [
137137
"ISC001",
138138
# Too many false positives, since Ruff cannot tell that the object is immutable.
139139
"RUF009",
140+
# Way too restrictive. Sometimes it is useful to import types from pytest.
141+
"PT013",
140142
]
141143

142144
# Allow autofix for all enabled rules (when `--fix`) is provided.

tests/conftest.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
1+
from collections.abc import AsyncIterator
2+
13
import pytest
24
from alysis import EVMVersion
35
from ethereum_rpc import Amount
46

5-
from pons import AccountSigner, Client, LocalProvider
7+
from pons import AccountSigner, Client, ClientSession, LocalProvider
68

79

810
@pytest.fixture
9-
def local_provider():
11+
def local_provider() -> LocalProvider:
1012
return LocalProvider(root_balance=Amount.ether(100), evm_version=EVMVersion.CANCUN)
1113

1214

1315
@pytest.fixture
14-
async def session(local_provider):
16+
async def session(local_provider: LocalProvider) -> AsyncIterator[ClientSession]:
1517
client = Client(provider=local_provider)
1618
async with client.session() as session:
1719
yield session
1820

1921

2022
@pytest.fixture
21-
def root_signer(local_provider):
23+
def root_signer(local_provider: LocalProvider) -> AccountSigner:
2224
return local_provider.root
2325

2426

2527
@pytest.fixture
26-
def another_signer():
28+
def another_signer() -> AccountSigner:
2729
return AccountSigner.create()

tests/test_abi_types.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import os
2+
from typing import Any
23

34
import pytest
45
from ethereum_rpc import Address, keccak
56

67
from pons import abi
78
from pons._abi_types import (
9+
ABI_JSON,
810
ABIDecodingError,
11+
ABIType,
12+
Type,
913
decode_args,
1014
dispatch_type,
1115
dispatch_types,
@@ -14,7 +18,7 @@
1418
)
1519

1620

17-
def test_uint():
21+
def test_uint() -> None:
1822
for val in [0, 255, 10]:
1923
assert abi.uint(8)._normalize(val) == val
2024
assert abi.uint(8)._denormalize(val) == val
@@ -45,7 +49,7 @@ def test_uint():
4549
abi.uint(128)._normalize(2**128)
4650

4751

48-
def test_int():
52+
def test_int() -> None:
4953
for val in [0, 127, -128, 10]:
5054
assert abi.int(8)._normalize(val) == val
5155
assert abi.int(8)._denormalize(val) == val
@@ -78,7 +82,7 @@ def test_int():
7882
abi.int(128)._normalize(2**127)
7983

8084

81-
def test_bytes():
85+
def test_bytes() -> None:
8286
assert abi.bytes(3)._normalize(b"foo") == b"foo"
8387
assert abi.bytes(3)._denormalize(b"foo") == b"foo"
8488
assert abi.bytes()._normalize(b"foobar") == b"foobar"
@@ -102,7 +106,7 @@ def test_bytes():
102106
abi.bytes(4)._normalize(b"foo")
103107

104108

105-
def test_address():
109+
def test_address() -> None:
106110
addr_bytes = os.urandom(20)
107111
addr = Address(addr_bytes)
108112

@@ -122,7 +126,7 @@ def test_address():
122126
abi.address._denormalize(addr_bytes)
123127

124128

125-
def test_string():
129+
def test_string() -> None:
126130
assert abi.string._normalize("foo") == "foo"
127131
assert abi.string._denormalize("foo") == "foo"
128132

@@ -136,7 +140,7 @@ def test_string():
136140
abi.string._normalize(b"foo")
137141

138142

139-
def test_bool():
143+
def test_bool() -> None:
140144
assert abi.bool._normalize(True) is True
141145
assert abi.bool._denormalize(True) is True
142146

@@ -148,7 +152,7 @@ def test_bool():
148152
abi.bool._normalize(1)
149153

150154

151-
def test_array():
155+
def test_array() -> None:
152156
assert abi.uint(8)[2]._normalize([1, 2]) == [1, 2]
153157
assert abi.uint(8)[2]._denormalize([1, 2]) == [1, 2]
154158
assert abi.uint(8)[...]._normalize([1, 2, 3]) == [1, 2, 3]
@@ -171,7 +175,7 @@ def test_array():
171175
abi.uint(8)[2]._normalize([1, 2, 3])
172176

173177

174-
def test_struct():
178+
def test_struct() -> None:
175179
u8 = abi.uint(8)
176180
s1 = abi.struct(a=u8, b=abi.bool)
177181

@@ -198,7 +202,7 @@ def test_struct():
198202
s1._normalize(dict(a=1, c=True))
199203

200204

201-
def test_type_from_abi_string():
205+
def test_type_from_abi_string() -> None:
202206
assert type_from_abi_string("uint32") == abi.uint(32)
203207
assert type_from_abi_string("int64") == abi.int(64)
204208
assert type_from_abi_string("bytes11") == abi.bytes(11)
@@ -210,11 +214,11 @@ def test_type_from_abi_string():
210214
type_from_abi_string("uintx")
211215

212216

213-
def test_dispatch_type():
217+
def test_dispatch_type() -> None:
214218
assert dispatch_type(dict(type="uint8")) == abi.uint(8)
215219
assert dispatch_type(dict(type="uint8[2][]")) == abi.uint(8)[2][...]
216220

217-
struct_array = dict(
221+
struct_array: ABI_JSON = dict(
218222
type="tuple[2]",
219223
components=[
220224
dict(name="field1", type="bool"),
@@ -229,7 +233,7 @@ def test_dispatch_type():
229233
dispatch_type(dict(type="uint8(2)[3]"))
230234

231235

232-
def test_dispatch_types():
236+
def test_dispatch_types() -> None:
233237
entries = [
234238
dict(name="param2", type="uint8"),
235239
dict(name="param1", type="uint16[2]"),
@@ -238,7 +242,9 @@ def test_dispatch_types():
238242
assert dispatch_types(entries) == dict(param2=abi.uint(8), param1=abi.uint(16)[2])
239243

240244
# Check that the order is preserved, too
241-
assert list(dispatch_types(entries).items()) == [
245+
typed_entries = dispatch_types(entries)
246+
assert isinstance(typed_entries, dict)
247+
assert list(typed_entries.items()) == [
242248
("param2", abi.uint(8)),
243249
("param1", abi.uint(16)[2]),
244250
]
@@ -259,15 +265,15 @@ def test_dispatch_types():
259265
dispatch_types([dict(name="foo", type="uint8"), dict(name="foo", type="uint16[2]")])
260266

261267

262-
def test_making_arrays():
268+
def test_making_arrays() -> None:
263269
assert abi.uint(8)[2].canonical_form == "uint8[2]"
264270
assert abi.uint(8)[...][3][...].canonical_form == "uint8[][3][]"
265271

266272
with pytest.raises(TypeError, match="Invalid array size specifier type: float"):
267-
abi.uint(8)[1.0]
273+
abi.uint(8)[1.0] # type: ignore[index]
268274

269275

270-
def test_normalization_roundtrip():
276+
def test_normalization_roundtrip() -> None:
271277
struct = abi.struct(
272278
field1=abi.uint(8),
273279
field2=abi.uint(16)[2],
@@ -279,7 +285,7 @@ def test_normalization_roundtrip():
279285

280286
value = dict(field1=1, field2=[2, 3], field3=addr, field4=dict(inner2="abcd", inner1=True))
281287

282-
expected_normalized = [1, [2, 3], addr.checksum, [True, "abcd"]]
288+
expected_normalized: ABIType = [1, [2, 3], addr.checksum, [True, "abcd"]]
283289

284290
# normalize() loses info on struct field names
285291
assert struct._normalize(value) == expected_normalized
@@ -288,15 +294,17 @@ def test_normalization_roundtrip():
288294
assert struct._denormalize(expected_normalized) == value
289295

290296

291-
def check_topic_encode_decode(tp, val, encoded_val, *, can_be_decoded=True):
297+
def check_topic_encode_decode(
298+
tp: Type, val: Any, encoded_val: bytes, *, can_be_decoded: bool = True
299+
) -> None:
292300
assert tp.encode_to_topic(val) == encoded_val
293301
if can_be_decoded:
294302
assert tp.decode_from_topic(encoded_val) == val
295303
else:
296304
assert tp.decode_from_topic(encoded_val) is None
297305

298306

299-
def test_encode_to_topic():
307+
def test_encode_to_topic() -> None:
300308
# Simple types
301309

302310
check_topic_encode_decode(
@@ -331,14 +339,14 @@ def test_encode_to_topic():
331339
tp = abi.bytes()[2]
332340
small_bytes = os.urandom(5)
333341
big_bytes = os.urandom(33)
334-
val = [small_bytes, big_bytes]
342+
val: Any = [small_bytes, big_bytes]
335343
# Values in the array are padded to multiples of 32 bytes
336344
encoded_val = keccak(small_bytes + b"\x00" * 27 + big_bytes + b"\x00" * 31)
337345
check_topic_encode_decode(tp, val, encoded_val, can_be_decoded=False)
338346

339347
# Structs
340348

341-
tp = abi.struct(
349+
struct_tp = abi.struct(
342350
field1=abi.bytes()[2],
343351
field2=abi.bool,
344352
field3=abi.struct(inner1=abi.bytes(5), inner2=abi.string),
@@ -355,10 +363,10 @@ def test_encode_to_topic():
355363
+ (small_bytes + b"\x00" * 27)
356364
+ (string.encode() + b"\x00" * 28)
357365
)
358-
check_topic_encode_decode(tp, val, encoded_val, can_be_decoded=False)
366+
check_topic_encode_decode(struct_tp, val, encoded_val, can_be_decoded=False)
359367

360368

361-
def test_encode_decode_args():
369+
def test_encode_decode_args() -> None:
362370
args = ("some string", b"bytestring", 1234)
363371
types = [abi.string, abi.bytes(), abi.uint(256)]
364372
encoded = encode_args(*zip(types, args, strict=True))
@@ -368,7 +376,7 @@ def test_encode_decode_args():
368376
assert encode_args() == b""
369377

370378

371-
def test_decoding_error():
379+
def test_decoding_error() -> None:
372380
types = [abi.uint(256), abi.uint(256)]
373381
encoded_bytes = b"\x00" * 31 + b"\x01" # Only one uint256
374382

0 commit comments

Comments
 (0)