From 2e10a06a388a2afad414e8842bf1b42db7cb57ff Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 15 Oct 2019 12:28:31 -0600 Subject: [PATCH] Creating example plugin. --- wrolpi/plugins/example_plugin/__init__.py | 3 ++ wrolpi/plugins/example_plugin/api.py | 43 +++++++++++++++ wrolpi/plugins/example_plugin/common.py | 7 +++ wrolpi/plugins/example_plugin/main.py | 52 +++++++++++++++++++ .../example_plugin/templates/index.html | 18 +++++++ .../plugins/example_plugin/test/__init__.py | 0 .../plugins/example_plugin/test/test_api.py | 24 +++++++++ wrolpi/plugins/example_plugin/v1.sql | 9 ++++ wrolpi/plugins/example_plugin/web.py | 33 ++++++++++++ wrolpi/user_plugins.py | 7 ++- 10 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 wrolpi/plugins/example_plugin/__init__.py create mode 100644 wrolpi/plugins/example_plugin/api.py create mode 100644 wrolpi/plugins/example_plugin/common.py create mode 100755 wrolpi/plugins/example_plugin/main.py create mode 100644 wrolpi/plugins/example_plugin/templates/index.html create mode 100644 wrolpi/plugins/example_plugin/test/__init__.py create mode 100644 wrolpi/plugins/example_plugin/test/test_api.py create mode 100644 wrolpi/plugins/example_plugin/v1.sql create mode 100644 wrolpi/plugins/example_plugin/web.py diff --git a/wrolpi/plugins/example_plugin/__init__.py b/wrolpi/plugins/example_plugin/__init__.py new file mode 100644 index 00000000..e50ecbce --- /dev/null +++ b/wrolpi/plugins/example_plugin/__init__.py @@ -0,0 +1,3 @@ +from wrolpi.plugins.example_plugin.api import APIRoot +from wrolpi.plugins.example_plugin.main import PRETTY_NAME, init_parser, import_settings_config, save_settings_config +from wrolpi.plugins.example_plugin.web import ClientRoot, set_plugins, LINK diff --git a/wrolpi/plugins/example_plugin/api.py b/wrolpi/plugins/example_plugin/api.py new file mode 100644 index 00000000..bf3565e0 --- /dev/null +++ b/wrolpi/plugins/example_plugin/api.py @@ -0,0 +1,43 @@ +""" +These are the API methods for your plugin. It will be accessed through the key you set in user_plugins.py. These +should return JSON, if anything. These will be required to be accessed through a cherrypy.dispatch.MethodDispatcher(), +which means the request will be routed by the HTTP method you use. For example, the video plugin: + + PUT /api/videos/settings + + will be routed to videos.api.APIRoot.settings.PUT + + +Required: APIRoot +""" +import json + +import cherrypy +from dictorm import DictDB + +from wrolpi.plugins.example_plugin.common import hello +from wrolpi.tools import setup_tools + +setup_tools() + + +# Do not change the name of this class, it is expected by wrolpi.web +class APIRoot(object): + + def __init__(self): + self.settings = SettingsAPI() + + +@cherrypy.expose +class SettingsAPI(object): + + @cherrypy.tools.db() + def GET(self, db: DictDB): + # curl http://127.0.0.1:8080/api/example_plugin/settings + return json.dumps({'hello': hello()}) + + @cherrypy.tools.db() + def POST(self, db: DictDB, **form_data): + # curl -X POST -d arg=val http://127.0.0.1:8080/api/example_plugin/settings + with db.transaction(commit=True): + return json.dumps({'hello': hello(), 'form_data': form_data}) diff --git a/wrolpi/plugins/example_plugin/common.py b/wrolpi/plugins/example_plugin/common.py new file mode 100644 index 00000000..1ae5020f --- /dev/null +++ b/wrolpi/plugins/example_plugin/common.py @@ -0,0 +1,7 @@ +""" +This module should contain any functions you will use in more than one module. +""" + + +def hello(): + return 'world' diff --git a/wrolpi/plugins/example_plugin/main.py b/wrolpi/plugins/example_plugin/main.py new file mode 100755 index 00000000..5c30a1d9 --- /dev/null +++ b/wrolpi/plugins/example_plugin/main.py @@ -0,0 +1,52 @@ +#! /usr/bin/env python3 +""" +This module will be the entry-point for your plugin. The wrolpi.main will expect these basic functions to be here. Add +onto them to build your plugin. + +Required: PRETTY_NAME, init_parser, main +""" +import argparse +import logging + +from wrolpi.plugins.example_plugin.common import hello +from wrolpi.tools import setup_tools + +# Pretty Name will be displayed when your plugin is linked to in the UI +PRETTY_NAME = 'Example Plugin' +logger = logging.getLogger('wrolpi') + +setup_tools() + + +def init_parser(sub_commands): + # This function is called by WROLPI's main() function during startup, use it to setup your + # command-line arguments. If you don't have any, put a "pass" here + download_parser = sub_commands.add_parser('example_plugin') + # Add your own arguments + download_parser.add_argument('-a', '--asdf', action='store_true', default=False, help='Asdf') + + +def main(args): + # This function will be called when the parsers you defined above are passed via the command-line. + if args.sub_commands and 'example_plugin' in args.sub_commands: + print(f'hello {hello()}!') + return 0 + + +def import_settings_config(): + # If you build a config, it will be imported on startup here + pass + + +def save_settings_config(): + # This could be called by the command-line to save the DB settings to a config. + pass + + +if __name__ == '__main__': + # If run directly, we'll make our own parser in the same form that wrolpi.main does + parser = argparse.ArgumentParser() + sub_commands = parser.add_subparsers(title='sub-commands', dest='sub_commands') + init_parser(sub_commands) + args = parser.parse_args() + main(args) diff --git a/wrolpi/plugins/example_plugin/templates/index.html b/wrolpi/plugins/example_plugin/templates/index.html new file mode 100644 index 00000000..ba1fd103 --- /dev/null +++ b/wrolpi/plugins/example_plugin/templates/index.html @@ -0,0 +1,18 @@ +{# This is built off the main base.html. Please keep this, the plugin shouldn't be drastically different. #} +{% extends 'wrolpi/templates/base.html' %} +{% set title=title or 'Your Example Plugin Title' %} {# The title on the browser tab #} + +{% block body %} +

