Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use cyclopts instead of click for CLI #10

Merged
merged 5 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 27 additions & 69 deletions audius/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,42 @@
import difflib
import re
from typing import Any
from typing import TYPE_CHECKING, Optional

import click
from cyclopts import App

from audius import Audius
from audius.cli.config import config
from audius.cli.options import player_option
from audius.cli.playlists import playlists
from audius.cli.tips import tips
from audius.cli.tracks import tracks
from audius.cli.users import users
from audius.cli.utils import sdk
from audius.cli.utils import print
from audius.client_factory import get_hosts
from audius.exceptions import AudiusException
from audius.sdk import Audius

if TYPE_CHECKING:
from audius.types import PlayerType

class AudiusCLI(click.Group):
def invoke(self, ctx) -> Any:
try:
return super().invoke(ctx)
except click.UsageError as err:
self._suggest_cmd(err)
except AudiusException as err:
raise click.ClickException(f"({type(err).__name__}) {err}") from err

@staticmethod
def _suggest_cmd(usage_error):
if usage_error.message is None:
raise usage_error
audius = App()
audius.command(config)
audius.command(playlists)
audius.command(tips)
audius.command(tracks)
audius.command(users)

match = re.match("No such command '(.*)'.", usage_error.message)
if not match:
raise usage_error

bad_arg = match.groups()[0]
suggested_commands = difflib.get_close_matches(
bad_arg, list(usage_error.ctx.command.commands.keys()), cutoff=0.6
)
if suggested_commands:
if bad_arg not in suggested_commands:
usage_error.message = (
f"No such command '{bad_arg}'. Did you mean {' or '.join(suggested_commands)}?"
)
@audius.command
def hosts():
"""
List available hosts
"""
all_hosts = list(get_hosts())
for host in all_hosts:
print(host)

raise usage_error


def create_cli(sdk_cls=Audius):
@click.group(cls=AudiusCLI)
def cli():
"Audius CLI"

@cli.command()
def hosts():
"""
List available hosts.
"""

all_hosts = list(get_hosts())
for host in all_hosts:
click.echo(host)

@cli.command()
@sdk.audius()
@click.argument("track_id", required=False)
@player_option()
def play(sdk, track_id, player):
"""
Play something from Audius.
"""
sdk.tracks.play(track_id, player=player)

cli.add_command(users(sdk_cls))
cli.add_command(playlists(sdk_cls))
cli.add_command(tracks(sdk_cls))
cli.add_command(tips(sdk_cls))
cli.add_command(config(sdk_cls))

return cli


audius = create_cli()
@audius.command
def play(track_id: Optional[str] = None, player: Optional["PlayerType"] = None):
"""
Play something from Audius
"""
sdk = Audius()
sdk.tracks.play(track_id, player=player)
33 changes: 11 additions & 22 deletions audius/cli/config.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
from typing import Type
from cyclopts import App

import click
from audius.cli.utils import print
from audius.sdk import Audius

from audius.cli.utils import sdk
config = App(name="config", help="Manage config")


def config(sdk_cls: Type):
sdk.py = sdk_cls

@click.group(name="config")
def cli():
"""
Show configuration.
"""

@cli.command()
@sdk.audius()
def app_name(sdk):
"""
Show the app name.
"""

click.echo(sdk.config.app_name)

return cli
@config.command
def app_name():
"""
Show the application name
"""
sdk = Audius()
print(sdk.config.app_name)
12 changes: 0 additions & 12 deletions audius/cli/options.py

This file was deleted.

121 changes: 60 additions & 61 deletions audius/cli/playlists.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,60 @@
from typing import Type

import click

from audius.cli.utils import sdk


def playlists(sdk_cls: Type):
sdk.py = sdk_cls

@click.group(name="playlists")
def cli():
"""
Manage playlists.
"""

@cli.command()
@sdk.audius()
def trending(sdk):
"""
Page through trending playlists.
"""

trending = list(sdk.playlists.trending())
gen = (f"{i + 1}: {x['playlist_name']} (id={x['id']})\n" for i, x in enumerate(trending))
click.echo_via_pager(gen)

@cli.command()
@sdk.audius()
@click.argument("playlist_id")
def get(sdk, playlist_id):
"""
Get a playlist.
"""

playlist = sdk.playlists.get(playlist_id)
_echo_playlist(playlist)

@cli.command()
@sdk.audius()
@click.argument("query")
def search(sdk, query):
"""
Search through playlists.
"""

result = sdk.playlists.search(query=query)
for idx, playlist in enumerate(result):
_echo_playlist(playlist)

if idx < len(result) - 1:
click.echo()

