added config and tests

This commit is contained in:
Simone Robutti 2021-05-23 14:40:50 +02:00
parent 5a6b00494f
commit 9187ffde68
12 changed files with 223 additions and 58 deletions

View File

@ -1,49 +1,66 @@
from typing import List
from dynaconf import Dynaconf, Validator
from mobilizon_bots.config import strategies, publishers, notifiers
from mobilizon_bots.config.notifiers import notifier_names
from mobilizon_bots.config.publishers import publisher_names
SETTINGS_FILE = [
"mobilizon_bots/settings.toml",
"mobilizon_bots/.secrets.toml",
"/etc/mobilizon_bots.toml",
"/etc/mobilizon_bots_secrets.toml",
]
ENVVAR_PREFIX = "MOBILIZON_BOTS"
base_validators = (
[Validator("selection.strategy", must_exist=True)]
+ [
Validator(
f"publisher.{publisher_name}.active", must_exist=True, is_type_of=bool
)
for publisher_name in publisher_names
def build_settings(
settings_files: List[str] = None, validators: List[Validator] = None
):
SETTINGS_FILE = settings_files or [
"mobilizon_bots/settings.toml",
"mobilizon_bots/.secrets.toml",
"/etc/mobilizon_bots.toml",
"/etc/mobilizon_bots_secrets.toml",
]
+ [
Validator(f"notifier.{notifier_name}.active", must_exist=True, is_type_of=bool)
for notifier_name in notifier_names
]
)
ENVVAR_PREFIX = "MOBILIZON_BOTS"
raw_settings = Dynaconf(
environments=True,
envvar_prefix=ENVVAR_PREFIX,
settings_files=SETTINGS_FILE,
validators=base_validators,
)
return Dynaconf(
environments=True,
envvar_prefix=ENVVAR_PREFIX,
settings_files=SETTINGS_FILE,
validators=validators or [],
)
strategy_validators = strategies.get_validators(raw_settings)
publisher_validators = publishers.get_validators(raw_settings)
notifier_validators = notifiers.get_validators(raw_settings)
settings = Dynaconf(
environments=True,
envvar_prefix=ENVVAR_PREFIX,
settings_files=SETTINGS_FILE,
validators=base_validators
+ strategy_validators
+ publisher_validators
+ notifier_validators,
)
# TODO use validation control in dynaconf 3.2.0 once released
settings.validators.validate()
def build_and_validate_settings(settings_files: List[str] = None):
base_validators = (
[
Validator("selection.strategy", must_exist=True),
Validator("source.mobilizon.url", must_exist=True),
]
+ [
Validator(
f"publisher.{publisher_name}.active", must_exist=True, is_type_of=bool
)
for publisher_name in publisher_names
]
+ [
Validator(
f"notifier.{notifier_name}.active", must_exist=True, is_type_of=bool
)
for notifier_name in notifier_names
]
)
raw_settings = build_settings(settings_files=settings_files)
strategy_validators = strategies.get_validators(raw_settings)
publisher_validators = publishers.get_validators(raw_settings)
notifier_validators = notifiers.get_validators(raw_settings)
settings = build_settings(
settings_files,
base_validators
+ strategy_validators
+ publisher_validators
+ notifier_validators,
)
# TODO use validation control in dynaconf 3.2.0 once released
settings.validators.validate()
return settings
settings = build_and_validate_settings()

View File

@ -1,4 +1,4 @@
from dataclasses import dataclass, asdict
from dataclasses import dataclass, asdict, field
from enum import Enum
from typing import Optional
@ -18,19 +18,20 @@ class MobilizonEvent:
"""Class representing an event retrieved from Mobilizon."""
name: str
description: str
begin_datetime: arrow.Arrow
end_datetime: arrow.Arrow
last_accessed: arrow.Arrow
mobilizon_link: str
description: Optional[str]
begin_datetime: Optional[arrow.Arrow]
end_datetime: Optional[arrow.Arrow]
mobilizon_link: Optional[str]
mobilizon_id: str
thumbnail_link: Optional[str] = None
location: Optional[str] = None
publication_time: Optional[arrow.Arrow] = None
publication_status: PublicationStatus = PublicationStatus.WAITING
last_accessed: arrow.Arrow = field(compare=False, default=None)
def __post_init__(self):
assert self.begin_datetime < self.end_datetime
if self.begin_datetime and self.end_datetime:
assert self.begin_datetime < self.end_datetime
if self.publication_time:
assert self.publication_status in [
PublicationStatus.COMPLETED,

View File

@ -1,8 +1,10 @@
from typing import List, Optional
from mobilizon_bots.event.event import MobilizonEvent, PublicationStatus
import requests
import arrow
import requests
from mobilizon_bots.config.config import settings
from mobilizon_bots.event.event import MobilizonEvent, PublicationStatus
def parse_location(data):
@ -19,11 +21,11 @@ def parse_picture(data):
def parse_event(data):
return MobilizonEvent(
name=data["title"],
description=data["description"],
begin_datetime=arrow.get(data["beginsOn"]),
end_datetime=arrow.get(data["endsOn"]),
description=data.get("description", None),
begin_datetime=arrow.get(data["beginsOn"]) if "beginsOn" in data else None,
end_datetime=arrow.get(data["endsOn"]) if "endsOn" in data else None,
last_accessed=arrow.now(),
mobilizon_link=data["url"],
mobilizon_link=data.get("url", None),
mobilizon_id=data["uuid"],
thumbnail_link=parse_picture(data),
location=parse_location(data),
@ -64,15 +66,12 @@ query_future_events = """{{
def get_mobilizon_future_events(
page: int = 1, from_date: Optional[arrow.Arrow] = None
) -> List[MobilizonEvent]:
url = "https://apero.bzh/api"
url = settings["source"]["mobilizon"]["url"]
query = query_future_events.format(
group="test", page=page, afterDatetime=from_date or arrow.now().isoformat()
)
r = requests.post(url, json={"query": query})
return list(
map(parse_event, r.json()["data"]["group"]["organizedEvents"]["elements"])
)
get_mobilizon_future_events()

View File

@ -6,6 +6,9 @@ log_dir = "/var/log/mobots"
db_name = "events.db"
db_path = "@format {this.local_state_dir}/{this.db_name}"
[default.source.mobilizon]
url="https://some_mobilizon"
[default.selection]
strategy = "next_event"

22
poetry.lock generated
View File

@ -234,6 +234,22 @@ urllib3 = ">=1.21.1,<1.27"
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]]
name = "responses"
version = "0.13.3"
description = "A utility library for mocking out the `requests` Python library."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
requests = ">=2.0"
six = "*"
urllib3 = ">=1.25.10"
[package.extras]
tests = ["coverage (>=3.7.1,<6.0.0)", "pytest-cov", "pytest-localserver", "flake8", "pytest (>=4.6,<5.0)", "pytest (>=4.6)", "mypy"]
[[package]]
name = "six"
version = "1.16.0"
@ -295,7 +311,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "75f5eb1e5eda7daba1871ae4f550e72075894e1152c8ee34686c5164bc36580a"
content-hash = "be81df312539b9c6d1020d05e5001c8819f5ca47435a84e398b35cd39020e7bf"
[metadata.files]
aiosqlite = [
@ -412,6 +428,10 @@ requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
]
responses = [
{file = "responses-0.13.3-py2.py3-none-any.whl", hash = "sha256:b54067596f331786f5ed094ff21e8d79e6a1c68ef625180a7d34808d6f36c11b"},
{file = "responses-0.13.3.tar.gz", hash = "sha256:18a5b88eb24143adbf2b4100f328a2f5bfa72fbdacf12d97d41f07c26c45553d"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},

View File

@ -15,6 +15,7 @@ arrow = "^1.1.0"
[tool.poetry.dev-dependencies]
pytest = "^5.3"
responses = "^0.13.3"
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@ -1,6 +1,9 @@
import arrow
import pkg_resources
import pytest
from dynaconf import settings
from mobilizon_bots.config.config import build_and_validate_settings
from mobilizon_bots.event.event import MobilizonEvent, PublicationStatus
@ -8,6 +11,16 @@ def generate_publication_status(published):
return PublicationStatus.COMPLETED if published else PublicationStatus.WAITING
@pytest.fixture(scope="session", autouse=True)
def set_test_settings():
config_file = pkg_resources.resource_filename(
"tests.resources", "test_settings.toml"
)
settings.configure(FORCE_ENV_FOR_DYNACONF="testing")
build_and_validate_settings([config_file])
@pytest.fixture
def event_generator():
def _event_generator(

View File

View File

@ -0,0 +1,18 @@
import pytest
import responses
from mobilizon_bots.config.config import settings
@responses.activate
@pytest.fixture
def mock_mobilizon_success_answer(mobilizon_answer):
with responses.RequestsMock() as rsps:
rsps.add(
responses.POST,
settings["source"]["mobilizon"]["url"],
json=mobilizon_answer,
status=200,
)
yield

View File

@ -0,0 +1,54 @@
import pytest
from mobilizon_bots.event.event import PublicationStatus, MobilizonEvent
from mobilizon_bots.mobilizon.events import get_mobilizon_future_events
@pytest.mark.parametrize(
"mobilizon_answer", [{"data": {"group": {"organizedEvents": {"elements": []}}}}]
)
def test_get_mobilizon_future_events(mock_mobilizon_success_answer):
"""
Testing a response with no content
"""
assert get_mobilizon_future_events() == []
@pytest.mark.parametrize(
"mobilizon_answer",
[
{
"data": {
"group": {
"organizedEvents": {
"elements": [
{
"id": "57194",
"title": "test event",
"uuid": "1e2e5943-4a5c-497a-b65d-90457b715d7b",
}
]
}
}
}
}
],
)
def test_simple_event(mock_mobilizon_success_answer):
"""
Testing a minimal event that is valid in Mobilizon
"""
assert get_mobilizon_future_events() == [
MobilizonEvent(
name="test event",
description=None,
begin_datetime=None,
end_datetime=None,
mobilizon_link=None,
mobilizon_id="1e2e5943-4a5c-497a-b65d-90457b715d7b",
thumbnail_link=None,
location=None,
publication_time=None,
publication_status=PublicationStatus.WAITING,
)
]

View File

View File

@ -0,0 +1,39 @@
[default]
debug = true
default = true
local_state_dir = "/var/mobots"
log_dir = "/var/log/mobots"
db_name = "events.db"
db_path = "@format {this.local_state_dir}/{this.db_name}"
[default.source.mobilizon]
url=""
[default.selection]
strategy = "next_event"
[default.selection.strategy_options]
break_between_events_in_minutes =5
[default.publisher.telegram]
active=true
chat_id="xxx"
[default.publisher.facebook]
active=false
[default.publisher.zulip]
active=false
[default.publisher.twitter]
active=false
[default.publisher.mastodon]
active=false
[default.notifier.telegram]
active=true
chat_id="xxx"
[default.notifier.zulip]
active=false
[default.notifier.twitter]
active=false
[default.notifier.mastodon]
active=false