From c7e01c77f22373b07dee982e1e55cbb52f207554 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Tue, 12 Dec 2023 09:45:57 +0100 Subject: [PATCH] Add --json option to tag commands --- changelog.yaml | 3 +- tests/integration/test_read.py | 33 ------------- tests/integration/test_tags.py | 84 ++++++++++++++++++++++++++++++++++ toot/api.py | 8 ++-- toot/cli/tags.py | 33 +++++++++---- toot/entities.py | 19 ++++++++ 6 files changed, 132 insertions(+), 48 deletions(-) create mode 100644 tests/integration/test_tags.py diff --git a/changelog.yaml b/changelog.yaml index 7f7854e..0816d17 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1,9 +1,10 @@ 0.40.0: date: TBA changes: + - "BREAKING: Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" - "Migrate to `click` for commandline arguments. BC should be mostly preserved, please report any issues." - "Add shell completion, see: https://toot.bezdomni.net/shell_completion.html" - - "Remove deprecated `--disable-https` option for `login` and `login_cli`, pass the base URL instead" + - "Add `--json` option to tag commands" 0.39.0: date: 2023-11-23 diff --git a/tests/integration/test_read.py b/tests/integration/test_read.py index 78cd231..6bd2bf0 100644 --- a/tests/integration/test_read.py +++ b/tests/integration/test_read.py @@ -130,39 +130,6 @@ def test_search_hashtag_json(app, user, run): assert h3["name"] == "hashtag_z" -def test_tags(run, base_url): - result = run(cli.tags_followed) - assert result.exit_code == 0 - assert result.stdout.strip() == "You're not following any hashtags." - - result = run(cli.tags_follow, "foo") - assert result.exit_code == 0 - assert result.stdout.strip() == "✓ You are now following #foo" - - result = run(cli.tags_followed) - assert result.exit_code == 0 - assert result.stdout.strip() == f"* #foo\t{base_url}/tags/foo" - - result = run(cli.tags_follow, "bar") - assert result.exit_code == 0 - assert result.stdout.strip() == "✓ You are now following #bar" - - result = run(cli.tags_followed) - assert result.exit_code == 0 - assert result.stdout.strip() == "\n".join([ - f"* #bar\t{base_url}/tags/bar", - f"* #foo\t{base_url}/tags/foo", - ]) - - result = run(cli.tags_unfollow, "foo") - assert result.exit_code == 0 - assert result.stdout.strip() == "✓ You are no longer following #foo" - - result = run(cli.tags_followed) - assert result.exit_code == 0 - assert result.stdout.strip() == f"* #bar\t{base_url}/tags/bar" - - def test_status(app, user, run): uuid = str(uuid4()) status_id = api.post_status(app, user, uuid).json()["id"] diff --git a/tests/integration/test_tags.py b/tests/integration/test_tags.py new file mode 100644 index 0000000..491d2c9 --- /dev/null +++ b/tests/integration/test_tags.py @@ -0,0 +1,84 @@ +from toot import cli +from toot.entities import Tag, from_dict, from_dict_list + + +def test_tags(run, base_url): + result = run(cli.tags) + assert result.exit_code == 0 + assert result.stdout.strip() == "You're not following any hashtags." + + result = run(cli.tags, "follow", "foo") + assert result.exit_code == 0 + assert result.stdout.strip() == "✓ You are now following #foo" + + result = run(cli.tags) + assert result.exit_code == 0 + assert result.stdout.strip() == f"* #foo\t{base_url}/tags/foo" + + result = run(cli.tags, "follow", "bar") + assert result.exit_code == 0 + assert result.stdout.strip() == "✓ You are now following #bar" + + result = run(cli.tags) + assert result.exit_code == 0 + assert result.stdout.strip() == "\n".join([ + f"* #bar\t{base_url}/tags/bar", + f"* #foo\t{base_url}/tags/foo", + ]) + + result = run(cli.tags, "unfollow", "foo") + assert result.exit_code == 0 + assert result.stdout.strip() == "✓ You are no longer following #foo" + + result = run(cli.tags) + assert result.exit_code == 0 + assert result.stdout.strip() == f"* #bar\t{base_url}/tags/bar" + + result = run(cli.tags, "unfollow", "bar") + assert result.exit_code == 0 + assert result.stdout.strip() == "✓ You are no longer following #bar" + + result = run(cli.tags) + assert result.exit_code == 0 + assert result.stdout.strip() == "You're not following any hashtags." + + +def test_tags_json(run_json): + result = run_json(cli.tags, "--json") + assert result == [] + + result = run_json(cli.tags, "follow", "foo", "--json") + tag = from_dict(Tag, result) + assert tag.name == "foo" + assert tag.following is True + + result = run_json(cli.tags, "--json") + [tag] = from_dict_list(Tag, result) + assert tag.name == "foo" + assert tag.following is True + + result = run_json(cli.tags, "follow", "bar", "--json") + tag = from_dict(Tag, result) + assert tag.name == "bar" + assert tag.following is True + + result = run_json(cli.tags, "--json") + tags = from_dict_list(Tag, result) + [bar, foo] = sorted(tags, key=lambda t: t.name) + assert foo.name == "foo" + assert foo.following is True + assert bar.name == "bar" + assert bar.following is True + + result = run_json(cli.tags, "unfollow", "foo", "--json") + tag = from_dict(Tag, result) + assert tag.name == "foo" + assert tag.following is False + + result = run_json(cli.tags, "unfollow", "bar", "--json") + tag = from_dict(Tag, result) + assert tag.name == "bar" + assert tag.following is False + + result = run_json(cli.tags, "--json") + assert result == [] diff --git a/toot/api.py b/toot/api.py index 151b53f..2a58f30 100644 --- a/toot/api.py +++ b/toot/api.py @@ -48,9 +48,9 @@ def _status_action(app, user, status_id, action, data=None) -> Response: return http.post(app, user, url, data=data) -def _tag_action(app, user, tag_name, action): +def _tag_action(app, user, tag_name, action) -> Response: url = f"/api/v1/tags/{tag_name}/{action}" - return http.post(app, user, url).json() + return http.post(app, user, url) def create_app(base_url): @@ -499,11 +499,11 @@ def unfollow(app, user, account): return _account_action(app, user, account, 'unfollow') -def follow_tag(app, user, tag_name): +def follow_tag(app, user, tag_name) -> Response: return _tag_action(app, user, tag_name, 'follow') -def unfollow_tag(app, user, tag_name): +def unfollow_tag(app, user, tag_name) -> Response: return _tag_action(app, user, tag_name, 'unfollow') diff --git a/toot/cli/tags.py b/toot/cli/tags.py index 5193541..40c0800 100644 --- a/toot/cli/tags.py +++ b/toot/cli/tags.py @@ -1,39 +1,52 @@ import click +import json as pyjson from toot import api -from toot.cli.base import cli, pass_context, Context +from toot.cli.base import cli, pass_context, json_option, Context from toot.output import print_tag_list, print_warning @cli.group(invoke_without_command=True) +@json_option @click.pass_context -def tags(ctx: click.Context): +def tags(ctx: click.Context, json): """List, follow, and unfollow tags When invoked without a command, lists followed tags.""" if ctx.invoked_subcommand is None: - response = api.followed_tags(ctx.obj.app, ctx.obj.user) - print_tag_list(response) + tags = api.followed_tags(ctx.obj.app, ctx.obj.user) + if json: + click.echo(pyjson.dumps(tags)) + else: + print_tag_list(tags) @tags.command() @click.argument("tag") +@json_option @pass_context -def follow(ctx: Context, tag: str): +def follow(ctx: Context, tag: str, json: bool): """Follow a hashtag""" tag = tag.lstrip("#") - api.follow_tag(ctx.app, ctx.user, tag) - click.secho(f"✓ You are now following #{tag}", fg="green") + response = api.follow_tag(ctx.app, ctx.user, tag) + if json: + click.echo(response.text) + else: + click.secho(f"✓ You are now following #{tag}", fg="green") @tags.command() @click.argument("tag") +@json_option @pass_context -def unfollow(ctx: Context, tag: str): +def unfollow(ctx: Context, tag: str, json: bool): """Unfollow a hashtag""" tag = tag.lstrip("#") - api.unfollow_tag(ctx.app, ctx.user, tag) - click.secho(f"✓ You are no longer following #{tag}", fg="green") + response = api.unfollow_tag(ctx.app, ctx.user, tag) + if json: + click.echo(response.text) + else: + click.secho(f"✓ You are no longer following #{tag}", fg="green") # -- Deprecated commands ------------------------------------------------------- diff --git a/toot/entities.py b/toot/entities.py index 8ef51e3..47d0cd7 100644 --- a/toot/entities.py +++ b/toot/entities.py @@ -409,6 +409,25 @@ class Relationship: note: str +@dataclass +class TagHistory: + day: str + uses: str + accounts: str + + +@dataclass +class Tag: + """ + Represents a hashtag used within the content of a status. + https://docs.joinmastodon.org/entities/Tag/ + """ + name: str + url: str + history: List[TagHistory] + following: Optional[bool] + + # Generic data class instance T = TypeVar("T")