mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-26 21:14: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
|
from __future__ import annotations
|
||||||
|
|
Loading…
Reference in a new issue