diff --git a/python/.changelog.d/+1d468db6.deprecated b/python/.changelog.d/+1d468db6.deprecated new file mode 100644 index 0000000000..761ca842d7 --- /dev/null +++ b/python/.changelog.d/+1d468db6.deprecated @@ -0,0 +1 @@ +`trezorlib.__version__` is deprecated, use `importlib.metadata.version("trezor")`. diff --git a/python/.changelog.d/+47cd2085.removed b/python/.changelog.d/+47cd2085.removed new file mode 100644 index 0000000000..09cab08212 --- /dev/null +++ b/python/.changelog.d/+47cd2085.removed @@ -0,0 +1 @@ +Dropped support for Python 3.8. diff --git a/python/.changelog.d/+cabe85ab.changed b/python/.changelog.d/+cabe85ab.changed new file mode 100644 index 0000000000..9365e091ff --- /dev/null +++ b/python/.changelog.d/+cabe85ab.changed @@ -0,0 +1 @@ +Changed build system to uv. diff --git a/python/.gitignore b/python/.gitignore index c45fe6b78a..169f85c720 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -8,3 +8,5 @@ MANIFEST *.py.cache /.tox mypy_report +uv.lock +/.venv diff --git a/python/MANIFEST.in b/python/MANIFEST.in deleted file mode 100644 index a55bb16423..0000000000 --- a/python/MANIFEST.in +++ /dev/null @@ -1,13 +0,0 @@ -recursive-include bash_completion.d *.sh -include tools/* -graft src -graft tests -graft stubs - -include AUTHORS README.md COPYING CHANGELOG.md -include requirements*.txt -include tox.ini pyrightconfig.json -exclude src/trezorlib/_proto_messages.mako -exclude tests/*.bin -global-exclude *.pyc -global-exclude */__pycache__/* diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000000..91e718917b --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,91 @@ +[project] +name = "trezor" +version = "0.14.0" +description = "Python library for communicating with Trezor Hardware Wallet" +readme = "README.md" +license = "LGPL-3.0-only" +license-files = ["COPYING"] +authors = [ + {name = "Trezor", email = "info@trezor.io"} +] +maintainers = [ + {name = "matejcik", email = "jan.matejek@satoshilabs.com"} +] +keywords = ["trezor", "hardware", "wallet", "cryptocurrency", "bitcoin", "ethereum"] +classifiers = [ + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Programming Language :: Python :: 3 :: Only", +] +requires-python = ">=3.9" +dependencies = [ + "ecdsa>=0.9", + "mnemonic>=0.20", + "shamir-mnemonic>=0.3.0", + "slip10>=1.0.1", + "requests>=2.4.0", + "click>=8,<8.3", + "libusb1>=1.6.4", + "construct>=2.9,!=2.10.55", + "typing_extensions>=4.7.1", + "construct-classes>=0.1.2", + "cryptography>=41", + "noiseprotocol>=0.3.1,<0.4.0", +] + +[project.optional-dependencies] +hidapi = ["hidapi>=0.7.99.post20"] +ethereum = ["web3>=5"] +qt-widgets = ["PyQt5"] +extra = ["Pillow>=10"] +stellar = ["stellar-sdk>=6"] +full = [ + "hidapi>=0.7.99.post20", + "web3>=5", + "PyQt5", + "Pillow>=10", + "stellar-sdk>=6", +] + +[project.urls] +Homepage = "https://github.com/trezor/trezor-firmware/tree/main/python" +Repository = "https://github.com/trezor/trezor-firmware" +Documentation = "https://github.com/trezor/trezor-firmware/tree/main/python" + +[project.scripts] +trezorctl = "trezorlib.cli.trezorctl:cli" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +package = true + +[tool.hatch.build.targets.wheel] +packages = ["src/trezorlib"] + +[tool.hatch.build.targets.sdist] +exclude = [ + "/.*", + "/helper-scripts", + "/CHANGELOG.unreleased", + "/default.nix", + "/Makefile", + "/towncrier.toml", + "tests/*.bin", + "src/trezorlib/_proto_messages.mako", +] + +[dependency-groups] +dev = [ + "autoflake>=2.3.1", + "black>=25", + "flake8>=2.3.0", + "isort>=5.13.2", + "pytest>=8.3.5", + "pytest-random-order>=1.2.0", + "tox>=4.25.0", + "tox-uv>=1.13.1", +] diff --git a/python/requirements-optional.txt b/python/requirements-optional.txt deleted file mode 100644 index 4c0d35dc2c..0000000000 --- a/python/requirements-optional.txt +++ /dev/null @@ -1,4 +0,0 @@ -hidapi>=0.7.99.post20 -web3>=5 -Pillow>=10 -stellar-sdk>=6 diff --git a/python/requirements.txt b/python/requirements.txt deleted file mode 100644 index 99cc0a0bc2..0000000000 --- a/python/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -ecdsa>=0.9 -mnemonic>=0.20 -shamir-mnemonic>=0.3.0 -slip10>=1.0.1 -requests>=2.4.0 -click>=7,<8.2 -libusb1>=1.6.4 -construct>=2.9,!=2.10.55 -typing_extensions>=4.7.1 -construct-classes>=0.1.2 -cryptography>=41 -noiseprotocol>=0.3.1,<0.4.0 diff --git a/python/setup.cfg b/python/setup.cfg index 430658bd1c..7ff9af9b49 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -2,6 +2,7 @@ filename = *.py exclude = .tox/, + .venv/, build/, dist/, vendor/, diff --git a/python/setup.py b/python/setup.py deleted file mode 100755 index cad2216420..0000000000 --- a/python/setup.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of the Trezor project. -# -# Copyright (C) SatoshiLabs and contributors -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License version 3 -# as published by the Free Software Foundation. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the License along with this library. -# If not, see . - -import re -from pathlib import Path - -from setuptools import find_packages, setup - -CWD = Path(__file__).resolve().parent - -install_requires = (CWD / "requirements.txt").read_text().splitlines() - -extras_require = { - "hidapi": ["hidapi>=0.7.99.post20"], - "ethereum": ["web3>=5"], - "qt-widgets": ["PyQt5"], - "extra": ["Pillow>=10"], - "stellar": ["stellar-sdk>=6"], -} - -extras_require["full"] = sum(extras_require.values(), []) - - -def find_version(): - version_file = (CWD / "src" / "trezorlib" / "__init__.py").read_text() - version_match = re.search(r"^__version__ = \"(.*)\"$", version_file, re.M) - if version_match: - return version_match.group(1) - else: - raise RuntimeError("Version string not found") - - -setup( - name="trezor", - version=find_version(), - author="Trezor", - author_email="info@trezor.io", - license="LGPLv3", - description="Python library for communicating with Trezor Hardware Wallet", - long_description=(CWD / "README.md").read_text() - + "\n\n" - + (CWD / "CHANGELOG.md").read_text(), - long_description_content_type="text/markdown", - url="https://github.com/trezor/trezor-firmware/tree/master/python", - package_data={"trezorlib": ["py.typed"]}, - packages=find_packages("src"), - package_dir={"": "src"}, - entry_points={"console_scripts": ["trezorctl=trezorlib.cli.trezorctl:cli"]}, - install_requires=install_requires, - extras_require=extras_require, - python_requires=">=3.8", - include_package_data=True, - zip_safe=False, - classifiers=[ - "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS :: MacOS X", - "Programming Language :: Python :: 3 :: Only", - ], -) diff --git a/python/src/trezorlib/__init__.py b/python/src/trezorlib/__init__.py index 35e1ac44e9..39fcc36fb3 100644 --- a/python/src/trezorlib/__init__.py +++ b/python/src/trezorlib/__init__.py @@ -14,4 +14,15 @@ # You should have received a copy of the License along with this library. # If not, see . -__version__ = "0.14.0" +import importlib.metadata +import warnings + + +def __getattr__(name: str) -> str: + if name == "__version__": + warnings.warn( + "__version__ is deprecated and will be removed in 0.15.0, use importlib.metadata.version('trezor') instead", + DeprecationWarning, + ) + return importlib.metadata.version("trezor") + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py index 2fd52bf643..c23d596d36 100755 --- a/python/src/trezorlib/cli/trezorctl.py +++ b/python/src/trezorlib/cli/trezorctl.py @@ -16,6 +16,7 @@ # You should have received a copy of the License along with this library. # If not, see . +import importlib.metadata import json import logging import os @@ -24,7 +25,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, TypeVar, ca import click -from .. import __version__, log, messages, protobuf +from .. import log, messages, protobuf from ..transport import DeviceIsBusy, enumerate_devices from ..transport.session import Session from ..transport.udp import UdpTransport @@ -141,7 +142,7 @@ class TrezorctlGroup(AliasedGroup): # This means that there is no reasonable way to use `hasattr` to detect where we # are, unless we want to look at the private `_result_callback` attribute. # Instead, we look at Click version and hope for the best. - from click import __version__ as click_version + click_version = importlib.metadata.version("click") if click_version.startswith("7."): return super().resultcallback() # type: ignore [Cannot access attribute] @@ -194,7 +195,7 @@ def configure_logging(verbose: int) -> None: "--record", help="Record screen changes into a specified directory.", ) -@click.version_option(version=__version__) +@click.version_option(package_name="trezor") @click.pass_context def cli_main( ctx: click.Context, @@ -310,7 +311,7 @@ def list_devices(no_resolve: bool) -> Optional[Iterable["Transport"]]: @cli.command() def version() -> str: """Show version of trezorctl/trezorlib.""" - return __version__ + return importlib.metadata.version("trezor") # diff --git a/python/tests/test_protobuf_encoding.py b/python/tests/test_protobuf_encoding.py index ebf55a4118..514c72c4e0 100644 --- a/python/tests/test_protobuf_encoding.py +++ b/python/tests/test_protobuf_encoding.py @@ -165,7 +165,7 @@ def test_simple_message(): uvarint=12345678910, svarint=-12345678910, bool=True, - bytes=b"\xDE\xAD\xCA\xFE", + bytes=b"\xde\xad\xca\xfe", unicode="Příliš žluťoučký kůň úpěl ďábelské ódy 😊", enum=SomeEnum.Five, ) @@ -177,7 +177,7 @@ def test_simple_message(): assert retr.uvarint == 12345678910 assert retr.svarint == -12345678910 assert retr.bool is True - assert retr.bytes == b"\xDE\xAD\xCA\xFE" + assert retr.bytes == b"\xde\xad\xca\xfe" assert retr.unicode == "Příliš žluťoučký kůň úpěl ďábelské ódy 😊" assert retr.enum == SomeEnum.Five assert retr.enum == 5 diff --git a/python/tools/encfs_aes_getpass.py b/python/tools/encfs_aes_getpass.py index 139979e174..c22f9639cf 100755 --- a/python/tools/encfs_aes_getpass.py +++ b/python/tools/encfs_aes_getpass.py @@ -25,6 +25,7 @@ encfs --standard --extpass=./encfs_aes_getpass.py ~/.crypt ~/crypt """ import hashlib +import importlib.metadata import json import os import sys @@ -36,7 +37,8 @@ from trezorlib.client import TrezorClient from trezorlib.tools import Address from trezorlib.transport import enumerate_devices -version_tuple = tuple(map(int, trezorlib.__version__.split("."))) +trezor_version = importlib.metadata.version("trezor") +version_tuple = tuple(map(int, trezor_version.split("."))) if not (0, 11) <= version_tuple < (0, 14): raise RuntimeError("trezorlib version mismatch (required: 0.13, 0.12, or 0.11)") diff --git a/python/tox.ini b/python/tox.ini index 9e832836fa..b97120fafd 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -5,21 +5,17 @@ [tox] envlist = - py{38,39,310,311,312}-{minimal,default,full} - py{38,39,310,311,312}-click{7,80} - py{38,39,310,311,312}-click81 + py{39,310,311,312,313}-{minimal,default,full} + py{39,310,311,312,313}-click8{0,1} + py{310,311,312,313}-click82 [testenv] -deps = - -rrequirements.txt - !minimal: pytest>=3.6 - !minimal: pytest-random-order - !minimal: importlib-metadata!=0.21 - full: -rrequirements-optional.txt - py312: setuptools +runner = uv-venv-runner +dependency_groups = + !minimal: dev +extras = + full: full commands = - # Generate local files - python setup.py build # Working in the local directory, try to compile all bytecode python -m compileall src tests # Smoke-test trezorctl @@ -27,12 +23,10 @@ commands = # Run test suite !minimal: pytest -c setup.cfg --random-order tests -[testenv:py{38,39,310,311,312}-click{7,80,81}] -deps = - -rrequirements.txt - click7: click>=7,<8 - click80: click>=8.0,<8.1 - click81: click>=8.1,<8.2 +[testenv:py{39,310,311,312,313}-click{80,81,82}] commands = + click80: uv pip install "click>=8.0,<8.1" + click81: uv pip install "click>=8.1,<8.2" + click82: uv pip install "click>=8.2,<8.3" # Smoke-test trezorctl trezorctl --version diff --git a/tools/bump-version.py b/tools/bump-version.py index 9caaaeddc4..43bbecf2f0 100755 --- a/tools/bump-version.py +++ b/tools/bump-version.py @@ -8,7 +8,6 @@ import click VERSION_RE = re.compile(r"^(\d+)[.](\d+)[.](\d+)$") HEADER_LINE_RE = re.compile(r"^#define ([A-Z_]+) \S+$") -PYTHON_VERSION_RE = re.compile(r'^__version__ = "\d+[.]\d+[.]\d+"$', flags=re.MULTILINE) def bump_header(filename, **kwargs): @@ -29,14 +28,8 @@ def bump_header(filename, **kwargs): fh.write(line) -def bump_python(filename, new_version): - with open(filename, "r+") as fh: - contents = fh.read() - result = PYTHON_VERSION_RE.sub(f'__version__ = "{new_version}"', contents) - - fh.seek(0) - fh.truncate(0) - fh.write(result) +def bump_python(subdir: Path, new_version: str): + subprocess.check_call(["uv", "version", new_version], cwd=subdir) def hex_lit(version): @@ -89,9 +82,7 @@ def cli(project, version): VERSION_PATCH=patch, ) elif parts[-1] == "python": - bump_python( - project / "src" / "trezorlib" / "__init__.py", f"{major}.{minor}.{patch}" - ) + bump_python(project / "python", f"{major}.{minor}.{patch}") else: raise click.ClickException(f"Unknown project {project}.")