1
0
mirror of https://github.com/craigerl/aprsd.git synced 2024-11-10 10:33:31 -05:00

Upgraded the send-message POC to use websockets

This patch updates the send message Admi page to use
websockets.  It makes updates to the messages list instant.
This commit is contained in:
Hemna 2021-08-27 15:10:37 -04:00
parent 23cbf32814
commit c941379a5c
9 changed files with 351 additions and 271 deletions

View File

@ -123,6 +123,11 @@ class Aprsdis(aprslib.IS):
self.select_timeout, self.select_timeout,
) )
if not readable: if not readable:
if not blocking:
#self.logger.warning("not fucking readable, not blocking, break!")
break
else:
#self.logger.warning("not fucking readable, continue")
continue continue
try: try:
@ -224,7 +229,7 @@ class Aprsdis(aprslib.IS):
line = b"" line = b""
while True: while True and not self.thread_stop:
try: try:
for line in self._socket_readlines(blocking): for line in self._socket_readlines(blocking):
if line[0:1] != b"#": if line[0:1] != b"#":
@ -270,7 +275,9 @@ class Aprsdis(aprslib.IS):
raise raise
if not blocking: if not blocking:
#self.logger.error("Not blocking, bail bitch")
break break
#self.logger.error("Consumer exiting")
def get_client(): def get_client():

View File

