diff --git a/aprsd/flask.py b/aprsd/flask.py index b49c862..d6136b2 100644 --- a/aprsd/flask.py +++ b/aprsd/flask.py @@ -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 diff --git a/aprsd/plugin.py b/aprsd/plugin.py index bd7182a..10ed29a 100644 --- a/aprsd/plugin.py +++ b/aprsd/plugin.py @@ -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.""" diff --git a/aprsd/plugins/stock.py b/aprsd/plugins/stock.py new file mode 100644 index 0000000..4d3b20f --- /dev/null +++ b/aprsd/plugins/stock.py @@ -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() diff --git a/requirements.in b/requirements.in index b4775ee..f9e9ba7 100644 --- a/requirements.in +++ b/requirements.in @@ -15,3 +15,4 @@ opencage flask flask-classful flask-httpauth +yfinance diff --git a/requirements.txt b/requirements.txt index 5c91211..98e044c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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