mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-17 19:00:58 +00:00
feat(python): unify option arguments in trezorctl
This commit is contained in:
parent
d7d228e716
commit
068e97c258
1
python/.changelog.d/2123.changed
Normal file
1
python/.changelog.d/2123.changed
Normal file
@ -0,0 +1 @@
|
|||||||
|
Unify boolean arguments/options in trezorlib commands to on/off
|
@ -150,3 +150,53 @@ def with_client(func: "Callable[Concatenate[TrezorClient, P], R]") -> "Callable[
|
|||||||
# the return type of @click.pass_obj is improperly specified and pyright doesn't
|
# the return type of @click.pass_obj is improperly specified and pyright doesn't
|
||||||
# understand that it converts f(obj, *args, **kwargs) to f(*args, **kwargs)
|
# understand that it converts f(obj, *args, **kwargs) to f(*args, **kwargs)
|
||||||
return trezorctl_command_with_client # type: ignore [cannot be assigned to return type]
|
return trezorctl_command_with_client # type: ignore [cannot be assigned to return type]
|
||||||
|
|
||||||
|
|
||||||
|
class AliasedGroup(click.Group):
|
||||||
|
"""Command group that handles aliases and Click 6.x compatibility.
|
||||||
|
|
||||||
|
Click 7.0 silently switched all underscore_commands to dash-commands.
|
||||||
|
This implementation of `click.Group` responds to underscore_commands by invoking
|
||||||
|
the respective dash-command.
|
||||||
|
|
||||||
|
Supply an `aliases` dict at construction time to provide an alternative list of
|
||||||
|
command names:
|
||||||
|
|
||||||
|
>>> @click.command(cls=AliasedGroup, aliases={"do_bar", do_foo})
|
||||||
|
>>> def cli():
|
||||||
|
>>> ...
|
||||||
|
|
||||||
|
If these commands are not known at the construction time, they can be set later:
|
||||||
|
|
||||||
|
>>> @click.command(cls=AliasedGroup)
|
||||||
|
>>> def cli():
|
||||||
|
>>> ...
|
||||||
|
>>>
|
||||||
|
>>> @cli.command()
|
||||||
|
>>> def do_foo():
|
||||||
|
>>> ...
|
||||||
|
>>>
|
||||||
|
>>> cli.aliases={"do_bar", do_foo}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
aliases: Optional[Dict[str, click.Command]] = None,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.aliases = aliases or {}
|
||||||
|
|
||||||
|
def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]:
|
||||||
|
cmd_name = cmd_name.replace("_", "-")
|
||||||
|
# try to look up the real name
|
||||||
|
cmd = super().get_command(ctx, cmd_name)
|
||||||
|
if cmd:
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
# look for a backwards compatibility alias
|
||||||
|
if cmd_name in self.aliases:
|
||||||
|
return self.aliases[cmd_name]
|
||||||
|
|
||||||
|
return None
|
||||||
|
@ -39,8 +39,8 @@ BACKUP_TYPE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SD_PROTECT_OPERATIONS = {
|
SD_PROTECT_OPERATIONS = {
|
||||||
"enable": messages.SdProtectOperationType.ENABLE,
|
"on": messages.SdProtectOperationType.ENABLE,
|
||||||
"disable": messages.SdProtectOperationType.DISABLE,
|
"off": messages.SdProtectOperationType.DISABLE,
|
||||||
"refresh": messages.SdProtectOperationType.REFRESH,
|
"refresh": messages.SdProtectOperationType.REFRESH,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +259,8 @@ def sd_protect(
|
|||||||
device. The options are:
|
device. The options are:
|
||||||
|
|
||||||
\b
|
\b
|
||||||
enable - Generate SD card secret and use it to protect the PIN and storage.
|
on - Generate SD card secret and use it to protect the PIN and storage.
|
||||||
disable - Remove SD card secret protection.
|
off - Remove SD card secret protection.
|
||||||
refresh - Replace the current SD card secret with a new one.
|
refresh - Replace the current SD card secret with a new one.
|
||||||
"""
|
"""
|
||||||
if client.features.model == "1":
|
if client.features.model == "1":
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
# You should have received a copy of the License along with this library.
|
# 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>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, cast
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from .. import device, firmware, messages, toif
|
from .. import device, firmware, messages, toif
|
||||||
from . import ChoiceType, with_client
|
from . import AliasedGroup, ChoiceType, with_client
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..client import TrezorClient
|
from ..client import TrezorClient
|
||||||
@ -89,30 +89,49 @@ def image_to_tt(filename: str) -> bytes:
|
|||||||
return toif_image.to_bytes()
|
return toif_image.to_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
def _should_remove(enable: Optional[bool], remove: bool) -> bool:
|
||||||
|
"""Helper to decide whether to remove something or not.
|
||||||
|
|
||||||
|
Needed for backwards compatibility purposes, so we can support
|
||||||
|
both positive (enable) and negative (remove) args.
|
||||||
|
"""
|
||||||
|
if remove and enable:
|
||||||
|
raise click.ClickException("Argument and option contradict each other")
|
||||||
|
|
||||||
|
if remove or enable is False:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@click.group(name="set")
|
@click.group(name="set")
|
||||||
def cli() -> None:
|
def cli() -> None:
|
||||||
"""Device settings."""
|
"""Device settings."""
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option("-r", "--remove", is_flag=True)
|
@click.option("-r", "--remove", is_flag=True, hidden=True)
|
||||||
|
@click.argument("enable", type=ChoiceType({"on": True, "off": False}), required=False)
|
||||||
@with_client
|
@with_client
|
||||||
def pin(client: "TrezorClient", remove: bool) -> str:
|
def pin(client: "TrezorClient", enable: Optional[bool], remove: bool) -> str:
|
||||||
"""Set, change or remove PIN."""
|
"""Set, change or remove PIN."""
|
||||||
return device.change_pin(client, remove)
|
# Remove argument is there for backwards compatibility
|
||||||
|
return device.change_pin(client, remove=_should_remove(enable, remove))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option("-r", "--remove", is_flag=True)
|
@click.option("-r", "--remove", is_flag=True, hidden=True)
|
||||||
|
@click.argument("enable", type=ChoiceType({"on": True, "off": False}), required=False)
|
||||||
@with_client
|
@with_client
|
||||||
def wipe_code(client: "TrezorClient", remove: bool) -> str:
|
def wipe_code(client: "TrezorClient", enable: Optional[bool], remove: bool) -> str:
|
||||||
"""Set or remove the wipe code.
|
"""Set or remove the wipe code.
|
||||||
|
|
||||||
The wipe code functions as a "self-destruct PIN". If the wipe code is ever
|
The wipe code functions as a "self-destruct PIN". If the wipe code is ever
|
||||||
entered into any PIN entry dialog, then all private data will be immediately
|
entered into any PIN entry dialog, then all private data will be immediately
|
||||||
removed and the device will be reset to factory defaults.
|
removed and the device will be reset to factory defaults.
|
||||||
"""
|
"""
|
||||||
return device.change_wipe_code(client, remove)
|
# Remove argument is there for backwards compatibility
|
||||||
|
return device.change_wipe_code(client, remove=_should_remove(enable, remove))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -233,25 +252,39 @@ def experimental_features(client: "TrezorClient", enable: bool) -> str:
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@cli.group()
|
# Using special class AliasedGroup, so we can support multiple commands
|
||||||
def passphrase() -> None:
|
# to invoke the same function to keep backwards compatibility
|
||||||
|
@cli.command(cls=AliasedGroup, name="passphrase")
|
||||||
|
def passphrase_main() -> None:
|
||||||
"""Enable, disable or configure passphrase protection."""
|
"""Enable, disable or configure passphrase protection."""
|
||||||
# this exists in order to support command aliases for "enable-passphrase"
|
# this exists in order to support command aliases for "enable-passphrase"
|
||||||
# and "disable-passphrase". Otherwise `passphrase` would just take an argument.
|
# and "disable-passphrase". Otherwise `passphrase` would just take an argument.
|
||||||
|
|
||||||
|
|
||||||
@passphrase.command(name="enabled")
|
# Cast for type-checking purposes
|
||||||
|
passphrase = cast(AliasedGroup, passphrase_main)
|
||||||
|
|
||||||
|
|
||||||
|
@passphrase.command(name="on")
|
||||||
@click.option("-f/-F", "--force-on-device/--no-force-on-device", default=None)
|
@click.option("-f/-F", "--force-on-device/--no-force-on-device", default=None)
|
||||||
@with_client
|
@with_client
|
||||||
def passphrase_enable(client: "TrezorClient", force_on_device: Optional[bool]) -> str:
|
def passphrase_on(client: "TrezorClient", force_on_device: Optional[bool]) -> str:
|
||||||
"""Enable passphrase."""
|
"""Enable passphrase."""
|
||||||
return device.apply_settings(
|
return device.apply_settings(
|
||||||
client, use_passphrase=True, passphrase_always_on_device=force_on_device
|
client, use_passphrase=True, passphrase_always_on_device=force_on_device
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@passphrase.command(name="disabled")
|
@passphrase.command(name="off")
|
||||||
@with_client
|
@with_client
|
||||||
def passphrase_disable(client: "TrezorClient") -> str:
|
def passphrase_off(client: "TrezorClient") -> str:
|
||||||
"""Disable passphrase."""
|
"""Disable passphrase."""
|
||||||
return device.apply_settings(client, use_passphrase=False)
|
return device.apply_settings(client, use_passphrase=False)
|
||||||
|
|
||||||
|
|
||||||
|
# Registering the aliases for backwards compatibility
|
||||||
|
# (these are not shown in --help docs)
|
||||||
|
passphrase.aliases = {
|
||||||
|
"enabled": passphrase_on,
|
||||||
|
"disabled": passphrase_off,
|
||||||
|
}
|
||||||
|
@ -29,6 +29,7 @@ from ..client import TrezorClient
|
|||||||
from ..transport import enumerate_devices
|
from ..transport import enumerate_devices
|
||||||
from ..transport.udp import UdpTransport
|
from ..transport.udp import UdpTransport
|
||||||
from . import (
|
from . import (
|
||||||
|
AliasedGroup,
|
||||||
TrezorConnection,
|
TrezorConnection,
|
||||||
binance,
|
binance,
|
||||||
btc,
|
btc,
|
||||||
@ -57,8 +58,8 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
COMMAND_ALIASES = {
|
COMMAND_ALIASES = {
|
||||||
"change-pin": settings.pin,
|
"change-pin": settings.pin,
|
||||||
"enable-passphrase": settings.passphrase_enable,
|
"enable-passphrase": settings.passphrase_on,
|
||||||
"disable-passphrase": settings.passphrase_disable,
|
"disable-passphrase": settings.passphrase_off,
|
||||||
"wipe-device": device.wipe,
|
"wipe-device": device.wipe,
|
||||||
"reset-device": device.setup,
|
"reset-device": device.setup,
|
||||||
"recovery-device": device.recover,
|
"recovery-device": device.recover,
|
||||||
@ -86,16 +87,9 @@ COMMAND_ALIASES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TrezorctlGroup(click.Group):
|
class TrezorctlGroup(AliasedGroup):
|
||||||
"""Command group that handles compatibility for trezorctl.
|
"""Command group that handles compatibility for trezorctl.
|
||||||
|
|
||||||
The purpose is twofold: convert underscores to dashes, and ensure old-style commands
|
|
||||||
still work with new-style groups.
|
|
||||||
|
|
||||||
Click 7.0 silently switched all underscore_commands to dash-commands.
|
|
||||||
This implementation of `click.Group` responds to underscore_commands by invoking
|
|
||||||
the respective dash-command.
|
|
||||||
|
|
||||||
With trezorctl 0.11.5, we started to convert old-style long commands
|
With trezorctl 0.11.5, we started to convert old-style long commands
|
||||||
(such as "binance-sign-tx") to command groups ("binance") with subcommands
|
(such as "binance-sign-tx") to command groups ("binance") with subcommands
|
||||||
("sign-tx"). The `TrezorctlGroup` can perform subcommand lookup: if a command
|
("sign-tx"). The `TrezorctlGroup` can perform subcommand lookup: if a command
|
||||||
@ -104,16 +98,12 @@ class TrezorctlGroup(click.Group):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]:
|
def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]:
|
||||||
cmd_name = cmd_name.replace("_", "-")
|
|
||||||
# try to look up the real name
|
|
||||||
cmd = super().get_command(ctx, cmd_name)
|
cmd = super().get_command(ctx, cmd_name)
|
||||||
if cmd:
|
if cmd:
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
# look for a backwards compatibility alias
|
# the subsequent lookups rely on dash-separated command names
|
||||||
if cmd_name in COMMAND_ALIASES:
|
cmd_name = cmd_name.replace("_", "-")
|
||||||
return COMMAND_ALIASES[cmd_name]
|
|
||||||
|
|
||||||
# look for subcommand in btc - "sign-tx" is now "btc sign-tx"
|
# look for subcommand in btc - "sign-tx" is now "btc sign-tx"
|
||||||
cmd = btc.cli.get_command(ctx, cmd_name)
|
cmd = btc.cli.get_command(ctx, cmd_name)
|
||||||
if cmd:
|
if cmd:
|
||||||
@ -138,7 +128,11 @@ def configure_logging(verbose: int) -> None:
|
|||||||
log.OMITTED_MESSAGES.add(messages.Features)
|
log.OMITTED_MESSAGES.add(messages.Features)
|
||||||
|
|
||||||
|
|
||||||
@click.command(cls=TrezorctlGroup, context_settings={"max_content_width": 400})
|
@click.command(
|
||||||
|
cls=TrezorctlGroup,
|
||||||
|
context_settings={"max_content_width": 400},
|
||||||
|
aliases=COMMAND_ALIASES,
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-p",
|
"-p",
|
||||||
"--path",
|
"--path",
|
||||||
|
Loading…
Reference in New Issue
Block a user