Updated email addresses and black'd some old source files.

This commit is contained in:
Fabio Manganiello 2023-07-22 23:02:44 +02:00
parent cf8ecf349b
commit 66981bd00b
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
18 changed files with 358 additions and 256 deletions

View file

@ -25,7 +25,7 @@ from .message.request import Request
from .message.response import Response
from .utils import set_thread_name, get_enabled_plugins
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
__version__ = '0.50.2'
log = logging.getLogger('platypush')

View file

@ -1,8 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
.. license: MIT
"""
import logging
import re
import socket
@ -15,9 +10,17 @@ from platypush.bus import Bus
from platypush.common import ExtensionWithManifest
from platypush.config import Config
from platypush.context import get_backend
from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, ZeroconfServiceRemovedEvent
from platypush.utils import set_timeout, clear_timeout, \
get_redis_queue_name_by_message, set_thread_name, get_backend_name_by_class
from platypush.message.event.zeroconf import (
ZeroconfServiceAddedEvent,
ZeroconfServiceRemovedEvent,
)
from platypush.utils import (
set_timeout,
clear_timeout,
get_redis_queue_name_by_message,
set_thread_name,
get_backend_name_by_class,
)
from platypush import __version__
from platypush.event import EventGenerator
@ -44,7 +47,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
# Loop function, can be implemented by derived classes
loop = None
def __init__(self, bus: Optional[Bus] = None, poll_seconds: Optional[float] = None, **kwargs):
def __init__(
self, bus: Optional[Bus] = None, poll_seconds: Optional[float] = None, **kwargs
):
"""
:param bus: Reference to the bus object to be used in the backend
:param poll_seconds: If the backend implements a ``loop`` method, this parameter expresses how often the
@ -65,14 +70,15 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
self.thread_id = None
self._stop_event = ThreadEvent()
self._kwargs = kwargs
self.logger = logging.getLogger('platypush:backend:' + get_backend_name_by_class(self.__class__))
self.logger = logging.getLogger(
'platypush:backend:' + get_backend_name_by_class(self.__class__)
)
self.zeroconf = None
self.zeroconf_info = None
# Internal-only, we set the request context on a backend if that
# backend is intended to react for a response to a specific request
self._request_context = kwargs['_req_ctx'] if '_req_ctx' in kwargs \
else None
self._request_context = kwargs['_req_ctx'] if '_req_ctx' in kwargs else None
if 'logging' in kwargs:
self.logger.setLevel(getattr(logging, kwargs.get('logging').upper()))
@ -90,11 +96,14 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
msg = Message.build(msg)
if not getattr(msg, 'target') or msg.target != self.device_id:
if not getattr(msg, 'target', None) or msg.target != self.device_id:
return # Not for me
self.logger.debug('Message received on the {} backend: {}'.format(
self.__class__.__name__, msg))
self.logger.debug(
'Message received on the {} backend: {}'.format(
self.__class__.__name__, msg
)
)
if self._is_expected_response(msg):
# Expected response, trigger the response handler
@ -104,26 +113,31 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
self.stop()
return
msg.backend = self # Augment message to be able to process responses
msg.backend = self # Augment message to be able to process responses
self.bus.post(msg)
def _is_expected_response(self, msg):
""" Internal only - returns true if we are expecting for a response
and msg is that response """
"""Internal only - returns true if we are expecting for a response
and msg is that response"""
# pylint: disable=unsubscriptable-object
return self._request_context \
and isinstance(msg, Response) \
return (
self._request_context
and isinstance(msg, Response)
and msg.id == self._request_context['request'].id
)
def _get_backend_config(self):
config_name = 'backend.' + self.__class__.__name__.split('Backend', maxsplit=1)[0].lower()
config_name = (
'backend.' + self.__class__.__name__.split('Backend', maxsplit=1)[0].lower()
)
return Config.get(config_name)
def _setup_response_handler(self, request, on_response, response_timeout):
def _timeout_hndl():
raise RuntimeError('Timed out while waiting for a response from {}'.
format(request.target))
raise RuntimeError(
'Timed out while waiting for a response from {}'.format(request.target)
)
req_ctx = {
'request': request,
@ -131,8 +145,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
'response_timeout': response_timeout,
}
resp_backend = self.__class__(bus=self.bus, _req_ctx=req_ctx,
**self._get_backend_config(), **self._kwargs)
resp_backend = self.__class__(
bus=self.bus, _req_ctx=req_ctx, **self._get_backend_config(), **self._kwargs
)
# Set the response timeout
if response_timeout:
@ -157,8 +172,13 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
self.send_message(event, **kwargs)
def send_request(self, request, on_response=None,
response_timeout=_default_response_timeout, **kwargs):
def send_request(
self,
request,
on_response=None,
response_timeout=_default_response_timeout,
**kwargs
):
"""
Send a request message on the backend.
@ -215,16 +235,18 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
if not redis:
raise KeyError()
except KeyError:
self.logger.warning((
"Backend {} does not implement send_message "
"and the fallback Redis backend isn't configured"
).format(self.__class__.__name__))
self.logger.warning(
(
"Backend {} does not implement send_message "
"and the fallback Redis backend isn't configured"
).format(self.__class__.__name__)
)
return
redis.send_message(msg, queue_name=queue_name)
def run(self):
""" Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined. """
"""Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined."""
self.thread_id = get_ident()
set_thread_name(self._thread_name)
if not callable(self.loop):
@ -249,24 +271,29 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
elif has_error:
time.sleep(5)
except Exception as e:
self.logger.error('{} initialization error: {}'.format(self.__class__.__name__, str(e)))
self.logger.error(
'{} initialization error: {}'.format(
self.__class__.__name__, str(e)
)
)
self.logger.exception(e)
time.sleep(self.poll_seconds or 5)
def __enter__(self):
""" Invoked when the backend is initialized, if the main logic is within a ``loop()`` function """
"""Invoked when the backend is initialized, if the main logic is within a ``loop()`` function"""
self.logger.info('Initialized backend {}'.format(self.__class__.__name__))
def __exit__(self, exc_type, exc_val, exc_tb):
""" Invoked when the backend is terminated, if the main logic is within a ``loop()`` function """
"""Invoked when the backend is terminated, if the main logic is within a ``loop()`` function"""
self.on_stop()
self.logger.info('Terminated backend {}'.format(self.__class__.__name__))
def on_stop(self):
""" Callback invoked when the process stops """
"""Callback invoked when the process stops"""
def stop(self):
""" Stops the backend thread by sending a STOP event on its bus """
"""Stops the backend thread by sending a STOP event on its bus"""
def _async_stop():
self._stop_event.set()
self.unregister_service()
@ -285,8 +312,10 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
redis_backend = get_backend('redis')
if not redis_backend:
self.logger.warning('Redis backend not configured - some '
'web server features may not be working properly')
self.logger.warning(
'Redis backend not configured - some '
'web server features may not be working properly'
)
redis_args = {}
else:
redis_args = redis_backend.redis_args
@ -305,7 +334,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
return response
except Exception as e:
self.logger.error('Error while processing response to {}: {}'.format(msg, str(e)))
self.logger.error(
'Error while processing response to {}: {}'.format(msg, str(e))
)
@staticmethod
def _get_ip() -> str:
@ -318,13 +349,15 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
s.close()
return addr
def register_service(self,
port: Optional[int] = None,
name: Optional[str] = None,
srv_type: Optional[str] = None,
srv_name: Optional[str] = None,
udp: bool = False,
properties: Optional[Dict] = None):
def register_service(
self,
port: Optional[int] = None,
name: Optional[str] = None,
srv_type: Optional[str] = None,
srv_name: Optional[str] = None,
udp: bool = False,
properties: Optional[Dict] = None,
):
"""
Initialize the Zeroconf service configuration for this backend.
@ -348,7 +381,9 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
from zeroconf import ServiceInfo, Zeroconf
from platypush.plugins.zeroconf import ZeroconfListener
except ImportError:
self.logger.warning('zeroconf package not available, service discovery will be disabled.')
self.logger.warning(
'zeroconf package not available, service discovery will be disabled.'
)
return
self.zeroconf = Zeroconf()
@ -360,28 +395,40 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
}
name = name or re.sub(r'Backend$', '', self.__class__.__name__).lower()
srv_type = srv_type or '_platypush-{name}._{proto}.local.'.format(name=name, proto='udp' if udp else 'tcp')
srv_name = srv_name or '{host}.{type}'.format(host=self.device_id, type=srv_type)
srv_type = srv_type or '_platypush-{name}._{proto}.local.'.format(
name=name, proto='udp' if udp else 'tcp'
)
srv_name = srv_name or '{host}.{type}'.format(
host=self.device_id, type=srv_type
)
if port:
srv_port = port
else:
srv_port = self.port if hasattr(self, 'port') else None
self.zeroconf_info = ServiceInfo(srv_type, srv_name,
addresses=[socket.inet_aton(self._get_ip())],
port=srv_port,
weight=0,
priority=0,
properties=srv_desc)
self.zeroconf_info = ServiceInfo(
srv_type,
srv_name,
addresses=[socket.inet_aton(self._get_ip())],
port=srv_port,
weight=0,
priority=0,
properties=srv_desc,
)
if not self.zeroconf_info:
self.logger.warning('Could not register Zeroconf service')
return
self.zeroconf.register_service(self.zeroconf_info)
self.bus.post(ZeroconfServiceAddedEvent(service_type=srv_type, service_name=srv_name,
service_info=ZeroconfListener.parse_service_info(self.zeroconf_info)))
self.bus.post(
ZeroconfServiceAddedEvent(
service_type=srv_type,
service_name=srv_name,
service_info=ZeroconfListener.parse_service_info(self.zeroconf_info),
)
)
def unregister_service(self):
"""
@ -391,17 +438,26 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
try:
self.zeroconf.unregister_service(self.zeroconf_info)
except Exception as e:
self.logger.warning('Could not register Zeroconf service {}: {}: {}'.format(
self.zeroconf_info.name, type(e).__name__, str(e)))
self.logger.warning(
'Could not register Zeroconf service {}: {}: {}'.format(
self.zeroconf_info.name, type(e).__name__, str(e)
)
)
if self.zeroconf:
self.zeroconf.close()
if self.zeroconf_info:
self.bus.post(ZeroconfServiceRemovedEvent(service_type=self.zeroconf_info.type,
service_name=self.zeroconf_info.name))
self.bus.post(
ZeroconfServiceRemovedEvent(
service_type=self.zeroconf_info.type,
service_name=self.zeroconf_info.name,
)
)
else:
self.bus.post(ZeroconfServiceRemovedEvent(service_type=None, service_name=None))
self.bus.post(
ZeroconfServiceRemovedEvent(service_type=None, service_name=None)
)
self.zeroconf_info = None
self.zeroconf = None

View file

@ -1,8 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
.. license: MIT
"""
import json
import os
import time

