From d5b409489d1975cb5d441d94fa539f397a17ea5c Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 29 Dec 2020 16:36:55 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=97=91=20Remove=20campaigns=20module,=20a?= =?UTF-8?q?s=20it=20has=20been=20replaced=20by=20engineer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- royalnet/campaigns/__init__.py | 19 ---- royalnet/campaigns/asynccampaign.py | 99 ------------------- royalnet/campaigns/asyncchallenge.py | 24 ----- royalnet/campaigns/campaign.py | 77 --------------- royalnet/campaigns/challenge.py | 24 ----- royalnet/campaigns/exc.py | 19 ---- .../campaigns/tests/test_asynccampaign.py | 55 ----------- royalnet/campaigns/tests/test_campaign.py | 51 ---------- royalnet/engineer/bullet.py | 2 +- 9 files changed, 1 insertion(+), 369 deletions(-) delete mode 100644 royalnet/campaigns/__init__.py delete mode 100644 royalnet/campaigns/asynccampaign.py delete mode 100644 royalnet/campaigns/asyncchallenge.py delete mode 100644 royalnet/campaigns/campaign.py delete mode 100644 royalnet/campaigns/challenge.py delete mode 100644 royalnet/campaigns/exc.py delete mode 100644 royalnet/campaigns/tests/test_asynccampaign.py delete mode 100644 royalnet/campaigns/tests/test_campaign.py diff --git a/royalnet/campaigns/__init__.py b/royalnet/campaigns/__init__.py deleted file mode 100644 index 3324b8e8..00000000 --- a/royalnet/campaigns/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -This module defines multiple classes that can be used to facilitate the implementation of a conversational interface -in chat bots. -""" - -from .campaign import * -from .asynccampaign import * -from .challenge import * -from .asyncchallenge import * - - -__all__ = ( - "Campaign", - "AsyncCampaign", - "Challenge", - "TrueChallenge", - "AsyncChallenge", - "TrueAsyncChallenge", -) diff --git a/royalnet/campaigns/asynccampaign.py b/royalnet/campaigns/asynccampaign.py deleted file mode 100644 index 00324dbf..00000000 --- a/royalnet/campaigns/asynccampaign.py +++ /dev/null @@ -1,99 +0,0 @@ -from __future__ import annotations -from royalnet.royaltyping import * -import logging -import inspect -import datetime -from .asyncchallenge import AsyncChallenge, TrueAsyncChallenge -from .exc import * -log = logging.getLogger(__name__) - -__all__ = ( - "AsyncCampaign", -) - - -class AsyncCampaign: - """ - The AsyncCampaign module allows for branching asyncgenerator-based back-and-forths between the software and the - user. - - An :class:`.AsyncCampaign` consists of multiple chained AsyncAdventures, which are AsyncGenerators yielding tuples - with an AsyncChallenge and optional data. - """ - def __init__(self, start: AsyncAdventure, challenge: Optional[AsyncChallenge], *args, **kwargs): - """ - Initialize an :class:`.AsyncCampaign` object. - - .. warning:: Do not use this, use the :meth:`.create()` method instead! - - :param start: The starting adventure for the :class:`.AsyncCampaign`. - """ - self.adventure: AsyncAdventure = start - self.challenge: AsyncChallenge = challenge or TrueAsyncChallenge() - self.last_update: datetime.datetime = ... - - @classmethod - async def create(cls, start: AsyncAdventure, challenge: Optional[AsyncChallenge] = None, *args, **kwargs) -> AsyncCampaign: - """ - Create a new :class:`.AsyncCampaign` object. - - :param start: The starting Adventure for the :class:`.AsyncCampaign`. - :param challenge: The AsyncChallenge the campaign should start with. - :return: The created :class:`.AsyncCampaign`. - """ - campaign = cls(start=start, challenge=challenge, *args, **kwargs) - await campaign._asend(None) - return campaign - - async def _asend(self, data: Any) -> Any: - try: - return await self.adventure.asend(data) - except RuntimeError: - log.error(f"{self.adventure} is being used unexpectedly by something else!") - raise - - async def _athrow(self, typ: Type[BaseException], val: Optional[BaseException], tb: Any) -> Any: - try: - return await self.adventure.athrow(typ, val, tb) - except RuntimeError: - log.error(f"{self.adventure} is being used unexpectedly by something else!") - raise - - async def _aclose(self) -> None: - try: - await self.adventure.aclose() - except RuntimeError: - log.error(f"{self.adventure} is being used unexpectedly by something else!") - raise - - async def next(self, data: Any = None) -> List: - """ - Try to advance the :class:`.AsyncCampaign` with the passed data. - - :param data: The data to pass to the current AsyncAdventure. - :return: Optional additional data returned by the AsyncAdventure. - :raises ChallengeFailedError: if the data passed fails the AsyncChallenge check. - """ - self.last_update = datetime.datetime.now() - if not await self.challenge.filter(data): - raise ChallengeFailedError(f"{data} failed the {self.challenge} challenge") - result = await self._asend(data) - if inspect.isasyncgen(result): - await self._aclose() - self.adventure = result - await self._asend(None) - return await self.next(data) - elif isinstance(result, AsyncChallenge): - self.challenge = result - return [] - elif result is None: - return [] - elif isinstance(result, tuple) and len(result) > 0: - if isinstance(result[0], AsyncChallenge): - self.challenge, *output = result - return output - elif result[0] is None: - _, *output = result - return output - else: - raise TypeError(f"AsyncAdventure yielded an invalid type: {result.__class_}") diff --git a/royalnet/campaigns/asyncchallenge.py b/royalnet/campaigns/asyncchallenge.py deleted file mode 100644 index cc1b689f..00000000 --- a/royalnet/campaigns/asyncchallenge.py +++ /dev/null @@ -1,24 +0,0 @@ -from royalnet.royaltyping import * -import abc - -__all__ = ( - "AsyncChallenge", - "TrueAsyncChallenge", -) - - -class AsyncChallenge(metaclass=abc.ABCMeta): - """A filter for inputs passed to an :class:`.AsyncCampaign`.""" - - @abc.abstractmethod - async def filter(self, data: Any) -> bool: - """Decide if the data should be skipped or not.""" - raise NotImplementedError() - - -class TrueAsyncChallenge(AsyncChallenge): - """An :class:`.AsyncChallenge` which always returns :data:`True`.""" - - async def filter(self, data: Any) -> bool: - """Decide if the data should be skipped or not.""" - return True diff --git a/royalnet/campaigns/campaign.py b/royalnet/campaigns/campaign.py deleted file mode 100644 index c3920440..00000000 --- a/royalnet/campaigns/campaign.py +++ /dev/null @@ -1,77 +0,0 @@ -from __future__ import annotations -from royalnet.royaltyping import * -import logging -import inspect -import datetime -from .challenge import Challenge, TrueChallenge -from .exc import * -log = logging.getLogger(__name__) - -__all__ = ( - "Campaign", -) - - -class Campaign: - """ - The Campaign module allows for branching generator-based back-and-forths between the software and the - user. - - A :class:`.Campaign` consists of multiple chained Adventures, which are Generators yielding tuples with a - :class:`.Campaign` and optional data. - """ - - def __init__(self, start: Adventure, challenge: Optional[Challenge] = None, *args, **kwargs): - """ - Initialize a :class:`.Campaign` object. - - .. warning:: Do not use this, use the :meth:`.create` method instead! - """ - self.adventure: Adventure = start - self.challenge: Challenge = challenge or TrueChallenge() - self.last_update: datetime.datetime = ... - - @classmethod - def create(cls, start: Adventure, challenge: Optional[Challenge] = None, *args, **kwargs) -> Campaign: - """ - Create a new :class:`.Campaign` object. - - :param start: The starting Adventure for the :class:`.Campaign`. - :param challenge: The Challenge the :class:`.Campaign` should start with. - :return: The created :class:`.Campaign`. - """ - campaign = cls(start=start, challenge=challenge, *args, **kwargs) - campaign.adventure.send(None) - return campaign - - def next(self, data: Any = None) -> List: - """ - Try to advance the :class:`.Campaign` with the passed data. - - :param data: The data to pass to the current Adventure. - :return: Optional additional data returned by the Adventure. - :raises .ChallengeFailedError: if the data passed fails the Challenge check. - """ - self.last_update = datetime.datetime.now() - if not self.challenge.filter(data): - raise ChallengeFailedError(f"{data} failed the {self.challenge} challenge") - result = self.adventure.send(data) - if inspect.isgenerator(result): - self.adventure.close() - self.adventure = result - self.adventure.send(None) - return self.next(data) - elif isinstance(result, Challenge): - self.challenge = result - return [] - elif result is None: - return [] - elif isinstance(result, tuple) and len(result) > 0: - if isinstance(result[0], Challenge): - self.challenge, *output = result - return output - elif result[0] is None: - _, *output = result - return output - else: - raise TypeError(f"Adventure yielded an invalid type: {result.__class_}") diff --git a/royalnet/campaigns/challenge.py b/royalnet/campaigns/challenge.py deleted file mode 100644 index bad1b406..00000000 --- a/royalnet/campaigns/challenge.py +++ /dev/null @@ -1,24 +0,0 @@ -from royalnet.royaltyping import * -import abc - -__all__ = ( - "Challenge", - "TrueChallenge", -) - - -class Challenge(metaclass=abc.ABCMeta): - """A filter for inputs passed to a :class:`.Campaign`.""" - - @abc.abstractmethod - def filter(self, data: Any) -> bool: - """Decide if the data should be skipped or not.""" - raise NotImplementedError() - - -class TrueChallenge(Challenge): - """A :class:`.Challenge` which always returns :data:`True`.""" - - async def filter(self, data: Any) -> bool: - """Decide if the data should be skipped or not.""" - return True diff --git a/royalnet/campaigns/exc.py b/royalnet/campaigns/exc.py deleted file mode 100644 index 7a9d039f..00000000 --- a/royalnet/campaigns/exc.py +++ /dev/null @@ -1,19 +0,0 @@ -from ..exc import RoyalnetException - - -class CampaignsError(RoyalnetException): - """ - An error related to the campaigns module. - """ - - -class ChallengeFailedError(CampaignsError): - """ - The data passed to the Campaign (or its async equivalent) failed the challenge. - """ - - -__all__ = ( - "CampaignsError", - "ChallengeFailedError", -) diff --git a/royalnet/campaigns/tests/test_asynccampaign.py b/royalnet/campaigns/tests/test_asynccampaign.py deleted file mode 100644 index af277fca..00000000 --- a/royalnet/campaigns/tests/test_asynccampaign.py +++ /dev/null @@ -1,55 +0,0 @@ -import pytest -from ..asynccampaign import AsyncCampaign -from ..asyncchallenge import AsyncChallenge -from ..exc import * - - -@pytest.mark.asyncio -async def test_creation(): - async def gen(): - yield - - await AsyncCampaign.create(start=gen()) - - -@pytest.mark.asyncio -async def test_send_receive(): - async def gen(): - ping = yield - assert ping == "Ping!" - yield None, "Pong!" - - campaign = await AsyncCampaign.create(start=gen()) - pong, = await campaign.next("Ping!") - assert pong == "Pong!" - - -class FalseChallenge(AsyncChallenge): - async def filter(self, data) -> bool: - return False - - -@pytest.mark.asyncio -async def test_failing_check(): - async def gen(): - yield - - campaign = await AsyncCampaign.create(start=gen(), challenge=FalseChallenge()) - with pytest.raises(ChallengeFailedError): - await campaign.next() - - -@pytest.mark.asyncio -async def test_switching(): - async def gen_1(): - yield - yield gen_2() - - async def gen_2(): - yield - yield None, "Second message!" - yield None - - campaign = await AsyncCampaign.create(start=gen_1()) - data, = await campaign.next() - assert data == "Second message!" diff --git a/royalnet/campaigns/tests/test_campaign.py b/royalnet/campaigns/tests/test_campaign.py deleted file mode 100644 index 66cbaada..00000000 --- a/royalnet/campaigns/tests/test_campaign.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest -from ..campaign import Campaign -from ..challenge import Challenge, TrueChallenge -from ..exc import * - - -def test_creation(): - def gen(): - yield - - Campaign.create(start=gen()) - - -def test_send_receive(): - def gen(): - ping = yield - assert ping == "Ping!" - yield None, "Pong!" - - campaign = Campaign.create(start=gen()) - pong, = campaign.next("Ping!") - assert pong == "Pong!" - - -class FalseChallenge(Challenge): - def filter(self, data) -> bool: - return False - - -def test_failing_check(): - def gen(): - yield - - campaign = Campaign.create(start=gen(), challenge=FalseChallenge()) - with pytest.raises(ChallengeFailedError): - campaign.next() - - -def test_switching(): - def gen_1(): - yield - yield gen_2() - - def gen_2(): - yield - yield None, "Second message!" - yield None - - campaign = Campaign.create(start=gen_1()) - data, = campaign.next() - assert data == "Second message!" diff --git a/royalnet/engineer/bullet.py b/royalnet/engineer/bullet.py index e32e9a22..459bd46d 100644 --- a/royalnet/engineer/bullet.py +++ b/royalnet/engineer/bullet.py @@ -1,5 +1,5 @@ """ - +Bullets are parts of the data model """ from __future__ import annotations