Skip to content

Commit

Permalink
migrate from typer to cyclopts
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianPugh committed Dec 5, 2023
1 parent 788afb5 commit c6a7995
Show file tree
Hide file tree
Showing 33 changed files with 927 additions and 895 deletions.
21 changes: 6 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: "v0.0.284"
rev: "v0.1.7"
hooks:
- id: ruff
args: []
exclude: ^(belay/snippets/)

- repo: https://github.com/psf/black
rev: 23.7.0
rev: 23.11.0
hooks:
- id: black
args:
Expand All @@ -20,13 +20,13 @@ repos:
exclude: ^(belay/snippets/)

- repo: https://github.com/asottile/blacken-docs
rev: 1.15.0
rev: 1.16.0
hooks:
- id: blacken-docs
exclude: ^(docs/source/How Belay Works.rst)

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-ast
Expand All @@ -53,21 +53,12 @@ repos:
exclude: \.(html|svg)$

- repo: https://github.com/fredrikaverpil/creosote.git
rev: v2.6.3
rev: v3.0.0
hooks:
- id: creosote
args:
- "--venv=.venv"
- "--paths=belay"
- "--deps-file=pyproject.toml"
- "--sections=tool.poetry.dependencies"
- "--exclude-deps"
- "importlib_resources"
- "pydantic"

- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
rev: v2.2.6
hooks:
- id: codespell
exclude: ^(poetry.lock)
args: ["-L", "ser,"]
13 changes: 12 additions & 1 deletion belay/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
from .main import app
from belay.cli._cache import cache_app
from belay.cli._clean import clean
from belay.cli._exec import exec
from belay.cli._info import info
from belay.cli._install import install
from belay.cli._new import new
from belay.cli._run import run
from belay.cli._select import select
from belay.cli._sync import sync
from belay.cli._terminal import terminal
from belay.cli._update import update
from belay.cli.main import app
52 changes: 31 additions & 21 deletions belay/cli/cache.py → belay/cli/_cache.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
import builtins
import contextlib
import shutil

import questionary
from cyclopts import App, Parameter
from typing_extensions import Annotated

with contextlib.suppress(ImportError):
import readline
import shutil

import typer
from typer import Argument, Option, Typer

from belay.cli.main import app
from belay.project import find_cache_folder

app = Typer(no_args_is_help=True, help="Perform action's on Belay's cache.")
app.command(cache_app := App(name="cache", help="Perform action's on Belay's cache."))


@app.command()
@cache_app.command()
def clear(
prefix: str = Argument("", help="Clear all caches that start with this."),
yes: bool = Option(
False,
"--yes",
"-y",
help='Automatically answer "yes" to all confirmation prompts.',
),
all: bool = Option(False, "--all", "-a", help="Clear all caches."),
prefix: str = "",
*,
yes: Annotated[bool, Parameter(name=["--yes", "-y"])] = False,
all_: Annotated[bool, Parameter(name=["--all", "-a"])] = False,
):
"""Clear cache."""
if (not prefix and not all) or (prefix and all):
"""Clear cache.
Parameters
----------
prefix: str
Clear all caches that start with this.
yes: bool
Skip interactive prompts confirming clear action.
all_: bool
Clear all caches.
"""
if (not prefix and not all_) or (prefix and all_):
print('Either provide a prefix OR set the "--all" flag.')
raise typer.Exit()
return 1

cache_folder = find_cache_folder()

