mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-22 19:14:20 +00:00
✨ Start implementing blueprints
This commit is contained in:
parent
3b5ac09386
commit
fa6288c5db
12 changed files with 175 additions and 26 deletions
|
@ -1,7 +1,4 @@
|
||||||
``alchemist`` - SQLAlchemy utilities
|
``alchemist`` - SQLAlchemy utilities
|
||||||
====================================
|
====================================
|
||||||
|
|
||||||
.. currentmodule:: royalnet.alchemist
|
|
||||||
.. automodule:: royalnet.alchemist
|
.. automodule:: royalnet.alchemist
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
``campaigns`` - Conversation state manager
|
``campaigns`` - Conversation state manager
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
.. currentmodule:: royalnet.campaigns
|
|
||||||
.. automodule:: royalnet.campaigns
|
.. automodule:: royalnet.campaigns
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
|
@ -1,8 +1,34 @@
|
||||||
``engineer`` - Chat command router
|
``engineer`` - Chatbot framework
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
.. currentmodule:: royalnet.engineer
|
|
||||||
.. automodule:: royalnet.engineer
|
.. automodule:: royalnet.engineer
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:imported-members:
|
``blueprints`` - ABCs for common chat entities
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: royalnet.engineer.blueprints
|
||||||
|
|
||||||
|
|
||||||
|
``teleporter`` - Function parameter validation
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: royalnet.engineer.teleporter
|
||||||
|
|
||||||
|
|
||||||
|
``sentry`` - Async queue
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: royalnet.engineer.sentry
|
||||||
|
|
||||||
|
|
||||||
|
``dispenser`` - Function parameter validation
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: royalnet.engineer.dispenser
|
||||||
|
|
||||||
|
|
||||||
|
``exc`` - Exceptions
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: royalnet.engineer.exc
|
||||||
|
|
|
@ -3,7 +3,4 @@
|
||||||
|
|
||||||
.. note:: The documentation for this section hasn't been written yet.
|
.. note:: The documentation for this section hasn't been written yet.
|
||||||
|
|
||||||
.. currentmodule:: royalnet.lazy
|
|
||||||
.. automodule:: royalnet.lazy
|
.. automodule:: royalnet.lazy
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
``royaltyping`` - Additional type information
|
``royaltyping`` - Additional type information
|
||||||
=============================================
|
=============================================
|
||||||
|
|
||||||
.. currentmodule:: royalnet.royaltyping
|
|
||||||
.. automodule:: royalnet.royaltyping
|
.. automodule:: royalnet.royaltyping
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
``scrolls`` - Configuration loader
|
``scrolls`` - Configuration loader
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
.. currentmodule:: royalnet.scrolls
|
|
||||||
.. automodule:: royalnet.scrolls
|
.. automodule:: royalnet.scrolls
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
|
|
|
@ -73,12 +73,21 @@ intersphinx_mapping = {
|
||||||
|
|
||||||
# -- Setup function ----------------------------------------------------------
|
# -- Setup function ----------------------------------------------------------
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.connect("autodoc-skip-member", skip)
|
|
||||||
app.add_css_file('royalblue.css')
|
app.add_css_file('royalblue.css')
|
||||||
|
|
||||||
|
|
||||||
# -- Skip function -----------------------------------------------------------
|
# -- Substitutions -----------------------------------------------------------
|
||||||
def skip(app, what, name: str, obj, would_skip, options):
|
|
||||||
if name == "__init__" or name == "__getitem__" or name == "__getattr__":
|
|
||||||
return not bool(obj.__doc__)
|
rst_prolog = """
|
||||||
return would_skip
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -- Automodule settings -----------------------------------------------------
|
||||||
|
|
||||||
|
autodoc_default_options = {
|
||||||
|
'members': True,
|
||||||
|
'member-order': 'bysource',
|
||||||
|
'special-members': '__init__',
|
||||||
|
'undoc-members': True,
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
"""
|
"""
|
||||||
A chatbot command router inspired by :mod:`fastapi`.
|
A chatbot command router inspired by :mod:`fastapi`.
|
||||||
|
|
||||||
|
All names are inspired by the `Engineer Class of Team Fortress 2 <https://wiki.teamfortress.com/wiki/Engineer>`_.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .blueprints import *
|
||||||
from .teleporter import *
|
from .teleporter import *
|
||||||
|
from .sentry import *
|
||||||
|
from .exc import *
|
||||||
|
|
81
royalnet/engineer/blueprints.py
Normal file
81
royalnet/engineer/blueprints.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from royalnet.royaltyping import *
|
||||||
|
import abc
|
||||||
|
import datetime
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from . import exc
|
||||||
|
|
||||||
|
|
||||||
|
class Message(metaclass=abc.ABCMeta):
|
||||||
|
"""
|
||||||
|
An abstract class representing a generic chat message sent in any platform.
|
||||||
|
|
||||||
|
To implement it for a specific platform, override :meth:`__hash__` and the methods returning information that the
|
||||||
|
platform sometimes provides, either returning the value or raising :exc:`.exc.NotAvailableError`.
|
||||||
|
|
||||||
|
All properties are cached using :func:`functools.lru_cache`, so that if they are successful, they are executed only
|
||||||
|
one time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __hash__(self):
|
||||||
|
"""
|
||||||
|
:return: A value that uniquely identifies the message inside this Python process.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def requires(self, *fields) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that this message has the specified fields, raising the highest priority exception between all the
|
||||||
|
fields.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
def print_msg(message: Message):
|
||||||
|
message.requires(Message.text, Message.timestamp)
|
||||||
|
print(f"{message.timestamp().isoformat()}: {message.text()}")
|
||||||
|
|
||||||
|
:raises .exc.NeverAvailableError: If at least one of the fields raised a :exc:`.exc.NeverAvailableError`.
|
||||||
|
:raises .exc.NotAvailableError: If no field raised a :exc:`.exc.NeverAvailableError`, but at least one raised a
|
||||||
|
:exc:`.exc.NotAvailableError`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
exceptions = []
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
try:
|
||||||
|
field(self)
|
||||||
|
except exc.NeverAvailableError as ex:
|
||||||
|
exceptions.append(ex)
|
||||||
|
except exc.NotAvailableError as ex:
|
||||||
|
exceptions.append(ex)
|
||||||
|
|
||||||
|
if len(exceptions) > 0:
|
||||||
|
raise max(exceptions, key=lambda e: e.priority)
|
||||||
|
|
||||||
|
@functools.lru_cache()
|
||||||
|
def text(self) -> str:
|
||||||
|
"""
|
||||||
|
:return: The raw text contents of the message.
|
||||||
|
:raises .exc.NeverAvailableError: If the chat platform does not support text messages.
|
||||||
|
:raises .exc.NotAvailableError: If this message does not have any text.
|
||||||
|
"""
|
||||||
|
raise exc.NeverAvailableError()
|
||||||
|
|
||||||
|
@functools.lru_cache()
|
||||||
|
def timestamp(self) -> datetime.datetime:
|
||||||
|
"""
|
||||||
|
:return: The :class:`datetime.datetime` at which the message was sent / received.
|
||||||
|
:raises .exc.NeverAvailableError: If the chat platform does not support timestamps.
|
||||||
|
:raises .exc.NotAvailableError: If this message is special and does not have any timestamp.
|
||||||
|
"""
|
||||||
|
raise exc.NeverAvailableError()
|
||||||
|
|
||||||
|
@functools.lru_cache()
|
||||||
|
def reply_to(self) -> Message:
|
||||||
|
"""
|
||||||
|
:return: The :class:`.Message` this message is a reply to.
|
||||||
|
:raises .exc.NeverAvailableError: If the chat platform does not support replies.
|
||||||
|
:raises .exc.NotAvailableError: If this message is not a reply to any other message.
|
||||||
|
"""
|
0
royalnet/engineer/dispenser.py
Normal file
0
royalnet/engineer/dispenser.py
Normal file
|
@ -8,6 +8,28 @@ class EngineerException(royalnet.exc.RoyalnetException):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class BlueprintError(EngineerException):
|
||||||
|
"""
|
||||||
|
An error related to the :mod:`royalnet.engineer.blueprints`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class NeverAvailableError(BlueprintError, NotImplementedError):
|
||||||
|
"""
|
||||||
|
The requested property is never supplied by the chat platform the message was sent in.
|
||||||
|
"""
|
||||||
|
|
||||||
|
priority = -1
|
||||||
|
|
||||||
|
|
||||||
|
class NotAvailableError(BlueprintError):
|
||||||
|
"""
|
||||||
|
The requested property was not supplied by the chat platform for the specific message this exception was raised in.
|
||||||
|
"""
|
||||||
|
|
||||||
|
priority = -2
|
||||||
|
|
||||||
|
|
||||||
class TeleporterError(EngineerException, pydantic.ValidationError):
|
class TeleporterError(EngineerException, pydantic.ValidationError):
|
||||||
"""
|
"""
|
||||||
The validation of some object though a :mod:`pydantic` model failed.
|
The validation of some object though a :mod:`pydantic` model failed.
|
||||||
|
|
21
royalnet/engineer/sentry.py
Normal file
21
royalnet/engineer/sentry.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from royalnet.royaltyping import *
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Sentry:
|
||||||
|
"""
|
||||||
|
A class that allows using the ``await`` keyword to suspend a command execution until a new message is received.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.queue = asyncio.queues.Queue()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Sentry, {self.queue.qsize()} items queued>"
|
||||||
|
|
||||||
|
async def wait_for_item(self) -> Any:
|
||||||
|
log.debug("Waiting for an item...")
|
||||||
|
return await self.queue.get()
|
Loading…
Reference in a new issue