From 3fe3c8efc62d06147bd164566e89e8ee6065df7a Mon Sep 17 00:00:00 2001 From: Simone Robutti Date: Tue, 4 May 2021 22:58:00 +0200 Subject: [PATCH] added event selector (w/o tests) --- mobilizon_bots/event/event.py | 19 +++++++++-- poetry.lock | 46 ++++++++++++++++++++++++++- pyproject.toml | 1 + tests/__init__.py | 33 +++++++++++++++++++ tests/event/event_selector.py | 58 ++++++++++++++++++++++++++++++++++ tests/event/test_event.py | 29 +++-------------- tests/event/test_strategies.py | 12 +++++++ 7 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 tests/event/event_selector.py create mode 100644 tests/event/test_strategies.py diff --git a/mobilizon_bots/event/event.py b/mobilizon_bots/event/event.py index 4e0742f..fedd368 100644 --- a/mobilizon_bots/event/event.py +++ b/mobilizon_bots/event/event.py @@ -1,10 +1,18 @@ from __future__ import annotations from dataclasses import dataclass, asdict from datetime import datetime +from enum import Enum from typing import Optional from jinja2 import Template +class PublicationStatus(Enum): + WAITING = 1 + FAILED = 2 + PARTIAL = 3 + COMPLETED = 4 + + @dataclass class MobilizonEvent: """Class representing an event retrieved from Mobilizon.""" @@ -18,9 +26,16 @@ class MobilizonEvent: mobilizon_id: str thumbnail_link: Optional[str] = None location: Optional[str] = None + publication_time: Optional[datetime] = None + publication_status: PublicationStatus = PublicationStatus.WAITING - def begins_before(self, other: MobilizonEvent) -> bool: - return self.begin_datetime < other.begin_datetime + def __post_init__(self): + assert self.begin_datetime < self.end_datetime + if self.publication_time: + assert self.publication_status in [ + PublicationStatus.COMPLETED, + PublicationStatus.PARTIAL, + ] def _fill_template(self, pattern: Template) -> str: return pattern.render(**asdict(self)) diff --git a/poetry.lock b/poetry.lock index d148a88..028610e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -56,6 +56,34 @@ toml = ["toml"] vault = ["hvac"] yaml = ["ruamel.yaml"] +[[package]] +name = "hypothesis" +version = "6.10.1" +description = "A library for property-based testing" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +attrs = ">=19.2.0" +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "importlib-resources (>=3.3.0)", "importlib-metadata (>=3.6)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"] +cli = ["click (>=7.0)", "black (>=19.10b0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["pytz (>=2014.1)", "django (>=2.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark-parser (>=0.6.5)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=0.25)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["importlib-resources (>=3.3.0)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"] + [[package]] name = "iso8601" version = "0.1.14" @@ -170,6 +198,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "sortedcontainers" +version = "2.3.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "tortoise-orm" version = "0.17.2" @@ -210,7 +246,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "2a9d2f27ed4c4197d4dd022c2e1f8968ef7fff2ab73145a4bd74712f35d88855" +content-hash = "f78f8e295d8c94dfe0e4e0f29ce5056f5cea948037cc62b9903b6bc8263a0db6" [metadata.files] aiosqlite = [ @@ -233,6 +269,10 @@ dynaconf = [ {file = "dynaconf-3.1.4-py2.py3-none-any.whl", hash = "sha256:e6f383b84150b70fc439c8b2757581a38a58d07962aa14517292dcce1a77e160"}, {file = "dynaconf-3.1.4.tar.gz", hash = "sha256:b2f472d83052f809c5925565b8a2ba76a103d5dc1dbb9748b693ed67212781b9"}, ] +hypothesis = [ + {file = "hypothesis-6.10.1-py3-none-any.whl", hash = "sha256:1d65f58d82d1cbd35b6441bda3bb11cb1adc879d6b2af191aea388fa412171b1"}, + {file = "hypothesis-6.10.1.tar.gz", hash = "sha256:586b6c46e90878c2546743afbed348bca51e1f30e3461fa701fad58c2c47c650"}, +] iso8601 = [ {file = "iso8601-0.1.14-py2.py3-none-any.whl", hash = "sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004"}, {file = "iso8601-0.1.14.tar.gz", hash = "sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79"}, @@ -303,6 +343,10 @@ pytz = [ {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, ] +sortedcontainers = [ + {file = "sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f"}, + {file = "sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1"}, +] tortoise-orm = [ {file = "tortoise-orm-0.17.2.tar.gz", hash = "sha256:1a742b2f15a31d47a8dea7706b478cc9a7ce9af268b61d77d0fa22cfbaea271a"}, {file = "tortoise_orm-0.17.2-py3-none-any.whl", hash = "sha256:b0c02be3800398053058377ddca91fa051eb98eebb704d2db2a3ab1c6a58e347"}, diff --git a/pyproject.toml b/pyproject.toml index c243b95..8d67d2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ Jinja2 = "^2.11.3" [tool.poetry.dev-dependencies] pytest = "^5.2" +hypothesis = "^6.10.1" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..af165a0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,33 @@ +import hypothesis +from hypothesis import assume +from hypothesis.provisional import urls +from hypothesis.strategies import characters, datetimes, text, sampled_from + +from mobilizon_bots.event.event import MobilizonEvent, PublicationStatus + + +@hypothesis.strategies.composite +def events(draw, published: bool = False): + begin_datetime = draw(datetimes()) + end_datetime = draw(datetimes()) + assume(begin_datetime < end_datetime) + + return MobilizonEvent( + name=draw(characters()), + description=draw(text()), + begin_datetime=begin_datetime, + end_datetime=end_datetime, + last_accessed=draw(datetimes()), + mobilizon_link=draw(urls()), + mobilizon_id=draw(characters()), + thumbnail_link=draw(urls()), + location=draw(text()), + publication_time=draw(datetimes()) if published else None, + publication_status=draw( + sampled_from( + [PublicationStatus.COMPLETED, PublicationStatus.PARTIAL] + if published + else [PublicationStatus.WAITING, PublicationStatus.FAILED] + ) + ), + ) diff --git a/tests/event/event_selector.py b/tests/event/event_selector.py new file mode 100644 index 0000000..0fd15a6 --- /dev/null +++ b/tests/event/event_selector.py @@ -0,0 +1,58 @@ +from datetime import timedelta, datetime +from typing import List, Optional + +from mobilizon_bots.event.event import MobilizonEvent + +from abc import ABC, abstractmethod + + +class EventSelectionStrategy(ABC): + @abstractmethod + def select( + self, + published_events: List[MobilizonEvent], + unpublished_events: List[MobilizonEvent], + ) -> Optional[MobilizonEvent]: + pass + + +class SelectNextEventStrategy(EventSelectionStrategy): + def __init__(self, minimum_break_between_events: timedelta = timedelta(days=0)): + self.minimum_break_between_events = minimum_break_between_events + + def select( + self, + published_events: List[MobilizonEvent], + unpublished_events: List[MobilizonEvent], + ) -> Optional[MobilizonEvent]: + + last_published_event = published_events[-1] + first_unpublished_event = unpublished_events[0] + assert ( + last_published_event.publication_time < datetime.now() + ), "Last published event has been published in the future" + + if ( + last_published_event.publication_time + self.minimum_break_between_events + > first_unpublished_event.begin_datetime + ): + return None + + return first_unpublished_event + + +class EventSelector: + def __init__( + self, + published_events: List[MobilizonEvent], + unpublished_events: List[MobilizonEvent], + ): + self.published_events = published_events.sort(key=lambda x: x.begin_datetime) + self.unpublished_events = unpublished_events.sort( + key=lambda x: x.begin_datetime + ) + + def select_event_to_publish( + self, strategy: EventSelectionStrategy + ) -> Optional[MobilizonEvent]: + return strategy.select(self.published_events, self.unpublished_events) diff --git a/tests/event/test_event.py b/tests/event/test_event.py index ff77362..0ada83d 100644 --- a/tests/event/test_event.py +++ b/tests/event/test_event.py @@ -1,9 +1,11 @@ from datetime import datetime import pytest +from hypothesis import given from jinja2 import Template from mobilizon_bots.event.event import MobilizonEvent +from tests import events @pytest.fixture() @@ -42,27 +44,6 @@ def test_format(event, simple_template): ) -def test_is_newer(): - older_event = MobilizonEvent( - name="test event", - description="description of the event", - begin_datetime=datetime(year=2021, month=1, day=1, hour=11, minute=30), - end_datetime=datetime(year=2021, month=1, day=1, hour=12, minute=30), - last_accessed=datetime.now(), - mobilizon_link="http://some_link.com/123", - mobilizon_id="12345", - thumbnail_link="http://some_link.com/123.jpg", - location="location", - ) - newer_event = MobilizonEvent( - name="test event", - description="description of the event", - begin_datetime=datetime(year=2021, month=1, day=2, hour=11, minute=30), - end_datetime=datetime(year=2021, month=1, day=1, hour=12, minute=30), - last_accessed=datetime.now(), - mobilizon_link="http://some_link.com/123", - mobilizon_id="12345", - thumbnail_link="http://some_link.com/123.jpg", - location="location", - ) - assert older_event.begins_before(newer_event) +@given(events()) +def test_event_properties(event): + assert event.end_datetime > event.begin_datetime diff --git a/tests/event/test_strategies.py b/tests/event/test_strategies.py new file mode 100644 index 0000000..d3d8668 --- /dev/null +++ b/tests/event/test_strategies.py @@ -0,0 +1,12 @@ +from hypothesis import given +from hypothesis.strategies import lists + +from tests import events + + +@given( + unpublished_events=lists(events(published=True), max_size=5), + published_events=lists(events(published=False), max_size=3), +) +def test_select_next_event_properties(unpublished_events, published_events): + pass