Documentation v0.1
This commit is contained in:
parent
a4273f5619
commit
54dcb2cba3
28 changed files with 691 additions and 22 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@ dist/
|
||||||
package.sh
|
package.sh
|
||||||
.pypirc
|
.pypirc
|
||||||
platypush/backend/http/static/resources/*
|
platypush/backend/http/static/resources/*
|
||||||
|
docs/build
|
||||||
|
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
SPHINXPROJ = platypush
|
||||||
|
SOURCEDIR = source
|
||||||
|
BUILDDIR = build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
192
docs/source/conf.py
Normal file
192
docs/source/conf.py
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file does only contain a selection of the most common options. For a
|
||||||
|
# full list see the documentation:
|
||||||
|
# http://www.sphinx-doc.org/en/master/config
|
||||||
|
|
||||||
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
# import os
|
||||||
|
# import sys
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = 'platypush'
|
||||||
|
copyright = '2018, BlackLight'
|
||||||
|
author = 'BlackLight'
|
||||||
|
|
||||||
|
# The short X.Y version
|
||||||
|
version = ''
|
||||||
|
# The full version, including alpha/beta/rc tags
|
||||||
|
release = ''
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.imgmath',
|
||||||
|
'sphinx.ext.ifconfig',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'sphinx.ext.githubpages',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This pattern also affects html_static_path and html_extra_path .
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
# html_theme = 'alabaster'
|
||||||
|
html_theme = 'nature'
|
||||||
|
|
||||||
|
html_domain_indices = True
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# Custom sidebar templates, must be a dictionary that maps document names
|
||||||
|
# to template names.
|
||||||
|
#
|
||||||
|
# The default sidebars (for documents that don't match any pattern) are
|
||||||
|
# defined by theme itself. Builtin themes are using these templates by
|
||||||
|
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||||
|
# 'searchbox.html']``.
|
||||||
|
#
|
||||||
|
# html_sidebars = {}
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ---------------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'platypushdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ------------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'platypush.tex', 'platypush Documentation',
|
||||||
|
'BlackLight', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'platypush', 'platypush Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output ----------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'platypush', 'platypush Documentation',
|
||||||
|
author, 'platypush', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Extension configuration -------------------------------------------------
|
||||||
|
|
||||||
|
# -- Options for intersphinx extension ---------------------------------------
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||||
|
|
||||||
|
# -- Options for todo extension ----------------------------------------------
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath('../..'))
|
||||||
|
|
||||||
|
def skip(app, what, name, obj, skip, options):
|
||||||
|
if name == "__init__":
|
||||||
|
return False
|
||||||
|
return skip
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.connect("autodoc-skip-member", skip)
|
||||||
|
|
16
docs/source/index.rst
Normal file
16
docs/source/index.rst
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
Platypush
|
||||||
|
#########
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 3
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
|
plugins
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
``platypush.plugins.assistant.google.pushtotalk``
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.assistant.google.pushtotalk
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
|
6
docs/source/platypush/plugins/assistant.google.rst
Normal file
6
docs/source/platypush/plugins/assistant.google.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.assistant.google``
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.assistant.google
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/calendar.ical.rst
Normal file
6
docs/source/platypush/plugins/calendar.ical.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.calendar.ical``
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.calendar.ical
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/calendar.rst
Normal file
6
docs/source/platypush/plugins/calendar.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.calendar``
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.calendar
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/camera.pi.rst
Normal file
6
docs/source/platypush/plugins/camera.pi.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.camera.pi``
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.camera.pi
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/db.rst
Normal file
6
docs/source/platypush/plugins/db.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.db``
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.db
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/google.calendar.rst
Normal file
6
docs/source/platypush/plugins/google.calendar.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.google.calendar``
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.google.calendar
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/google.mail.rst
Normal file
6
docs/source/platypush/plugins/google.mail.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.google.mail``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.google.mail
|
||||||
|
:members:
|
||||||
|
|
6
docs/source/platypush/plugins/google.maps.rst
Normal file
6
docs/source/platypush/plugins/google.maps.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.google.maps``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.google.maps
|
||||||
|
:members:
|
||||||
|
|
7
docs/source/platypush/plugins/google.rst
Normal file
7
docs/source/platypush/plugins/google.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
``platypush.plugins.google``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.google
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
6
docs/source/platypush/plugins/gpio.rst
Normal file
6
docs/source/platypush/plugins/gpio.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
``platypush.plugins.gpio``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.gpio
|
||||||
|
:members:
|
||||||
|
|
19
docs/source/plugins.rst
Normal file
19
docs/source/plugins.rst
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Plugins
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Plugins:
|
||||||
|
|
||||||
|
platypush/plugins/assistant.google.rst
|
||||||
|
platypush/plugins/assistant.google.pushtotalk.rst
|
||||||
|
platypush/plugins/calendar.rst
|
||||||
|
platypush/plugins/calendar.ical.rst
|
||||||
|
platypush/plugins/camera.pi.rst
|
||||||
|
platypush/plugins/db.rst
|
||||||
|
platypush/plugins/google.rst
|
||||||
|
platypush/plugins/google.calendar.rst
|
||||||
|
platypush/plugins/google.mail.rst
|
||||||
|
platypush/plugins/google.maps.rst
|
||||||
|
platypush/plugins/gpio.rst
|
||||||
|
|
|
@ -1,15 +1,31 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
|
||||||
class AssistantGooglePlugin(Plugin):
|
class AssistantGooglePlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Google assistant plugin.
|
||||||
|
It acts like a wrapper around the :mod:`platypush.backend.assistant.google`
|
||||||
|
backend to programmatically control the conversation status.
|
||||||
|
"""
|
||||||
|
|
||||||
def start_conversation(self):
|
def start_conversation(self):
|
||||||
|
"""
|
||||||
|
Programmatically start a conversation with the assistant
|
||||||
|
"""
|
||||||
assistant = get_backend('assistant.google')
|
assistant = get_backend('assistant.google')
|
||||||
assistant.start_conversation()
|
assistant.start_conversation()
|
||||||
return Response(output='', errors=[])
|
return Response(output='', errors=[])
|
||||||
|
|
||||||
def stop_conversation(self):
|
def stop_conversation(self):
|
||||||
|
"""
|
||||||
|
Programmatically stop a running conversation with the assistant
|
||||||
|
"""
|
||||||
assistant = get_backend('assistant.google')
|
assistant = get_backend('assistant.google')
|
||||||
assistant.stop_conversation()
|
assistant.stop_conversation()
|
||||||
return Response(output='', errors=[])
|
return Response(output='', errors=[])
|
||||||
|
|
|
@ -1,15 +1,31 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
|
||||||
class AssistantGooglePushtotalkPlugin(Plugin):
|
class AssistantGooglePushtotalkPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Plugin for the Google assistant pushtotalk API. It acts as a wrapper to
|
||||||
|
programmatically control a
|
||||||
|
:mod:`platypush.backend.assistant.google.pushtotalk` backend.
|
||||||
|
"""
|
||||||
|
|
||||||
def start_conversation(self):
|
def start_conversation(self):
|
||||||
|
"""
|
||||||
|
Programmatically start a conversation with the assistant
|
||||||
|
"""
|
||||||
assistant = get_backend('assistant.google.pushtotalk')
|
assistant = get_backend('assistant.google.pushtotalk')
|
||||||
assistant.start_conversation()
|
assistant.start_conversation()
|
||||||
return Response(output='', errors=[])
|
return Response(output='', errors=[])
|
||||||
|
|
||||||
def stop_conversation(self):
|
def stop_conversation(self):
|
||||||
|
"""
|
||||||
|
Programmatically stop a running conversation with the assistant
|
||||||
|
"""
|
||||||
assistant = get_backend('assistant.google.pushtotalk')
|
assistant = get_backend('assistant.google.pushtotalk')
|
||||||
assistant.stop_conversation()
|
assistant.stop_conversation()
|
||||||
return Response(output='', errors=[])
|
return Response(output='', errors=[])
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
@ -17,16 +21,21 @@ class CalendarInterface:
|
||||||
|
|
||||||
class CalendarPlugin(Plugin, CalendarInterface):
|
class CalendarPlugin(Plugin, CalendarInterface):
|
||||||
"""
|
"""
|
||||||
The CalendarPlugin will allow you to keep track of multiple calendars
|
The CalendarPlugin allows you to keep track of multiple calendars (Google or
|
||||||
(Google or iCal URLs) and get joined events from all of them
|
iCal URLs) and get joined events from all of them.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
* **dateutil** (``pip install python-dateutil``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, calendars=[], *args, **kwargs):
|
def __init__(self, calendars=[], *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Example calendars format:
|
:param calendars: List of calendars to be queried. Supported types so far: Google Calendar and iCal URLs.
|
||||||
|
:type calendars: list
|
||||||
|
|
||||||
```
|
Example format::
|
||||||
[
|
|
||||||
|
calendars = [
|
||||||
{
|
{
|
||||||
"type": "platypush.plugins.google.calendar.GoogleCalendarPlugin"
|
"type": "platypush.plugins.google.calendar.GoogleCalendarPlugin"
|
||||||
},
|
},
|
||||||
|
@ -56,6 +65,45 @@ class CalendarPlugin(Plugin, CalendarInterface):
|
||||||
|
|
||||||
|
|
||||||
def get_upcoming_events(self, max_results=10):
|
def get_upcoming_events(self, max_results=10):
|
||||||
|
"""
|
||||||
|
Get a list of upcoming events merging all the available calendars.
|
||||||
|
|
||||||
|
: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.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
output = [
|
||||||
|
{
|
||||||
|
"id": "123456abcdef",
|
||||||
|
"kind": "calendar#event",
|
||||||
|
"status": "confirmed",
|
||||||
|
"htmlLink": "...",
|
||||||
|
"created": "2018-06-01T01:23:45.000Z",
|
||||||
|
"updated": "2018-06-01T01:23:45.000Z",
|
||||||
|
"creator": {
|
||||||
|
"email": "...",
|
||||||
|
"displayName": "...",
|
||||||
|
"self": true
|
||||||
|
},
|
||||||
|
"organizer" {
|
||||||
|
"email": "...",
|
||||||
|
"displayName": "...",
|
||||||
|
"self": true
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"dateTime": "2018-06-02T10:00:00.000Z",
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"dateTime": "2018-06-02T12:00:00.000Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
events = []
|
events = []
|
||||||
|
|
||||||
for calendar in self.calendars:
|
for calendar in self.calendars:
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import requests
|
import requests
|
||||||
|
@ -11,7 +15,15 @@ from platypush.plugins.calendar import CalendarInterface
|
||||||
|
|
||||||
|
|
||||||
class IcalCalendarPlugin(Plugin, CalendarInterface):
|
class IcalCalendarPlugin(Plugin, CalendarInterface):
|
||||||
|
"""
|
||||||
|
iCal calendars plugin. Interact with remote calendars in iCal format.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, url, *args, **kwargs):
|
def __init__(self, url, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param url: iCal URL to parse
|
||||||
|
:type url: str
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
||||||
|
@ -46,6 +58,11 @@ class IcalCalendarPlugin(Plugin, CalendarInterface):
|
||||||
|
|
||||||
|
|
||||||
def get_upcoming_events(self, max_results=10, only_participating=True):
|
def get_upcoming_events(self, max_results=10, only_participating=True):
|
||||||
|
"""
|
||||||
|
Get the upcoming events. See
|
||||||
|
:func:`~platypush.plugins.calendar.CalendarPlugin.get_upcoming_events`.
|
||||||
|
"""
|
||||||
|
|
||||||
events = []
|
events = []
|
||||||
response = requests.get(self.url)
|
response = requests.get(self.url)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
|
||||||
|
@ -5,17 +9,35 @@ from platypush.plugins import Plugin
|
||||||
|
|
||||||
|
|
||||||
class CameraPiPlugin(Plugin):
|
class CameraPiPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Plugin to control a Pi camera.
|
||||||
|
It acts as a wrapper around the :ref:`platypush.backend.camera.pi` backend
|
||||||
|
to programmatically control the status.
|
||||||
|
"""
|
||||||
|
|
||||||
def start_recording(self):
|
def start_recording(self):
|
||||||
|
"""
|
||||||
|
Start recording
|
||||||
|
"""
|
||||||
camera = get_backend('camera.pi')
|
camera = get_backend('camera.pi')
|
||||||
camera.send_camera_action(camera.CameraAction.START_RECORDING)
|
camera.send_camera_action(camera.CameraAction.START_RECORDING)
|
||||||
return Response(output={'status':'ok'})
|
return Response(output={'status':'ok'})
|
||||||
|
|
||||||
def stop_recording(self):
|
def stop_recording(self):
|
||||||
|
"""
|
||||||
|
Stop recording
|
||||||
|
"""
|
||||||
camera = get_backend('camera.pi')
|
camera = get_backend('camera.pi')
|
||||||
camera.send_camera_action(camera.CameraAction.STOP_RECORDING)
|
camera.send_camera_action(camera.CameraAction.STOP_RECORDING)
|
||||||
return Response(output={'status':'ok'})
|
return Response(output={'status':'ok'})
|
||||||
|
|
||||||
def take_picture(self, image_file):
|
def take_picture(self, image_file):
|
||||||
|
"""
|
||||||
|
Take a picture.
|
||||||
|
|
||||||
|
:param image_file: Path where the output image will be stored.
|
||||||
|
:type image_file: str
|
||||||
|
"""
|
||||||
camera = get_backend('camera.pi')
|
camera = get_backend('camera.pi')
|
||||||
camera.send_camera_action(camera.CameraAction.TAKE_PICTURE, image_file=image_file)
|
camera.send_camera_action(camera.CameraAction.TAKE_PICTURE, image_file=image_file)
|
||||||
return Response(output={'image_file':image_file})
|
return Response(output={'image_file':image_file})
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
from sqlalchemy import create_engine, Table, MetaData
|
from sqlalchemy import create_engine, Table, MetaData
|
||||||
|
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
@ -5,20 +9,26 @@ from platypush.message.response import Response
|
||||||
from .. import Plugin
|
from .. import Plugin
|
||||||
|
|
||||||
class DbPlugin(Plugin):
|
class DbPlugin(Plugin):
|
||||||
""" Database plugin. It allows you to programmatically select, insert,
|
"""
|
||||||
update and delete records on a database backend through requests,
|
Database plugin. It allows you to programmatically select, insert, update
|
||||||
procedures and event hooks """
|
and delete records on a database backend through requests, procedures and
|
||||||
|
event hooks.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
* **sqlalchemy** (``pip install sqlalchemy``)
|
||||||
|
|
||||||
|
.. todo::
|
||||||
|
Implement ``update`` and ``delete`` methods
|
||||||
|
"""
|
||||||
|
|
||||||
engine = None
|
engine = None
|
||||||
|
|
||||||
def __init__(self, engine=None, *args, **kwargs):
|
def __init__(self, engine=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Params:
|
:param engine: Default SQLAlchemy connection engine string (e.g. ``sqlite:///:memory:`` or ``mysql://user:pass@localhost/test``) that will be used. You can override the default engine in the db actions.
|
||||||
engine -- Default SQLAlchemy connection engine string
|
:type engine: str
|
||||||
(e.g. sqlite:///:memory: or mysql://user:pass@localhost/test)
|
:param args: Extra arguments that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
that will be used. You can override this value in your statement actions
|
:param kwargs: Extra kwargs that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
|
||||||
args, kwargs -- Extra arguments for sqlalchemy.create_engine
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.engine = self._get_engine(engine, *args, **kwargs)
|
self.engine = self._get_engine(engine, *args, **kwargs)
|
||||||
|
@ -31,7 +41,23 @@ class DbPlugin(Plugin):
|
||||||
return self.engine
|
return self.engine
|
||||||
|
|
||||||
def execute(self, statement, engine=None, *args, **kwargs):
|
def execute(self, statement, engine=None, *args, **kwargs):
|
||||||
""" Executes a raw SQL statement """
|
"""
|
||||||
|
Executes a raw SQL statement.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Avoid calling this method directly if possible. Use ``insert``,
|
||||||
|
``update`` and ``delete`` methods instead if possible. Don't use this
|
||||||
|
method if you need to select records, use the ``select`` method
|
||||||
|
instead, as this method is mostly meant to execute raw SQL without
|
||||||
|
returning anything.
|
||||||
|
|
||||||
|
:param statement: SQL to be executed
|
||||||
|
:type statement: str
|
||||||
|
:param engine: Engine to be used (default: default class engine)
|
||||||
|
:type engine: str
|
||||||
|
:param args: Extra arguments that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
:param kwargs: Extra kwargs that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
"""
|
||||||
|
|
||||||
engine = self._get_engine(engine, *args, **kwargs)
|
engine = self._get_engine(engine, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -43,7 +69,45 @@ class DbPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def select(self, query, engine=None, *args, **kwargs):
|
def select(self, query, engine=None, *args, **kwargs):
|
||||||
""" Returns rows (as a list of dicts) given a query """
|
"""
|
||||||
|
Returns rows (as a list of hashes) given a query.
|
||||||
|
|
||||||
|
:param query: SQL to be executed
|
||||||
|
:type query: str
|
||||||
|
:param engine: Engine to be used (default: default class engine)
|
||||||
|
:type engine: str
|
||||||
|
:param args: Extra arguments that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
:param kwargs: Extra kwargs that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
:returns: List of hashes representing the result rows.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Request::
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "request",
|
||||||
|
"target": "your_host",
|
||||||
|
"action": "db.select",
|
||||||
|
"args": {
|
||||||
|
"engine": "sqlite:///:memory:",
|
||||||
|
"query": "SELECT id, name FROM table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": foo
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": bar
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
engine = self._get_engine(engine, *args, **kwargs)
|
engine = self._get_engine(engine, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -59,7 +123,43 @@ class DbPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def insert(self, table, records, engine=None, *args, **kwargs):
|
def insert(self, table, records, engine=None, *args, **kwargs):
|
||||||
""" Inserts records (as a list of dicts) into a table """
|
"""
|
||||||
|
Inserts records (as a list of hashes) into a table.
|
||||||
|
|
||||||
|
:param table: Table name
|
||||||
|
:type table: str
|
||||||
|
:param records: Records to be inserted (as a list of hashes)
|
||||||
|
:type records: list
|
||||||
|
:param engine: Engine to be used (default: default class engine)
|
||||||
|
:type engine: str
|
||||||
|
:param args: Extra arguments that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
:param kwargs: Extra kwargs that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Request::
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "request",
|
||||||
|
"target": "your_host",
|
||||||
|
"action": "db.insert",
|
||||||
|
"args": {
|
||||||
|
"table": "table",
|
||||||
|
"engine": "sqlite:///:memory:",
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": foo
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": bar
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
engine = self._get_engine(engine, *args, **kwargs)
|
engine = self._get_engine(engine, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
@ -7,6 +11,7 @@ from platypush.plugins.google.credentials import get_credentials
|
||||||
class GooglePlugin(Plugin):
|
class GooglePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Executes calls to the Google APIs using the google-api-python-client.
|
Executes calls to the Google APIs using the google-api-python-client.
|
||||||
|
This class is extended by ``GoogleMailPlugin``, ``GoogleCalendarPlugin`` etc.
|
||||||
In order to use Google services (like GMail, Maps, Calendar etc.) with
|
In order to use Google services (like GMail, Maps, Calendar etc.) with
|
||||||
your account you need to:
|
your account you need to:
|
||||||
|
|
||||||
|
@ -19,12 +24,23 @@ class GooglePlugin(Plugin):
|
||||||
|
|
||||||
4. Click on the "Download JSON" icon next to your newly created client ID
|
4. Click on the "Download JSON" icon next to your newly created client ID
|
||||||
|
|
||||||
5. Generate a credentials file for the needed scope:
|
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:
|
||||||
|
|
||||||
|
* **google-api-python-client** (``pip install google-api-python-client``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, scopes, *args, **kwargs):
|
def __init__(self, scopes, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialized the Google plugin with the required scopes.
|
||||||
|
|
||||||
|
:param scopes: List of required scopes
|
||||||
|
:type scopes: list
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.credentials = {}
|
self.credentials = {}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import httplib2
|
import httplib2
|
||||||
|
@ -11,6 +15,10 @@ from platypush.plugins.calendar import CalendarInterface
|
||||||
|
|
||||||
|
|
||||||
class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
|
class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
|
||||||
|
"""
|
||||||
|
Google calendar plugin
|
||||||
|
"""
|
||||||
|
|
||||||
scopes = ['https://www.googleapis.com/auth/calendar.readonly']
|
scopes = ['https://www.googleapis.com/auth/calendar.readonly']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -18,6 +26,11 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
|
||||||
|
|
||||||
|
|
||||||
def get_upcoming_events(self, max_results=10):
|
def get_upcoming_events(self, max_results=10):
|
||||||
|
"""
|
||||||
|
Get the upcoming events. See
|
||||||
|
:func:`~platypush.plugins.calendar.CalendarPlugin.get_upcoming_events`.
|
||||||
|
"""
|
||||||
|
|
||||||
now = datetime.datetime.utcnow().isoformat() + 'Z'
|
now = datetime.datetime.utcnow().isoformat() + 'Z'
|
||||||
service = self._get_service()
|
service = self._get_service()
|
||||||
result = service.events().list(calendarId='primary', timeMin=now,
|
result = service.events().list(calendarId='primary', timeMin=now,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import httplib2
|
import httplib2
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
@ -18,6 +22,10 @@ from platypush.plugins.google import GooglePlugin
|
||||||
|
|
||||||
|
|
||||||
class GoogleMailPlugin(GooglePlugin):
|
class GoogleMailPlugin(GooglePlugin):
|
||||||
|
"""
|
||||||
|
GMail plugin. It allows you to programmatically compose and (TODO) get emails
|
||||||
|
"""
|
||||||
|
|
||||||
scopes = ['https://www.googleapis.com/auth/gmail.modify']
|
scopes = ['https://www.googleapis.com/auth/gmail.modify']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -25,6 +33,25 @@ class GoogleMailPlugin(GooglePlugin):
|
||||||
|
|
||||||
|
|
||||||
def compose(self, sender, to, subject, body, files=None):
|
def compose(self, sender, to, subject, body, files=None):
|
||||||
|
"""
|
||||||
|
Compose a message.
|
||||||
|
|
||||||
|
:param sender: Sender email/name
|
||||||
|
:type sender: str
|
||||||
|
|
||||||
|
:param to: Recipient email or comma-separated list of recipient emails
|
||||||
|
:type to: str
|
||||||
|
|
||||||
|
:param subject: Email subject
|
||||||
|
:type subject: str
|
||||||
|
|
||||||
|
:param body: Email body
|
||||||
|
:type body: str
|
||||||
|
|
||||||
|
:param files: Optional list of files to attach
|
||||||
|
:type files: list
|
||||||
|
"""
|
||||||
|
|
||||||
message = MIMEMultipart() if files else MIMEText(body)
|
message = MIMEMultipart() if files else MIMEText(body)
|
||||||
message['to'] = to
|
message['to'] = to
|
||||||
message['from'] = sender
|
message['from'] = sender
|
||||||
|
@ -68,6 +95,9 @@ class GoogleMailPlugin(GooglePlugin):
|
||||||
|
|
||||||
|
|
||||||
def get_labels(self):
|
def get_labels(self):
|
||||||
|
"""
|
||||||
|
Returns the available labels on the GMail account
|
||||||
|
"""
|
||||||
service = self._get_service()
|
service = self._get_service()
|
||||||
results = service.users().labels().list(userId='me').execute()
|
results = service.users().labels().list(userId='me').execute()
|
||||||
labels = results.get('labels', [])
|
labels = results.get('labels', [])
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -6,14 +10,33 @@ from platypush.plugins.google import GooglePlugin
|
||||||
|
|
||||||
|
|
||||||
class GoogleMapsPlugin(GooglePlugin):
|
class GoogleMapsPlugin(GooglePlugin):
|
||||||
|
"""
|
||||||
|
Plugins that provides utilities to interact with Google Maps API services.
|
||||||
|
"""
|
||||||
|
|
||||||
scopes = []
|
scopes = []
|
||||||
|
|
||||||
def __init__(self, api_key, *args, **kwargs):
|
def __init__(self, api_key, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param api_key: Server-side API key to be used for the requests, get one at https://console.developers.google.com
|
||||||
|
:type api_key: str
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(scopes=self.scopes, *args, **kwargs)
|
super().__init__(scopes=self.scopes, *args, **kwargs)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
|
|
||||||
|
|
||||||
def get_address_from_latlng(self, latitude, longitude):
|
def get_address_from_latlng(self, latitude, longitude):
|
||||||
|
"""
|
||||||
|
Get an address information given lat/long
|
||||||
|
|
||||||
|
:param latitude: Latitude
|
||||||
|
:type latitude: float
|
||||||
|
|
||||||
|
:param longitude: Longitude
|
||||||
|
: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 = {
|
params = {
|
||||||
'latlng': '{},{}'.format(latitude, longitude),
|
'latlng': '{},{}'.format(latitude, longitude),
|
||||||
|
|
|
@ -1,14 +1,45 @@
|
||||||
|
"""
|
||||||
|
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||||
|
"""
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import RPi.GPIO as gpio
|
|
||||||
|
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
|
||||||
|
|
||||||
class GpioPlugin(Plugin):
|
class GpioPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Plugin to handle raw read/write operation on the Raspberry Pi GPIO pins.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
* **RPi.GPIO** (`pip install RPi.GPIO`)
|
||||||
|
"""
|
||||||
|
|
||||||
def write(self, pin, val):
|
def write(self, pin, val):
|
||||||
|
"""
|
||||||
|
Write a byte value to a pin.
|
||||||
|
|
||||||
|
:param pin: PIN number
|
||||||
|
:type pin: int
|
||||||
|
|
||||||
|
:param val: Value to write
|
||||||
|
:type val: int
|
||||||
|
|
||||||
|
:returns: dict
|
||||||
|
|
||||||
|
Response::
|
||||||
|
|
||||||
|
output = {
|
||||||
|
"pin": <pin>,
|
||||||
|
"val": <val>,
|
||||||
|
"method": "write"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import RPi.GPIO as gpio
|
||||||
|
|
||||||
gpio.setmode(gpio.BCM)
|
gpio.setmode(gpio.BCM)
|
||||||
gpio.setup(pin, gpio.OUT)
|
gpio.setup(pin, gpio.OUT)
|
||||||
gpio.output(pin, val)
|
gpio.output(pin, val)
|
||||||
|
@ -19,7 +50,26 @@ class GpioPlugin(Plugin):
|
||||||
'method': 'write',
|
'method': 'write',
|
||||||
})
|
})
|
||||||
|
|
||||||
def read(self, pin, val):
|
def read(self, pin):
|
||||||
|
"""
|
||||||
|
Reads a value from a PIN.
|
||||||
|
|
||||||
|
:param pin: PIN number
|
||||||
|
:type pin: int
|
||||||
|
|
||||||
|
:returns: dict
|
||||||
|
|
||||||
|
Response::
|
||||||
|
|
||||||
|
output = {
|
||||||
|
"pin": <pin>,
|
||||||
|
"val": <val>,
|
||||||
|
"method": "read"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import RPi.GPIO as gpio
|
||||||
|
|
||||||
gpio.setmode(gpio.BCM)
|
gpio.setmode(gpio.BCM)
|
||||||
gpio.setup(pin, gpio.IN)
|
gpio.setup(pin, gpio.IN)
|
||||||
val = gpio.input(pin)
|
val = gpio.input(pin)
|
||||||
|
|
|
@ -15,6 +15,7 @@ class WeatherForecastPlugin(HttpRequestPlugin):
|
||||||
|
|
||||||
def get_current_weather(self, **kwargs):
|
def get_current_weather(self, **kwargs):
|
||||||
response = self.get(self.url)
|
response = self.get(self.url)
|
||||||
|
print(response)
|
||||||
return Response(output=response.output['currently'])
|
return Response(output=response.output['currently'])
|
||||||
|
|
||||||
def get_hourly_forecast(self, **kwargs):
|
def get_hourly_forecast(self, **kwargs):
|
||||||
|
|
Loading…
Reference in a new issue