Example plugin!

+ +

Hello {{ hello }}!

+ GET Settings + +
+ +
+ Arg: +
+ +
+{% endblock %} diff --git a/wrolpi/plugins/example_plugin/test/__init__.py b/wrolpi/plugins/example_plugin/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wrolpi/plugins/example_plugin/test/test_api.py b/wrolpi/plugins/example_plugin/test/test_api.py new file mode 100644 index 00000000..dc109fff --- /dev/null +++ b/wrolpi/plugins/example_plugin/test/test_api.py @@ -0,0 +1,24 @@ +""" +This will test your code (you're going to test your code, right?). Here are some simple tests. + +Keep your tests simple! Don't test things like the full HTML of an endpoint, what if the base.html is changed out from +under you? +""" +import unittest + +from wrolpi.common import get_db_context +from wrolpi.plugins.example_plugin.api import APIRoot +from wrolpi.plugins.example_plugin.common import hello +from wrolpi.test.common import test_db_wrapper + + +class TestAPI(unittest.TestCase): + + def test_hello(self): + self.assertEqual(hello(), 'world') + + @test_db_wrapper + def test_get(self): + api = APIRoot() + with get_db_context() as (db_conn, db): + self.assertEqual(api.settings.GET(db), '{"hello": "world"}') diff --git a/wrolpi/plugins/example_plugin/v1.sql b/wrolpi/plugins/example_plugin/v1.sql new file mode 100644 index 00000000..9538938c --- /dev/null +++ b/wrolpi/plugins/example_plugin/v1.sql @@ -0,0 +1,9 @@ +-- noinspection SqlNoDataSourceInspectionForFile + +-- Create the tables for plugin here. Don't step on any other plugin's tables! This will be applied during the DB +-- build/initialization. + +CREATE TABLE example_plugin +( + id SERIAL PRIMARY KEY +); diff --git a/wrolpi/plugins/example_plugin/web.py b/wrolpi/plugins/example_plugin/web.py new file mode 100644 index 00000000..5023ad00 --- /dev/null +++ b/wrolpi/plugins/example_plugin/web.py @@ -0,0 +1,33 @@ +""" +These are the UI classes for your plugin. They should all return a rendered HTML template from Jinja2. These methods +should NEVER make changes to the DB, do that in your API methods. + +Required: LINK, PLUGINS, set_plugins, ClientRoot +""" +import cherrypy +from dictorm import DictDB + +from wrolpi.common import env +from wrolpi.plugins.example_plugin.common import hello + +# This is your link suffix. It will be used to link to your web classes here. +LINK = 'example_plugin' + +# This will be set once all plugins are loaded +PLUGINS = None + + +def set_plugins(plugins): + global PLUGINS + PLUGINS = plugins + + +class ClientRoot(object): + + @cherrypy.expose + @cherrypy.tools.db() + def index(self, db: DictDB): + # curl http://127.0.0.1:8080/example_plugin + template = env.get_template('wrolpi/plugins/example_plugin/templates/index.html') + html = template.render(hello=hello(), plugins=PLUGINS) + return html diff --git a/wrolpi/user_plugins.py b/wrolpi/user_plugins.py index 2149a3a7..ae3b598f 100644 --- a/wrolpi/user_plugins.py +++ b/wrolpi/user_plugins.py @@ -1,13 +1,18 @@ """ -This document contains all plugins that will be loaded and linked to in the navbar. Add to the +This module contains all plugins that will be loaded and linked to in the navbar. Add to the plugins object below when adding a new plugin. + +You can uncomment the "example_plugin" lines below to explore building your own plugin! """ from collections import OrderedDict from wrolpi.plugins import videos +from wrolpi.plugins import example_plugin + PLUGINS = OrderedDict( videos=videos, + example_plugin=example_plugin, ) # Share this list of plugins with every plugin