return cli


def _echo_playlist(playlist: dict):
click.echo(f"Playlist: {playlist['playlist_name']} (id={playlist['id']})")
click.echo(f"Description: {playlist['description']}")
click.echo(f"User: {playlist['user']['name']} (id={playlist['user']['id']})")
click.echo(f"Total plays: {playlist['total_play_count']}")
from cyclopts import App

from audius.cli.utils import print
from audius.sdk import Audius

playlists = App(name="playlists", help="View playlists")


@playlists.command
def trending(top: int = 10):
"""
Show top trending playlists

Args:
top (int): The number of playlists to show
"""
sdk = Audius()
count = 0
for idx, trending_playlist in enumerate(sdk.playlists.trending()):
print(f"{idx + 1}: {trending_playlist['playlist_name']} (id={trending_playlist['id']})")
count += 1
if count >= top:
break


@playlists.command
def get(playlist_id: str):
"""
Get a playlist

Args:
playlist_id (str): The playlist ID
"""
sdk = Audius()
playlist = sdk.playlists.get(playlist_id)
print_playlist(playlist)


@playlists.command()
def search(query: str = ""):
"""
Search through playlists

Args:
query (str): The search query
"""
sdk = Audius()
result = sdk.playlists.search(query=query)
for idx, playlist in enumerate(result):
print_playlist(playlist)

if idx < len(result) - 1:
print()


def print_playlist(playlist: dict):
print(f"Playlist: {playlist['playlist_name']} (id={playlist['id']})")
print(f"Description: {playlist['description']}")
print(f"User: {playlist['user']['name']} (id={playlist['user']['id']})")
print(f"Total plays: {playlist['total_play_count']}")
110 changes: 50 additions & 60 deletions audius/cli/tips.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,51 @@
from typing import Type

import click

from audius.cli.utils import sdk


def tips(sdk_cls: Type):
sdk.py = sdk_cls

@click.group(name="tips")
def cli():
"""
Checkout artist tips.
"""

@cli.command()
@sdk.audius()
@click.option("--offset", help="Number of tips to skip (for pagination).", type=int)
@click.option("--limit", help="Number of tips to fetch.", type=int)
@click.option("--user-id", help="The user making the request.")
@click.option(
"--receiver-min-followers", help="Exclude recipients without min followers.", type=int
from typing import Optional

from cyclopts import App

from audius.cli.utils import print
from audius.sdk import Audius

tips = App(name="tips", help="View tips")


@tips.command
def get(
offset: Optional[int] = None,
limit: Optional[int] = None,
user_id: Optional[str] = None,
receiver_min_followers: Optional[int] = None,
receiver_is_verified: bool = False,
current_user_follows: Optional[str] = None,
unique_by: Optional[str] = None,
) -> None:
"""
Get tips

Args:
offset (int | None): Number of tips to skip
limit (int | None): Number of tips to fetch
user_id (str | None): User ID
receiver_min_followers (int | None): Exclude recipients without min followers
receiver_is_verified (bool): Exclude non-verified recipients
current_user_follows (str | None): Query by who recipient follows
unique_by (str | None): Require involvement in the given capacity
"""
sdk = Audius()
result = sdk.tips.get(
offset=offset,
limit=limit,
user_id=user_id,
receiver_min_followers=receiver_min_followers,
receiver_is_verified=receiver_is_verified,
current_user_follows=current_user_follows,
unique_by=unique_by,
)
@click.option("--receiver-is-verified", help="Exclude non-verified recipients.", is_flag=True)
@click.option("--current-user-follows", help="Query by who recipient follows.")
@click.option("--unique-by", help="Require involvement in the given capacity.")
def get(
sdk,
offset,
limit,
user_id,
receiver_min_followers,
receiver_is_verified,
current_user_follows,
unique_by,
):
"""
Get tips.
"""

result = sdk.tips.get(
offset=offset,
limit=limit,
user_id=user_id,
receiver_min_followers=receiver_min_followers,
receiver_is_verified=receiver_is_verified,
current_user_follows=current_user_follows,
unique_by=unique_by,
)
if not result:
click.echo("No tips found.")

else:
for idx, tip in enumerate(result):
click.echo(
f"'{tip['sender']['name']}' tipped '{tip['amount']}' "
f"to '{tip['receiver']['name']}' on '{tip['created_at']}'."
)

return cli
if not result:
print("No tips found.")

else:
for idx, tip in enumerate(result):
print(
f"'{tip['sender']['name']}' tipped '{tip['amount']}' "
f"to '{tip['receiver']['name']}' on '{tip['created_at']}'."
)
Loading