mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-26 21:14:19 +00:00
✨ Implement a dispenser locking mechanism
This commit is contained in:
parent
7af394d3bf
commit
c2b01d768f
3 changed files with 79 additions and 6 deletions
|
@ -32,7 +32,7 @@ class Command(c.Conversation):
|
||||||
are later completed by a runner.
|
are later completed by a runner.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, f: c.ConversationProtocol, *, names: t.List[str] = None, pattern: re.Pattern):
|
def __init__(self, f: c.ConversationProtocol, *, names: t.List[str] = None, pattern: re.Pattern, lock: bool = True):
|
||||||
"""
|
"""
|
||||||
Create a new :class:`.Command` .
|
Create a new :class:`.Command` .
|
||||||
"""
|
"""
|
||||||
|
@ -54,6 +54,11 @@ class Command(c.Conversation):
|
||||||
The pattern that should be matched by the command.
|
The pattern that should be matched by the command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.lock: bool = lock
|
||||||
|
"""
|
||||||
|
If calling this command should :meth:`~royalnet.engineer.dispenser.Dispenser.lock` the dispenser.
|
||||||
|
"""
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
"""
|
"""
|
||||||
:return: The main name of the Command.
|
:return: The main name of the Command.
|
||||||
|
@ -96,9 +101,17 @@ class Command(c.Conversation):
|
||||||
|
|
||||||
log.debug(f"Match successful, getting capture groups of: {match!r}")
|
log.debug(f"Match successful, getting capture groups of: {match!r}")
|
||||||
message_kwargs: t.Dict[str, str] = match.groupdict()
|
message_kwargs: t.Dict[str, str] = match.groupdict()
|
||||||
|
|
||||||
log.debug(f"Passing args to function: {message_kwargs!r}")
|
if self.lock:
|
||||||
return await super().run(_sentry=_sentry, _msg=bullet, **base_kwargs, **message_kwargs)
|
log.debug(f"Locking the dispenser...")
|
||||||
|
|
||||||
|
with _sentry.dispenser().lock(self):
|
||||||
|
log.debug(f"Passing args to function: {message_kwargs!r}")
|
||||||
|
return await super().run(_sentry=_sentry, _msg=bullet, **base_kwargs, **message_kwargs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.debug(f"Passing args to function: {message_kwargs!r}")
|
||||||
|
return await super().run(_sentry=_sentry, _msg=bullet, **base_kwargs, **message_kwargs)
|
||||||
|
|
||||||
def help(self) -> t.Optional[str]:
|
def help(self) -> t.Optional[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -116,7 +129,7 @@ class PartialCommand:
|
||||||
|
|
||||||
They can specified later using :meth:`.complete`.
|
They can specified later using :meth:`.complete`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, f: c.ConversationProtocol, syntax: str):
|
def __init__(self, f: c.ConversationProtocol, syntax: str, lock: bool = True):
|
||||||
"""
|
"""
|
||||||
Create a new :class:`.PartialCommand` .
|
Create a new :class:`.PartialCommand` .
|
||||||
|
|
||||||
|
@ -133,6 +146,11 @@ class PartialCommand:
|
||||||
Part of the pattern from where the arguments should be captured.
|
Part of the pattern from where the arguments should be captured.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.lock: bool = lock
|
||||||
|
"""
|
||||||
|
If calling this command should :meth:`~royalnet.engineer.dispenser.Dispenser.lock` the dispenser.
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, *args, **kwargs):
|
def new(cls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -167,8 +185,10 @@ class PartialCommand:
|
||||||
raise ValueError(f"Name is not alphanumeric: {name!r}")
|
raise ValueError(f"Name is not alphanumeric: {name!r}")
|
||||||
|
|
||||||
name_regex = f"(?:{'|'.join(names)})"
|
name_regex = f"(?:{'|'.join(names)})"
|
||||||
|
log.debug(f"Completed pattern: {name_regex!r}")
|
||||||
|
|
||||||
pattern: re.Pattern = re.compile(pattern.format(name=name_regex, syntax=self.syntax), re.IGNORECASE)
|
pattern: re.Pattern = re.compile(pattern.format(name=name_regex, syntax=self.syntax), re.IGNORECASE)
|
||||||
return Command(f=self.f, names=names, pattern=pattern)
|
return Command(f=self.f, names=names, pattern=pattern, lock=self.lock)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__qualname__} {self.f!r}>"
|
return f"<{self.__class__.__qualname__} {self.f!r}>"
|
||||||
|
|
|
@ -10,6 +10,7 @@ import contextlib
|
||||||
|
|
||||||
from .sentry import SentrySource
|
from .sentry import SentrySource
|
||||||
from .conversation import Conversation
|
from .conversation import Conversation
|
||||||
|
from .exc import LockedDispenserError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -21,6 +22,13 @@ class Dispenser:
|
||||||
A :class:`list` of all the running sentries of this dispenser.
|
A :class:`list` of all the running sentries of this dispenser.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self._locked_by: t.List[Conversation] = []
|
||||||
|
"""
|
||||||
|
The conversation that are currently locking this dispenser.
|
||||||
|
|
||||||
|
.. seealso:: :meth:`.lock`
|
||||||
|
"""
|
||||||
|
|
||||||
async def put(self, item: t.Any) -> None:
|
async def put(self, item: t.Any) -> None:
|
||||||
"""
|
"""
|
||||||
Insert a new item in the queues of all the running sentries.
|
Insert a new item in the queues of all the running sentries.
|
||||||
|
@ -54,7 +62,15 @@ class Dispenser:
|
||||||
Run the passed conversation.
|
Run the passed conversation.
|
||||||
|
|
||||||
:param conv: The conversation to run.
|
:param conv: The conversation to run.
|
||||||
|
:raises .LockedDispenserError: If the dispenser is currently :attr:`.locked_by` a :class:`.Conversation`.
|
||||||
"""
|
"""
|
||||||
|
log.debug(f"Trying to run: {conv!r}")
|
||||||
|
|
||||||
|
if self._locked_by is not None:
|
||||||
|
log.debug(f"Dispenser is locked by {self._locked_by!r}, refusing to run {conv!r}")
|
||||||
|
raise LockedDispenserError(self._locked_by, f"The Dispenser is currently locked by {self._locked_by!r} and "
|
||||||
|
f"cannot start new conversations.")
|
||||||
|
|
||||||
log.debug(f"Running: {conv}")
|
log.debug(f"Running: {conv}")
|
||||||
with self.sentry() as sentry:
|
with self.sentry() as sentry:
|
||||||
state = conv(_sentry=sentry, **kwargs)
|
state = conv(_sentry=sentry, **kwargs)
|
||||||
|
@ -63,6 +79,26 @@ class Dispenser:
|
||||||
while state := await state:
|
while state := await state:
|
||||||
log.debug(f"Switched to: {state}")
|
log.debug(f"Switched to: {state}")
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def lock(self, conv: Conversation):
|
||||||
|
"""
|
||||||
|
Lock the dispenser while this :func:`~contextlib.contextmanager` is in scope.
|
||||||
|
|
||||||
|
A locked dispenser will refuse to :meth:`.run` any new conversations, raising :exc:`.exc.LockedDispenserError`
|
||||||
|
instead.
|
||||||
|
|
||||||
|
:param conv: The conversation that requested the lock.
|
||||||
|
|
||||||
|
.. seealso:: :attr:`._locked_by`
|
||||||
|
"""
|
||||||
|
log.debug(f"Adding lock: {conv!r}")
|
||||||
|
self._locked_by.append(conv)
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
log.debug(f"Clearing lock: {conv!r}")
|
||||||
|
self._locked_by.remove(conv)
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"Dispenser",
|
"Dispenser",
|
||||||
|
|
|
@ -61,6 +61,21 @@ class ForbiddenError(FrontendError):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DispenserException(EngineerException):
|
||||||
|
"""
|
||||||
|
The base class for errors in :mod:`royalnet.engineer.dispenser`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class LockedDispenserError(DispenserException):
|
||||||
|
"""
|
||||||
|
The dispenser couldn't start a new conversation as it is currently locked.
|
||||||
|
"""
|
||||||
|
def __init__(self, locked_by, *args):
|
||||||
|
super().__init__(*args)
|
||||||
|
self.locked_by = locked_by
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"EngineerException",
|
"EngineerException",
|
||||||
"WrenchException",
|
"WrenchException",
|
||||||
|
@ -72,4 +87,6 @@ __all__ = (
|
||||||
"FrontendError",
|
"FrontendError",
|
||||||
"NotSupportedError",
|
"NotSupportedError",
|
||||||
"ForbiddenError",
|
"ForbiddenError",
|
||||||
|
"DispenserException",
|
||||||
|
"LockedDispenserError",
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue