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: 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
@ -112,18 +121,23 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
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,10 +235,12 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
if not redis:
raise KeyError()
except KeyError:
self.logger.warning((
self.logger.warning(
(
"Backend {} does not implement send_message "
"and the fallback Redis backend isn't configured"
).format(self.__class__.__name__))
).format(self.__class__.__name__)
)
return
redis.send_message(msg, queue_name=queue_name)
@ -249,7 +271,11 @@ 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)
@ -267,6 +293,7 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
def stop(self):
"""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,
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):
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,
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)
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(
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]
),
)[: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(
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()
.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():
@ -99,14 +99,22 @@ class CalendarIcalPlugin(Plugin, CalendarInterface):
if (
event['status'] != 'cancelled'
and event['end'].get('dateTime')
and event['end']['dateTime'] >= datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat()
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)
(
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',
response = requests.get(
'https://maps.googleapis.com/maps/api/geocode/json',
params={
'latlng': '{},{}'.format(latitude, longitude),
'key': self.api_key,
}).json()
},
).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',
response = requests.get(
'https://maps.googleapis.com/maps/api/elevation/json',
params={
'locations': '{},{}'.format(latitude, longitude),
'key': self.api_key,
}).json()
},
).json()
elevation = None
@ -106,7 +110,10 @@ class GoogleMapsPlugin(GooglePlugin):
return {'elevation': elevation}
@action
def get_travel_time(self, origins: List[str], destinations: List[str],
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',
@ -115,7 +122,8 @@ class GoogleMapsPlugin(GooglePlugin):
mode: Optional[str] = None,
traffic_model: Optional[str] = None,
transit_mode: Optional[List[str]] = None,
transit_route_preference: Optional[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',