mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-29 22:44:19 +00:00
🗑 Remove campaigns module, as it has been replaced by engineer
This commit is contained in:
parent
e2781c3743
commit
d5b409489d
9 changed files with 1 additions and 369 deletions
|
@ -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",
|
||||
)
|
|
@ -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_}")
|
|
@ -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
|
|
@ -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_}")
|
|
@ -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
|
|
@ -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",
|
||||
)
|
|
@ -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!"
|
|
@ -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!"
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
|
||||
Bullets are parts of the data model
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
Loading…
Reference in a new issue