Skip to content

Commit c9b54e3

Browse files
refactor(all): modernize codebase with python 3.11 features (#1812)
Co-authored-by: Mario Vega <marioevz@gmail.com>
1 parent 724131d commit c9b54e3

File tree

19 files changed

+195
-157
lines changed

19 files changed

+195
-157
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ Users can select any of the artifacts depending on their testing needs for their
5454

5555
### 📋 Misc
5656

57-
- Remove Python 3.10 support ([#1808](https://github.com/ethereum/execution-spec-tests/pull/1808)).
57+
- 🔀 Remove Python 3.10 support ([#1808](https://github.com/ethereum/execution-spec-tests/pull/1808)).
58+
- 🔀 Modernize codebase with Python 3.11 language features ([#1812](https://github.com/ethereum/execution-spec-tests/pull/1812)).
5859
- ✨ Add changelog formatting validation to CI to ensure consistent punctuation in bullet points [#1691](https://github.com/ethereum/execution-spec-tests/pull/1691).
5960
- ✨ Added the [EIP checklist template](https://eest.ethereum.org/main/writing_tests/checklist_templates/eip_testing_checklist_template/) that serves as a reference to achieve better coverage when implementing tests for new EIPs ([#1327](https://github.com/ethereum/execution-spec-tests/pull/1327)).
6061
- ✨ Added [Post-Mortems of Missed Test Scenarios](https://eest.ethereum.org/main/writing_tests/post_mortems/) to the documentation that serves as a reference list of all cases that were missed during the test implementation phase of a new EIP, and includes the steps taken in order to prevent similar test cases to be missed in the future ([#1327](https://github.com/ethereum/execution-spec-tests/pull/1327)).

src/cli/order_fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717

1818
import json
1919
from pathlib import Path
20-
from typing import Any, Dict, List, Union, cast
20+
from typing import Any, Dict, List, cast
2121

2222
import click
2323

2424

25-
def recursive_sort(item: Union[Dict[str, Any], List[Any]]) -> Union[Dict[str, Any], List[Any]]:
25+
def recursive_sort(item: Dict[str, Any] | List[Any]) -> Dict[str, Any] | List[Any]:
2626
"""
2727
Recursively sorts an item.
2828

src/ethereum_clis/transition_tool.py

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from abc import abstractmethod
1111
from dataclasses import dataclass
1212
from pathlib import Path
13-
from typing import Any, Dict, List, Mapping, Optional, Type
13+
from typing import Any, Dict, List, LiteralString, Mapping, Optional, Type
1414
from urllib.parse import urlencode
1515

1616
from requests import Response
@@ -20,6 +20,7 @@
2020
from ethereum_test_base_types import BlobSchedule
2121
from ethereum_test_exceptions import ExceptionMapper
2222
from ethereum_test_forks import Fork
23+
from ethereum_test_forks.helpers import get_development_forks, get_forks
2324
from ethereum_test_types import Alloc, Environment, Transaction
2425

2526
from .ethereum_cli import EthereumCLI
@@ -38,6 +39,12 @@
3839
SLOW_REQUEST_TIMEOUT = 180
3940

4041

42+
def get_valid_transition_tool_names() -> set[str]:
43+
"""Get all valid transition tool names from deployed and development forks."""
44+
all_available_forks = get_forks() + get_development_forks()
45+
return {fork.transition_tool_name() for fork in all_available_forks}
46+
47+
4148
class TransitionTool(EthereumCLI):
4249
"""
4350
Transition tool abstract base class which should be inherited by all transition tool
@@ -437,6 +444,49 @@ def _evaluate_stream(
437444

438445
return output
439446

447+
def safe_t8n_args(
448+
self, fork_name: str, chain_id: int, reward: int, temp_dir=None
449+
) -> List[str]:
450+
"""Safely construct t8n arguments with validated inputs."""
451+
# Validate fork name against actual transition tool names from all available forks
452+
valid_forks = get_valid_transition_tool_names()
453+
if fork_name not in valid_forks:
454+
raise ValueError(f"Invalid fork name: {fork_name}")
455+
456+
# Validate chain ID (should be positive integer)
457+
if not isinstance(chain_id, int) or chain_id <= 0:
458+
raise ValueError(f"Invalid chain ID: {chain_id}")
459+
460+
# Validate reward (should be non-negative integer)
461+
if not isinstance(reward, int) or reward < 0:
462+
raise ValueError(f"Invalid reward: {reward}")
463+
464+
# Use literal strings for command flags
465+
input_alloc: LiteralString = "--input.alloc=stdin"
466+
input_txs: LiteralString = "--input.txs=stdin"
467+
input_env: LiteralString = "--input.env=stdin"
468+
output_result: LiteralString = "--output.result=stdout"
469+
output_alloc: LiteralString = "--output.alloc=stdout"
470+
output_body: LiteralString = "--output.body=stdout"
471+
trace_flag: LiteralString = "--trace"
472+
473+
args = [
474+
input_alloc,
475+
input_txs,
476+
input_env,
477+
output_result,
478+
output_alloc,
479+
output_body,
480+
f"--state.fork={fork_name}",
481+
f"--state.chainid={chain_id}",
482+
f"--state.reward={reward}",
483+
]
484+
485+
if self.trace and temp_dir:
486+
args.extend([trace_flag, f"--output.basedir={temp_dir.name}"])
487+
488+
return args
489+
440490
def construct_args_stream(
441491
self, t8n_data: TransitionToolData, temp_dir: tempfile.TemporaryDirectory
442492
) -> List[str]:
@@ -445,22 +495,10 @@ def construct_args_stream(
445495
if self.subcommand:
446496
command.append(self.subcommand)
447497

448-
args = command + [
449-
"--input.alloc=stdin",
450-
"--input.txs=stdin",
451-
"--input.env=stdin",
452-
"--output.result=stdout",
453-
"--output.alloc=stdout",
454-
"--output.body=stdout",
455-
f"--state.fork={t8n_data.fork_name}",
456-
f"--state.chainid={t8n_data.chain_id}",
457-
f"--state.reward={t8n_data.reward}",
458-
]
459-
460-
if self.trace:
461-
args.append("--trace")
462-
args.append(f"--output.basedir={temp_dir.name}")
463-
return args
498+
safe_args = self.safe_t8n_args(
499+
t8n_data.fork_name, t8n_data.chain_id, t8n_data.reward, temp_dir
500+
)
501+
return command + safe_args
464502

465503
def dump_debug_stream(
466504
self,

src/ethereum_test_base_types/base_types.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
to_number,
2222
)
2323

24-
N = TypeVar("N", bound="Number")
25-
2624

2725
class ToStringSchema:
2826
"""
@@ -44,7 +42,7 @@ def __get_pydantic_core_schema__(
4442
class Number(int, ToStringSchema):
4543
"""Class that helps represent numbers in tests."""
4644

47-
def __new__(cls, input_number: NumberConvertible | N):
45+
def __new__(cls, input_number: NumberConvertible | Self):
4846
"""Create a new Number object."""
4947
return super(Number, cls).__new__(cls, to_number(input_number))
5048

@@ -57,7 +55,7 @@ def hex(self) -> str:
5755
return hex(self)
5856

5957
@classmethod
60-
def or_none(cls: Type[N], input_number: N | NumberConvertible | None) -> N | None:
58+
def or_none(cls: Type[Self], input_number: Self | NumberConvertible | None) -> Self | None:
6159
"""Convert the input to a Number while accepting None."""
6260
if input_number is None:
6361
return input_number
@@ -67,7 +65,7 @@ def or_none(cls: Type[N], input_number: N | NumberConvertible | None) -> N | Non
6765
class Wei(Number):
6866
"""Class that helps represent wei that can be parsed from strings."""
6967

70-
def __new__(cls, input_number: NumberConvertible | N):
68+
def __new__(cls, input_number: NumberConvertible | Self):
7169
"""Create a new Number object."""
7270
if isinstance(input_number, str):
7371
words = input_number.split()
@@ -209,9 +207,6 @@ def __get_pydantic_core_schema__(
209207
)
210208

211209

212-
S = TypeVar("S", bound="FixedSizeHexNumber")
213-
214-
215210
class FixedSizeHexNumber(int, ToStringSchema):
216211
"""
217212
A base class that helps represent an integer as a fixed byte-length
@@ -233,7 +228,7 @@ class Sized(cls): # type: ignore
233228

234229
return Sized
235230

236-
def __new__(cls, input_number: NumberConvertible | N):
231+
def __new__(cls, input_number: NumberConvertible | Self):
237232
"""Create a new Number object."""
238233
i = to_number(input_number)
239234
if i > cls.max_value:
@@ -276,9 +271,6 @@ class HashInt(FixedSizeHexNumber[32]): # type: ignore
276271
pass
277272

278273

279-
T = TypeVar("T", bound="FixedSizeBytes")
280-
281-
282274
class FixedSizeBytes(Bytes):
283275
"""Class that helps represent bytes of fixed length in tests."""
284276

@@ -296,7 +288,7 @@ class Sized(cls): # type: ignore
296288

297289
def __new__(
298290
cls,
299-
input_bytes: FixedSizeBytesConvertible | T,
291+
input_bytes: FixedSizeBytesConvertible | Self,
300292
*,
301293
left_padding: bool = False,
302294
right_padding: bool = False,
@@ -319,7 +311,9 @@ def __hash__(self) -> int:
319311
return super(FixedSizeBytes, self).__hash__()
320312

321313
@classmethod
322-
def or_none(cls: Type[T], input_bytes: T | FixedSizeBytesConvertible | None) -> T | None:
314+
def or_none(
315+
cls: Type[Self], input_bytes: Self | FixedSizeBytesConvertible | None
316+
) -> Self | None:
323317
"""Convert the input to a Fixed Size Bytes while accepting None."""
324318
if input_bytes is None:
325319
return input_bytes

src/ethereum_test_base_types/pydantic.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44

55
from pydantic import BaseModel, ConfigDict, RootModel
66
from pydantic.alias_generators import to_camel
7+
from typing_extensions import Self
78

89
from .mixins import ModelCustomizationsMixin
910

10-
Model = TypeVar("Model", bound=BaseModel)
11-
1211
RootModelRootType = TypeVar("RootModelRootType")
1312

1413

@@ -27,7 +26,7 @@ class EthereumTestRootModel(RootModel[RootModelRootType], ModelCustomizationsMix
2726
class CopyValidateModel(EthereumTestBaseModel):
2827
"""Model that supports copying with validation."""
2928

30-
def copy(self: Model, **kwargs) -> Model:
29+
def copy(self: Self, **kwargs) -> Self:
3130
"""Create a copy of the model with the updated fields that are validated."""
3231
return self.__class__(**(self.model_dump(exclude_unset=True) | kwargs))
3332

src/ethereum_test_fixtures/collector.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .file import Fixtures
1919

2020

21-
@dataclass(kw_only=True)
21+
@dataclass(kw_only=True, slots=True)
2222
class TestInfo:
2323
"""Contains test information from the current node."""
2424

@@ -34,9 +34,9 @@ class TestInfo:
3434
def strip_test_name(cls, name: str) -> str:
3535
"""Remove test prefix from a python test case name."""
3636
if name.startswith(cls.test_prefix):
37-
return name[len(cls.test_prefix) :]
37+
return name.removeprefix(cls.test_prefix)
3838
if name.endswith(cls.filler_suffix):
39-
return name[: -len(cls.filler_suffix)]
39+
return name.removesuffix(cls.filler_suffix)
4040
return name
4141

4242
def get_name_and_parameters(self) -> Tuple[str, str]:

src/ethereum_test_rpc/rpc.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import time
44
from itertools import count
55
from pprint import pprint
6-
from typing import Any, ClassVar, Dict, List, Literal, Union
6+
from typing import Any, ClassVar, Dict, List, Literal
77

88
import requests
99
from jwt import encode
@@ -23,7 +23,7 @@
2323
TransactionByHashResponse,
2424
)
2525

26-
BlockNumberType = Union[int, Literal["latest", "earliest", "pending"]]
26+
BlockNumberType = int | Literal["latest", "earliest", "pending"]
2727

2828

2929
class SendTransactionExceptionError(Exception):
@@ -71,7 +71,7 @@ def __init_subclass__(cls) -> None:
7171
"""Set namespace of the RPC class to the lowercase of the class name."""
7272
namespace = cls.__name__
7373
if namespace.endswith("RPC"):
74-
namespace = namespace[:-3]
74+
namespace = namespace.removesuffix("RPC")
7575
cls.namespace = namespace.lower()
7676

7777
def post_request(self, method: str, *params: Any, extra_headers: Dict | None = None) -> Any:
@@ -111,7 +111,7 @@ class EthRPC(BaseRPC):
111111

112112
transaction_wait_timeout: int = 60
113113

114-
BlockNumberType = Union[int, Literal["latest", "earliest", "pending"]]
114+
BlockNumberType = int | Literal["latest", "earliest", "pending"]
115115

116116
def __init__(
117117
self,

src/ethereum_test_rpc/types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from enum import Enum
44
from hashlib import sha256
5-
from typing import Annotated, Any, List, Union
5+
from typing import Annotated, Any, List
66

77
from pydantic import AliasChoices, Field, model_validator
88

@@ -97,7 +97,7 @@ class PayloadStatusEnum(str, Enum):
9797

9898

9999
class BlockTransactionExceptionWithMessage(
100-
ExceptionWithMessage[Union[BlockException, TransactionException]] # type: ignore
100+
ExceptionWithMessage[BlockException | TransactionException] # type: ignore
101101
):
102102
"""Exception returned from the execution client with a message."""
103103

src/ethereum_test_specs/base.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
from functools import reduce
66
from os import path
77
from pathlib import Path
8-
from typing import Callable, ClassVar, Dict, Generator, List, Sequence, Type, TypeVar
8+
from typing import Callable, ClassVar, Dict, Generator, List, Sequence, Type
99

1010
import pytest
1111
from pydantic import BaseModel, Field, PrivateAttr
12+
from typing_extensions import Self
1213

1314
from ethereum_clis import Result, TransitionTool
1415
from ethereum_test_base_types import to_hex
@@ -48,9 +49,6 @@ def verify_result(result: Result, env: Environment):
4849
assert result.withdrawals_root == to_hex(Withdrawal.list_root(env.withdrawals))
4950

5051

51-
T = TypeVar("T", bound="BaseTest")
52-
53-
5452
class BaseTest(BaseModel):
5553
"""Represents a base Ethereum test which must return a single test fixture."""
5654

@@ -91,11 +89,11 @@ def __pydantic_init_subclass__(cls, **kwargs):
9189

9290
@classmethod
9391
def from_test(
94-
cls: Type[T],
92+
cls: Type[Self],
9593
*,
9694
base_test: "BaseTest",
9795
**kwargs,
98-
) -> T:
96+
) -> Self:
9997
"""Create a test in a different format from a base test."""
10098
new_instance = cls(
10199
tag=base_test.tag,

src/ethereum_test_specs/helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Helper functions."""
22

33
from dataclasses import dataclass
4-
from enum import Enum
4+
from enum import StrEnum
55
from typing import Any, Dict, List
66

77
from ethereum_clis import Result
@@ -15,7 +15,7 @@
1515
from ethereum_test_types import Transaction, TransactionReceipt
1616

1717

18-
class ExecutionContext(Enum):
18+
class ExecutionContext(StrEnum):
1919
"""The execution context in which a test case can fail."""
2020

2121
BLOCK = "Block"

0 commit comments

Comments
 (0)