mirror of
https://github.com/craigerl/aprsd.git
synced 2024-12-22 09:31:42 -05:00
Added plugin live reload and StockPlugin
This patch adds 2 items. First it adds the new StockPlugin, which fetches stock quotes from yahoo finance rest API using the yfinance python module. 2nd, the web interface contains a new url /plugins, which allows aprsd to reload all of it's plugins from disk. This is useful for development where the dev is editing an existing plugin and wants to run the edited plugin without restarting aprsd itself. The /plugins url requires admin login credentials. TODO: would be nice to live reload the aprsd.yml config file, so plugin reloading can start new plugins defined in aprsd.yml between /plugins being reloaded.
This commit is contained in:
parent
9f66774541
commit
e6cafeb3d2
@ -2,7 +2,7 @@ import json
|
||||
import logging
|
||||
|
||||
import aprsd
|
||||
from aprsd import messaging, stats
|
||||
from aprsd import messaging, plugin, stats
|
||||
import flask
|
||||
import flask_classful
|
||||
from flask_httpauth import HTTPBasicAuth
|
||||
@ -53,6 +53,13 @@ class APRSDFlask(flask_classful.FlaskView):
|
||||
|
||||
return flask.render_template("messages.html", messages=json.dumps(msgs))
|
||||
|
||||
@auth.login_required
|
||||
def plugins(self):
|
||||
pm = plugin.PluginManager()
|
||||
pm.reload_plugins()
|
||||
|
||||
return "reloaded"
|
||||
|
||||
@auth.login_required
|
||||
def save(self):
|
||||
"""Save the existing queue to disk."""
|
||||
@ -86,4 +93,5 @@ def init_flask(config):
|
||||
flask_app.route("/stats", methods=["GET"])(server.stats)
|
||||
flask_app.route("/messages", methods=["GET"])(server.messages)
|
||||
flask_app.route("/save", methods=["GET"])(server.save)
|
||||
flask_app.route("/plugins", methods=["GET"])(server.plugins)
|
||||
return flask_app
|
||||
|
@ -6,6 +6,7 @@ import inspect
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
|
||||
import pluggy
|
||||
from thesmuggler import smuggle
|
||||
@ -22,6 +23,7 @@ CORE_PLUGINS = [
|
||||
"aprsd.plugins.location.LocationPlugin",
|
||||
"aprsd.plugins.ping.PingPlugin",
|
||||
"aprsd.plugins.query.QueryPlugin",
|
||||
"aprsd.plugins.stock.StockPlugin",
|
||||
"aprsd.plugins.time.TimePlugin",
|
||||
"aprsd.plugins.weather.USWeatherPlugin",
|
||||
"aprsd.plugins.version.VersionPlugin",
|
||||
@ -82,11 +84,14 @@ class PluginManager:
|
||||
# aprsd config dict
|
||||
config = None
|
||||
|
||||
lock = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""This magic turns this into a singleton."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
# Put any initialization here.
|
||||
cls._instance.lock = threading.Lock()
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, config=None):
|
||||
@ -135,6 +140,7 @@ class PluginManager:
|
||||
module_name, class_name = module_class_string.rsplit(".", 1)
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
module = importlib.reload(module)
|
||||
except Exception as ex:
|
||||
LOG.error("Failed to load Plugin '{}' : '{}'".format(module_name, ex))
|
||||
return
|
||||
@ -180,6 +186,11 @@ class PluginManager:
|
||||
except Exception as ex:
|
||||
LOG.exception("Couldn't load plugin '{}'".format(plugin_name), ex)
|
||||
|
||||
def reload_plugins(self):
|
||||
with self.lock:
|
||||
del self._pluggy_pm
|
||||
self.setup_plugins()
|
||||
|
||||
def setup_plugins(self):
|
||||
"""Create the plugin manager and register plugins."""
|
||||
|
||||
@ -223,7 +234,8 @@ class PluginManager:
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
"""Execute all the pluguns run method."""
|
||||
return self._pluggy_pm.hook.run(*args, **kwargs)
|
||||
with self.lock:
|
||||
return self._pluggy_pm.hook.run(*args, **kwargs)
|
||||
|
||||
def register(self, obj):
|
||||
"""Register the plugin."""
|
||||
|
45
aprsd/plugins/stock.py
Normal file
45
aprsd/plugins/stock.py
Normal file
@ -0,0 +1,45 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from aprsd import plugin, trace
|
||||
import yfinance as yf
|
||||
|
||||
LOG = logging.getLogger("APRSD")
|
||||
|
||||
|
||||
class StockPlugin(plugin.APRSDPluginBase):
|
||||
"""Stock market plugin for fetching stock quotes"""
|
||||
|
||||
version = "1.0"
|
||||
command_regex = "^[sS]"
|
||||
command_name = "stock"
|
||||
|
||||
@trace.trace
|
||||
def command(self, fromcall, message, ack):
|
||||
LOG.info("StockPlugin")
|
||||
|
||||
a = re.search(r"^.*\s+(.*)", message)
|
||||
if a is not None:
|
||||
searchcall = a.group(1)
|
||||
stock_symbol = searchcall.upper()
|
||||
else:
|
||||
reply = "No stock symbol"
|
||||
return reply
|
||||
|
||||
LOG.info("Fetch stock quote for '{}'".format(stock_symbol))
|
||||
|
||||
try:
|
||||
stock = yf.Ticker(stock_symbol)
|
||||
reply = "{} - ask: {} high: {} low: {}".format(
|
||||
stock_symbol,
|
||||
stock.info["ask"],
|
||||
stock.info["dayHigh"],
|
||||
stock.info["dayLow"],
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
"Failed to fetch stock '{}' from yahoo '{}'".format(stock_symbol, e),
|
||||
)
|
||||
reply = "Failed to fetch stock '{}'".format(stock_symbol)
|
||||
|
||||
return reply.rstrip()
|
@ -15,3 +15,4 @@ opencage
|
||||
flask
|
||||
flask-classful
|
||||
flask-httpauth
|
||||
yfinance
|
||||
|
@ -58,12 +58,22 @@ jinja2==2.11.2
|
||||
# via
|
||||
# click-completion
|
||||
# flask
|
||||
lxml==4.6.2
|
||||
# via yfinance
|
||||
markupsafe==1.1.1
|
||||
# via jinja2
|
||||
multitasking==0.0.9
|
||||
# via yfinance
|
||||
nodeenv==1.5.0
|
||||
# via pre-commit
|
||||
numpy==1.20.1
|
||||
# via
|
||||
# pandas
|
||||
# yfinance
|
||||
opencage==1.2.2
|
||||
# via -r requirements.in
|
||||
pandas==1.2.2
|
||||
# via yfinance
|
||||
pbr==5.5.1
|
||||
# via -r requirements.in
|
||||
pluggy==0.13.1
|
||||
@ -76,8 +86,12 @@ pycparser==2.20
|
||||
# via cffi
|
||||
pyopenssl==20.0.1
|
||||
# via opencage
|
||||
python-dateutil==2.8.1
|
||||
# via pandas
|
||||
pytz==2020.5
|
||||
# via -r requirements.in
|
||||
# via
|
||||
# -r requirements.in
|
||||
# pandas
|
||||
pyyaml==5.4.1
|
||||
# via
|
||||
# -r requirements.in
|
||||
@ -86,6 +100,7 @@ requests==2.25.1
|
||||
# via
|
||||
# -r requirements.in
|
||||
# opencage
|
||||
# yfinance
|
||||
shellingham==1.3.2
|
||||
# via click-completion
|
||||
six==1.15.0
|
||||
@ -96,6 +111,7 @@ six==1.15.0
|
||||
# imapclient
|
||||
# opencage
|
||||
# pyopenssl
|
||||
# python-dateutil
|
||||
# virtualenv
|
||||
thesmuggler==1.0.1
|
||||
# via -r requirements.in
|
||||
@ -107,3 +123,5 @@ virtualenv==20.4.0
|
||||
# via pre-commit
|
||||
werkzeug==1.0.1
|
||||
# via flask
|
||||
yfinance==0.1.55
|
||||
# via -r requirements.in
|
||||
|
Loading…
Reference in New Issue
Block a user