@ -13,6 +13,7 @@ import flask
from flask import request from flask import request
import flask_classful import flask_classful
from flask_httpauth import HTTPBasicAuth from flask_httpauth import HTTPBasicAuth
from flask_socketio import Namespace, SocketIO
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
import aprsd import aprsd
@ -108,11 +109,14 @@ class SendMessageThread(threads.APRSDThread):
aprsis_client = None aprsis_client = None
request = None request = None
got_ack = False got_ack = False
got_reply = False
def __init__(self, config, info, msg): def __init__(self, config, info, msg, namespace):
self.config = config self.config = config
self.request = info self.request = info
self.msg = msg self.msg = msg
self.namespace = namespace
self.start_time = datetime.datetime.now()
msg = "({} -> {}) : {}".format( msg = "({} -> {}) : {}".format(
info["from"], info["from"],
info["to"], info["to"],
@ -183,7 +187,7 @@ class SendMessageThread(threads.APRSDThread):
LOG.debug("Exiting") LOG.debug("Exiting")
def rx_packet(self, packet): def rx_packet(self, packet):
global got_ack, got_response global socketio
# LOG.debug("Got packet back {}".format(packet)) # LOG.debug("Got packet back {}".format(packet))
resp = packet.get("response", None) resp = packet.get("response", None)
if resp == "ack": if resp == "ack":
@ -191,10 +195,15 @@ class SendMessageThread(threads.APRSDThread):
LOG.info(f"We got ack for our sent message {ack_num}") LOG.info(f"We got ack for our sent message {ack_num}")
messaging.log_packet(packet) messaging.log_packet(packet)
SentMessages().ack(self.msg.id) SentMessages().ack(self.msg.id)
socketio.emit(
"ack", SentMessages().get(self.msg.id),
namespace="/ws",
)
stats.APRSDStats().ack_rx_inc() stats.APRSDStats().ack_rx_inc()
self.got_ack = True self.got_ack = True
if self.request["wait_reply"] == "0": if self.request["wait_reply"] == "0" or self.got_reply:
# We aren't waiting for a reply, so we can bail # We aren't waiting for a reply, so we can bail
self.stop()
self.thread_stop = self.aprs_client.thread_stop = True self.thread_stop = self.aprs_client.thread_stop = True
else: else:
packets.PacketList().add(packet) packets.PacketList().add(packet)
@ -209,9 +218,12 @@ class SendMessageThread(threads.APRSDThread):
fromcall=fromcall, fromcall=fromcall,
ack=msg_number, ack=msg_number,
) )
got_response = True
SentMessages().reply(self.msg.id, packet) SentMessages().reply(self.msg.id, packet)
SentMessages().set_status(self.msg.id, "Got Reply") SentMessages().set_status(self.msg.id, "Got Reply")
socketio.emit(
"reply", SentMessages().get(self.msg.id),
namespace="/ws",
)
# Send the ack back? # Send the ack back?
ack = messaging.AckMessage( ack = messaging.AckMessage(
@ -223,37 +235,37 @@ class SendMessageThread(threads.APRSDThread):
SentMessages().set_status(self.msg.id, "Ack Sent") SentMessages().set_status(self.msg.id, "Ack Sent")
# Now we can exit, since we are done. # Now we can exit, since we are done.
self.got_reply = True
if self.got_ack: if self.got_ack:
self.stop()
self.thread_stop = self.aprs_client.thread_stop = True self.thread_stop = self.aprs_client.thread_stop = True
def loop(self): def loop(self):
LOG.debug("LOOP Start") # we have a general time limit expecting results of
# around 120 seconds before we exit
now = datetime.datetime.now()
start_delta = str(now - self.start_time)
delta = utils.parse_delta_str(start_delta)
d = datetime.timedelta(**delta)
max_timeout = {"hours": 0.0, "minutes": 1, "seconds": 0}
max_delta = datetime.timedelta(**max_timeout)
if d > max_delta:
LOG.error("XXXXXX Haven't completed everything in 60 seconds. BAIL!")
return False
if self.got_ack and self.got_reply:
LOG.warning("We got everything already. BAIL")
return False
try: try:
# This will register a packet consumer with aprslib # This will register a packet consumer with aprslib
# When new packets come in the consumer will process # When new packets come in the consumer will process
# the packet # the packet
self.aprs_client.consumer(self.rx_packet, raw=False, blocking=False) self.aprs_client.consumer(self.rx_packet, raw=False, blocking=False)
except aprslib.exceptions.ConnectionDrop: except aprslib.exceptions.ConnectionDrop:
LOG.error("Connection dropped, reconnecting") LOG.error("Connection dropped.")
time.sleep(5)
# Force the deletion of the client object connected to aprs
# This will cause a reconnect, next time client.get_client()
# is called
del self.aprs_client
connecting = True
counter = 0;
while connecting:
try:
self.aprs_client = self.setup_connection()
connecting = False
except Exception:
LOG.error("Couldn't connect")
counter += 1
if counter >= 3:
LOG.error("Reached reconnect limit.")
return False return False
LOG.debug("LOOP END")
return True return True
@ -351,38 +363,7 @@ class APRSDFlask(flask_classful.FlaskView):
@auth.login_required @auth.login_required
def send_message(self): def send_message(self):
LOG.debug(request) LOG.debug(request)
if request.method == "POST": if request.method == "GET":
info = {
"from": request.form["from_call"],
"to": request.form["to_call"],
"password": request.form["from_call_password"],
"message": request.form["message"],
"wait_reply": request.form["wait_reply"],
}
LOG.debug(info)
msg = messaging.TextMessage(
info["from"], info["to"],
info["message"],
)
msgs = SentMessages()
msgs.add(msg)
msgs.set_status(msg.id, "Sending")
send_message_t = SendMessageThread(self.config, info, msg)
send_message_t.start()
info["from"]
result = "sending"
msg_id = msg.id
result = {
"msg_id": msg_id,
"status": "sending",
}
return json.dumps(result)
else:
result = "fail"
return flask.render_template( return flask.render_template(
"send-message.html", "send-message.html",
callsign=self.config["aprs"]["login"], callsign=self.config["aprs"]["login"],
@ -451,6 +432,60 @@ class APRSDFlask(flask_classful.FlaskView):
return json.dumps(self._stats()) return json.dumps(self._stats())
class SendMessageNamespace(Namespace):
_config = None
got_ack = False
reply_sent = False
msg = None
request = None
def __init__(self, namespace=None, config=None):
self._config = config
super().__init__(namespace)
def on_connect(self):
global socketio
LOG.debug("Web socket connected")
socketio.emit(
"connected", {"data": "Lets dance"},
namespace="/ws",
)
def on_disconnect(self):
LOG.debug("WS Disconnected")
def on_send(self, data):
global socketio
LOG.debug(f"WS: on_send {data}")
self.request = data
msg = messaging.TextMessage(
data["from"], data["to"],
data["message"],
)
self.msg = msg
msgs = SentMessages()
msgs.add(msg)
msgs.set_status(msg.id, "Sending")
socketio.emit(
"sent", SentMessages().get(self.msg.id),
namespace="/ws",
)
socketio.start_background_task(self._start, self._config, data, msg, self)
LOG.warning("WS: on_send: exit")
def _start(self, config, data, msg, namespace):
msg_thread = SendMessageThread(self._config, data, msg, self)
msg_thread.start()
def handle_message(self, data):
LOG.debug(f"WS Data {data}")
def handle_json(self, data):
LOG.debug(f"WS json {data}")
def setup_logging(config, flask_app, loglevel, quiet): def setup_logging(config, flask_app, loglevel, quiet):
flask_log = logging.getLogger("werkzeug") flask_log = logging.getLogger("werkzeug")
@ -485,6 +520,8 @@ def setup_logging(config, flask_app, loglevel, quiet):
def init_flask(config, loglevel, quiet): def init_flask(config, loglevel, quiet):
global socketio
flask_app = flask.Flask( flask_app = flask.Flask(
"aprsd", "aprsd",
static_url_path="/static", static_url_path="/static",
@ -498,8 +535,17 @@ def init_flask(config, loglevel, quiet):
flask_app.route("/stats", methods=["GET"])(server.stats) flask_app.route("/stats", methods=["GET"])(server.stats)
flask_app.route("/messages", methods=["GET"])(server.messages) flask_app.route("/messages", methods=["GET"])(server.messages)
flask_app.route("/packets", methods=["GET"])(server.packets) flask_app.route("/packets", methods=["GET"])(server.packets)
flask_app.route("/send-message", methods=["GET", "POST"])(server.send_message) flask_app.route("/send-message", methods=["GET"])(server.send_message)
flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status) flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status)
flask_app.route("/save", methods=["GET"])(server.save) flask_app.route("/save", methods=["GET"])(server.save)
flask_app.route("/plugins", methods=["GET"])(server.plugins) flask_app.route("/plugins", methods=["GET"])(server.plugins)
return flask_app
socketio = SocketIO(
flask_app, logger=False, engineio_logger=False,
async_mode="threading",
)
# import eventlet
# eventlet.monkey_patch()
socketio.on_namespace(SendMessageNamespace("/ws", config=config))
return socketio, flask_app

View File

@ -513,8 +513,9 @@ def server(
if web_enabled: if web_enabled:
flask_enabled = True flask_enabled = True
app = flask.init_flask(config, loglevel, quiet) (socketio, app) = flask.init_flask(config, loglevel, quiet)
app.run( socketio.run(
app,
host=config["aprsd"]["web"]["host"], host=config["aprsd"]["web"]["host"],
port=config["aprsd"]["web"]["port"], port=config["aprsd"]["web"]["port"],
) )

View File

@ -1,74 +1,145 @@
msgs_list = {}; msgs_list = {};
var cleared = false;
function size_dict(d){c=0; for (i in d) ++c; return c} function size_dict(d){c=0; for (i in d) ++c; return c}
function update_messages(data) { function init_messages() {
msgs_cnt = size_dict(data); const socket = io("/ws");
$('#msgs_count').html(msgs_cnt); socket.on('connect', function () {
console.log("Connected to socketio");
});
socket.on('connected', function(msg) {
console.log("Connected!");
console.log(msg);
});
socket.on("sent", function(msg) {
if (cleared == false) {
var msgsdiv = $("#msgsDiv"); var msgsdiv = $("#msgsDiv");
//nuke the contents first, then add to it.
if (size_dict(msgs_list) == 0 && size_dict(data) > 0) {
msgsdiv.html('') msgsdiv.html('')
cleared = true
}
add_msg(msg);
});
socket.on("ack", function(msg) {
update_msg(msg);
});
socket.on("reply", function(msg) {
update_msg(msg);
});
$("#sendform").submit(function(event) {
event.preventDefault();
var $checkboxes = $(this).find('input[type=checkbox]');
//loop through the checkboxes and change to hidden fields
$checkboxes.each(function() {
if ($(this)[0].checked) {
$(this).attr('type', 'hidden');
$(this).val(1);
} else {
$(this).attr('type', 'hidden');
$(this).val(0);
}
});
msg = {'from': $('#from').val(),
'password': $('#password').val(),
'to': $('#to').val(),
'message': $('#message').val(),
'wait_reply': $('#wait_reply').val(),
} }
jQuery.each(data, function(i, val) { socket.emit("send", msg);
if ( msgs_list.hasOwnProperty(val["ts"]) == false ) {
// Store the packet //loop through the checkboxes and change to hidden fields
msgs_list[val["ts"]] = val; $checkboxes.each(function() {
ts_str = val["ts"].toString(); $(this).attr('type', 'checkbox');
});
});
}
function add_msg(msg) {
var msgsdiv = $("#msgsDiv");
ts_str = msg["ts"].toString();
ts = ts_str.split(".")[0]*1000; ts = ts_str.split(".")[0]*1000;
var d = new Date(ts).toLocaleDateString("en-US") var d = new Date(ts).toLocaleDateString("en-US")
var t = new Date(ts).toLocaleTimeString("en-US") var t = new Date(ts).toLocaleTimeString("en-US")
from = val['from'] from = msg['from']
title_id = 'title_tx' title_id = 'title_tx'
var from_to = d + " " + t + "    " + from + " > " var from_to = d + " " + t + "    " + from + " > "
if (val.hasOwnProperty('to')) { if (msg.hasOwnProperty('to')) {
from_to = from_to + val['to'] from_to = from_to + msg['to']
} }
from_to = from_to + "  -  " + val['raw'] from_to = from_to + "  -  " + msg['message']
id = ts_str.split('.')[0] id = ts_str.split('.')[0]
pretty_id = "pretty_" + id pretty_id = "pretty_" + id
loader_id = "loader_" + id loader_id = "loader_" + id
ack_id = "ack_" + id
reply_id = "reply_" + id reply_id = "reply_" + id
json_pretty = Prism.highlight(JSON.stringify(val, null, '\t'), Prism.languages.json, 'json'); span_id = "span_" + id
json_pretty = Prism.highlight(JSON.stringify(msg, null, '\t'), Prism.languages.json, 'json');
msg_html = '<div class="ui title" id="' + title_id + '"><i class="dropdown icon"></i>'; msg_html = '<div class="ui title" id="' + title_id + '"><i class="dropdown icon"></i>';
msg_html += '<div class="ui active inline loader" id="' + loader_id +'" data-content="Waiting for Ack"></div>&nbsp;'; msg_html += '<div class="ui active inline loader" id="' + loader_id +'" data-content="Waiting for Ack"></div>&nbsp;';
msg_html += '<i class="thumbs down outline icon" id="' + reply_id + '" data-content="Waiting for Reply"></i>&nbsp;' + from_to + '</div>'; msg_html += '<i class="thumbs down outline icon" id="' + ack_id + '" data-content="Waiting for ACK"></i>&nbsp;';
msg_html += '<i class="thumbs down outline icon" id="' + reply_id + '" data-content="Waiting for Reply"></i>&nbsp;';
msg_html += '<span id="' + span_id + '">' + from_to +'</span></div>';
msg_html += '<div class="content"><p class="transition hidden"><pre id="' + pretty_id + '" class="language-json">' + json_pretty + '</p></p></div>' msg_html += '<div class="content"><p class="transition hidden"><pre id="' + pretty_id + '" class="language-json">' + json_pretty + '</p></p></div>'
msgsdiv.prepend(msg_html); msgsdiv.prepend(msg_html);
} else { $('.ui.accordion').accordion('refresh');
}
function update_msg(msg) {
var msgsdiv = $("#msgsDiv");
// We have an existing entry // We have an existing entry
msgs_list[val["ts"]] = val; ts_str = msg["ts"].toString();
ts_str = val["ts"].toString();
id = ts_str.split('.')[0] id = ts_str.split('.')[0]
pretty_id = "pretty_" + id pretty_id = "pretty_" + id
loader_id = "loader_" + id loader_id = "loader_" + id
reply_id = "reply_" + id reply_id = "reply_" + id
var pretty_pre = $("#" + pretty_id); ack_id = "ack_" + id
if (val['ack'] == true) { span_id = "span_" + id
if (msg['ack'] == true) {
var loader_div = $('#' + loader_id); var loader_div = $('#' + loader_id);
var ack_div = $('#' + ack_id);
loader_div.removeClass('ui active inline loader'); loader_div.removeClass('ui active inline loader');
loader_div.addClass('ui disabled loader'); loader_div.addClass('ui disabled loader');
loader_div.attr('data-content', 'Got reply'); ack_div.removeClass('thumbs up outline icon');
ack_div.addClass('thumbs up outline icon');
} }
if (val['reply'] !== null) { if (msg['reply'] !== null) {
var reply_div = $('#' + reply_id); var reply_div = $('#' + reply_id);
reply_div.removeClass("thumbs down outline icon"); reply_div.removeClass("thumbs down outline icon");
reply_div.addClass('thumbs up outline icon'); reply_div.addClass('reply icon');
reply_div.attr('data-content', 'Got Reply'); reply_div.attr('data-content', 'Got Reply');
var d = new Date(ts).toLocaleDateString("en-US")
var t = new Date(ts).toLocaleTimeString("en-US")
var from_to = d + " " + t + "&nbsp;&nbsp;&nbsp;&nbsp;" + from + " > "
if (msg.hasOwnProperty('to')) {
from_to = from_to + msg['to']
}
from_to = from_to + "&nbsp;&nbsp;-&nbsp;&nbsp;" + msg['message']
from_to += "&nbsp;&nbsp; ===> " + msg["reply"]["message_text"]
var span_div = $('#' + span_id);
span_div.html(from_to);
} }
var pretty_pre = $("#" + pretty_id);
pretty_pre.html(''); pretty_pre.html('');
json_pretty = Prism.highlight(JSON.stringify(val, null, '\t'), Prism.languages.json, 'json'); json_pretty = Prism.highlight(JSON.stringify(msg, null, '\t'), Prism.languages.json, 'json');
pretty_pre.html(json_pretty); pretty_pre.html(json_pretty);
}
});
$('.ui.accordion').accordion('refresh'); $('.ui.accordion').accordion('refresh');
} }

View File

@ -3,6 +3,9 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css"> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-simple-websocket@1.1.4/src/jquery.simple.websocket.min.js"></script>
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/prism.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-json.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-json.js"></script>
@ -17,51 +20,7 @@
<script language="JavaScript"> <script language="JavaScript">
$(document).ready(function() { $(document).ready(function() {
init_messages();
$("#sendform").submit(function(event) {
event.preventDefault();
var $checkboxes = $(this).find('input[type=checkbox]');
//loop through the checkboxes and change to hidden fields
$checkboxes.each(function() {
if ($(this)[0].checked) {
$(this).attr('type', 'hidden');
$(this).val(1);
} else {
$(this).attr('type', 'hidden');
$(this).val(0);
}
});
var posting = $.post("/send-message", $("#sendform").serialize());
posting.done(function(data, status){
console.log("Data: " + data + "\nStatus: " + status);
});
//loop through the checkboxes and change to hidden fields
$checkboxes.each(function() {
$(this).attr('type', 'checkbox');
});
});
(function sent_msg_worker() {
$.ajax({
url: "/send-message-status",
type: 'GET',
dataType: 'json',
success: function(data) {
update_messages(data);
},
complete: function() {
setTimeout(sent_msg_worker, 1000);
}
});
})();
}); });
</script> </script>
@ -87,12 +46,12 @@
<h3 class="ui dividing header">Send Message Form</h3> <h3 class="ui dividing header">Send Message Form</h3>
<form id="sendform" name="sendmsg" action=""> <form id="sendform" name="sendmsg" action="">
<p><label for="from_call">From Callsign:</label> <p><label for="from_call">From Callsign:</label>
<input type="text" name="from_call" id="fcall" value="WB4BOR"></p> <input type="text" name="from_call" id="from" value="WB4BOR"></p>
<p><label for="from_call_password">Password:</label> <p><label for="from_call_password">Password:</label>
<input type="text" name="from_call_password" value="24496"></p> <input type="password" name="from_call_password" id='password' value="24496"></p>
<p><label for="to_call">To Callsign:</label> <p><label for="to_call">To Callsign:</label>
<input type="text" name="to_call" id="tcall" value="WB4BOR-11"></p> <input type="text" name="to_call" id="to" value="WB4BOR-11"></p>
<p><label for="message">Message:</label> <p><label for="message">Message:</label>
<input type="text" name="message" id="message" value="ping"></p> <input type="text" name="message" id="message" value="ping"></p>

View File

@ -1,12 +1,12 @@
flake8 flake8
isort isort
mypy mypy
pytest
pytest-cov
pep8-naming pep8-naming
Sphinx Sphinx
tox tox
twine twine
pre-commit pre-commit
pip-tools pip-tools
#pytest
#pytest-cov
gray gray

View File

@ -9,28 +9,26 @@ add-trailing-comma==2.1.0
alabaster==0.7.12 alabaster==0.7.12
# via sphinx # via sphinx
appdirs==1.4.4 appdirs==1.4.4
# via # via black
# black attrs==21.2.0
# virtualenv # via jsonschema
attrs==20.3.0
# via
# jsonschema
# pytest
autoflake==1.4 autoflake==1.4
# via gray # via gray
babel==2.9.1 babel==2.9.1
# via sphinx # via sphinx
backports.entry-points-selectable==1.1.0
# via virtualenv
black==21.7b0 black==21.7b0
# via gray # via gray
bleach==3.3.0 bleach==4.1.0
# via readme-renderer # via readme-renderer
certifi==2020.12.5 certifi==2021.5.30
# via requests # via requests
cfgv==3.2.0 cfgv==3.3.1
# via pre-commit # via pre-commit
chardet==4.0.0 charset-normalizer==2.0.4
# via requests # via requests
click==7.1.2 click==8.0.1
# via # via
# black # black
# pip-tools # pip-tools
@ -40,11 +38,9 @@ colorlog==6.4.1
# via prettylog # via prettylog
configargparse==1.5.2 configargparse==1.5.2
# via gray # via gray
coverage==5.5 distlib==0.3.2
# via pytest-cov
distlib==0.3.1
# via virtualenv # via virtualenv
docutils==0.16 docutils==0.17.1
# via # via
# readme-renderer # readme-renderer
# sphinx # sphinx
@ -58,40 +54,39 @@ fixit==0.1.4
# via gray # via gray
flake8-polyfill==1.0.2 flake8-polyfill==1.0.2
# via pep8-naming # via pep8-naming
flake8==3.9.1 flake8==3.9.2
# via # via
# -r dev-requirements.in # -r dev-requirements.in
# fixit # fixit
# flake8-polyfill # flake8-polyfill
# pep8-naming
gray==0.10.1 gray==0.10.1
# via -r dev-requirements.in # via -r dev-requirements.in
identify==2.2.4 identify==2.2.13
# via pre-commit # via pre-commit
idna==2.10 idna==3.2
# via requests # via requests
imagesize==1.2.0 imagesize==1.2.0
# via sphinx # via sphinx
importlib-metadata==4.0.1 importlib-metadata==4.7.1
# via # via
# keyring # keyring
# twine # twine
importlib-resources==5.2.2 importlib-resources==5.2.2
# via fixit # via fixit
iniconfig==1.1.1 isort==5.9.3
# via pytest
isort==5.8.0
# via # via
# -r dev-requirements.in # -r dev-requirements.in
# gray # gray
jinja2==2.11.3 jinja2==3.0.1
# via sphinx # via sphinx
jsonschema==3.2.0 jsonschema==3.2.0
# via fixit # via fixit
keyring==23.0.1 keyring==23.1.0
# via twine # via twine
libcst==0.3.20 libcst==0.3.20
# via fixit # via fixit
markupsafe==1.1.1 markupsafe==2.0.1
# via jinja2 # via jinja2
mccabe==0.6.1 mccabe==0.6.1
# via flake8 # via flake8
@ -100,45 +95,42 @@ mypy-extensions==0.4.3
# black # black
# mypy # mypy
# typing-inspect # typing-inspect
mypy==0.812 mypy==0.910
# via -r dev-requirements.in # via -r dev-requirements.in
nodeenv==1.6.0 nodeenv==1.6.0
# via pre-commit # via pre-commit
packaging==20.9 packaging==21.0
# via # via
# bleach # bleach
# pytest
# sphinx # sphinx
# tox # tox
pathspec==0.8.1 pathspec==0.9.0
# via black # via black
pep517==0.10.0 pep517==0.11.0
# via pip-tools # via pip-tools
pep8-naming==0.11.1 pep8-naming==0.12.1
# via -r dev-requirements.in # via -r dev-requirements.in
pip-tools==6.1.0 pip-tools==6.2.0
# via -r dev-requirements.in # via -r dev-requirements.in
pkginfo==1.7.0 pkginfo==1.7.1
# via twine # via twine
pluggy==0.13.1 platformdirs==2.2.0
# via # via virtualenv
# pytest pluggy==1.0.0
# tox # via tox
pre-commit==2.12.1 pre-commit==2.14.0
# via -r dev-requirements.in # via -r dev-requirements.in
prettylog==0.3.0 prettylog==0.3.0
# via gray # via gray
py==1.10.0 py==1.10.0
# via # via tox
# pytest
# tox
pycodestyle==2.7.0 pycodestyle==2.7.0
# via flake8 # via flake8
pyflakes==2.3.1 pyflakes==2.3.1
# via # via
# autoflake # autoflake
# flake8 # flake8
pygments==2.9.0 pygments==2.10.0
# via # via
# readme-renderer # readme-renderer
# sphinx # sphinx
@ -146,12 +138,6 @@ pyparsing==2.4.7
# via packaging # via packaging
pyrsistent==0.18.0 pyrsistent==0.18.0
# via jsonschema # via jsonschema
pytest-cov==2.11.1
# via -r dev-requirements.in
pytest==6.2.3
# via
# -r dev-requirements.in
# pytest-cov
pytz==2021.1 pytz==2021.1
# via babel # via babel
pyupgrade==2.24.0 pyupgrade==2.24.0
@ -163,18 +149,18 @@ pyyaml==5.4.1
# pre-commit # pre-commit
readme-renderer==29.0 readme-renderer==29.0
# via twine # via twine
regex==2021.4.4 regex==2021.8.27
# via black # via black
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
# via twine # via twine
requests==2.25.1 requests==2.26.0
# via # via
# requests-toolbelt # requests-toolbelt
# sphinx # sphinx
# twine # twine
rfc3986==1.4.0 rfc3986==1.5.0
# via twine # via twine
six==1.15.0 six==1.16.0
# via # via
# bleach # bleach
# jsonschema # jsonschema
@ -183,19 +169,19 @@ six==1.15.0
# virtualenv # virtualenv
snowballstemmer==2.1.0 snowballstemmer==2.1.0
# via sphinx # via sphinx
sphinx==3.5.4 sphinx==4.1.2
# via -r dev-requirements.in # via -r dev-requirements.in
sphinxcontrib-applehelp==1.0.2 sphinxcontrib-applehelp==1.0.2
# via sphinx # via sphinx
sphinxcontrib-devhelp==1.0.2 sphinxcontrib-devhelp==1.0.2
# via sphinx # via sphinx
sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-htmlhelp==2.0.0
# via sphinx # via sphinx
sphinxcontrib-jsmath==1.0.1 sphinxcontrib-jsmath==1.0.1
# via sphinx # via sphinx
sphinxcontrib-qthelp==1.0.3 sphinxcontrib-qthelp==1.0.3
# via sphinx # via sphinx
sphinxcontrib-serializinghtml==1.1.4 sphinxcontrib-serializinghtml==1.1.5
# via sphinx # via sphinx
tokenize-rt==4.1.0 tokenize-rt==4.1.0
# via # via
@ -203,20 +189,19 @@ tokenize-rt==4.1.0
# pyupgrade # pyupgrade
toml==0.10.2 toml==0.10.2
# via # via
# pep517 # mypy
# pre-commit # pre-commit
# pytest
# tox # tox
tomli==1.2.1 tomli==1.2.1
# via black # via
tox==3.23.0 # black
# pep517
tox==3.24.3
# via -r dev-requirements.in # via -r dev-requirements.in
tqdm==4.60.0 tqdm==4.62.2
# via twine # via twine
twine==3.4.1 twine==3.4.2
# via -r dev-requirements.in # via -r dev-requirements.in
typed-ast==1.4.3
# via mypy
typing-extensions==3.10.0.0 typing-extensions==3.10.0.0
# via # via
# libcst # libcst
@ -230,15 +215,17 @@ unify==0.5
# via gray # via gray
untokenize==0.1.1 untokenize==0.1.1
# via unify # via unify
urllib3==1.26.5 urllib3==1.26.6
# via requests # via requests
virtualenv==20.4.4 virtualenv==20.7.2
# via # via
# pre-commit # pre-commit
# tox # tox
webencodings==0.5.1 webencodings==0.5.1
# via bleach # via bleach
zipp==3.4.1 wheel==0.37.0
# via pip-tools
zipp==3.5.0
# via # via
# importlib-metadata # importlib-metadata
# importlib-resources # importlib-resources

View File

@ -12,10 +12,12 @@ pbr
pyyaml pyyaml
# Allowing a newer version can lead to a conflict with # Allowing a newer version can lead to a conflict with
# requests. # requests.
py3-validate-email==0.2.16 # py3-validate-email
pytz pytz
requests requests
six six
thesmuggler thesmuggler
yfinance yfinance
update_checker update_checker
flask-socketio
eventlet

View File

@ -8,17 +8,19 @@ aioax25==0.0.10
# via -r requirements.in # via -r requirements.in
aprslib==0.6.47 aprslib==0.6.47
# via -r requirements.in # via -r requirements.in
backoff==1.10.0 backoff==1.11.1
# via opencage # via opencage
certifi==2020.12.5 bidict==0.21.2
# via python-socketio
certifi==2021.5.30
# via requests # via requests
cffi==1.14.5 cffi==1.14.6
# via cryptography # via cryptography
chardet==4.0.0 charset-normalizer==2.0.4
# via requests # via requests
click-completion==0.5.2 click-completion==0.5.2
# via -r requirements.in # via -r requirements.in
click==7.1.2 click==8.0.1
# via # via
# -r requirements.in # -r requirements.in
# click-completion # click-completion
@ -27,50 +29,51 @@ contexter==0.1.4
# via signalslot # via signalslot
cryptography==3.4.7 cryptography==3.4.7
# via pyopenssl # via pyopenssl
dnspython==2.1.0 dnspython==1.16.0
# via py3-validate-email # via eventlet
filelock==3.0.12 eventlet==0.31.1
# via py3-validate-email # via -r requirements.in
flask-classful==0.14.2 flask-classful==0.14.2
# via -r requirements.in # via -r requirements.in
flask-httpauth==4.3.0 flask-httpauth==4.4.0
# via -r requirements.in # via -r requirements.in
flask==1.1.2 flask-socketio==5.1.1
# via -r requirements.in
flask==2.0.1
# via # via
# -r requirements.in # -r requirements.in
# flask-classful # flask-classful
# flask-httpauth # flask-httpauth
idna==2.10 # flask-socketio
# via greenlet==1.1.1
# py3-validate-email # via eventlet
# requests idna==3.2
# via requests
imapclient==2.2.0 imapclient==2.2.0
# via -r requirements.in # via -r requirements.in
itsdangerous==1.1.0 itsdangerous==2.0.1
# via flask # via flask
jinja2==2.11.3 jinja2==3.0.1
# via # via
# click-completion # click-completion
# flask # flask
lxml==4.6.3 lxml==4.6.3
# via yfinance # via yfinance
markupsafe==1.1.1 markupsafe==2.0.1
# via jinja2 # via jinja2
multitasking==0.0.9 multitasking==0.0.9
# via yfinance # via yfinance
numpy==1.20.2 numpy==1.21.2
# via # via
# pandas # pandas
# yfinance # yfinance
opencage==1.2.2 opencage==2.0.0
# via -r requirements.in # via -r requirements.in
pandas==1.2.4 pandas==1.3.2
# via yfinance # via yfinance
pbr==5.6.0 pbr==5.6.0
# via -r requirements.in # via -r requirements.in
pluggy==0.13.1 pluggy==1.0.0
# via -r requirements.in
py3-validate-email==0.2.16
# via -r requirements.in # via -r requirements.in
pycparser==2.20 pycparser==2.20
# via cffi # via cffi
@ -80,13 +83,17 @@ pyserial==3.5
# via aioax25 # via aioax25
python-dateutil==2.8.1 python-dateutil==2.8.1
# via pandas # via pandas
python-engineio==4.2.1
# via python-socketio
python-socketio==5.4.0
# via flask-socketio
pytz==2021.1 pytz==2021.1
# via # via
# -r requirements.in # -r requirements.in
# pandas # pandas
pyyaml==5.4.1 pyyaml==5.4.1
# via -r requirements.in # via -r requirements.in
requests==2.25.1 requests==2.26.0
# via # via
# -r requirements.in # -r requirements.in
# opencage # opencage
@ -100,8 +107,8 @@ six==1.15.0
# via # via
# -r requirements.in # -r requirements.in
# click-completion # click-completion
# eventlet
# imapclient # imapclient
# opencage
# pyopenssl # pyopenssl
# python-dateutil # python-dateutil
# signalslot # signalslot
@ -109,11 +116,11 @@ thesmuggler==1.0.1
# via -r requirements.in # via -r requirements.in
update-checker==0.18.0 update-checker==0.18.0
# via -r requirements.in # via -r requirements.in
urllib3==1.26.5 urllib3==1.26.6
# via requests # via requests
weakrefmethod==1.0.3 weakrefmethod==1.0.3
# via signalslot # via signalslot
werkzeug==1.0.1 werkzeug==1.0.1
# via flask # via flask
yfinance==0.1.59 yfinance==0.1.63
# via -r requirements.in # via -r requirements.in