Expand All @@ -37,13 +45,15 @@ def clear(

if not cache_paths:
print(f'No caches found starting with "{prefix}"')
raise typer.Exit()
return 0

if not yes:
print("Found caches:")
for cache_name in cache_names:
print(f" • {cache_name}")
typer.confirm("Clear these caches?", abort=True)
confirmed = questionary.confirm("Clear these caches?").ask()
if not confirmed:
return 0

for path in cache_paths:
if path.is_file():
Expand All @@ -52,7 +62,7 @@ def clear(
shutil.rmtree(path)


@app.command()
@cache_app.command()
def list():
"""List cache elements."""
cache_folder = find_cache_folder()
Expand All @@ -62,7 +72,7 @@ def list():
print(item)


@app.command()
@cache_app.command()
def info():
"""Display cache location and size."""
cache_folder = find_cache_folder()
Expand Down
2 changes: 2 additions & 0 deletions belay/cli/clean.py → belay/cli/_clean.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import shutil

from belay.cli.main import app
from belay.project import find_dependencies_folder, load_groups


@app.command
def clean():
"""Remove any downloaded dependencies if they are no longer specified in pyproject."""
groups = load_groups()
Expand Down
25 changes: 25 additions & 0 deletions belay/cli/_exec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Optional

from belay import Device
from belay.cli.common import remove_stacktrace
from belay.cli.main import app


@app.command
def exec(port: str, statement: str, *, password: Optional[str] = None):
"""Execute python statement on-device.
Parameters
----------
port: str
Port (like /dev/ttyUSB0) or WebSocket (like ws://192.168.1.100) of device.
statement: str
Statement to execute on-device.
password: Optional[str]
Password for communication methods (like WebREPL) that require authentication.
"""
kwargs = {}
if password is not None:
kwargs["password"] = password
with Device(port, **kwargs) as device, remove_stacktrace():
device(statement)
27 changes: 27 additions & 0 deletions belay/cli/_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Optional

from belay import Device
from belay.cli.main import app


@app.command
def info(
port: str,
*,
password: Optional[str] = None,
):
"""Display device firmware information.
Parameters
----------
port: str
Port (like /dev/ttyUSB0) or WebSocket (like ws://192.168.1.100) of device.
password: Optional[str]
Password for communication methods (like WebREPL) that require authentication.
"""
kwargs = {}
if password is not None:
kwargs["password"] = password
with Device(port, **kwargs) as device:
version_str = "v" + ".".join(str(x) for x in device.implementation.version)
print(f"{device.implementation.name} {version_str} - {device.implementation.platform}")
51 changes: 39 additions & 12 deletions belay/cli/install.py → belay/cli/_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,63 @@
from tempfile import TemporaryDirectory
from typing import List, Optional

from cyclopts import Parameter
from rich.progress import Progress
from typer import Argument, Option
from typing_extensions import Annotated

from belay import Device
from belay.cli.common import help_password, help_port, remove_stacktrace
from belay.cli.sync import sync_device as _sync_device
from belay.cli._sync import sync_device as _sync_device
from belay.cli.common import remove_stacktrace
from belay.cli.main import app
from belay.project import find_project_folder, load_groups, load_pyproject


@app.command
def install(
port: str = Argument(..., help=help_port),
password: str = Option("", help=help_password),
mpy_cross_binary: Optional[Path] = Option(None, help="Compile py files with this executable."),
run: Optional[Path] = Option(None, help="Run script on-device after installing."),
main: Optional[Path] = Option(None, help="Sync script to /main.py after installing."),
with_groups: List[str] = Option(None, "--with", help="Include specified optional dependency group."),
follow: bool = Option(False, "--follow", "-f", help="Follow the stdout after upload."),
port: str,
*,
password: Optional[str] = None,
mpy_cross_binary: Optional[Path] = None,
run: Optional[Path] = None,
main: Optional[Path] = None,
with_groups: Annotated[Optional[List[str]], Parameter(name="--with")] = None,
follow: Annotated[bool, Parameter(name=["--follow", "-f"])] = False,
):
"""Sync dependencies and project itself to device."""
"""Sync dependencies and project itself to device.
Parameters
----------
port: str
Port (like /dev/ttyUSB0) or WebSocket (like ws://192.168.1.100) of device.
password: Optional[str]
Password for communication methods (like WebREPL) that require authentication.
mpy_cross_binary: Optional[Path]
Compile py files with this executable.
run: Optional[Path]
Run script on-device after installing.
main: Optional[Path]
Sync script to /main.py after installing.
with_groups: List[str]
Include specified optional dependency group.
follow: bool
Follow the stdout after upload.
"""
kwargs = {}
if run and run.suffix != ".py":
raise ValueError("Run script MUST be a python file.")
if main and main.suffix != ".py":
raise ValueError("Main script MUST be a python file.")
if with_groups is None:
with_groups = []
if password is not None:
kwargs["password"] = password

config = load_pyproject()
project_folder = find_project_folder()
project_package = config.name
groups = load_groups()

with Device(port, password=password) as device:
with Device(port, **kwargs) as device:
sync_device = partial(
_sync_device,
device,
Expand Down
14 changes: 11 additions & 3 deletions belay/cli/new.py → belay/cli/_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@
from pathlib import Path

from packaging.utils import canonicalize_name
from typer import Argument, Option

if sys.version_info < (3, 9, 0):
import importlib_resources
else:
import importlib.resources as importlib_resources

from belay.cli.main import app

def new(project_name: str = Argument(..., help="Project Name.")):
"""Create a new micropython project structure."""

@app.command
def new(project_name: str):
"""Create a new micropython project structure.
Parameters
----------
project_name: str
New project name.
"""
package_name = canonicalize_name(project_name)
dst_dir = Path() / project_name
template_dir = importlib_resources.files("belay") / "cli" / "new_template"
Expand Down
43 changes: 43 additions & 0 deletions belay/cli/_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from pathlib import Path
from typing import Optional

from belay import Device
from belay.cli.common import remove_stacktrace
from belay.cli.main import app


@app.command
def run(
port: str,
file: Path,
*,
password: Optional[str] = None,
):
"""Run file on-device.
If the first argument, ``port``, is resolvable to an executable,
the remainder of the command will be interpreted as a shell command
that will be executed in a pseudo-micropython-virtual-environment.
As of right now, this just sets ``MICROPYPATH`` to all of the dependency
groups' folders.
.. code-block:: console
$ belay run micropython -m unittest
Parameters
----------
port: str
Port (like /dev/ttyUSB0) or WebSocket (like ws://192.168.1.100) of device.
file: Path
File to run on-device.
password: Optional[str]
Password for communication methods (like WebREPL) that require authentication.
"""
kwargs = {}
if password is not None:
kwargs["password"] = password

content = file.read_text()
with Device(port, **kwargs) as device, remove_stacktrace():
device(content)
Loading

0 comments on commit c6a7995

Please sign in to comment.