From 6f8f96d5b671f6b4f193b4bc203a9f1580075111 Mon Sep 17 00:00:00 2001 From: Giacomo Leidi Date: Wed, 1 Dec 2021 01:08:37 +0100 Subject: [PATCH] cli: inspect: Refactor command. Now the inspect command can display informations about different kind of objects including events and publications. This patch also changes the cli to output columnar values suitable for further processing with standard Unix tool, such as awk. $ mobilizon-reshare.sh inspect publication -s completed | awk '{ print }' | sort | uniq -c [2021-12-01 01:05:55,321] [20] [INFO] Tortoise-ORM shutdown 2 mastodon 2 telegram 2 zulip --- mobilizon_reshare/cli/cli.py | 53 +++++++++++-------- .../cli/commands/inspect/inspect_event.py | 4 +- .../commands/inspect/inspect_publication.py | 41 ++++++++++++++ mobilizon_reshare/models/publication.py | 4 +- mobilizon_reshare/storage/query/read.py | 24 +++++++-- tests/publishers/test_coordinator.py | 4 +- 6 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 mobilizon_reshare/cli/commands/inspect/inspect_publication.py diff --git a/mobilizon_reshare/cli/cli.py b/mobilizon_reshare/cli/cli.py index 2c4c8b2..5deac7e 100644 --- a/mobilizon_reshare/cli/cli.py +++ b/mobilizon_reshare/cli/cli.py @@ -1,26 +1,35 @@ import functools -from enum import Enum import click from arrow import Arrow -from click import pass_context from mobilizon_reshare.cli import safe_execution from mobilizon_reshare.cli.commands.format.format import format_event from mobilizon_reshare.cli.commands.inspect.inspect_event import inspect_events +from mobilizon_reshare.cli.commands.inspect.inspect_publication import ( + inspect_publications, +) from mobilizon_reshare.cli.commands.start.main import main as start_main from mobilizon_reshare.cli.commands.recap.main import main as recap_main from mobilizon_reshare.config.config import current_version from mobilizon_reshare.config.publishers import publisher_names from mobilizon_reshare.event.event import EventPublicationStatus +from mobilizon_reshare.models.publication import PublicationStatus status_name_to_enum = { - "waiting": EventPublicationStatus.WAITING, - "completed": EventPublicationStatus.COMPLETED, - "failed": EventPublicationStatus.FAILED, - "partial": EventPublicationStatus.PARTIAL, - "all": None, + "event": { + "waiting": EventPublicationStatus.WAITING, + "completed": EventPublicationStatus.COMPLETED, + "failed": EventPublicationStatus.FAILED, + "partial": EventPublicationStatus.PARTIAL, + "all": None, + }, + "publication": { + "completed": PublicationStatus.COMPLETED, + "failed": PublicationStatus.FAILED, + "all": None, + }, } settings_file_option = click.option( @@ -41,6 +50,13 @@ to_date_option = click.option( expose_value=True, help="Include only events that begin before this datetime", ) +status_option = click.option( + "-s", + "--status", + default="all", + expose_value=True, + help="Include only objects with the given status", +) def print_version(ctx, param, value): @@ -50,14 +66,6 @@ def print_version(ctx, param, value): ctx.exit() -class InspectTarget(Enum): - ALL = "all" - WAITING = "waiting" - - def __str__(self): - return self.value - - @click.group() @click.option( "--version", is_flag=True, callback=print_version, expose_value=False, is_eager=True @@ -78,22 +86,25 @@ def recap(settings_file): safe_execution(recap_main, settings_file=settings_file) -@mobilizon_reshare.command(help="Print events in the database that are in STATUS") +@mobilizon_reshare.command(help="List objects in the database with different criteria") @from_date_option @to_date_option @click.argument( - "status", type=click.Choice(list(status_name_to_enum.keys())), + "object", + type=click.Choice(list(status_name_to_enum.keys())), ) @settings_file_option -@pass_context -def inspect(ctx, status, begin, end, settings_file): - ctx.ensure_object(dict) +@status_option +def inspect(object, begin, end, settings_file, status): begin = Arrow.fromdatetime(begin) if begin else None end = Arrow.fromdatetime(end) if end else None safe_execution( functools.partial( - inspect_events, status_name_to_enum[status], frm=begin, to=end, + inspect_events if object == "event" else inspect_publications, + status_name_to_enum[object][status], + frm=begin, + to=end, ), settings_file, ) diff --git a/mobilizon_reshare/cli/commands/inspect/inspect_event.py b/mobilizon_reshare/cli/commands/inspect/inspect_event.py index 838b760..43b0643 100644 --- a/mobilizon_reshare/cli/commands/inspect/inspect_event.py +++ b/mobilizon_reshare/cli/commands/inspect/inspect_event.py @@ -27,8 +27,8 @@ def show_events(events: Iterable[MobilizonEvent]): def pretty(event: MobilizonEvent): return ( - f"{event.name}\t{click.style(event.status.name, fg=status_to_color[event.status])}" - f"\t{event.mobilizon_id}\t{event.begin_datetime.isoformat()}->{event.end_datetime.isoformat()}" + f"{event.name : ^40}{click.style(event.status.name, fg=status_to_color[event.status]) : ^22}" + f"{str(event.mobilizon_id) : <40}{event.begin_datetime.isoformat() : <29}{event.end_datetime.isoformat()}" ) diff --git a/mobilizon_reshare/cli/commands/inspect/inspect_publication.py b/mobilizon_reshare/cli/commands/inspect/inspect_publication.py new file mode 100644 index 0000000..4a77770 --- /dev/null +++ b/mobilizon_reshare/cli/commands/inspect/inspect_publication.py @@ -0,0 +1,41 @@ +from typing import Iterable + +import click +from arrow import Arrow + +from mobilizon_reshare.models.publication import Publication, PublicationStatus +from mobilizon_reshare.storage.query.read import ( + get_all_publications, + publications_with_status, +) + +status_to_color = { + PublicationStatus.COMPLETED: "green", + PublicationStatus.FAILED: "red", +} + + +def show_publications(publications: Iterable[Publication]): + click.echo_via_pager("\n".join(map(pretty, publications))) + + +def pretty(publication: Publication): + return ( + f"{str(publication.id) : <40}{publication.timestamp.isoformat() : <36}" + f"{click.style(publication.status.name, fg=status_to_color[publication.status]) : <22}" + f"{publication.publisher.name : <12}{str(publication.event.id)}" + ) + + +async def inspect_publications( + status: PublicationStatus = None, frm: Arrow = None, to: Arrow = None +): + if status is None: + publications = await get_all_publications(from_date=frm, to_date=to) + else: + publications = await publications_with_status(status, from_date=frm, to_date=to) + + if publications: + show_publications(list(publications)) + else: + click.echo(f"No publication found with status: {status}") diff --git a/mobilizon_reshare/models/publication.py b/mobilizon_reshare/models/publication.py index 927146e..bf954ec 100644 --- a/mobilizon_reshare/models/publication.py +++ b/mobilizon_reshare/models/publication.py @@ -13,9 +13,7 @@ class Publication(Model): id = fields.UUIDField(pk=True) status = fields.IntEnumField(PublicationStatus) - # When a Publication's status is WAITING - # we don't need a timestamp nor a reason - timestamp = fields.DatetimeField(null=True) + timestamp = fields.DatetimeField() reason = fields.TextField(null=True) event = fields.ForeignKeyField("models.Event", related_name="publications") diff --git a/mobilizon_reshare/storage/query/read.py b/mobilizon_reshare/storage/query/read.py index 87baa20..2991d60 100644 --- a/mobilizon_reshare/storage/query/read.py +++ b/mobilizon_reshare/storage/query/read.py @@ -55,6 +55,14 @@ async def events_with_status( ) +async def get_all_publications( + from_date: Optional[Arrow] = None, to_date: Optional[Arrow] = None, +) -> Iterable[Publication]: + return await prefetch_publication_relations( + _add_date_window(Publication.all(), "timestamp", from_date, to_date) + ) + + async def get_all_events( from_date: Optional[Arrow] = None, to_date: Optional[Arrow] = None, ) -> Iterable[MobilizonEvent]: @@ -74,6 +82,14 @@ async def prefetch_event_relations(queryset: QuerySet[Event]) -> list[Event]: ) +async def prefetch_publication_relations(queryset: QuerySet[Publication]) -> list[Publication]: + return ( + await queryset.prefetch_related("publisher", "event") + .order_by("timestamp") + .distinct() + ) + + def _add_date_window( query, field_name: str, @@ -93,7 +109,7 @@ async def publications_with_status( event_mobilizon_id: Optional[UUID] = None, from_date: Optional[Arrow] = None, to_date: Optional[Arrow] = None, -) -> Publication: +) -> Iterable[Publication]: query = Publication.filter(status=status) if event_mobilizon_id: @@ -101,9 +117,9 @@ async def publications_with_status( event__mobilizon_id=event_mobilizon_id ) - query = _add_date_window(query, "timestamp", from_date, to_date) - - return await query.prefetch_related("publisher").order_by("timestamp").distinct() + return await prefetch_publication_relations( + _add_date_window(query, "timestamp", from_date, to_date) + ) async def events_without_publications( diff --git a/tests/publishers/test_coordinator.py b/tests/publishers/test_coordinator.py index ceaf1ae..cb59e50 100644 --- a/tests/publishers/test_coordinator.py +++ b/tests/publishers/test_coordinator.py @@ -1,4 +1,5 @@ import logging +from datetime import timedelta from uuid import UUID import pytest @@ -18,6 +19,7 @@ from mobilizon_reshare.publishers.coordinator import ( PublicationFailureNotifiersCoordinator, RecapCoordinator, ) +from tests import today @pytest.fixture() @@ -91,7 +93,7 @@ async def mock_publications( id=UUID(int=i + 1), event=event, publisher=publisher, - timestamp=None, + timestamp=today + timedelta(hours=i), reason=None, ) publication = EventPublication.from_orm(publication, test_event)