View file

@ -1,8 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
.. license: MIT
"""
import os
import threading
@ -89,10 +84,14 @@ class AssistantSnowboyBackend(AssistantBackend):
self.detector = snowboydecoder.HotwordDetector(
[model['voice_model_file'] for model in self.models.values()],
sensitivity=[model['sensitivity'] for model in self.models.values()],
audio_gain=self.audio_gain)
audio_gain=self.audio_gain,
)
self.logger.info('Initialized Snowboy hotword detection with {} voice model configurations'.
format(len(self.models)))
self.logger.info(
'Initialized Snowboy hotword detection with {} voice model configurations'.format(
len(self.models)
)
)
def _init_models(self, models):
if not models:
@ -107,7 +106,9 @@ class AssistantSnowboyBackend(AssistantBackend):
detect_sound = conf.get('detect_sound')
if not model_file:
raise AttributeError('No voice_model_file specified for model {}'.format(name))
raise AttributeError(
'No voice_model_file specified for model {}'.format(name)
)
model_file = os.path.abspath(os.path.expanduser(model_file))
assistant_plugin_name = conf.get('assistant_plugin')
@ -116,14 +117,19 @@ class AssistantSnowboyBackend(AssistantBackend):
detect_sound = os.path.abspath(os.path.expanduser(detect_sound))
if not os.path.isfile(model_file):
raise FileNotFoundError('Voice model file {} does not exist or it not a regular file'.
format(model_file))
raise FileNotFoundError(
'Voice model file {} does not exist or it not a regular file'.format(
model_file
)
)
self.models[name] = {
'voice_model_file': model_file,
'sensitivity': conf.get('sensitivity', 0.5),
'detect_sound': detect_sound,
'assistant_plugin': get_plugin(assistant_plugin_name) if assistant_plugin_name else None,
'assistant_plugin': get_plugin(assistant_plugin_name)
if assistant_plugin_name
else None,
'assistant_language': conf.get('assistant_language'),
'tts_plugin': conf.get('tts_plugin'),
'tts_args': conf.get('tts_args', {}),
@ -143,7 +149,9 @@ class AssistantSnowboyBackend(AssistantBackend):
def callback():
if not self.is_detecting():
self.logger.info('Hotword detected but assistant response currently paused')
self.logger.info(
'Hotword detected but assistant response currently paused'
)
return
self.bus.post(HotwordDetectedEvent(hotword=hotword))
@ -159,8 +167,11 @@ class AssistantSnowboyBackend(AssistantBackend):
threading.Thread(target=sound_thread, args=(detect_sound,)).start()
if assistant_plugin:
assistant_plugin.start_conversation(language=assistant_language, tts_plugin=tts_plugin,
tts_args=tts_args)
assistant_plugin.start_conversation(
language=assistant_language,
tts_plugin=tts_plugin,
tts_args=tts_args,
)
return callback
@ -172,10 +183,11 @@ class AssistantSnowboyBackend(AssistantBackend):
def run(self):
super().run()
self.detector.start(detected_callback=[
self.hotword_detected(hotword)
for hotword in self.models.keys()
])
self.detector.start(
detected_callback=[
self.hotword_detected(hotword) for hotword in self.models.keys()
]
)
# vim:sw=4:ts=4:et:

View file

@ -1,13 +1,12 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import re
import time
from platypush.backend import Backend
from platypush.message.event.wiimote import WiimoteEvent, \
WiimoteConnectionEvent, WiimoteDisconnectionEvent
from platypush.message.event.wiimote import (
WiimoteEvent,
WiimoteConnectionEvent,
WiimoteDisconnectionEvent,
)
class WiimoteBackend(Backend):
@ -30,8 +29,9 @@ class WiimoteBackend(Backend):
_last_btn_event_time = 0
_bdaddr = None
def __init__(self, bdaddr=_bdaddr, inactivity_timeout=_inactivity_timeout,
*args, **kwargs):
def __init__(
self, bdaddr=_bdaddr, inactivity_timeout=_inactivity_timeout, *args, **kwargs
):
"""
:param bdaddr: If set, connect to this specific Wiimote physical address (example: 00:11:22:33:44:55)
:type bdaddr: str
@ -55,7 +55,9 @@ class WiimoteBackend(Backend):
self._wiimote = cwiid.Wiimote()
self._wiimote.enable(cwiid.FLAG_MOTIONPLUS)
self._wiimote.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_MOTIONPLUS
self._wiimote.rpt_mode = (
cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_MOTIONPLUS
)
self.logger.info('WiiMote connected')
self._last_btn_event_time = time.time()
@ -65,24 +67,34 @@ class WiimoteBackend(Backend):
def get_state(self):
import cwiid
wm = self.get_wiimote()
state = wm.state
parsed_state = {}
# Get buttons
all_btns = [attr for attr in dir(cwiid) if attr.startswith('BTN_')]
parsed_state['buttons'] = {btn: True for btn in all_btns
if state.get('buttons', 0) & getattr(cwiid, btn) != 0}
parsed_state['buttons'] = {
btn: True
for btn in all_btns
if state.get('buttons', 0) & getattr(cwiid, btn) != 0
}
# Get LEDs
all_leds = [attr for attr in dir(cwiid) if re.match('LED\d_ON', attr)]
parsed_state['led'] = {led[:4]: True for led in all_leds
if state.get('leds', 0) & getattr(cwiid, led) != 0}
all_leds = [attr for attr in dir(cwiid) if re.match(r'LED\d_ON', attr)]
parsed_state['led'] = {
led[:4]: True
for led in all_leds
if state.get('leds', 0) & getattr(cwiid, led) != 0
}
# Get errors
all_errs = [attr for attr in dir(cwiid) if attr.startswith('ERROR_')]
parsed_state['error'] = {err: True for err in all_errs
if state.get('errs', 0) & getattr(cwiid, err) != 0}
parsed_state['error'] = {
err: True
for err in all_errs
if state.get('errs', 0) & getattr(cwiid, err) != 0
}
parsed_state['battery'] = round(state.get('battery', 0) / cwiid.BATTERY_MAX, 3)
parsed_state['rumble'] = bool(state.get('rumble', 0))
@ -92,8 +104,9 @@ class WiimoteBackend(Backend):
if 'motionplus' in state:
parsed_state['motionplus'] = {
'angle_rate': tuple(int(angle / 100) for angle
in state['motionplus']['angle_rate']),
'angle_rate': tuple(
int(angle / 100) for angle in state['motionplus']['angle_rate']
),
'low_speed': state['motionplus']['low_speed'],
}
@ -121,23 +134,30 @@ class WiimoteBackend(Backend):
while not self.should_stop():
try:
state = self.get_state()
changed_state = {k: state[k] for k in state.keys()
if state[k] != last_state.get(k)}
changed_state = {
k: state[k] for k in state.keys() if state[k] != last_state.get(k)
}
if changed_state:
self.bus.post(WiimoteEvent(**changed_state))
if 'buttons' in changed_state:
self._last_btn_event_time = time.time()
elif last_state and time.time() - \
self._last_btn_event_time >= self._inactivity_timeout:
elif (
last_state
and time.time() - self._last_btn_event_time
>= self._inactivity_timeout
):
self.logger.info('Wiimote disconnected upon timeout')
self.close()
last_state = state
time.sleep(0.1)
except RuntimeError as e:
if type(e) == RuntimeError and str(e) == 'Error opening wiimote connection':
if (
type(e) == RuntimeError
and str(e) == 'Error opening wiimote connection'
):
if self._connection_attempts == 0:
self.logger.info('Press 1+2 to pair your WiiMote controller')
else:
@ -146,4 +166,5 @@ class WiimoteBackend(Backend):
self.close()
self._connection_attempts += 1
# vim:sw=4:ts=4:et:

