Merge branch 'daemon-rework' into develop

develop
Jef Roosens 2020-08-31 22:38:57 +02:00
commit 593fe189cd
6 changed files with 78 additions and 30 deletions

View File

@ -1,3 +1,7 @@
## Unreleased
### Added
- Daemons can now accept an interval value, removing the need for a manual while True loop
## v0.1 (2020/08/26)
### Added

View File

@ -88,7 +88,10 @@ class Frank(discord.Client):
except ValueError:
return
if cmd and cmd[0] == self.PREFIX:
# Exit if no commands are given or prefix is wrong
if not (cmd and cmd[0] == self.PREFIX):
return
module = next((mod for mod in self._loaded_modules
if mod.match(cmd[1])), None)

View File

@ -4,6 +4,13 @@ from __future__ import annotations
# Built-in imports
import re
import asyncio
# Typing imports
from typing import TYPE_CHECKING
if TYPE_CHECKING:
# Built-in imports
from typing import Union
class Simple:
@ -95,16 +102,37 @@ class RegexCommand(Command):
class Daemon(Simple):
"""
Represents a daemon. Currently, it's only used as its own type, but writing
it this way allows us to easily expand upon its functionality later.
Represents a daemon a.k.a. a background process.
"""
pass
def __init__(self, func: callable, interval: Union[int, float] = 0):
"""
Args
func: function to wrap
interval: time between calls of the function; if < 0, the function
is assumed to contain its own infinite loop, allowing for
fine-grained control of the daemon, if desired
"""
super().__init__(func)
self.interval = interval
# If an interval > 0 is given, it wraps the function inside an infinite
# loop with the desired delay
if interval > 0:
async def loop(self, *args, **kwargs):
while True:
# TODO: does this make func and sleep run at the same time?
await func(self, *args, **kwargs)
await asyncio.sleep(interval)
self.func = loop
class Default(Simple):
"""
Represents a default command (a.k.a. when the module is called without a
Represents a default command a.k.a. when the module is called without a
command.
"""

View File

@ -1,7 +1,16 @@
# =====IMPORTS=====
# Future imports
from __future__ import annotations
# Own imports
from .classes import Command, RegexCommand, Daemon, Default
# Typing imports
from typing import TYPE_CHECKING
if TYPE_CHECKING:
# Built-in imports
from typing import Union
def command(cmd, help_str: str = None) -> callable:
"""
@ -33,14 +42,14 @@ def regex_command(pattern: str, help_str: str = None) -> callable:
return inner
def daemon() -> callable:
def daemon(interval: Union[int, float] = 0) -> callable:
"""
Converts the method into a Daemon, which will then be run when the module
is started.
"""
def inner(func):
return Daemon(func)
return Daemon(func, interval)
return inner

View File

@ -100,7 +100,10 @@ class Module(ModuleMeta):
func = next((func for func in self.commands
if func.match(cmd[0])), None)
if func:
# Throw error if no function is found
if not func:
raise InvalidCommand(f'Unknown command: {cmd}')
# A RegexCommand can use the prefix, as it's not a fixed string
if isinstance(func, RegexCommand):
await func(prefix=cmd[0], cmd=cmd[1:], author=author,
@ -110,9 +113,6 @@ class Module(ModuleMeta):
await func(cmd=cmd[1:], author=author, channel=channel,
mid=mid)
else:
raise InvalidCommand(f'Unknown command: {cmd}')
elif self.default:
await self.default(author=author, channel=channel, mid=mid)
@ -125,12 +125,12 @@ class Module(ModuleMeta):
prefix: prefix to check
"""
if cls.PREFIX:
# Always return False if there's no PREFIX defined
if not cls.PREFIX:
return False
if isinstance(cls.PREFIX, list):
return prefix in cls.PREFIX
else:
return prefix == cls.PREFIX
else:
return False

View File

@ -1,4 +1,7 @@
# =====IMPORTS=====
# Third-party imports
import pytest
# Own imports
from frank import default, command, daemon
@ -27,7 +30,7 @@ class TestDecorators:
return 'daemon'
@daemon()
def daemon_dec(self):
async def daemon_dec(self):
return self.daemon_no_dec()
def test_default(self):
@ -36,5 +39,6 @@ class TestDecorators:
def test_command(self):
assert self.command_no_dec() == self.command_dec()
def test_daemon(self):
assert self.daemon_no_dec() == self.daemon_dec()
@pytest.mark.asyncio
async def test_daemon(self):
assert self.daemon_no_dec() == await self.daemon_dec()