From d6c60983c41dc4e122974e4f019550ea8c9c8728 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Tue, 9 Jul 2019 23:28:47 +0200 Subject: [PATCH] Added playlist track listing --- funkwhale_cli/cli.py | 144 ++++++++++++++++++++++++++++++---------- funkwhale_cli/output.py | 6 ++ tests/test_cli.py | 25 +++++++ 3 files changed, 139 insertions(+), 36 deletions(-) diff --git a/funkwhale_cli/cli.py b/funkwhale_cli/cli.py index 2feefef..39eb281 100644 --- a/funkwhale_cli/cli.py +++ b/funkwhale_cli/cli.py @@ -92,6 +92,10 @@ def ignore_aiohttp_ssl_eror(loop): loop.set_exception_handler(ignore_ssl_error) +def noop_decorator(f): + return f + + def URL(v): if v is NOOP: raise click.ClickException( @@ -385,25 +389,57 @@ def get_pagination_data(payload): return data -def get_ls_command(group, endpoint, output_conf): +def get_ls_command( + group, + endpoint, + output_conf, + pagination=True, + filter=True, + ordering=True, + with_id=False, + name="ls", +): available_fields = sorted( set(output_conf["labels"]) | set(output.FIELDS["*"].keys()) ) + id_decorator = click.argument("id") if with_id else noop_decorator + page_decorator = ( + click.option("--page", "-p", type=click.INT, default=1) + if pagination + else noop_decorator + ) + page_size_decorator = ( + click.option("--page-size", "-s", type=click.INT, default=None) + if pagination + else noop_decorator + ) + limit_decorator = ( + click.option("--limit", "-l", type=click.INT, default=1) + if pagination + else noop_decorator + ) + ordering_decorator = ( + click.option("--ordering", "-o", default=None) if ordering else noop_decorator + ) + filter_decorator = ( + click.option("--filter", "-f", multiple=True) if filter else noop_decorator + ) - @group.command("ls") + @group.command(name) + @id_decorator @click.argument("query", nargs=-1) @RAW_DECORATOR @click.option( "--format", "-t", type=click.Choice(output.TABLE_FORMATS), default="simple" ) @click.option("--no-headers", "-h", is_flag=True, default=False) - @click.option("--page", "-p", type=click.INT, default=1) - @click.option("--page-size", "-s", type=click.INT, default=None) - @click.option("--ordering", "-o", default=None) - @click.option("--filter", "-f", multiple=True) @click.option("--ids", "-i", is_flag=True) - @click.option("--limit", "-l", type=click.INT, default=1) + @page_decorator + @page_size_decorator + @ordering_decorator + @filter_decorator + @limit_decorator @click.option( "--column", "-c", @@ -414,33 +450,32 @@ def get_ls_command(group, endpoint, output_conf): ) @click.pass_context @async_command - async def ls( - ctx, - raw, - page, - page_size, - ordering, - filter, - query, - column, - format, - no_headers, - ids, - limit, - ): + async def ls(ctx, raw, column, format, no_headers, ids, **kwargs): + id = kwargs.get("id") + limit = kwargs.get("limit") + page = kwargs.get("page") + page_size = kwargs.get("page_size") + ordering = kwargs.get("ordering") + filter = kwargs.get("filter") + query = kwargs.get("query") if ids: no_headers = True column = [output_conf.get("id_field", "UUID")] format = "plain" + base_url = endpoint + if with_id: + base_url = base_url.format(id) next_page_url = None page_count = 0 while True: if limit and page_count >= limit: break async with ctx.obj["remote"]: - if page_count == 0: - url = endpoint - params = {"page": page} + if not pagination or page_count == 0: + url = base_url + params = {} + if page: + params["page"] = page if page_size: params["page_size"] = page_size if ordering: @@ -460,7 +495,7 @@ def get_ls_command(group, endpoint, output_conf): result = await ctx.obj["remote"].request("get", url, params=params) result.raise_for_status() payload = await result.json() - next_page_url = payload["next"] + next_page_url = payload.get("next") page_count += 1 if raw: click.echo(json.dumps(payload, sort_keys=True, indent=4)) @@ -474,6 +509,8 @@ def get_ls_command(group, endpoint, output_conf): headers=not no_headers, ) ) + if not pagination: + break pagination_data = get_pagination_data(payload) if pagination_data["page_size"]: start = ( @@ -499,17 +536,20 @@ def get_ls_command(group, endpoint, output_conf): return ls -def get_show_command(group, url_template, output_conf, name='show', force_id=None): +def get_show_command(group, url_template, output_conf, name="show", force_id=None): available_fields = sorted( set(output_conf["labels"]) | set(output.FIELDS["*"].keys()) ) if force_id: + def id_decorator(f): @functools.wraps(f) def inner(raw, column, format): return f(raw, force_id, column, format) + return inner + else: id_decorator = click.argument("id") @@ -529,16 +569,12 @@ def get_show_command(group, url_template, output_conf, name='show', force_id=Non ) @click.pass_context @async_command - async def show( - ctx, - raw, - id, - column, - format, - ): + async def show(ctx, raw, id, column, format): async with ctx.obj["remote"]: - async with ctx.obj["remote"].request("get", url_template.format(id)) as result: + async with ctx.obj["remote"].request( + "get", url_template.format(id) + ) as result: result.raise_for_status() payload = await result.json() if raw: @@ -1052,6 +1088,42 @@ async def playlists_create(ctx, raw, name, visibility): ) +@playlists.command("tracks-add") +@click.argument("id") +@click.argument("track", nargs=-1) +@click.option( + "--no-duplicates", + is_flag=True, + default=False, + help="Prevent insertion of tracks that already exist in the playlist", +) +@click.pass_context +@async_command +async def playlists_tracks_add(ctx, id, track, no_duplicates): + async with ctx.obj["remote"]: + async with ctx.obj["remote"].request( + "post", + "api/v1/playlists/{}/".format(id), + data={"tracks": track, "allow_duplicates": not no_duplicates}, + ) as response: + response.raise_for_status() + + +playlists_tracks = get_ls_command( + playlists, + "api/v1/playlists/{}/tracks/", + name="tracks", + with_id=True, + pagination=False, + ordering=False, + filter=False, + output_conf={ + "labels": ["Position", "ID", "Title", "Artist", "Album", "Created"], + "id_field": "ID", + "type": "PLAYLIST_TRACK", + }, +) + @cli.group() @click.pass_context @@ -1077,8 +1149,8 @@ users_me = get_show_command( ], "type": "USER", }, - force_id='me', - name='me', + force_id="me", + name="me", ) diff --git a/funkwhale_cli/output.py b/funkwhale_cli/output.py index 21ab2d5..0616153 100644 --- a/funkwhale_cli/output.py +++ b/funkwhale_cli/output.py @@ -50,6 +50,12 @@ FIELDS = { "Tracks Count": {"field": "tracks_count"}, "User": {"field": "user.username"}, }, + "PLAYLIST_TRACK": { + "Position": {"field": "index"}, + "Title": {"field": "track.title"}, + "Artist": {"field": "track.artist.name"}, + "Album": {"field": "track.album.title"}, + }, "USER": { "Username": {"field": "username"}, "Federation ID": {"field": "full_username"}, diff --git a/tests/test_cli.py b/tests/test_cli.py index bcf8764..4f0a1f9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -337,3 +337,28 @@ def test_playlists_rm(cli_ctx, session, responses, get_requests): assert len(get_requests("delete", url + "1/")) == 1 assert len(get_requests("delete", url + "42/")) == 1 + + +def test_playlists_tracks_add(cli_ctx, session, responses, get_requests): + command = cli.playlists_tracks_add + url = "https://test.funkwhale/api/v1/playlists/66/" + responses.post(url) + + command.callback(id=66, track=[1, 42], no_duplicates=True, _async_reraise=True) + + requests = get_requests("post", url) + assert len(requests) == 1 + assert requests[0].kwargs["data"] == {"tracks": [1, 42], "allow_duplicates": False} + + +def test_playlists_tracks(cli_ctx, session, responses, get_requests): + command = cli.playlists_tracks + url = "https://test.funkwhale/api/v1/playlists/66/tracks/" + responses.get(url, payload={"results": [], "count": 0}) + + command.callback( + id=66, raw=False, column=None, format=None, no_headers=False, ids=False + ) + + requests = get_requests("get", url) + assert len(requests) == 1 -- GitLab