View file

@ -3,9 +3,6 @@ Platydock
Platydock is a helper that allows you to easily manage (create, destroy, start,
stop and list) Platypush instances as Docker images.
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
.. license: MIT
"""
import argparse

View file

@ -1,15 +1,16 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import os
from typing import Optional
from platypush.context import get_bus
from platypush.plugins import action
from platypush.plugins.assistant import AssistantPlugin
from platypush.message.event.assistant import ConversationStartEvent, \
ConversationEndEvent, SpeechRecognizedEvent, ResponseEvent
from platypush.message.event.assistant import (
ConversationStartEvent,
ConversationEndEvent,
SpeechRecognizedEvent,
ResponseEvent,
)
class AssistantEchoPlugin(AssistantPlugin):
@ -38,8 +39,13 @@ class AssistantEchoPlugin(AssistantPlugin):
* **avs** (``pip install avs``)
"""
def __init__(self, avs_config_file: str = None, audio_device: str = 'default',
audio_player: str = 'default', **kwargs):
def __init__(
self,
avs_config_file: Optional[str] = None,
audio_device: str = 'default',
audio_player: str = 'default',
**kwargs
):
"""
:param avs_config_file: AVS credentials file - default: ~/.avs.json. If the file doesn't exist then
an instance of the AVS authentication service will be spawned. You can login through an Amazon
@ -61,9 +67,12 @@ class AssistantEchoPlugin(AssistantPlugin):
if not avs_config_file or not os.path.isfile(avs_config_file):
from avs.auth import auth
auth(None, avs_config_file)
self.logger.warning('Amazon Echo assistant credentials not configured. Open http://localhost:3000 ' +
'to authenticate this client')
self.logger.warning(
'Amazon Echo assistant credentials not configured. Open http://localhost:3000 '
+ 'to authenticate this client'
)
self.audio_device = audio_device
self.audio_player = audio_player
@ -84,37 +93,43 @@ class AssistantEchoPlugin(AssistantPlugin):
def _on_ready(self):
def _callback():
self._ready = True
return _callback
def _on_listening(self):
def _callback():
get_bus().post(ConversationStartEvent(assistant=self))
return _callback
def _on_speaking(self):
def _callback():
# AVS doesn't provide a way to access the response text
get_bus().post(ResponseEvent(assistant=self, response_text=''))
return _callback
def _on_finished(self):
def _callback():
get_bus().post(ConversationEndEvent(assistant=self))
return _callback
def _on_disconnected(self):
def _callback():
self._ready = False
return _callback
def _on_thinking(self):
def _callback():
# AVS doesn't provide a way to access the detected text
get_bus().post(SpeechRecognizedEvent(assistant=self, phrase=''))
return _callback
@action
def start_conversation(self, **kwargs):
def start_conversation(self, **_):
if not self._ready:
raise RuntimeError('Echo assistant not ready')

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
from platypush.backend.assistant.google import AssistantGoogleBackend
from platypush.context import get_backend
from platypush.plugins import action
@ -19,10 +15,12 @@ class AssistantGooglePlugin(AssistantPlugin):
super().__init__(**kwargs)
def _get_assistant(self) -> AssistantGoogleBackend:
return get_backend('assistant.google')
backend = get_backend('assistant.google')
assert backend, 'The assistant.google backend is not configured.'
return backend
@action
def start_conversation(self, **kwargs):
def start_conversation(self):
"""
Programmatically start a conversation with the assistant
"""

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import json
import os
from typing import Optional, Dict, Any

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import dateutil.parser
import importlib
@ -53,7 +49,9 @@ class CalendarPlugin(Plugin, CalendarInterface):
for calendar in calendars:
if 'type' not in calendar:
self.logger.warning("Invalid calendar with no type specified: {}".format(calendar))
self.logger.warning(
"Invalid calendar with no type specified: {}".format(calendar)
)
continue
cal_type = calendar.pop('type')
@ -62,7 +60,6 @@ class CalendarPlugin(Plugin, CalendarInterface):
module = importlib.import_module(module_name)
self.calendars.append(getattr(module, class_name)(**calendar))
@action
def get_upcoming_events(self, max_results=10):
"""
@ -71,7 +68,8 @@ class CalendarPlugin(Plugin, CalendarInterface):
:param max_results: Maximum number of results to be returned (default: 10)
:type max_results: int
:returns: platypush.message.Response -- Response object with the list of events in the Google calendar API format.
:returns: platypush.message.Response -- Response object with the list of
events in the Google calendar API format.
Example::
@ -113,15 +111,16 @@ class CalendarPlugin(Plugin, CalendarInterface):
except Exception as e:
self.logger.warning('Could not retrieve events: {}'.format(str(e)))
events = sorted(events, key=lambda event:
dateutil.parser.parse(
event['start']['dateTime']
if 'dateTime' in event['start']
else event['start']['date'] + 'T00:00:00+00:00'
))[:max_results]
events = sorted(
events,
key=lambda event: dateutil.parser.parse(
event['start']['dateTime']
if 'dateTime' in event['start']
else event['start']['date'] + 'T00:00:00+00:00'
),
)[:max_results]
return events
# vim:sw=4:ts=4:et:

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import datetime
import requests
from typing import Optional
@ -35,15 +31,14 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
return
if type(t.dt) == datetime.date:
return (
datetime.datetime(
t.dt.year, t.dt.month, t.dt.day, tzinfo=datetime.timezone.utc
).isoformat()
)
return datetime.datetime(
t.dt.year, t.dt.month, t.dt.day, tzinfo=datetime.timezone.utc
).isoformat()
return (
datetime.datetime.utcfromtimestamp(t.dt.timestamp())
.replace(tzinfo=datetime.timezone.utc).isoformat()
datetime.datetime.utcfromtimestamp(t.dt.timestamp())
.replace(tzinfo=datetime.timezone.utc)
.isoformat()
)
@classmethod
@ -52,23 +47,27 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
'id': str(event.get('uid')) if event.get('uid') else None,
'kind': 'calendar#event',
'summary': str(event.get('summary')) if event.get('summary') else None,
'description': str(event.get('description')) if event.get('description') else None,
'description': str(event.get('description'))
if event.get('description')
else None,
'status': str(event.get('status')).lower() if event.get('status') else None,
'responseStatus': str(event.get('partstat')).lower() if event.get('partstat') else None,
'responseStatus': str(event.get('partstat')).lower()
if event.get('partstat')
else None,
'location': str(event.get('location')) if event.get('location') else None,
'htmlLink': str(event.get('url')) if event.get('url') else None,
'organizer': {
'email': str(event.get('organizer')).replace('MAILTO:', ''),
'displayName': event.get('organizer').params.get('cn')
} if event.get('organizer') else None,
'displayName': event.get('organizer').params.get('cn'),
}
if event.get('organizer')
else None,
'created': cls._convert_timestamp(event, 'created'),
'updated': cls._convert_timestamp(event, 'last-modified'),
'start': {
'dateTime': cls._convert_timestamp(event, 'dtstart'),
'timeZone': 'UTC',
},
'end': {
'dateTime': cls._convert_timestamp(event, 'dtend'),
'timeZone': 'UTC',
@ -76,7 +75,7 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
}
@action
def get_upcoming_events(self, max_results=10, only_participating=True):
def get_upcoming_events(self, *_, only_participating=True, **__):
"""
Get the upcoming events. See
:func:`~platypush.plugins.calendar.CalendarPlugin.get_upcoming_events`.
@ -86,8 +85,9 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
events = []
response = requests.get(self.url)
assert response.ok, \
"HTTP error while getting events from {}: {}".format(self.url, response.text)
assert response.ok, "HTTP error while getting events from {}: {}".format(
self.url, response.text
)
calendar = Calendar.from_ical(response.text)
for event in calendar.walk():
@ -97,16 +97,24 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
event = self._translate_event(event)
if (
event['status'] != 'cancelled'
and event['end'].get('dateTime')
and event['end']['dateTime'] >= datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
and (
(only_participating
and event.get('responseStatus') in [None, 'accepted', 'tentative'])
or not only_participating)
event['status'] != 'cancelled'
and event['end'].get('dateTime')
and event['end']['dateTime']
>= datetime.datetime.utcnow()
.replace(tzinfo=datetime.timezone.utc)
.isoformat()
and (
(
only_participating
and event.get('responseStatus')
in [None, 'accepted', 'tentative']
)
or not only_participating
)
):
events.append(event)
return events
# vim:sw=4:ts=4:et:

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
from platypush.plugins import Plugin
@ -23,7 +19,8 @@ class GooglePlugin(Plugin):
5. Generate a credentials file for the needed scope::
python -m platypush.plugins.google.credentials 'https://www.googleapis.com/auth/gmail.compose' ~/client_secret.json
python -m platypush.plugins.google.credentials \
'https://www.googleapis.com/auth/gmail.compose' ~/client_secret.json
Requires:
@ -32,7 +29,7 @@ class GooglePlugin(Plugin):
"""
def __init__(self, scopes=None, *args, **kwargs):
def __init__(self, scopes=None, **kwargs):
"""
Initialized the Google plugin with the required scopes.
@ -41,14 +38,13 @@ class GooglePlugin(Plugin):
"""
from platypush.plugins.google.credentials import get_credentials
super().__init__(**kwargs)
self._scopes = scopes or []
if self._scopes:
scopes = ' '.join(sorted(self._scopes))
self.credentials = {
scopes: get_credentials(scopes)
}
self.credentials = {scopes: get_credentials(scopes)}
else:
self.credentials = {}
@ -57,7 +53,7 @@ class GooglePlugin(Plugin):
from apiclient import discovery
if scopes is None:
scopes = getattr(self, 'scopes') if hasattr(self, 'scopes') else []
scopes = getattr(self, 'scopes', [])
scopes = ' '.join(sorted(scopes))
credentials = self.credentials[scopes]

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import datetime
from platypush.plugins import action
@ -34,9 +30,17 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
now = datetime.datetime.utcnow().isoformat() + 'Z'
service = self.get_service('calendar', 'v3')
result = service.events().list(calendarId='primary', timeMin=now,
maxResults=max_results, singleEvents=True,
orderBy='startTime').execute()
result = (
service.events()
.list(
calendarId='primary',
timeMin=now,
maxResults=max_results,
singleEvents=True,
orderBy='startTime',
)
.execute()
)
events = result.get('items', [])
return events

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import base64
import mimetypes
import os
@ -81,8 +77,9 @@ class GoogleMailPlugin(GooglePlugin):
elif main_type == 'audio':
msg = MIMEAudio(content, _subtype=sub_type)
elif main_type == 'application':
msg = MIMEApplication(content, _subtype=sub_type,
_encoder=encode_base64)
msg = MIMEApplication(
content, _subtype=sub_type, _encoder=encode_base64
)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(content)
@ -93,8 +90,7 @@ class GoogleMailPlugin(GooglePlugin):
service = self.get_service('gmail', 'v1')
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
message = (service.users().messages().send(
userId='me', body=body).execute())
message = service.users().messages().send(userId='me', body=body).execute()
return message
@ -108,4 +104,5 @@ class GoogleMailPlugin(GooglePlugin):
labels = results.get('labels', [])
return labels
# vim:sw=4:ts=4:et:

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
from datetime import datetime
from typing import List, Union, Optional
@ -50,23 +46,29 @@ class GoogleMapsPlugin(GooglePlugin):
:type longitude: float
"""
response = requests.get('https://maps.googleapis.com/maps/api/geocode/json',
params={
'latlng': '{},{}'.format(latitude, longitude),
'key': self.api_key,
}).json()
response = requests.get(
'https://maps.googleapis.com/maps/api/geocode/json',
params={
'latlng': '{},{}'.format(latitude, longitude),
'key': self.api_key,
},
).json()
address = dict(
(t, None) for t in ['street_number', 'street', 'locality', 'country', 'postal_code']
)
address = {
t: None
for t in ['street_number', 'street', 'locality', 'country', 'postal_code']
}
address['latitude'] = latitude
address['longitude'] = longitude
if 'results' in response and response['results']:
result = response['results'][0]
self.logger.info('Google Maps geocode response for latlng ({},{}): {}'.
format(latitude, longitude, result))
self.logger.info(
'Google Maps geocode response for latlng ({},{}): {}'.format(
latitude, longitude, result
)
)
address['address'] = result['formatted_address'].split(',')[0]
for addr_component in result['address_components']:
@ -92,11 +94,13 @@ class GoogleMapsPlugin(GooglePlugin):
:type longitude: float
"""
response = requests.get('https://maps.googleapis.com/maps/api/elevation/json',
params={
'locations': '{},{}'.format(latitude, longitude),
'key': self.api_key,
}).json()
response = requests.get(
'https://maps.googleapis.com/maps/api/elevation/json',
params={
'locations': '{},{}'.format(latitude, longitude),
'key': self.api_key,
},
).json()
elevation = None
@ -106,16 +110,20 @@ class GoogleMapsPlugin(GooglePlugin):
return {'elevation': elevation}
@action
def get_travel_time(self, origins: List[str], destinations: List[str],
departure_time: Optional[datetime_types] = None,
arrival_time: Optional[datetime_types] = None,
units: str = 'metric',
avoid: Optional[List[str]] = None,
language: Optional[str] = None,
mode: Optional[str] = None,
traffic_model: Optional[str] = None,
transit_mode: Optional[List[str]] = None,
transit_route_preference: Optional[str] = None):
def get_travel_time(
self,
origins: List[str],
destinations: List[str],
departure_time: Optional[datetime_types] = None,
arrival_time: Optional[datetime_types] = None,
units: str = 'metric',
avoid: Optional[List[str]] = None,
language: Optional[str] = None,
mode: Optional[str] = None,
traffic_model: Optional[str] = None,
transit_mode: Optional[List[str]] = None,
transit_route_preference: Optional[str] = None,
):
"""
Get the estimated travel time between a set of departure points and a set of destinations.
@ -194,17 +202,25 @@ class GoogleMapsPlugin(GooglePlugin):
'origins': '|'.join(origins),
'destinations': '|'.join(destinations),
'units': units,
**({'departure_time': to_datetime(departure_time)} if departure_time else {}),
**(
{'departure_time': to_datetime(departure_time)}
if departure_time
else {}
),
**({'arrival_time': to_datetime(arrival_time)} if arrival_time else {}),
**({'avoid': '|'.join(avoid)} if avoid else {}),
**({'language': language} if language else {}),
**({'mode': mode} if mode else {}),
**({'traffic_model': traffic_model} if traffic_model else {}),
**({'transit_mode': transit_mode} if transit_mode else {}),
**({'transit_route_preference': transit_route_preference}
if transit_route_preference else {}),
**(
{'transit_route_preference': transit_route_preference}
if transit_route_preference
else {}
),
'key': self.api_key,
}).json()
},
).json()
assert not rs.get('error_message'), f'{rs["status"]}: {rs["error_message"]}'
rows = rs.get('rows', [])

View file

@ -1,7 +1,3 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
from platypush.plugins import action
from platypush.plugins.google import GooglePlugin
@ -48,10 +44,12 @@ class GoogleYoutubePlugin(GooglePlugin):
:type max_results: int
:param kwargs: Any extra arguments that will be transparently passed to the YouTube API.
See the `Getting started - parameters <https://developers.google.com/youtube/v3/docs/search/list#parameters>`_.
See the `Getting started - parameters
<https://developers.google.com/youtube/v3/docs/search/list#parameters>`_.
:return: A list of YouTube resources.
See the `Getting started - Resource <https://developers.google.com/youtube/v3/docs/search#resource>`_.
See the `Getting started - Resource
<https://developers.google.com/youtube/v3/docs/search#resource>`_.
"""
parts = parts or self._default_parts[:]
@ -63,9 +61,11 @@ class GoogleYoutubePlugin(GooglePlugin):
types = ','.join(types)
service = self.get_service('youtube', 'v3')
result = service.search().list(part=parts, q=query, type=types,
maxResults=max_results,
**kwargs).execute()
result = (
service.search()
.list(part=parts, q=query, type=types, maxResults=max_results, **kwargs)
.execute()
)
events = result.get('items', [])
return events

