format event cli (#61)
* added get_formatted_event * fixed cli settings * added format command * refactored inspect
This commit is contained in:
parent
394d84b0a5
commit
360740eae0
|
@ -4,7 +4,7 @@ import traceback
|
|||
from logging.config import dictConfig
|
||||
from pathlib import Path
|
||||
|
||||
from mobilizon_reshare.config.config import update_settings_files
|
||||
from mobilizon_reshare.config.config import get_settings
|
||||
from mobilizon_reshare.storage.db import tear_down, MoReDB
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -16,7 +16,7 @@ async def graceful_exit(code):
|
|||
|
||||
|
||||
async def init(settings_file):
|
||||
settings = update_settings_files(settings_file)
|
||||
settings = get_settings(settings_file)
|
||||
dictConfig(settings["logging"])
|
||||
db_path = Path(settings.db_path)
|
||||
db = MoReDB(db_path)
|
||||
|
|
|
@ -2,9 +2,10 @@ import functools
|
|||
|
||||
import click
|
||||
from arrow import Arrow
|
||||
from click import pass_context, pass_obj
|
||||
from click import pass_context
|
||||
|
||||
from mobilizon_reshare.cli import safe_execution
|
||||
from mobilizon_reshare.cli.format import format_event
|
||||
from mobilizon_reshare.cli.inspect_event import inspect_events
|
||||
from mobilizon_reshare.cli.main import main
|
||||
from mobilizon_reshare.event.event import EventPublicationStatus
|
||||
|
@ -35,88 +36,36 @@ def start(settings_file):
|
|||
safe_execution(main, settings_file=settings_file)
|
||||
|
||||
|
||||
@mobilizon_reshare.group()
|
||||
@mobilizon_reshare.command()
|
||||
@from_date_option
|
||||
@to_date_option
|
||||
@click.argument("target", type=str)
|
||||
@settings_file_option
|
||||
@pass_context
|
||||
def inspect(ctx, begin, end):
|
||||
def inspect(ctx, target, begin, end, settings_file):
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["begin"] = Arrow.fromdatetime(begin) if begin else None
|
||||
ctx.obj["end"] = Arrow.fromdatetime(end) if end else None
|
||||
pass
|
||||
|
||||
|
||||
@inspect.command()
|
||||
@settings_file_option
|
||||
@pass_obj
|
||||
def all(obj, settings_file):
|
||||
begin = Arrow.fromdatetime(begin) if begin else None
|
||||
end = Arrow.fromdatetime(end) if end else None
|
||||
target_to_status = {
|
||||
"waiting": EventPublicationStatus.WAITING,
|
||||
"completed": EventPublicationStatus.COMPLETED,
|
||||
"failed": EventPublicationStatus.FAILED,
|
||||
"partial": EventPublicationStatus.PARTIAL,
|
||||
"all": None,
|
||||
}
|
||||
safe_execution(
|
||||
functools.partial(
|
||||
inspect_events,
|
||||
frm=obj["begin"],
|
||||
to=obj["end"],
|
||||
),
|
||||
functools.partial(inspect_events, target_to_status[target], frm=begin, to=end,),
|
||||
settings_file,
|
||||
)
|
||||
|
||||
|
||||
@inspect.command()
|
||||
@pass_obj
|
||||
@mobilizon_reshare.command()
|
||||
@settings_file_option
|
||||
def waiting(obj, settings_file):
|
||||
@click.argument("event-id", type=str)
|
||||
@click.argument("publisher", type=str)
|
||||
def format(settings_file, event_id, publisher):
|
||||
safe_execution(
|
||||
functools.partial(
|
||||
inspect_events,
|
||||
EventPublicationStatus.WAITING,
|
||||
frm=obj["begin"],
|
||||
to=obj["end"],
|
||||
),
|
||||
settings_file,
|
||||
)
|
||||
|
||||
|
||||
@inspect.command()
|
||||
@pass_obj
|
||||
@settings_file_option
|
||||
def failed(obj, settings_file):
|
||||
safe_execution(
|
||||
functools.partial(
|
||||
inspect_events,
|
||||
EventPublicationStatus.FAILED,
|
||||
frm=obj["begin"],
|
||||
to=obj["end"],
|
||||
),
|
||||
settings_file,
|
||||
)
|
||||
|
||||
|
||||
@inspect.command()
|
||||
@pass_obj
|
||||
@settings_file_option
|
||||
def partial(obj, settings_file):
|
||||
safe_execution(
|
||||
functools.partial(
|
||||
inspect_events,
|
||||
EventPublicationStatus.PARTIAL,
|
||||
frm=obj["begin"],
|
||||
to=obj["end"],
|
||||
),
|
||||
settings_file,
|
||||
)
|
||||
|
||||
|
||||
@inspect.command()
|
||||
@settings_file_option
|
||||
@pass_obj
|
||||
def completed(obj, settings_file):
|
||||
safe_execution(
|
||||
functools.partial(
|
||||
inspect_events,
|
||||
EventPublicationStatus.COMPLETED,
|
||||
frm=obj["begin"],
|
||||
to=obj["end"],
|
||||
),
|
||||
settings_file,
|
||||
functools.partial(format_event, event_id, publisher), settings_file,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import click
|
||||
|
||||
from mobilizon_reshare.event.event import MobilizonEvent
|
||||
from mobilizon_reshare.models.event import Event
|
||||
from mobilizon_reshare.publishers.coordinator import PublisherCoordinator
|
||||
|
||||
|
||||
async def format_event(event_id, publisher):
|
||||
event = await Event.get_or_none(mobilizon_id=event_id).prefetch_related(
|
||||
"publications__publisher"
|
||||
)
|
||||
if not event:
|
||||
click.echo(f"Event with mobilizon_id {event_id} not found.")
|
||||
return
|
||||
event = MobilizonEvent.from_model(event)
|
||||
message = PublisherCoordinator.get_formatted_message(event, publisher)
|
||||
click.echo(message)
|
|
@ -16,11 +16,7 @@ base_validators = [
|
|||
# strategy to decide events to publish
|
||||
Validator("selection.strategy", must_exist=True, is_type_of=str),
|
||||
Validator(
|
||||
"publishing.window.begin",
|
||||
must_exist=True,
|
||||
is_type_of=int,
|
||||
gte=0,
|
||||
lte=24,
|
||||
"publishing.window.begin", must_exist=True, is_type_of=int, gte=0, lte=24,
|
||||
),
|
||||
Validator("publishing.window.end", must_exist=True, is_type_of=int, gte=0, lte=24),
|
||||
# url of the main Mobilizon instance to download events from
|
||||
|
@ -61,21 +57,15 @@ def build_settings(
|
|||
with importlib.resources.path(
|
||||
mobilizon_reshare, "settings.toml"
|
||||
) as bundled_settings_path:
|
||||
SETTINGS_FILE = (
|
||||
[
|
||||
bundled_settings_path,
|
||||
Path(dirs.site_config_dir, "mobilizon_reshare.toml"),
|
||||
Path(dirs.user_config_dir, "mobilizon_reshare.toml"),
|
||||
os.environ.get("MOBILIZION_RESHARE_SETTINGS_FILE"),
|
||||
settings_file,
|
||||
]
|
||||
# FIXME: This is needed because otherwise dynaconf would load the bundled settings.toml file.
|
||||
if os.environ.get("ENV_FOR_DYNACONF", "") != "testing"
|
||||
else [settings_file]
|
||||
)
|
||||
SETTINGS_FILE = [
|
||||
bundled_settings_path,
|
||||
Path(dirs.site_config_dir, "mobilizon_reshare.toml"),
|
||||
Path(dirs.user_config_dir, "mobilizon_reshare.toml"),
|
||||
os.environ.get("MOBILIZION_RESHARE_SETTINGS_FILE"),
|
||||
settings_file,
|
||||
]
|
||||
|
||||
ENVVAR_PREFIX = "MOBILIZON_RESHARE"
|
||||
|
||||
return Dynaconf(
|
||||
environments=True,
|
||||
envvar_prefix=ENVVAR_PREFIX,
|
||||
|
@ -123,11 +113,21 @@ def build_and_validate_settings(settings_file: Optional[str] = None):
|
|||
# better in the future.
|
||||
class CustomConfig:
|
||||
_instance = None
|
||||
_settings_file = None
|
||||
|
||||
def __new__(cls, settings_file: Optional[str] = None):
|
||||
if cls._instance is None:
|
||||
if (
|
||||
settings_file is None and cls._settings_file is not None
|
||||
): # normal access, I don't want to reload
|
||||
return cls._instance
|
||||
|
||||
if (
|
||||
cls._instance is None and cls._settings_file is None
|
||||
) or settings_file != cls._settings_file:
|
||||
cls._settings_file = settings_file
|
||||
cls._instance = super(CustomConfig, cls).__new__(cls)
|
||||
cls.settings = build_and_validate_settings(settings_file)
|
||||
|
||||
return cls._instance
|
||||
|
||||
def update(self, settings_file: Optional[str] = None):
|
||||
|
@ -137,8 +137,3 @@ class CustomConfig:
|
|||
def get_settings(settings_file: Optional[str] = None):
|
||||
config = CustomConfig(settings_file)
|
||||
return config.settings
|
||||
|
||||
|
||||
def update_settings_files(settings_file: Optional[str] = None):
|
||||
CustomConfig().update(settings_file)
|
||||
return CustomConfig().settings
|
||||
|
|
|
@ -46,7 +46,7 @@ class AbstractNotifier(ABC):
|
|||
"Abstract classes cannot access notifiers/publishers' settings"
|
||||
)
|
||||
try:
|
||||
t, n = cls._conf or tuple() # Avoid unpacking ``None``
|
||||
t, n = cls._conf or tuple()
|
||||
return get_settings()[t][n]
|
||||
except (KeyError, ValueError):
|
||||
raise InvalidAttribute(
|
||||
|
|
|
@ -12,13 +12,14 @@ from mobilizon_reshare.publishers.telegram import TelegramPublisher
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
name_to_publisher_class = {"telegram": TelegramPublisher}
|
||||
|
||||
|
||||
class BuildPublisherMixin:
|
||||
@staticmethod
|
||||
def build_publishers(
|
||||
event: MobilizonEvent, publisher_names
|
||||
) -> dict[str, AbstractPublisher]:
|
||||
name_to_publisher_class = {"telegram": TelegramPublisher}
|
||||
|
||||
return {
|
||||
publisher_name: name_to_publisher_class[publisher_name](event)
|
||||
|
@ -112,6 +113,19 @@ class PublisherCoordinator(BuildPublisherMixin):
|
|||
|
||||
return errors
|
||||
|
||||
@staticmethod
|
||||
def get_formatted_message(event: MobilizonEvent, publisher: str) -> str:
|
||||
"""
|
||||
Returns the formatted message for a given event and publisher.
|
||||
"""
|
||||
if publisher not in name_to_publisher_class:
|
||||
raise ValueError(
|
||||
f"Publisher {publisher} does not exist.\nSupported publishers: "
|
||||
f"{', '.join(list(name_to_publisher_class.keys()))}"
|
||||
)
|
||||
|
||||
return name_to_publisher_class[publisher](event).get_message_from_event()
|
||||
|
||||
|
||||
class AbstractNotifiersCoordinator(BuildPublisherMixin):
|
||||
def __init__(self, event: MobilizonEvent):
|
||||
|
|
|
@ -45,3 +45,4 @@ encoding = "utf8"
|
|||
[default.logging.root]
|
||||
level = "DEBUG"
|
||||
handlers = ['console', 'file']
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import pkg_resources
|
||||
|
||||
from mobilizon_reshare.config.config import get_settings, update_settings_files
|
||||
from mobilizon_reshare.config.config import get_settings
|
||||
|
||||
|
||||
def test_singleton():
|
||||
|
@ -12,12 +12,22 @@ def test_singleton():
|
|||
assert id(config_1) == id(config_2)
|
||||
|
||||
|
||||
def test_singleton_update():
|
||||
def test_same_file():
|
||||
settings_file = pkg_resources.resource_filename(
|
||||
"tests.resources.config", "test_singleton.toml"
|
||||
)
|
||||
config_1 = get_settings(settings_file)
|
||||
config_2 = update_settings_files(settings_file)
|
||||
config_3 = get_settings()
|
||||
config_2 = get_settings(settings_file)
|
||||
assert id(config_1) == id(config_2)
|
||||
|
||||
|
||||
def test_singleton_new_file():
|
||||
settings_file = pkg_resources.resource_filename(
|
||||
"tests.resources.config", "test_singleton.toml"
|
||||
)
|
||||
settings_file_2 = pkg_resources.resource_filename(
|
||||
"tests.resources.config", "test_singleton_2.toml"
|
||||
)
|
||||
config_1 = get_settings(settings_file)
|
||||
config_2 = get_settings(settings_file_2)
|
||||
assert id(config_1) != id(config_2)
|
||||
assert id(config_2) == id(config_3)
|
||||
|
|
|
@ -4,7 +4,7 @@ import dynaconf
|
|||
import pkg_resources
|
||||
import pytest
|
||||
|
||||
from mobilizon_reshare.config.config import update_settings_files
|
||||
from mobilizon_reshare.config.config import get_settings
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -15,53 +15,30 @@ def invalid_settings_file(tmp_path, toml_content):
|
|||
|
||||
|
||||
@pytest.mark.parametrize("toml_content", ["invalid toml["])
|
||||
def test_update_failure_invalid_toml(invalid_settings_file):
|
||||
def test_get_settings_failure_invalid_toml(invalid_settings_file):
|
||||
with pytest.raises(dynaconf.vendor.toml.decoder.TomlDecodeError):
|
||||
update_settings_files(invalid_settings_file.absolute())
|
||||
get_settings(invalid_settings_file.absolute())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("toml_content", [""])
|
||||
def test_update_failure_invalid_preliminary_config(invalid_settings_file):
|
||||
def test_get_settings_failure_invalid_preliminary_config(invalid_settings_file):
|
||||
os.environ["SECRETS_FOR_DYNACONF"] = ""
|
||||
|
||||
with pytest.raises(dynaconf.validator.ValidationError):
|
||||
update_settings_files(invalid_settings_file.absolute())
|
||||
get_settings(invalid_settings_file.absolute())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_toml,pattern_in_exception",
|
||||
[
|
||||
["config_with_invalid_strategy.toml", "break_between_events_in_minutes"],
|
||||
["config_with_invalid_mobilizon.toml", "mobilizon"],
|
||||
["config_with_preliminary.toml", "publishing.window.begin"],
|
||||
],
|
||||
[["config_with_invalid_strategy.toml", "break_between_events_in_minutes"]],
|
||||
)
|
||||
def test_update_failure_config_base_validators(invalid_toml, pattern_in_exception):
|
||||
def test_get_settings_failure_config_base_validators(
|
||||
invalid_toml, pattern_in_exception
|
||||
):
|
||||
|
||||
with pytest.raises(dynaconf.validator.ValidationError) as e:
|
||||
update_settings_files(
|
||||
get_settings(
|
||||
pkg_resources.resource_filename("tests.resources.config", invalid_toml)
|
||||
)
|
||||
assert e.match(pattern_in_exception)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_toml,pattern_in_exception",
|
||||
[
|
||||
["empty.toml", "publisher.*.active"],
|
||||
["config_with_invalid_telegram.toml", "token"],
|
||||
],
|
||||
)
|
||||
def test_update_failure_config_all_validators(invalid_toml, pattern_in_exception):
|
||||
|
||||
os.environ["SECRETS_FOR_DYNACONF"] = pkg_resources.resource_filename(
|
||||
"tests.resources.config", invalid_toml
|
||||
)
|
||||
|
||||
with pytest.raises(dynaconf.validator.ValidationError) as e:
|
||||
update_settings_files(
|
||||
pkg_resources.resource_filename(
|
||||
"tests.resources.config", "test_singleton.toml"
|
||||
)
|
||||
)
|
||||
|
||||
assert e.match(pattern_in_exception)
|
||||
|
|
|
@ -145,9 +145,7 @@ def event_model_generator():
|
|||
|
||||
@pytest.fixture()
|
||||
def publisher_model_generator():
|
||||
def _publisher_model_generator(
|
||||
idx=1,
|
||||
):
|
||||
def _publisher_model_generator(idx=1,):
|
||||
return Publisher(name=f"publisher_{idx}", account_ref=f"account_ref_{idx}")
|
||||
|
||||
return _publisher_model_generator
|
||||
|
|
|
@ -3,6 +3,7 @@ from uuid import UUID
|
|||
import pytest
|
||||
from asynctest import MagicMock
|
||||
|
||||
from mobilizon_reshare.config.config import get_settings
|
||||
from mobilizon_reshare.event.event import MobilizonEvent
|
||||
from mobilizon_reshare.models.publication import PublicationStatus, Publication
|
||||
from mobilizon_reshare.models.publisher import Publisher
|
||||
|
@ -12,6 +13,7 @@ from mobilizon_reshare.publishers.coordinator import (
|
|||
PublisherCoordinator,
|
||||
PublicationFailureNotifiersCoordinator,
|
||||
)
|
||||
from mobilizon_reshare.publishers.telegram import TelegramPublisher
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -35,9 +37,7 @@ def test_publication_report_successful(statuses, successful):
|
|||
|
||||
@pytest.fixture
|
||||
@pytest.mark.asyncio
|
||||
async def mock_publication(
|
||||
test_event: MobilizonEvent,
|
||||
):
|
||||
async def mock_publication(test_event: MobilizonEvent,):
|
||||
event = test_event.to_model()
|
||||
await event.save()
|
||||
publisher = Publisher(name="telegram")
|
||||
|
@ -136,3 +136,10 @@ async def test_notifier_coordinator_publication_failed(
|
|||
|
||||
# 4 = 2 reports * 2 notifiers
|
||||
assert mock_send.call_count == 4
|
||||
|
||||
|
||||
def test_get_formatted_message(test_event):
|
||||
settings = get_settings()
|
||||
settings.update({"publisher.telegram.msg_template_path": None})
|
||||
message = PublisherCoordinator.get_formatted_message(test_event, "telegram")
|
||||
assert message == TelegramPublisher(test_event).get_message_from_event()
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
[default.publishing.window]
|
||||
begin=12
|
||||
end=18
|
||||
|
||||
[default.selection]
|
||||
strategy = "next_event"
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
[default.source.mobilizon]
|
||||
[testing.source.mobilizon]
|
||||
url="https://some_mobilizon"
|
||||
group="my_group"
|
||||
|
||||
[default.publishing.window]
|
||||
[testing.publishing.window]
|
||||
begin=12
|
||||
end=18
|
||||
|
||||
[default.selection]
|
||||
[testing.selection]
|
||||
strategy = "next_event"
|
||||
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
|
||||
[default.selection]
|
||||
[testing.selection]
|
||||
strategy = "next_event"
|
||||
|
||||
|
||||
[default.source.mobilizon]
|
||||
[testing.source.mobilizon]
|
||||
url="https://mobilizon.it/api"
|
||||
group="tech_workers_coalition_italia"
|
||||
|
||||
|
||||
|
||||
[default.publishing.window]
|
||||
[testing.publishing.window]
|
||||
begin=12
|
||||
end=24
|
||||
|
||||
[default.selection.strategy_options]
|
||||
[testing.selection.strategy_options]
|
||||
break_between_events_in_minutes =1
|
||||
|
||||
|
||||
[default.publisher.telegram]
|
||||
[testing.publisher.telegram]
|
||||
active=true
|
||||
chat_id="xxx"
|
||||
token="xxx"
|
||||
username="xxx"
|
||||
|
||||
[default.publisher.facebook]
|
||||
[testing.publisher.facebook]
|
||||
active=false
|
||||
[default.publisher.zulip]
|
||||
[testing.publisher.zulip]
|
||||
active=false
|
||||
[default.publisher.twitter]
|
||||
[testing.publisher.twitter]
|
||||
active=false
|
||||
[default.publisher.mastodon]
|
||||
[testing.publisher.mastodon]
|
||||
active=false
|
||||
|
||||
|
||||
[default.notifier.telegram]
|
||||
[testing.notifier.telegram]
|
||||
active=true
|
||||
chat_id="xxx"
|
||||
misspelled_token="xxx"
|
||||
username="xxx"
|
||||
[default.notifier.zulip]
|
||||
[testing.notifier.zulip]
|
||||
active=false
|
||||
[default.notifier.twitter]
|
||||
[testing.notifier.twitter]
|
||||
active=false
|
||||
[default.notifier.mastodon]
|
||||
[testing.notifier.mastodon]
|
||||
active=false
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
[default.selection]
|
||||
[testing.selection]
|
||||
strategy = "next_event"
|
||||
[default.selection.strategy_options]
|
||||
[testing.selection.strategy_options]
|
||||
break_between_events_in_minutes = 60
|
|
@ -1,12 +1,12 @@
|
|||
[default.source.mobilizon]
|
||||
[testing.source.mobilizon]
|
||||
url="https://some_mobilizon"
|
||||
group="my_group"
|
||||
|
||||
[default.publishing.window]
|
||||
[testing.publishing.window]
|
||||
begin=12
|
||||
end=18
|
||||
|
||||
[default.selection]
|
||||
[testing.selection]
|
||||
strategy = "next_event"
|
||||
[default.selection.strategy_options]
|
||||
[testing.selection.strategy_options]
|
||||
break_between_events_in_minutes = 60
|
|
@ -1,13 +1,13 @@
|
|||
[default.source.mobilizon]
|
||||
[testing.source.mobilizon]
|
||||
url="https://some_mobilizon"
|
||||
group="my_group"
|
||||
|
||||
[default.selection]
|
||||
[testing.selection]
|
||||
strategy = "next_event"
|
||||
|
||||
[default.publishing.window]
|
||||
[testing.publishing.window]
|
||||
begin=12
|
||||
end=18
|
||||
|
||||
[default.selection.strategy_options]
|
||||
[testing.selection.strategy_options]
|
||||
break_between_events_in_minutes =60
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
[testing.source.mobilizon]
|
||||
url="https://some_mobilizon"
|
||||
group="my_group"
|
||||
|
||||
[testing.selection]
|
||||
strategy = "next_event"
|
||||
|
||||
[testing.publishing.window]
|
||||
begin=12
|
||||
end=18
|
||||
|
||||
[testing.selection.strategy_options]
|
||||
break_between_events_in_minutes =60
|
Loading…
Reference in New Issue