Implemented RunnablePlugin.wait_stop() utility method

This commit is contained in:
Fabio Manganiello 2022-04-05 23:33:02 +02:00
parent 061268cdaf
commit d52ae2fb80
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
3 changed files with 93 additions and 71 deletions

View File

@ -19,12 +19,12 @@ def action(f):
result = f(*args, **kwargs) result = f(*args, **kwargs)
if result and isinstance(result, Response): if result and isinstance(result, Response):
result.errors = result.errors \ result.errors = (
if isinstance(result.errors, list) else [result.errors] result.errors if isinstance(result.errors, list) else [result.errors]
)
response = result response = result
elif isinstance(result, tuple) and len(result) == 2: elif isinstance(result, tuple) and len(result) == 2:
response.errors = result[1] \ response.errors = result[1] if isinstance(result[1], list) else [result[1]]
if isinstance(result[1], list) else [result[1]]
if len(response.errors) == 1 and response.errors[0] is None: if len(response.errors) == 1 and response.errors[0] is None:
response.errors = [] response.errors = []
@ -39,12 +39,14 @@ def action(f):
return _execute_action return _execute_action
class Plugin(EventGenerator, ExtensionWithManifest): # lgtm [py/missing-call-to-init] class Plugin(EventGenerator, ExtensionWithManifest): # lgtm [py/missing-call-to-init]
""" Base plugin class """ """Base plugin class"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__() super().__init__()
self.logger = logging.getLogger('platypush:plugin:' + get_plugin_name_by_class(self.__class__)) self.logger = logging.getLogger(
'platypush:plugin:' + get_plugin_name_by_class(self.__class__)
)
if 'logging' in kwargs: if 'logging' in kwargs:
self.logger.setLevel(getattr(logging, kwargs['logging'].upper())) self.logger.setLevel(getattr(logging, kwargs['logging'].upper()))
@ -53,8 +55,9 @@ class Plugin(EventGenerator, ExtensionWithManifest): # lgtm [py/missing-call-t
) )
def run(self, method, *args, **kwargs): def run(self, method, *args, **kwargs):
assert method in self.registered_actions, '{} is not a registered action on {}'.\ assert (
format(method, self.__class__.__name__) method in self.registered_actions
), '{} is not a registered action on {}'.format(method, self.__class__.__name__)
return getattr(self, method)(*args, **kwargs) return getattr(self, method)(*args, **kwargs)
@ -62,6 +65,7 @@ class RunnablePlugin(Plugin):
""" """
Class for runnable plugins - i.e. plugins that have a start/stop method and can be started. Class for runnable plugins - i.e. plugins that have a start/stop method and can be started.
""" """
def __init__(self, poll_interval: Optional[float] = None, **kwargs): def __init__(self, poll_interval: Optional[float] = None, **kwargs):
""" """
:param poll_interval: How often the :meth:`.loop` function should be execute (default: None, no pause/interval). :param poll_interval: How often the :meth:`.loop` function should be execute (default: None, no pause/interval).
@ -78,6 +82,9 @@ class RunnablePlugin(Plugin):
def should_stop(self): def should_stop(self):
return self._should_stop.is_set() return self._should_stop.is_set()
def wait_stop(self, timeout=None):
return self._should_stop.wait(timeout)
def start(self): def start(self):
set_thread_name(self.__class__.__name__) set_thread_name(self.__class__.__name__)
self._thread = threading.Thread(target=self._runner) self._thread = threading.Thread(target=self._runner)

View File

@ -2,7 +2,11 @@ import os
from typing import Sequence, Dict, Tuple, Union, Optional from typing import Sequence, Dict, Tuple, Union, Optional
from platypush.plugins import RunnablePlugin, action from platypush.plugins import RunnablePlugin, action
from platypush.schemas.irc import IRCServerSchema, IRCServerStatusSchema, IRCChannelSchema from platypush.schemas.irc import (
IRCServerSchema,
IRCServerStatusSchema,
IRCChannelSchema,
)
from ._bot import IRCBot from ._bot import IRCBot
from .. import ChatPlugin from .. import ChatPlugin
@ -59,29 +63,19 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@property @property
def _bots_by_server(self) -> Dict[str, IRCBot]: def _bots_by_server(self) -> Dict[str, IRCBot]:
return { return {bot.server: bot for srv, bot in self._bots.items()}
bot.server: bot
for srv, bot in self._bots.items()
}
@property @property
def _bots_by_server_and_port(self) -> Dict[Tuple[str, int], IRCBot]: def _bots_by_server_and_port(self) -> Dict[Tuple[str, int], IRCBot]:
return { return {(bot.server, bot.port): bot for srv, bot in self._bots.items()}
(bot.server, bot.port): bot
for srv, bot in self._bots.items()
}
@property @property
def _bots_by_alias(self) -> Dict[str, IRCBot]: def _bots_by_alias(self) -> Dict[str, IRCBot]:
return { return {bot.alias: bot for srv, bot in self._bots.items() if bot.alias}
bot.alias: bot
for srv, bot in self._bots.items()
if bot.alias
}
def main(self): def main(self):
self._connect() self._connect()
self._should_stop.wait() self.wait_stop()
def _connect(self): def _connect(self):
for srv, bot in self._bots.items(): for srv, bot in self._bots.items():
@ -109,7 +103,11 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@action @action
def send_file( def send_file(
self, file: str, server: Union[str, Tuple[str, int]], nick: str, bind_address: Optional[str] = None self,
file: str,
server: Union[str, Tuple[str, int]],
nick: str,
bind_address: Optional[str] = None,
): ):
""" """
Send a file to an IRC user over DCC connection. Send a file to an IRC user over DCC connection.
@ -127,7 +125,10 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@action @action
def send_message( def send_message(
self, text: str, server: Union[str, Tuple[str, int]], target: Union[str, Sequence[str]] self,
text: str,
server: Union[str, Tuple[str, int]],
target: Union[str, Sequence[str]],
): ):
""" """
Send a message to a channel or a nick. Send a message to a channel or a nick.
@ -139,15 +140,14 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
""" """
bot = self._get_bot(server) bot = self._get_bot(server)
method = ( method = (
bot.connection.privmsg if isinstance(target, str) bot.connection.privmsg
if isinstance(target, str)
else bot.connection.privmsg_many else bot.connection.privmsg_many
) )
method(target, text) method(target, text)
@action @action
def send_notice( def send_notice(self, text: str, server: Union[str, Tuple[str, int]], target: str):
self, text: str, server: Union[str, Tuple[str, int]], target: str
):
""" """
Send a notice to a channel or a nick. Send a notice to a channel or a nick.
@ -192,22 +192,28 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
channel_name = channel channel_name = channel
channel = bot.channels.get(channel) channel = bot.channels.get(channel)
assert channel, f'Not connected to channel {channel}' assert channel, f'Not connected to channel {channel}'
return IRCChannelSchema().dump({ return IRCChannelSchema().dump(
'is_invite_only': channel.is_invite_only(), {
'is_moderated': channel.is_moderated(), 'is_invite_only': channel.is_invite_only(),
'is_protected': channel.is_protected(), 'is_moderated': channel.is_moderated(),
'is_secret': channel.is_secret(), 'is_protected': channel.is_protected(),
'name': channel_name, 'is_secret': channel.is_secret(),
'modes': channel.modes, 'name': channel_name,
'opers': list(channel.opers()), 'modes': channel.modes,
'owners': channel.owners(), 'opers': list(channel.opers()),
'users': list(channel.users()), 'owners': channel.owners(),
'voiced': list(channel.voiced()), 'users': list(channel.users()),
}) 'voiced': list(channel.voiced()),
}
)
@action @action
def send_ctcp_message( def send_ctcp_message(
self, ctcp_type: str, body: str, server: Union[str, Tuple[str, int]], target: str self,
ctcp_type: str,
body: str,
server: Union[str, Tuple[str, int]],
target: str,
): ):
""" """
Send a CTCP message to a target. Send a CTCP message to a target.
@ -222,7 +228,7 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@action @action
def send_ctcp_reply( def send_ctcp_reply(
self, body: str, server: Union[str, Tuple[str, int]], target: str self, body: str, server: Union[str, Tuple[str, int]], target: str
): ):
""" """
Send a CTCP REPLY command. Send a CTCP REPLY command.
@ -235,7 +241,9 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
bot.connection.ctcp_reply(target, body) bot.connection.ctcp_reply(target, body)
@action @action
def disconnect(self, server: Union[str, Tuple[str, int]], message: Optional[str] = None): def disconnect(
self, server: Union[str, Tuple[str, int]], message: Optional[str] = None
):
""" """
Disconnect from a server. Disconnect from a server.
@ -246,9 +254,7 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
bot.connection.disconnect(message or bot.stop_message) bot.connection.disconnect(message or bot.stop_message)
@action @action
def invite( def invite(self, nick: str, channel: str, server: Union[str, Tuple[str, int]]):
self, nick: str, channel: str, server: Union[str, Tuple[str, int]]
):
""" """
Invite a nick to a channel. Invite a nick to a channel.
@ -272,7 +278,11 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@action @action
def kick( def kick(
self, nick: str, channel: str, server: Union[str, Tuple[str, int]], reason: Optional[str] = None self,
nick: str,
channel: str,
server: Union[str, Tuple[str, int]],
reason: Optional[str] = None,
): ):
""" """
Kick a nick from a channel. Kick a nick from a channel.
@ -286,9 +296,7 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
bot.connection.kick(channel, nick, reason) bot.connection.kick(channel, nick, reason)
@action @action
def mode( def mode(self, target: str, command: str, server: Union[str, Tuple[str, int]]):
self, target: str, command: str, server: Union[str, Tuple[str, int]]
):
""" """
Send a MODE command on the selected target. Send a MODE command on the selected target.
@ -324,8 +332,10 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
@action @action
def part( def part(
self, channel: Union[str, Sequence[str]], server: Union[str, Tuple[str, int]], self,
message: Optional[str] = None channel: Union[str, Sequence[str]],
server: Union[str, Tuple[str, int]],
message: Optional[str] = None,
): ):
""" """
Parts/exits a channel. Parts/exits a channel.
@ -339,9 +349,7 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
bot.connection.part(channels=channels, message=message or bot.stop_message) bot.connection.part(channels=channels, message=message or bot.stop_message)
@action @action
def quit( def quit(self, server: Union[str, Tuple[str, int]], message: Optional[str] = None):
self, server: Union[str, Tuple[str, int]], message: Optional[str] = None
):
""" """
Send a QUIT command. Send a QUIT command.
@ -363,7 +371,12 @@ class ChatIrcPlugin(RunnablePlugin, ChatPlugin):
bot.connection.send_raw(message) bot.connection.send_raw(message)
@action @action
def topic(self, channel: str, server: Union[str, Tuple[str, int]], topic: Optional[str] = None) -> str: def topic(
self,
channel: str,
server: Union[str, Tuple[str, int]],
topic: Optional[str] = None,
) -> str:
""" """
Get/set the topic of an IRC channel. Get/set the topic of an IRC channel.

View File

@ -27,11 +27,11 @@ class GpioPlugin(RunnablePlugin):
""" """
def __init__( def __init__(
self, self,
pins: Optional[Dict[str, int]] = None, pins: Optional[Dict[str, int]] = None,
monitored_pins: Optional[Collection[Union[str, int]]] = None, monitored_pins: Optional[Collection[Union[str, int]]] = None,
mode: str = 'board', mode: str = 'board',
**kwargs **kwargs
): ):
""" """
:param mode: Specify ``board`` if you want to use the board PIN numbers, :param mode: Specify ``board`` if you want to use the board PIN numbers,
@ -64,8 +64,9 @@ class GpioPlugin(RunnablePlugin):
self._initialized_pins = {} self._initialized_pins = {}
self._monitored_pins = monitored_pins or [] self._monitored_pins = monitored_pins or []
self.pins_by_name = pins if pins else {} self.pins_by_name = pins if pins else {}
self.pins_by_number = {number: name self.pins_by_number = {
for (name, number) in self.pins_by_name.items()} number: name for (name, number) in self.pins_by_name.items()
}
def _init_board(self): def _init_board(self):
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
@ -98,6 +99,7 @@ class GpioPlugin(RunnablePlugin):
def on_gpio_event(self): def on_gpio_event(self):
def callback(pin: int): def callback(pin: int):
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
value = GPIO.input(pin) value = GPIO.input(pin)
pin = self.pins_by_number.get(pin, pin) pin = self.pins_by_number.get(pin, pin)
get_bus().post(GPIOEvent(pin=pin, value=value)) get_bus().post(GPIOEvent(pin=pin, value=value))
@ -106,23 +108,23 @@ class GpioPlugin(RunnablePlugin):
def main(self): def main(self):
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
if not self._monitored_pins: if not self._monitored_pins:
return # No need to start the monitor return # No need to start the monitor
self._init_board() self._init_board()
monitored_pins = [ monitored_pins = [self._get_pin_number(pin) for pin in self._monitored_pins]
self._get_pin_number(pin) for pin in self._monitored_pins
]
for pin in monitored_pins: for pin in monitored_pins:
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(pin, GPIO.BOTH, callback=self.on_gpio_event()) GPIO.add_event_detect(pin, GPIO.BOTH, callback=self.on_gpio_event())
self._should_stop.wait() self.wait_stop()
@action @action
def write(self, pin: Union[int, str], value: Union[int, bool], def write(
name: Optional[str] = None) -> Dict[str, Any]: self, pin: Union[int, str], value: Union[int, bool], name: Optional[str] = None
) -> Dict[str, Any]:
""" """
Write a byte value to a pin. Write a byte value to a pin.