chore(common, tests, tools): enforce PEP 585 style
What changed, and why it matters
This commit is a routine code cleanup that updates Python type annotations to follow a newer Python style guide (PEP 585). It replaces older-style imports like `List`, `Tuple`, and `Dict` from the `typing` module with built-in equivalents like `list`, `tuple`, and `dict`. It also adjusts the project's linting configuration to enforce this style. There is no change to runtime behavior, no security fix, and no vulnerability introduced.
No security action required. Treat as a normal style/linting maintenance commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit enforces PEP 585 style type annotations across common tooling, tests, and helper scripts. Changes are purely syntactic: typing.List/Tuple/Dict/Set/Callable/Iterable/Iterator/Generator/Sequence are replaced with list/tuple/dict/set or collections.abc counterparts where appropriate. The pyproject.toml Ruff configuration is simplified, and Makefiles are updated to pass file lists to ruff check explicitly. No runtime logic, cryptographic operations, or device firmware behavior is modified.
Changed components
common/protob/pb2pycommon/tools/coin_info.pycommon/tools/cointool.pytests/* (multiple test helper and click-test files)tools/* (various helper scripts)Makefilepython/Makefilepyproject.tomlInspect captured patch +86 / −81
### Makefile
@@ -69,7 +69,7 @@ pystyle_check: ## run code style check on application sources and tests
@echo [BLACK]
@black --check $(BLACK_FLAGS) $(PY_FILES)
@echo [RUFF - PEP 585]
- @ruff check
+ @ruff check $(PY_FILES)
@echo [PYLINT]
@pylint $(PY_FILES)
@echo [PYTHON]
@@ -86,7 +86,7 @@ pystyle: ## apply code style on application sources and tests
@echo [BLACK]
@black $(BLACK_FLAGS) $(PY_FILES)
@echo [RUFF - PEP 585]
- @ruff check --fix
+ @ruff check --fix $(PY_FILES)
@echo [TYPECHECK]
@make -C core typecheck
@echo [TYPECHECK - COMMON and TOOLS]
### common/protob/pb2py
@@ -12,7 +12,7 @@ import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
-from typing import List, Optional
+from typing import Optional
import click
import construct as c
@@ -238,7 +238,7 @@ class ProtoMessage:
orig: object
extensions: dict
- fields: List[ProtoField]
+ fields: list[ProtoField]
@classmethod
def from_message(cls, descriptor: "Descriptor", message):
### common/tools/coin_info.py
@@ -5,8 +5,9 @@
import logging
import re
from collections import OrderedDict, defaultdict
+from collections.abc import Callable, Iterable, Iterator
from pathlib import Path
-from typing import Any, Callable, Iterable, Iterator, Literal, TypedDict, cast
+from typing import Any, Literal, TypedDict, cast
try:
import requests
### common/tools/cointool.py
@@ -10,9 +10,10 @@
import re
import sys
from collections import defaultdict
+from collections.abc import Callable, Iterator
from hashlib import sha256
from pathlib import Path
-from typing import Any, Callable, Iterator, TextIO, cast
+from typing import Any, TextIO, cast
import click
### pyproject.toml
@@ -98,19 +98,14 @@ extra_standard_library = [
known_first_party = ["trezorlib", "apps", "coin_info", "marketcap", "ui_tests"]
known_third_party = ["trezor", "storage"]
-# Ruff is used as a PEP 585 linter
+# Ruff is used as a PEP 585 linter only; all other Python style checks are
+# handled by flake8 / isort / black / pylint. The set of files to lint is not
+# configured here -- the Makefiles pass it in, so that ruff covers exactly what
+# every other style tool covers (tools/style.py.include, python/Makefile).
[tool.ruff]
target-version = "py310"
+# `python` is a published library that still supports 3.9 (see python/pyproject.toml)
per-file-target-version = { "python/**/*.py" = "py39" }
-include = [
- "core/**/*.py",
- "python/**/*.py",
-]
-force-exclude = true
-extend-exclude = [
- "core/translations",
- "vendor",
-]
[tool.ruff.lint]
select = [
### python/Makefile
@@ -44,12 +44,14 @@ style:
black $(BLACK_FLAGS) $(STYLE_TARGETS)
isort $(STYLE_TARGETS)
autoflake -i --remove-all-unused-imports -r $(STYLE_TARGETS)
+ ruff check --fix $(STYLE_TARGETS)
flake8
make pyright
style_check:
black --check $(BLACK_FLAGS) $(STYLE_TARGETS)
isort --check-only $(STYLE_TARGETS)
+ ruff check $(STYLE_TARGETS)
flake8
make pyright
### tests/bip32.py
@@ -18,7 +18,7 @@
import hmac
import struct
from copy import copy
-from typing import Any, List, Tuple
+from typing import Any
from ecdsa.curves import SECP256k1
from ecdsa.ecdsa import generator_secp256k1
@@ -37,14 +37,14 @@ def point_to_pubkey(point: Point) -> bytes:
return struct.pack("B", (vk[63] & 1) + 2) + vk[0:32] # To compressed key
-def sec_to_public_pair(pubkey: bytes) -> Tuple[int, Any]:
+def sec_to_public_pair(pubkey: bytes) -> tuple[int, Any]:
"""Convert a public key in sec binary format to a public pair."""
x = string_to_number(pubkey[1:33])
sec0 = pubkey[:1]
if sec0 not in (b"\2", b"\3"):
raise ValueError("Compressed pubkey expected")
- def public_pair_for_x(generator, x: int, is_even: bool) -> Tuple[int, Any]:
+ def public_pair_for_x(generator, x: int, is_even: bool) -> tuple[int, Any]:
curve = generator.curve()
p = curve.p()
alpha = (pow(x, 3, p) + curve.a() * x + curve.b()) % p
@@ -64,7 +64,7 @@ def get_address(public_node: messages.HDNodeType, address_type: int) -> str:
return tools.public_key_to_bc_address(public_node.public_key, address_type)
-def public_ckd(public_node: messages.HDNodeType, n: List[int]):
+def public_ckd(public_node: messages.HDNodeType, n: list[int]):
if not isinstance(n, list):
raise ValueError("Parameter must be a list")
### tests/click_tests/device_menu/common.py
@@ -14,8 +14,9 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+from collections.abc import Callable
from enum import Enum, auto
-from typing import TYPE_CHECKING, Callable
+from typing import TYPE_CHECKING
from trezorlib.messages import BackupAvailability
from trezorlib.testing import translations as TR
### tests/click_tests/test_autolock.py
@@ -16,7 +16,7 @@
import math
import time
-from typing import TYPE_CHECKING, Tuple
+from typing import TYPE_CHECKING
import pytest
@@ -53,14 +53,14 @@
PIN4 = "1234"
-def _passphrase_j(debug: DebugLink) -> Tuple[int, int]:
+def _passphrase_j(debug: DebugLink) -> tuple[int, int]:
if debug.layout_type is LayoutType.Bolt:
return debug.screen_buttons.pin_passphrase_grid(1, 1)
else:
return debug.screen_buttons.pin_passphrase_grid(0, 1)
-def _center_button(debug: DebugLink) -> Tuple[int, int]:
+def _center_button(debug: DebugLink) -> tuple[int, int]:
return debug.screen_buttons.pin_passphrase_grid(1, 1)
### tests/click_tests/test_passphrase_bde.py
@@ -15,8 +15,9 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
+from collections.abc import Generator
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Generator, Optional
+from typing import TYPE_CHECKING, Optional
import pytest
### tests/click_tests/test_passphrase_caesar.py
@@ -14,8 +14,9 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+from collections.abc import Generator
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Generator, Optional
+from typing import TYPE_CHECKING, Optional
import pytest
### tests/click_tests/test_pin.py
@@ -15,9 +15,10 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
+from collections.abc import Generator
from contextlib import contextmanager
from enum import Enum
-from typing import TYPE_CHECKING, Generator
+from typing import TYPE_CHECKING
import pytest
### tests/click_tests/test_recovery.py
@@ -15,8 +15,9 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
+from collections.abc import Generator
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Generator
+from typing import TYPE_CHECKING
import pytest
### tests/click_tests/test_tutorial_caesar.py
@@ -14,8 +14,9 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+from collections.abc import Generator
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Generator
+from typing import TYPE_CHECKING
import pytest
### tests/common.py
@@ -19,8 +19,9 @@
import json
import re
import time
+from collections.abc import Generator
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Generator, Optional
+from typing import TYPE_CHECKING, Any, Optional
from unittest import mock
import pytest
### tests/device_tests/bitcoin/signtx.py
@@ -1,6 +1,6 @@
import os
+from collections.abc import Sequence
from decimal import Decimal
-from typing import Sequence, Tuple
import bitcoin
import requests
@@ -92,8 +92,8 @@ def get_tx_hex(hash_link: str) -> str:
def forge_prevtx(
- vouts: Sequence[Tuple[str, int]], network: str = "mainnet"
-) -> Tuple[bytes, messages.TransactionType]:
+ vouts: Sequence[tuple[str, int]], network: str = "mainnet"
+) -> tuple[bytes, messages.TransactionType]:
"""
Forge a transaction with the given vouts.
"""
### tests/device_tests/certificate.py
@@ -1,4 +1,4 @@
-from typing import Sequence
+from collections.abc import Sequence
from cryptography import x509
from cryptography.hazmat.primitives import hashes
### tests/device_tests/ethereum/test_definitions.py
@@ -1,6 +1,7 @@
from __future__ import annotations
-from typing import Any, Callable
+from collections.abc import Callable
+from typing import Any
import pytest
### tests/device_tests/ethereum/test_definitions_request.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from binascii import hexlify
-from typing import Callable
+from collections.abc import Callable
from unittest.mock import Mock
import pytest
### tests/device_tests/evolu/common.py
@@ -1,6 +1,5 @@
import os
from hashlib import sha256
-from typing import List
from ecdsa import NIST256p, SigningKey
@@ -19,7 +18,7 @@
def get_proof(
client: Client,
header: bytes,
- arguments: List[bytes],
+ arguments: list[bytes],
rotation_index: int | None = None,
) -> bytes:
private_key = get_delegated_identity_key(client, rotation_index).private_key
@@ -34,7 +33,7 @@ def get_proof(
return signing_key.sign_digest(ctx.digest())
-def get_invalid_proof(client: Client, header: bytes, arguments: List[bytes]) -> bytes:
+def get_invalid_proof(client: Client, header: bytes, arguments: list[bytes]) -> bytes:
valid_proof = get_proof(client, header, arguments)
# tamper with the proof to make it invalid
invalid_proof = (
### tests/device_tests/reset_recovery/test_reset_bip39_t1.py
@@ -14,7 +14,7 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
-from typing import Generator
+from collections.abc import Generator
import pytest
from mnemonic import Mnemonic
### tests/device_tests/test_language.py
@@ -16,8 +16,9 @@
from __future__ import annotations
+from collections.abc import Iterator
from copy import deepcopy
-from typing import Any, Iterator
+from typing import Any
import pytest
### tests/emulators.py
@@ -19,8 +19,8 @@
import os
import tempfile
from collections import defaultdict
+from collections.abc import Sequence
from pathlib import Path
-from typing import Sequence, Tuple
from trezorlib._internal.emulator import CoreEmulator, Emulator, LegacyEmulator
from trezorlib.models import CORE_MODELS, LEGACY_MODELS
@@ -67,7 +67,7 @@ def gen_from_model(model_internal_name: str) -> str:
raise ValueError(f"Unknown model: {model_internal_name}")
-def check_version(tag: str, version_tuple: Tuple[int, int, int]) -> None:
+def check_version(tag: str, version_tuple: tuple[int, int, int]) -> None:
if tag is not None and tag.startswith("v") and len(tag.split(".")) == 3:
version = ".".join(str(i) for i in version_tuple)
if tag[1:] != version:
### tests/github.py
@@ -6,9 +6,10 @@
from __future__ import annotations
+from collections.abc import Iterable, Iterator
from concurrent.futures import Future, ThreadPoolExecutor
from pathlib import Path
-from typing import Any, Iterable, Iterator
+from typing import Any
import requests
### tests/input_flows.py
@@ -12,7 +12,7 @@
from __future__ import annotations
import time
-from typing import Callable, Generator, Sequence
+from collections.abc import Callable, Generator, Sequence
import pytest
### tests/ui_tests/reporting/html.py
@@ -2,8 +2,8 @@
import shutil
import urllib.parse
+from collections.abc import Iterable
from pathlib import Path
-from typing import Iterable
from dominate import document
from dominate.tags import a, i, img, span, table, td, th, tr
### tests/update_fixtures.py
@@ -4,7 +4,7 @@
import json
import subprocess
-from typing import Iterable
+from collections.abc import Iterable
import click
### tests/upgrade_tests/__init__.py
@@ -17,7 +17,6 @@
import os
import re
from pathlib import Path
-from typing import List, Tuple
import pytest
from _pytest.mark.structures import MarkDecorator
@@ -135,10 +134,10 @@ def version_from_tag(tag: str | None) -> tuple | None:
def for_all(
*args: str,
- t1b1_minimum_version: Tuple[int, int, int] = (1, 0, 0),
- t2t1_minimum_version: Tuple[int, int, int] = (2, 0, 0),
+ t1b1_minimum_version: tuple[int, int, int] = (1, 0, 0),
+ t2t1_minimum_version: tuple[int, int, int] = (2, 0, 0),
# Intentionally starts at 2.9.3 for T3W1 upgrade coverage.
- t3w1_minimum_version: Tuple[int, int, int] = (2, 9, 3),
+ t3w1_minimum_version: tuple[int, int, int] = (2, 9, 3),
) -> "MarkDecorator":
"""Parametrizing decorator for test cases.
@@ -205,7 +204,7 @@ def for_all(
return pytest.mark.parametrize("tag, model", sorted(all_params))
-def for_tags(*args: Tuple[str, List[str]]) -> "MarkDecorator":
+def for_tags(*args: tuple[str, list[str]]) -> "MarkDecorator":
"""Parametrizing decorator for tests that need specific version tags.
Usage: @for_tags(("T1B1", ["v1.7.0", "v1.8.0"]))
### tests/upgrade_tests/conftest.py
@@ -1,7 +1,8 @@
import logging
import tempfile
+from collections.abc import Generator
from pathlib import Path
-from typing import Any, Generator
+from typing import Any
import pytest
### tests/upgrade_tests/test_firmware_upgrades.py
@@ -17,7 +17,7 @@
import dataclasses
import functools
-from typing import TYPE_CHECKING, List
+from typing import TYPE_CHECKING
import pytest
from shamir_mnemonic import shamir
@@ -193,7 +193,7 @@ def asserts(client: "Client") -> None:
)
@lower_models_minimum_version
def test_storage_upgrade_progressive(
- tags: List[str], model: str, shared_profile_dir: str
+ tags: list[str], model: str, shared_profile_dir: str
):
PIN = "1234"
### tests/upgrade_tests/test_passphrase_consistency.py
@@ -14,7 +14,7 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
-from typing import Iterator
+from collections.abc import Iterator
import pytest
### tools/changelog.py
@@ -4,8 +4,8 @@
import datetime
import re
import subprocess
+from collections.abc import Iterator
from pathlib import Path
-from typing import Iterator
import click
### tools/check_docs_summary.py
@@ -8,8 +8,8 @@
import re
import sys
+from collections.abc import Generator, Iterable
from pathlib import Path
-from typing import Generator, Iterable, Set
DOCS_DIR = "docs/"
SUMMARY_FILENAME = "SUMMARY.md"
@@ -44,7 +44,7 @@ def gen_skip(inputs: Iterable[str], what: str) -> Generator[str, None, None]:
def difference(g1: Iterable[str], g2: Iterable[str]) -> Generator[str, None, None]:
- set_g2: Set[str] = set(g2)
+ set_g2: set[str] = set(g2)
for item in g1:
if item not in set_g2:
yield item
### tools/gen-nightly-release-jsons.py
@@ -11,9 +11,10 @@
import json
import re
from collections import defaultdict
+from collections.abc import Sequence
from enum import StrEnum, auto
from pathlib import Path
-from typing import Any, Dict, Sequence
+from typing import Any
import click
@@ -117,7 +118,7 @@ def collect_firmware_files(
root: Path, version: list[int], deploy_type: DeployType
) -> dict:
"""Collect all firmware files for the given version."""
- firmware_files: Dict[str, Any] = defaultdict(
+ firmware_files: dict[str, Any] = defaultdict(
lambda: {
DeployType.NIGHTLY: {},
"translations": {
### tools/pyright_tool.py
@@ -44,26 +44,24 @@
import subprocess
import sys
import tempfile
+from collections.abc import Iterator
from dataclasses import dataclass
from pathlib import Path
-from typing import Dict # for python38 support, must be used in type aliases
-from typing import List # for python38 support, must be used in type aliases
-from typing import TYPE_CHECKING, Any, Iterator
+from typing import TYPE_CHECKING, Any, Final
import click
from typing_extensions import ( # for python37 support, is not present in typing there
- Final,
TypedDict,
)
if TYPE_CHECKING:
- LineIgnores = List["LineIgnore"]
+ LineIgnores = list["LineIgnore"]
- FileIgnores = Dict[str, LineIgnores]
- FileSpecificIgnores = Dict[str, List["FileSpecificIgnore"]]
+ FileIgnores = dict[str, LineIgnores]
+ FileSpecificIgnores = dict[str, list["FileSpecificIgnore"]]
- PyrightOffIgnores = List["PyrightOffIgnore"]
- FilePyrightOffIgnores = Dict[str, PyrightOffIgnores]
+ PyrightOffIgnores = list["PyrightOffIgnore"]
+ FilePyrightOffIgnores = dict[str, PyrightOffIgnores]
class RangeDetail(TypedDict):
### tools/snippets/font_multiplier.py
@@ -4,12 +4,10 @@
from __future__ import annotations
-from typing import Tuple
-
-from typing_extensions import Literal
+from typing import Literal
Bit = Literal[0, 1]
-Point = Tuple[int, int]
+Point = tuple[int, int]
def magnify_glyph_by_two(width: int, height: int, bytes_data: list[int]) -> list[int]:
### tools/snippets/monero_unused_functions.py
@@ -6,7 +6,7 @@
import subprocess
import sys
from pathlib import Path
-from typing import Any, Dict, List, Set
+from typing import Any
CURRENT_DIR = Path(__file__).resolve().parent
ROOT_DIR = CURRENT_DIR.parent.parent
@@ -15,11 +15,11 @@
MOCK_FILE = ROOT_DIR / "core/mocks/generated/trezorcrypto/monero.pyi"
-def generate_function_mapping() -> Dict[str, List[str]]:
+def generate_function_mapping() -> dict[str, list[str]]:
"""Look at all Monero functions and generate a mapping of their usage"""
# Load all the function names in .pyi file
- pyi_functions: Set[str] = set()
+ pyi_functions: set[str] = set()
with open(MOCK_FILE, "r") as f:
lines = f.readlines()
for line in lines:
@@ -28,7 +28,7 @@ def generate_function_mapping() -> Dict[str, List[str]]:
pyi_functions.add(f_name)
# Load definitions of helper functions
- helper_func_defs: Dict[str, str] = {}
+ helper_func_defs: dict[str, str] = {}
with open(HELPER_FILE, "r") as f:
lines = f.readlines()
current_func = ""
@@ -42,7 +42,7 @@ def generate_function_mapping() -> Dict[str, List[str]]:
helper_func_defs[current_func] += line
# Try to connect function names with helper definitions
- func_mapping: Dict[str, List[str]] = {}
+ func_mapping: dict[str, list[str]] = {}
for func_name in pyi_functions:
func_mapping[func_name] = []
for func_def_name, func_code in helper_func_defs.items():
@@ -56,14 +56,14 @@ def generate_function_mapping() -> Dict[str, List[str]]:
return func_mapping
-def check_usage_of_functions(func_mapping: Dict[str, List[str]]) -> None:
+def check_usage_of_functions(func_mapping: dict[str, list[str]]) -> None:
"""Go through all the functions and check if they are used in the Monero app.
Generates a report and exits with an appropriate exit code.
"""
# Include boolean field to know what is used
- is_used_mappings: Dict[str, Dict[str, Any]] = {}
+ is_used_mappings: dict[str, dict[str, Any]] = {}
for func_name, mapping in func_mapping.items():
is_used_mappings[func_name] = {"mapping": mapping, "is_used": False}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.