Merge branch 'daemon-rework' into develop
commit
593fe189cd
|
@ -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)
|
## v0.1 (2020/08/26)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -88,7 +88,10 @@ class Frank(discord.Client):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
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
|
module = next((mod for mod in self._loaded_modules
|
||||||
if mod.match(cmd[1])), None)
|
if mod.match(cmd[1])), None)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,13 @@ from __future__ import annotations
|
||||||
|
|
||||||
# Built-in imports
|
# Built-in imports
|
||||||
import re
|
import re
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# Typing imports
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
# Built-in imports
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
class Simple:
|
class Simple:
|
||||||
|
@ -95,16 +102,37 @@ class RegexCommand(Command):
|
||||||
|
|
||||||
class Daemon(Simple):
|
class Daemon(Simple):
|
||||||
"""
|
"""
|
||||||
Represents a daemon. Currently, it's only used as its own type, but writing
|
Represents a daemon a.k.a. a background process.
|
||||||
it this way allows us to easily expand upon its functionality later.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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):
|
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.
|
command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
# =====IMPORTS=====
|
# =====IMPORTS=====
|
||||||
|
# Future imports
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
# Own imports
|
# Own imports
|
||||||
from .classes import Command, RegexCommand, Daemon, Default
|
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:
|
def command(cmd, help_str: str = None) -> callable:
|
||||||
"""
|
"""
|
||||||
|
@ -33,14 +42,14 @@ def regex_command(pattern: str, help_str: str = None) -> callable:
|
||||||
return inner
|
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
|
Converts the method into a Daemon, which will then be run when the module
|
||||||
is started.
|
is started.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def inner(func):
|
def inner(func):
|
||||||
return Daemon(func)
|
return Daemon(func, interval)
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,10 @@ class Module(ModuleMeta):
|
||||||
func = next((func for func in self.commands
|
func = next((func for func in self.commands
|
||||||
if func.match(cmd[0])), None)
|
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
|
# A RegexCommand can use the prefix, as it's not a fixed string
|
||||||
if isinstance(func, RegexCommand):
|
if isinstance(func, RegexCommand):
|
||||||
await func(prefix=cmd[0], cmd=cmd[1:], author=author,
|
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,
|
await func(cmd=cmd[1:], author=author, channel=channel,
|
||||||
mid=mid)
|
mid=mid)
|
||||||
|
|
||||||
else:
|
|
||||||
raise InvalidCommand(f'Unknown command: {cmd}')
|
|
||||||
|
|
||||||
elif self.default:
|
elif self.default:
|
||||||
await self.default(author=author, channel=channel, mid=mid)
|
await self.default(author=author, channel=channel, mid=mid)
|
||||||
|
|
||||||
|
@ -125,12 +125,12 @@ class Module(ModuleMeta):
|
||||||
prefix: prefix to check
|
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):
|
if isinstance(cls.PREFIX, list):
|
||||||
return prefix in cls.PREFIX
|
return prefix in cls.PREFIX
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return prefix == cls.PREFIX
|
return prefix == cls.PREFIX
|
||||||
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# =====IMPORTS=====
|
# =====IMPORTS=====
|
||||||
|
# Third-party imports
|
||||||
|
import pytest
|
||||||
|
|
||||||
# Own imports
|
# Own imports
|
||||||
from frank import default, command, daemon
|
from frank import default, command, daemon
|
||||||
|
|
||||||
|
@ -27,7 +30,7 @@ class TestDecorators:
|
||||||
return 'daemon'
|
return 'daemon'
|
||||||
|
|
||||||
@daemon()
|
@daemon()
|
||||||
def daemon_dec(self):
|
async def daemon_dec(self):
|
||||||
return self.daemon_no_dec()
|
return self.daemon_no_dec()
|
||||||
|
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
|
@ -36,5 +39,6 @@ class TestDecorators:
|
||||||
def test_command(self):
|
def test_command(self):
|
||||||
assert self.command_no_dec() == self.command_dec()
|
assert self.command_no_dec() == self.command_dec()
|
||||||
|
|
||||||
def test_daemon(self):
|
@pytest.mark.asyncio
|
||||||
assert self.daemon_no_dec() == self.daemon_dec()
|
async def test_daemon(self):
|
||||||
|
assert self.daemon_no_dec() == await self.daemon_dec()
|
||||||
|
|
Reference in New Issue