View file

@ -1,12 +1,9 @@
"""
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
"""
import time
from platypush.context import get_backend
from platypush.plugins import Plugin, action
class WiimotePlugin(Plugin):
"""
WiiMote plugin.
@ -20,7 +17,6 @@ class WiimotePlugin(Plugin):
def _get_wiimote(cls):
return get_backend('wiimote').get_wiimote()
@action
def connect(self):
"""
@ -28,7 +24,6 @@ class WiimotePlugin(Plugin):
"""
self._get_wiimote()
@action
def close(self):
"""
@ -36,7 +31,6 @@ class WiimotePlugin(Plugin):
"""
get_backend('wiimote').close()
@action
def rumble(self, secs):
"""
@ -47,7 +41,6 @@ class WiimotePlugin(Plugin):
time.sleep(secs)
wm.rumble = False
@action
def state(self):
"""
@ -55,23 +48,22 @@ class WiimotePlugin(Plugin):
"""
return get_backend('wiimote').get_state()
@action
def set_leds(self, leds):
"""
Set the LEDs state on the controller
:param leds: Iterable with the new states to be applied to the LEDs. Example: [1, 0, 0, 0] or (False, True, False, False)
:param leds: Iterable with the new states to be applied to the LEDs.
Example: [1, 0, 0, 0] or (False, True, False, False)
:type leds: list
"""
new_led = 0
for i, led in enumerate(leds):
if led:
new_led |= (1 << i)
new_led |= 1 << i
self._get_wiimote().led = new_led
# vim:sw=4:ts=4:et:

View file

@ -30,7 +30,7 @@ setup(
name="platypush",
version="0.50.2",
author="Fabio Manganiello",
author_email="info@fabiomanganiello.com",
author_email="fabio@manganiello.tech",
description="Platypush service",
license="MIT",
python_requires='>= 3.6',