Verified Commit b947f834 authored by Agate's avatar Agate 💬

Added playlists ls, create and rm

parent 162fd96c
Pipeline #4564 passed with stages
in 2 minutes and 20 seconds
......@@ -45,6 +45,7 @@ except ImportError:
else:
SSL_PROTOCOLS = (*SSL_PROTOCOLS, uvloop.loop.SSLProtocol)
def ignore_aiohttp_ssl_eror(loop):
"""Ignore aiohttp #3535 / cpython #13548 issue with SSL data after close
......@@ -73,15 +74,15 @@ def ignore_aiohttp_ssl_eror(loop):
"Fatal error on transport",
}:
# validate we have the right exception, transport and protocol
exception = context.get('exception')
protocol = context.get('protocol')
exception = context.get("exception")
protocol = context.get("protocol")
if (
isinstance(exception, ssl.SSLError)
and exception.reason == 'KRB5_S_INIT'
and exception.reason == "KRB5_S_INIT"
and isinstance(protocol, SSL_PROTOCOLS)
):
if loop.get_debug():
asyncio.log.logger.debug('Ignoring asyncio SSL KRB5_S_INIT error')
asyncio.log.logger.debug("Ignoring asyncio SSL KRB5_S_INIT error")
return
if orig_handler is not None:
orig_handler(loop, context)
......@@ -129,8 +130,10 @@ def async_command(f):
if _async_reraise:
raise
message = str(e)
if hasattr(e, 'status') and e.status == 401:
message = "Remote answered with {}, ensure your are logged in first".format(e.status)
if hasattr(e, "status") and e.status == 401:
message = "Remote answered with {}, ensure your are logged in first".format(
e.status
)
raise click.ClickException(message)
except (exceptions.FunkwhaleError) as e:
if _async_reraise:
......@@ -161,11 +164,13 @@ RAW_DECORATOR = click.option(
"--raw", is_flag=True, help="Directly output JSON returned by the happy"
)
class lazy_credential():
class lazy_credential:
"""
A proxy object to request access to the proxy object at the later possible point,
cf #4
"""
def __init__(self, *args):
self.args = args
self._cached_value = None
......@@ -177,9 +182,15 @@ class lazy_credential():
try:
v = keyring.get_password(*self.args)
except ValueError as e:
raise click.ClickException("Error while retrieving password from keyring: {}. Your password may be incorrect.".format(e.args[0]))
raise click.ClickException(
"Error while retrieving password from keyring: {}. Your password may be incorrect.".format(
e.args[0]
)
)
except Exception as e:
raise click.ClickException("Error while retrieving password from keyring: {}".format(e.args[0]))
raise click.ClickException(
"Error while retrieving password from keyring: {}".format(e.args[0])
)
self._cached_value = v
return v
......@@ -265,9 +276,15 @@ async def login(ctx, username, password):
try:
keyring.set_password(ctx.obj["SERVER_URL"], "_", token)
except ValueError as e:
raise click.ClickException("Error while retrieving password from keyring: {}. Your password may be incorrect.".format(e.args[0]))
raise click.ClickException(
"Error while retrieving password from keyring: {}. Your password may be incorrect.".format(
e.args[0]
)
)
except Exception as e:
raise click.ClickException("Error while retrieving password from keyring: {}".format(e.args[0]))
raise click.ClickException(
"Error while retrieving password from keyring: {}".format(e.args[0])
)
click.echo("Login successfull!")
......@@ -443,7 +460,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["next"]
page_count += 1
if raw:
click.echo(json.dumps(payload, sort_keys=True, indent=4))
......@@ -766,7 +783,9 @@ def flatten(d, parent_key="", sep="_"):
)
@click.pass_context
@async_command
async def track_download(ctx, id, format, directory, template, overwrite, ignore_errors, skip_existing):
async def track_download(
ctx, id, format, directory, template, overwrite, ignore_errors, skip_existing
):
async with ctx.obj["remote"]:
progressbar = tqdm.tqdm(id, unit="Files")
for i in progressbar:
......@@ -776,8 +795,8 @@ async def track_download(ctx, id, format, directory, template, overwrite, ignore
logs.logger.info("Downloading from {}".format(download_url))
filename_params = flatten(track_data)
filename_params["album"] = filename_params['album_title']
filename_params["artist"] = filename_params['artist_name']
filename_params["album"] = filename_params["album_title"]
filename_params["artist"] = filename_params["artist_name"]
filename_params["extension"] = format
filename_params["year"] = (
filename_params["album_release_date"][:4]
......@@ -792,7 +811,11 @@ async def track_download(ctx, id, format, directory, template, overwrite, ignore
full_path = os.path.join(directory, filename)
existing = os.path.exists(full_path)
if skip_existing and existing:
logs.logger.info("'{}' already exists on disk, skipping download".format(full_path))
logs.logger.info(
"'{}' already exists on disk, skipping download".format(
full_path
)
)
continue
elif not overwrite and existing:
raise click.ClickException(
......@@ -801,15 +824,25 @@ async def track_download(ctx, id, format, directory, template, overwrite, ignore
)
)
async with ctx.obj["remote"].request("get", download_url, timeout=0) as response:
async with ctx.obj["remote"].request(
"get", download_url, timeout=0
) as response:
try:
response.raise_for_status()
except aiohttp.ClientResponseError as e:
if response.status in ignore_errors:
logs.logger.warning("Remote answered with {} for url {}, skipping".format(response.status, download_url))
logs.logger.warning(
"Remote answered with {} for url {}, skipping".format(
response.status, download_url
)
)
continue
else:
raise click.ClickException("Remote answered with {} for url {}, exiting".format(response.status, download_url))
raise click.ClickException(
"Remote answered with {} for url {}, exiting".format(
response.status, download_url
)
)
if directory:
final_directory = os.path.dirname(full_path)
pathlib.Path(final_directory).mkdir(parents=True, exist_ok=True)
......@@ -930,7 +963,7 @@ async def favorites_tracks_create(ctx, id):
async with ctx.obj["remote"]:
for i in id:
data = {'track': i}
data = {"track": i}
async with ctx.obj["remote"].request(
"post", "api/v1/favorites/tracks/", data=data
) as response:
......@@ -947,7 +980,7 @@ async def favorites_tracks_rm(ctx, id):
async with ctx.obj["remote"]:
for i in id:
data = {'track': i}
data = {"track": i}
async with ctx.obj["remote"].request(
"delete", "api/v1/favorites/tracks/remove/", data=data
) as response:
......@@ -965,6 +998,60 @@ favorites_tracks_ls = get_ls_command( # noqa
)
@cli.group()
@click.pass_context
def playlists(ctx):
pass
playlists_ls = get_ls_command(
playlists,
"api/v1/playlists/",
output_conf={
"labels": [
"ID",
"Name",
"Visibility",
"Tracks Count",
"User",
"Created",
"Modified",
],
"type": "PLAYLIST",
},
)
playlists_rm = get_delete_command(playlists, "api/v1/playlists/{}/")
@playlists.command("create")
@click.option("--name", prompt=True)
@click.option(
"--visibility",
type=click.Choice(["me", "instance", "everyone"]),
default="me",
prompt=True,
)
@click.option("--raw", is_flag=True)
@click.pass_context
@async_command
async def playlists_create(ctx, raw, name, visibility):
async with ctx.obj["remote"]:
result = await ctx.obj["remote"].request(
"post", "api/v1/playlists/", data={"name": name, "visibility": visibility}
)
result.raise_for_status()
payload = await result.json()
if raw:
click.echo(json.dumps(payload, sort_keys=True, indent=4))
else:
click.echo("Playlist created:")
click.echo(
output.table(
[payload], ["ID", "Name", "Visibility", "Tracks Count"], type="PLAYLIST"
)
)
@cli.group()
@click.pass_context
......
......@@ -37,7 +37,6 @@ FIELDS = {
},
"LIBRARY": {
"Name": {"field": "name"},
"Visibility": {"field": "privacy_level"},
"Description": {"field": "description"},
"Uploads": {"field": "uploads_count"},
},
......@@ -47,6 +46,10 @@ FIELDS = {
"Artist": {"field": "track.artist.name"},
"Favorite Date": {"field": "creation_date"},
},
"PLAYLIST": {
"Tracks Count": {"field": "tracks_count"},
"User": {"field": "user.username"},
},
"USER": {
"Username": {"field": "username"},
"Federation ID": {"field": "full_username"},
......
......@@ -288,3 +288,52 @@ def test_albums_ls(cli_ctx, session, responses, get_requests):
requests = get_requests("get", url)
assert len(requests) == 1
def test_playlists_create(cli_ctx, session, responses, get_requests):
command = cli.playlists_create
url = "https://test.funkwhale/api/v1/playlists/"
responses.post(url)
command.callback(name="test", visibility="public", raw=False, _async_reraise=True)
requests = get_requests("post", url)
assert len(requests) == 1
assert requests[0].kwargs["data"] == {"name": "test", "visibility": "public"}
def test_playlists_ls(cli_ctx, session, responses, get_requests):
command = cli.playlists_ls
url = "https://test.funkwhale/api/v1/playlists/?ordering=-creation_date&page=1&page_size=5&q=hello"
responses.get(
url, payload={"results": [], "next": None, "previous": None, "count": 0}
)
command.callback(
raw=False,
page=1,
page_size=5,
ordering="-creation_date",
filter="favorites=true",
query=["hello"],
column=None,
format=None,
no_headers=False,
ids=False,
limit=1,
)
requests = get_requests("get", url)
assert len(requests) == 1
def test_playlists_rm(cli_ctx, session, responses, get_requests):
command = cli.playlists_rm
url = "https://test.funkwhale/api/v1/playlists/"
responses.delete(url + "1/")
responses.delete(url + "42/")
command.callback(id=[1, 42], raw=False, no_input=True, _async_reraise=True)
assert len(get_requests("delete", url + "1/")) == 1
assert len(get_requests("delete", url + "42/")) == 1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment