diff --git a/aprsd/main.py b/aprsd/main.py index 1f5a4eb..f55be0b 100644 --- a/aprsd/main.py +++ b/aprsd/main.py @@ -145,6 +145,8 @@ def sample_config(ctx): if not sys.argv[1:]: raise SystemExit raise + LOG.warning(conf.namespace) + return generator.generate(conf) diff --git a/aprsd/packets/core.py b/aprsd/packets/core.py index 1b3dd8c..c9362d8 100644 --- a/aprsd/packets/core.py +++ b/aprsd/packets/core.py @@ -1,7 +1,6 @@ import abc from dataclasses import asdict, dataclass, field -import datetime -import json +from datetime import datetime import logging import re import time @@ -9,9 +8,9 @@ import time from typing import List import dacite +from dataclasses_json import dataclass_json from aprsd.utils import counter -from aprsd.utils import json as aprsd_json LOG = logging.getLogger("APRSD") @@ -28,12 +27,20 @@ PACKET_TYPE_BEACON = "beacon" PACKET_TYPE_THIRDPARTY = "thirdparty" PACKET_TYPE_UNCOMPRESSED = "uncompressed" +NO_DATE = datetime(1900, 10, 24) + def _init_timestamp(): """Build a unix style timestamp integer""" return int(round(time.time())) +def _init_send_time(): + # We have to use a datetime here, or the json encoder + # Fails on a NoneType. + return NO_DATE + + def _init_msgNo(): # noqa: N802 """For some reason __post__init doesn't get called. @@ -45,6 +52,20 @@ def _init_msgNo(): # noqa: N802 return c.value +def factory_from_dict(packet_dict): + pkt_type = get_packet_type(packet_dict) + if pkt_type: + cls = TYPE_LOOKUP[pkt_type] + return cls.from_dict(packet_dict) + + +def factory_from_json(packet_dict): + pkt_type = get_packet_type(packet_dict) + if pkt_type: + return TYPE_LOOKUP[pkt_type].from_json(packet_dict) + + +@dataclass_json @dataclass(unsafe_hash=True) class Packet(metaclass=abc.ABCMeta): from_call: str = field(default=None) @@ -64,7 +85,19 @@ class Packet(metaclass=abc.ABCMeta): # Fields related to sending packets out send_count: int = field(repr=False, default=0, compare=False, hash=False) retry_count: int = field(repr=False, default=3, compare=False, hash=False) - last_send_time: datetime.timedelta = field(repr=False, default=None, compare=False, hash=False) + # last_send_time: datetime = field( + # metadata=dc_json_config( + # encoder=datetime.isoformat, + # decoder=datetime.fromisoformat, + # ), + # repr=True, + # default_factory=_init_send_time, + # compare=False, + # hash=False + # ) + last_send_time: float = field(repr=False, default=0, compare=False, hash=False) + last_send_attempt: int = field(repr=False, default=0, compare=False, hash=False) + # Do we allow this packet to be saved to send later? allow_delay: bool = field(repr=False, default=True, compare=False, hash=False) path: List[str] = field(default_factory=list, compare=False, hash=False) @@ -73,16 +106,12 @@ class Packet(metaclass=abc.ABCMeta): def __post__init__(self): LOG.warning(f"POST INIT {self}") - @property - def __dict__(self): - return asdict(self) - @property def json(self): """ get the json formated string """ - return json.dumps(self.__dict__, cls=aprsd_json.EnhancedJSONEncoder) + return self.to_json() def get(self, key, default=None): """Emulate a getter on a dict.""" @@ -289,6 +318,7 @@ class RejectPacket(Packet): self.payload = f":{self.to_call.ljust(9)} :rej{self.msgNo}" +@dataclass_json @dataclass(unsafe_hash=True) class MessagePacket(Packet): message_text: str = field(default=None) @@ -406,12 +436,12 @@ class GPSPacket(Packet): def _build_time_zulu(self): """Build the timestamp in UTC/zulu.""" if self.timestamp: - local_dt = datetime.datetime.fromtimestamp(self.timestamp) + local_dt = datetime.fromtimestamp(self.timestamp) else: - local_dt = datetime.datetime.now() - self.timestamp = datetime.datetime.timestamp(local_dt) + local_dt = datetime.now() + self.timestamp = datetime.timestamp(local_dt) - utc_offset_timedelta = datetime.datetime.utcnow() - local_dt + utc_offset_timedelta = datetime.utcnow() - local_dt result_utc_datetime = local_dt + utc_offset_timedelta time_zulu = result_utc_datetime.strftime("%d%H%M") return time_zulu @@ -567,7 +597,7 @@ class WeatherPacket(GPSPacket): class ThirdParty(Packet): # Holds the encapsulated packet - subpacket: Packet = None + subpacket: Packet = field(default=None, compare=True, hash=False) def __repr__(self): """Build the repr version of the packet.""" @@ -600,7 +630,7 @@ def get_packet_type(packet: dict): pkt_format = packet.get("format", None) msg_response = packet.get("response", None) - packet_type = "unknown" + packet_type = PACKET_TYPE_UNKNOWN if pkt_format == "message" and msg_response == "ack": packet_type = PACKET_TYPE_ACK elif pkt_format == "message" and msg_response == "rej": @@ -620,6 +650,10 @@ def get_packet_type(packet: dict): packet_type = PACKET_TYPE_WX elif pkt_format == PACKET_TYPE_THIRDPARTY: packet_type = PACKET_TYPE_THIRDPARTY + + if packet_type == PACKET_TYPE_UNKNOWN: + if "latitude" in packet: + packet_type = PACKET_TYPE_BEACON return packet_type diff --git a/aprsd/packets/packet_list.py b/aprsd/packets/packet_list.py index a6dc6f7..3d75a07 100644 --- a/aprsd/packets/packet_list.py +++ b/aprsd/packets/packet_list.py @@ -19,11 +19,12 @@ class PacketList(MutableMapping): lock = threading.Lock() _total_rx: int = 0 _total_tx: int = 0 + types = {} def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) - cls._maxlen = 1000 + cls._maxlen = 100 cls.d = OrderedDict() return cls._instance @@ -32,6 +33,10 @@ class PacketList(MutableMapping): """Add a packet that was received.""" self._total_rx += 1 self._add(packet) + ptype = packet.__class__.__name__ + if not ptype in self.types: + self.types[ptype] = {"tx": 0, "rx": 0} + self.types[ptype]["rx"] += 1 seen_list.SeenList().update_seen(packet) stats.APRSDStats().rx(packet) @@ -40,6 +45,10 @@ class PacketList(MutableMapping): """Add a packet that was received.""" self._total_tx += 1 self._add(packet) + ptype = packet.__class__.__name__ + if not ptype in self.types: + self.types[ptype] = {"tx": 0, "rx": 0} + self.types[ptype]["tx"] += 1 seen_list.SeenList().update_seen(packet) stats.APRSDStats().tx(packet) @@ -50,6 +59,9 @@ class PacketList(MutableMapping): def _add(self, packet): self[packet.key] = packet + def copy(self): + return self.d.copy() + @property def maxlen(self): return self._maxlen diff --git a/aprsd/packets/tracker.py b/aprsd/packets/tracker.py index b512320..1246dc1 100644 --- a/aprsd/packets/tracker.py +++ b/aprsd/packets/tracker.py @@ -65,6 +65,7 @@ class PacketTrack(objectstore.ObjectStoreMixin): @wrapt.synchronized(lock) def add(self, packet): key = packet.msgNo + packet._last_send_attempt = 0 self.data[key] = packet self.total_tracked += 1 @@ -83,7 +84,7 @@ class PacketTrack(objectstore.ObjectStoreMixin): """Walk the list of messages and restart them if any.""" for key in self.data.keys(): pkt = self.data[key] - if pkt.last_send_attempt < pkt.retry_count: + if pkt._last_send_attempt < pkt.retry_count: tx.send(pkt) def _resend(self, packet): diff --git a/aprsd/threads/tx.py b/aprsd/threads/tx.py index 0ceb355..708d263 100644 --- a/aprsd/threads/tx.py +++ b/aprsd/threads/tx.py @@ -1,4 +1,3 @@ -import datetime import logging import time @@ -128,10 +127,10 @@ class SendPacketThread(aprsd_threads.APRSDThread): # Message is still outstanding and needs to be acked. if packet.last_send_time: # Message has a last send time tracking - now = datetime.datetime.now() + now = int(round(time.time())) sleeptime = (packet.send_count + 1) * 31 delta = now - packet.last_send_time - if delta > datetime.timedelta(seconds=sleeptime): + if delta > sleeptime: # It's time to try to send it again send_now = True else: @@ -140,7 +139,7 @@ class SendPacketThread(aprsd_threads.APRSDThread): if send_now: # no attempt time, so lets send it, and start # tracking the time. - packet.last_send_time = datetime.datetime.now() + packet.last_send_time = int(round(time.time())) send(packet, direct=True) packet.send_count += 1 @@ -173,13 +172,13 @@ class SendAckThread(aprsd_threads.APRSDThread): if self.packet.last_send_time: # Message has a last send time tracking - now = datetime.datetime.now() + now = int(round(time.time())) # aprs duplicate detection is 30 secs? # (21 only sends first, 28 skips middle) sleep_time = 31 delta = now - self.packet.last_send_time - if delta > datetime.timedelta(seconds=sleep_time): + if delta > sleep_time: # It's time to try to send it again send_now = True elif self.loop_count % 10 == 0: @@ -190,7 +189,7 @@ class SendAckThread(aprsd_threads.APRSDThread): if send_now: send(self.packet, direct=True) self.packet.send_count += 1 - self.packet.last_send_time = datetime.datetime.now() + self.packet.last_send_time = int(round(time.time())) time.sleep(1) self.loop_count += 1 diff --git a/aprsd/web/admin/static/js/echarts.js b/aprsd/web/admin/static/js/echarts.js new file mode 100644 index 0000000..adeb5f6 --- /dev/null +++ b/aprsd/web/admin/static/js/echarts.js @@ -0,0 +1,403 @@ +var packet_list = {}; + +var tx_data = []; +var rx_data = []; + +var packet_types_data = {}; + +var mem_current = [] +var mem_peak = [] + + +function start_charts() { + console.log("start_charts() called"); + // Initialize the echarts instance based on the prepared dom + create_packets_chart(); + create_packets_types_chart(); + create_messages_chart(); + create_ack_chart(); + create_memory_chart(); +} + + +function create_packets_chart() { + // The packets totals TX/RX chart. + pkt_c_canvas = document.getElementById('packetsChart'); + packets_chart = echarts.init(pkt_c_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'APRS Packet totals' + }, + legend: {}, + tooltip : { + trigger: 'axis' + }, + toolbox: { + show : true, + feature : { + mark : {show: true}, + dataView : {show: true, readOnly: true}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable : true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'tx', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'tx' // refer sensor 1 value + } + },{ + name: 'rx', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'rx' + } + }] + }; + + // Display the chart using the configuration items and data just specified. + packets_chart.setOption(option); +} + + +function create_packets_types_chart() { + // The packets types chart + pkt_types_canvas = document.getElementById('packetTypesChart'); + packet_types_chart = echarts.init(pkt_types_canvas); + + // The series and data are built and updated on the fly + // as packets come in. + var option = { + title: { + text: 'Packet Types' + }, + legend: {}, + tooltip : { + trigger: 'axis' + }, + toolbox: { + show : true, + feature : { + mark : {show: true}, + dataView : {show: true, readOnly: true}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable : true, + xAxis: { type: 'time' }, + yAxis: { }, + } + + packet_types_chart.setOption(option); +} + + +function create_messages_chart() { + msg_c_canvas = document.getElementById('messagesChart'); + message_chart = echarts.init(msg_c_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'Message Packets' + }, + legend: {}, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + mark : {show: true}, + dataView : {show: true, readOnly: true}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable: true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'tx', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'tx' // refer sensor 1 value + } + },{ + name: 'rx', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'rx' + } + }] + }; + + // Display the chart using the configuration items and data just specified. + message_chart.setOption(option); +} + +function create_ack_chart() { + ack_canvas = document.getElementById('acksChart'); + ack_chart = echarts.init(ack_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'Ack Packets' + }, + legend: {}, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + mark : {show: true}, + dataView : {show: true, readOnly: false}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable: true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'tx', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'tx' // refer sensor 1 value + } + },{ + name: 'rx', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'rx' + } + }] + }; + + ack_chart.setOption(option); +} + +function create_memory_chart() { + ack_canvas = document.getElementById('memChart'); + memory_chart = echarts.init(ack_canvas); + + // Specify the configuration items and data for the chart + var option = { + title: { + text: 'Memory Usage' + }, + legend: {}, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + mark : {show: true}, + dataView : {show: true, readOnly: false}, + magicType : {show: true, type: ['line', 'bar']}, + restore : {show: true}, + saveAsImage : {show: true} + } + }, + calculable: true, + xAxis: { type: 'time' }, + yAxis: { }, + series: [ + { + name: 'current', + type: 'line', + smooth: true, + color: 'red', + encode: { + x: 'timestamp', + y: 'current' // refer sensor 1 value + } + },{ + name: 'peak', + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: 'peak' + } + }] + }; + + memory_chart.setOption(option); +} + + + + +function updatePacketData(chart, time, first, second) { + tx_data.push([time, first]); + rx_data.push([time, second]); + option = { + series: [ + { + name: 'tx', + data: tx_data, + }, + { + name: 'rx', + data: rx_data, + } + ] + } + chart.setOption(option); +} + +function updatePacketTypesData(time, typesdata) { + //The options series is created on the fly each time based on + //the packet types we have in the data + var series = [] + + for (const k in typesdata) { + tx = [time, typesdata[k]["tx"]] + rx = [time, typesdata[k]["rx"]] + + if (packet_types_data.hasOwnProperty(k)) { + packet_types_data[k]["tx"].push(tx) + packet_types_data[k]["rx"].push(rx) + } else { + packet_types_data[k] = {'tx': [tx], 'rx': [rx]} + } + } +} + +function updatePacketTypesChart() { + series = [] + for (const k in packet_types_data) { + entry = { + name: k+"tx", + data: packet_types_data[k]["tx"], + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: k+'tx' // refer sensor 1 value + } + } + series.push(entry) + entry = { + name: k+"rx", + data: packet_types_data[k]["rx"], + type: 'line', + smooth: true, + encode: { + x: 'timestamp', + y: k+'rx' // refer sensor 1 value + } + } + series.push(entry) + } + + option = { + series: series + } + console.log(option) + packet_types_chart.setOption(option); +} + +function updateTypeChart(chart, key) { + //Generic function to update a packet type chart + if (! packet_types_data.hasOwnProperty(key)) { + return; + } + + if (! packet_types_data[key].hasOwnProperty('tx')) { + return; + } + var option = { + series: [{ + name: "tx", + data: packet_types_data[key]["tx"], + }, + { + name: "rx", + data: packet_types_data[key]["rx"] + }] + } + + chart.setOption(option); +} + +function updateMemChart(time, current, peak) { + mem_current.push([time, current]); + mem_peak.push([time, peak]); + option = { + series: [ + { + name: 'current', + data: mem_current, + }, + { + name: 'peak', + data: mem_peak, + } + ] + } + memory_chart.setOption(option); +} + +function updateMessagesChart() { + updateTypeChart(message_chart, "MessagePacket") +} + +function updateAcksChart() { + updateTypeChart(ack_chart, "AckPacket") +} + +function update_stats( data ) { + console.log(data); + our_callsign = data["stats"]["aprsd"]["callsign"]; + $("#version").text( data["stats"]["aprsd"]["version"] ); + $("#aprs_connection").html( data["aprs_connection"] ); + $("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] ); + const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json'); + $("#jsonstats").html(html_pretty); + + t = Date.parse(data["time"]); + ts = new Date(t); + updatePacketData(packets_chart, ts, data["stats"]["packets"]["sent"], data["stats"]["packets"]["received"]); + updatePacketTypesData(ts, data["stats"]["packets"]["types"]); + updatePacketTypesChart(); + updateMessagesChart(); + updateAcksChart(); + updateMemChart(ts, data["stats"]["aprsd"]["memory_current"], data["stats"]["aprsd"]["memory_peak"]); + //updateQuadData(message_chart, short_time, data["stats"]["messages"]["sent"], data["stats"]["messages"]["received"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]); + //updateDualData(email_chart, short_time, data["stats"]["email"]["sent"], data["stats"]["email"]["recieved"]); + //updateDualData(memory_chart, short_time, data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]); +} diff --git a/aprsd/web/admin/templates/index.html b/aprsd/web/admin/templates/index.html index fe9bac1..3bd95bb 100644 --- a/aprsd/web/admin/templates/index.html +++ b/aprsd/web/admin/templates/index.html @@ -6,6 +6,7 @@ + @@ -15,7 +16,7 @@ - + @@ -83,6 +84,7 @@
Plugins
Config
LogFile
+
Raw JSON
@@ -92,25 +94,25 @@
-
- -
-
-
-
- -
+
-
- -
+
+
+
+
+
+
-
- +
+
+
+
+
+
@@ -118,7 +120,7 @@
{{ stats }}
-
--!> +
//-->
@@ -164,9 +166,15 @@
+ +

Raw JSON

-
{{ stats|safe }}
+
{{ stats|safe }}
diff --git a/aprsd/wsgi.py b/aprsd/wsgi.py index 704455c..0e873cc 100644 --- a/aprsd/wsgi.py +++ b/aprsd/wsgi.py @@ -1,4 +1,6 @@ import datetime +import importlib.metadata as imp +import io import json import logging from logging.handlers import RotatingFileHandler @@ -8,7 +10,7 @@ import flask from flask import Flask from flask.logging import default_handler from flask_httpauth import HTTPBasicAuth -from oslo_config import cfg +from oslo_config import cfg, generator import socketio from werkzeug.security import check_password_hash @@ -96,9 +98,17 @@ def _stats(): if packet_list: rx = packet_list.total_rx() tx = packet_list.total_tx() + types = {} + + types_copy = packet_list.types.copy() + + for key in types_copy: + types[str(key)] = dict(types_copy[key]) + stats_dict["packets"] = { "sent": tx, "received": rx, + "types": types, } if track: size_tracker = len(track) @@ -123,7 +133,6 @@ def stats(): @app.route("/") def index(): stats = _stats() - LOG.debug(stats) wl = aprsd_rpc_client.RPCClient().get_watch_list() if wl and wl.is_enabled(): watch_count = len(wl) @@ -185,6 +194,7 @@ def index(): watch_age=watch_age, seen_count=seen_count, plugin_count=plugin_count, + # oslo_out=generate_oslo() ) @@ -205,10 +215,12 @@ def get_packets(): LOG.debug("/packets called") packet_list = aprsd_rpc_client.RPCClient().get_packet_list() if packet_list: - packets = packet_list.get() tmp_list = [] - for pkt in packets: - tmp_list.append(pkt.json) + pkts = packet_list.copy() + for key in pkts: + pkt = packet_list.get(key) + if pkt: + tmp_list.append(pkt.json) return json.dumps(tmp_list) else: @@ -225,6 +237,36 @@ def plugins(): return "reloaded" +def _get_namespaces(): + args = [] + + all = imp.entry_points() + selected = [] + if "oslo.config.opts" in all: + for x in all["oslo.config.opts"]: + if x.group == "oslo.config.opts": + selected.append(x) + for entry in selected: + if "aprsd" in entry.name: + args.append("--namespace") + args.append(entry.name) + + return args + + +def generate_oslo(): + CONF.namespace = _get_namespaces() + string_out = io.StringIO() + generator.generate(CONF, string_out) + return string_out.getvalue() + + +@auth.login_required +@app.route("/oslo") +def oslo(): + return generate_oslo() + + @auth.login_required @app.route("/save") def save(): @@ -348,6 +390,8 @@ if __name__ == "uwsgi_file_aprsd_wsgi": log_level = init_app( log_level="DEBUG", config_file="/config/aprsd.conf", + # Commented out for local development. + # config_file=cli_helper.DEFAULT_CONFIG_FILE ) setup_logging(app, log_level) sio.register_namespace(LoggingNamespace("/logs")) @@ -362,7 +406,11 @@ if __name__ == "aprsd.wsgi": sio = socketio.Server(logger=True, async_mode=async_mode) app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app) - log_level = init_app(config_file="/config/aprsd.conf", log_level="DEBUG") + log_level = init_app( + log_level="DEBUG", + # config_file="/config/aprsd.conf", + config_file=cli_helper.DEFAULT_CONFIG_FILE, + ) setup_logging(app, log_level) sio.register_namespace(LoggingNamespace("/logs")) CONF.log_opt_values(LOG, logging.DEBUG) diff --git a/dev-requirements.txt b/dev-requirements.txt index 2c3ceef..477ee6e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,212 +1,87 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --annotation-style=line dev-requirements.in # -add-trailing-comma==3.1.0 - # via gray -alabaster==0.7.13 - # via sphinx -attrs==23.1.0 - # via - # jsonschema - # referencing -autoflake==1.5.3 - # via gray -babel==2.12.1 - # via sphinx -black==23.7.0 - # via gray -build==1.0.3 - # via pip-tools -cachetools==5.3.1 - # via tox -certifi==2023.7.22 - # via requests -cfgv==3.4.0 - # via pre-commit -chardet==5.2.0 - # via tox -charset-normalizer==3.2.0 - # via requests -click==8.1.7 - # via - # black - # pip-tools -colorama==0.4.6 - # via tox -commonmark==0.9.1 - # via rich -configargparse==1.7 - # via gray -coverage[toml]==7.3.1 - # via pytest-cov -distlib==0.3.7 - # via virtualenv -docutils==0.20.1 - # via sphinx -filelock==3.12.3 - # via - # tox - # virtualenv -fixit==0.1.4 - # via gray -flake8==6.1.0 - # via - # -r dev-requirements.in - # fixit - # pep8-naming -gray==0.13.0 - # via -r dev-requirements.in -identify==2.5.27 - # via pre-commit -idna==3.4 - # via requests -imagesize==1.4.1 - # via sphinx -importlib-resources==6.0.1 - # via fixit -iniconfig==2.0.0 - # via pytest -isort==5.12.0 - # via - # -r dev-requirements.in - # gray -jinja2==3.1.2 - # via sphinx -jsonschema==4.19.0 - # via fixit -jsonschema-specifications==2023.7.1 - # via jsonschema -libcst==1.0.1 - # via fixit -markupsafe==2.1.3 - # via jinja2 -mccabe==0.7.0 - # via flake8 -mypy==1.5.1 - # via -r dev-requirements.in -mypy-extensions==1.0.0 - # via - # black - # mypy - # typing-inspect -nodeenv==1.8.0 - # via pre-commit -packaging==23.1 - # via - # black - # build - # pyproject-api - # pytest - # sphinx - # tox -pathspec==0.11.2 - # via black -pep8-naming==0.13.3 - # via -r dev-requirements.in -pip-tools==7.3.0 - # via -r dev-requirements.in -platformdirs==3.10.0 - # via - # black - # tox - # virtualenv -pluggy==1.3.0 - # via - # pytest - # tox -pre-commit==3.4.0 - # via -r dev-requirements.in -pycodestyle==2.11.0 - # via flake8 -pyflakes==3.1.0 - # via - # autoflake - # flake8 -pygments==2.16.1 - # via - # rich - # sphinx -pyproject-api==1.6.1 - # via tox -pyproject-hooks==1.0.0 - # via build -pytest==7.4.2 - # via - # -r dev-requirements.in - # pytest-cov -pytest-cov==4.1.0 - # via -r dev-requirements.in -pyupgrade==3.10.1 - # via gray -pyyaml==6.0.1 - # via - # fixit - # libcst - # pre-commit -referencing==0.30.2 - # via - # jsonschema - # jsonschema-specifications -requests==2.31.0 - # via sphinx -rich==12.6.0 - # via gray -rpds-py==0.10.2 - # via - # jsonschema - # referencing -snowballstemmer==2.2.0 - # via sphinx -sphinx==7.2.5 - # via - # -r dev-requirements.in - # sphinxcontrib-applehelp - # sphinxcontrib-devhelp - # sphinxcontrib-htmlhelp - # sphinxcontrib-qthelp - # sphinxcontrib-serializinghtml -sphinxcontrib-applehelp==1.0.7 - # via sphinx -sphinxcontrib-devhelp==1.0.5 - # via sphinx -sphinxcontrib-htmlhelp==2.0.4 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.6 - # via sphinx -sphinxcontrib-serializinghtml==1.1.9 - # via sphinx -tokenize-rt==5.2.0 - # via - # add-trailing-comma - # pyupgrade -toml==0.10.2 - # via autoflake -tox==4.11.2 - # via -r dev-requirements.in -typing-extensions==4.7.1 - # via - # libcst - # mypy - # typing-inspect -typing-inspect==0.9.0 - # via libcst -unify==0.5 - # via gray -untokenize==0.1.1 - # via unify -urllib3==2.0.7 - # via requests -virtualenv==20.24.5 - # via - # pre-commit - # tox -wheel==0.41.2 - # via pip-tools +add-trailing-comma==3.1.0 # via gray +alabaster==0.7.13 # via sphinx +attrs==23.1.0 # via jsonschema, referencing +autoflake==1.5.3 # via gray +babel==2.13.1 # via sphinx +black==23.11.0 # via gray +build==1.0.3 # via pip-tools +cachetools==5.3.2 # via tox +certifi==2023.7.22 # via requests +cfgv==3.4.0 # via pre-commit +chardet==5.2.0 # via tox +charset-normalizer==3.3.2 # via requests +click==8.1.7 # via black, pip-tools +colorama==0.4.6 # via tox +commonmark==0.9.1 # via rich +configargparse==1.7 # via gray +coverage[toml]==7.3.2 # via coverage, pytest-cov +distlib==0.3.7 # via virtualenv +docutils==0.20.1 # via sphinx +exceptiongroup==1.1.3 # via pytest +filelock==3.13.1 # via tox, virtualenv +fixit==0.1.4 # via gray +flake8==6.1.0 # via -r dev-requirements.in, fixit, pep8-naming +gray==0.13.0 # via -r dev-requirements.in +identify==2.5.31 # via pre-commit +idna==3.4 # via requests +imagesize==1.4.1 # via sphinx +importlib-resources==6.1.1 # via fixit +iniconfig==2.0.0 # via pytest +isort==5.12.0 # via -r dev-requirements.in, gray +jinja2==3.1.2 # via sphinx +jsonschema==4.20.0 # via fixit +jsonschema-specifications==2023.11.1 # via jsonschema +libcst==1.1.0 # via fixit +markupsafe==2.1.3 # via jinja2 +mccabe==0.7.0 # via flake8 +mypy==1.7.0 # via -r dev-requirements.in +mypy-extensions==1.0.0 # via black, mypy, typing-inspect +nodeenv==1.8.0 # via pre-commit +packaging==23.2 # via black, build, pyproject-api, pytest, sphinx, tox +pathspec==0.11.2 # via black +pep8-naming==0.13.3 # via -r dev-requirements.in +pip-tools==7.3.0 # via -r dev-requirements.in +platformdirs==3.11.0 # via black, tox, virtualenv +pluggy==1.3.0 # via pytest, tox +pre-commit==3.5.0 # via -r dev-requirements.in +pycodestyle==2.11.1 # via flake8 +pyflakes==3.1.0 # via autoflake, flake8 +pygments==2.16.1 # via rich, sphinx +pyproject-api==1.6.1 # via tox +pyproject-hooks==1.0.0 # via build +pytest==7.4.3 # via -r dev-requirements.in, pytest-cov +pytest-cov==4.1.0 # via -r dev-requirements.in +pyupgrade==3.15.0 # via gray +pyyaml==6.0.1 # via fixit, libcst, pre-commit +referencing==0.31.0 # via jsonschema, jsonschema-specifications +requests==2.31.0 # via sphinx +rich==12.6.0 # via gray +rpds-py==0.13.0 # via jsonschema, referencing +snowballstemmer==2.2.0 # via sphinx +sphinx==7.2.6 # via -r dev-requirements.in, sphinxcontrib-applehelp, sphinxcontrib-devhelp, sphinxcontrib-htmlhelp, sphinxcontrib-qthelp, sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx +sphinxcontrib-devhelp==1.0.5 # via sphinx +sphinxcontrib-htmlhelp==2.0.4 # via sphinx +sphinxcontrib-jsmath==1.0.1 # via sphinx +sphinxcontrib-qthelp==1.0.6 # via sphinx +sphinxcontrib-serializinghtml==1.1.9 # via sphinx +tokenize-rt==5.2.0 # via add-trailing-comma, pyupgrade +toml==0.10.2 # via autoflake +tomli==2.0.1 # via black, build, coverage, mypy, pip-tools, pyproject-api, pyproject-hooks, pytest, tox +tox==4.11.3 # via -r dev-requirements.in +typing-extensions==4.8.0 # via black, libcst, mypy, typing-inspect +typing-inspect==0.9.0 # via libcst +unify==0.5 # via gray +untokenize==0.1.1 # via unify +urllib3==2.1.0 # via requests +virtualenv==20.24.6 # via pre-commit, tox +wheel==0.41.3 # via pip-tools # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements.in b/requirements.in index bcbe328..ed0c86e 100644 --- a/requirements.in +++ b/requirements.in @@ -36,3 +36,4 @@ rpyc shellingham geopy rush +dataclasses-json diff --git a/requirements.txt b/requirements.txt index a42b7a4..56284a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,180 +1,83 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --annotation-style=line requirements.in # -aprslib==0.7.2 - # via -r requirements.in -attrs==23.1.0 - # via - # -r requirements.in - # ax253 - # kiss3 - # rush -ax253==0.1.5.post1 - # via kiss3 -beautifulsoup4==4.12.2 - # via -r requirements.in -bidict==0.22.1 - # via python-socketio -bitarray==2.8.1 - # via - # ax253 - # kiss3 -blinker==1.6.2 - # via flask -certifi==2023.7.22 - # via requests -charset-normalizer==3.2.0 - # via requests -click==8.1.7 - # via - # -r requirements.in - # click-completion - # click-params - # flask -click-completion==0.5.2 - # via -r requirements.in -click-params==0.4.1 - # via -r requirements.in -commonmark==0.9.1 - # via rich -dacite2==2.0.0 - # via -r requirements.in -dataclasses==0.6 - # via -r requirements.in -debtcollector==2.5.0 - # via oslo-config -decorator==5.1.1 - # via validators -dnspython==2.4.2 - # via eventlet -eventlet==0.33.3 - # via -r requirements.in -flask==2.3.3 - # via - # -r requirements.in - # flask-httpauth - # flask-socketio -flask-httpauth==4.8.0 - # via -r requirements.in -flask-socketio==5.3.6 - # via -r requirements.in -geographiclib==2.0 - # via geopy -geopy==2.4.0 - # via -r requirements.in -gevent==23.9.1 - # via -r requirements.in -greenlet==3.0.0rc3 - # via - # eventlet - # gevent -idna==3.4 - # via requests -imapclient==2.3.1 - # via -r requirements.in -importlib-metadata==6.8.0 - # via - # ax253 - # kiss3 -itsdangerous==2.1.2 - # via flask -jinja2==3.1.2 - # via - # click-completion - # flask -kiss3==8.0.0 - # via -r requirements.in -markupsafe==2.1.3 - # via - # jinja2 - # werkzeug -netaddr==0.8.0 - # via oslo-config -oslo-config==9.2.0 - # via -r requirements.in -oslo-i18n==6.1.0 - # via oslo-config -pbr==5.11.1 - # via - # -r requirements.in - # oslo-i18n - # stevedore -pluggy==1.3.0 - # via -r requirements.in -plumbum==1.8.2 - # via rpyc -pygments==2.16.1 - # via rich -pyserial==3.5 - # via pyserial-asyncio -pyserial-asyncio==0.6 - # via kiss3 -python-engineio==4.7.0 - # via python-socketio -python-socketio==5.9.0 - # via - # -r requirements.in - # flask-socketio -pytz==2023.3.post1 - # via -r requirements.in -pyyaml==6.0.1 - # via - # -r requirements.in - # oslo-config -requests==2.31.0 - # via - # -r requirements.in - # oslo-config - # update-checker -rfc3986==2.0.0 - # via oslo-config -rich==12.6.0 - # via -r requirements.in -rpyc==5.3.1 - # via -r requirements.in -rush==2021.4.0 - # via -r requirements.in -shellingham==1.5.3 - # via - # -r requirements.in - # click-completion -six==1.16.0 - # via - # -r requirements.in - # click-completion - # eventlet - # imapclient -soupsieve==2.5 - # via beautifulsoup4 -stevedore==5.1.0 - # via oslo-config -tabulate==0.9.0 - # via -r requirements.in -thesmuggler==1.0.1 - # via -r requirements.in -update-checker==0.18.0 - # via -r requirements.in -urllib3==2.0.7 - # via requests -validators==0.20.0 - # via click-params -werkzeug==3.0.1 - # via - # -r requirements.in - # flask -wrapt==1.15.0 - # via - # -r requirements.in - # debtcollector -zipp==3.16.2 - # via importlib-metadata -zope-event==5.0 - # via gevent -zope-interface==6.0 - # via gevent +aprslib==0.7.2 # via -r requirements.in +attrs==23.1.0 # via -r requirements.in, ax253, kiss3, rush +ax253==0.1.5.post1 # via kiss3 +beautifulsoup4==4.12.2 # via -r requirements.in +bidict==0.22.1 # via python-socketio +bitarray==2.8.3 # via ax253, kiss3 +blinker==1.7.0 # via flask +certifi==2023.7.22 # via requests +charset-normalizer==3.3.2 # via requests +click==8.1.7 # via -r requirements.in, click-completion, click-params, flask +click-completion==0.5.2 # via -r requirements.in +click-params==0.4.1 # via -r requirements.in +commonmark==0.9.1 # via rich +dacite2==2.0.0 # via -r requirements.in +dataclasses==0.6 # via -r requirements.in +dataclasses-json==0.6.2 # via -r requirements.in +debtcollector==2.5.0 # via oslo-config +decorator==5.1.1 # via validators +dnspython==2.4.2 # via eventlet +eventlet==0.33.3 # via -r requirements.in +flask==3.0.0 # via -r requirements.in, flask-httpauth, flask-socketio +flask-httpauth==4.8.0 # via -r requirements.in +flask-socketio==5.3.6 # via -r requirements.in +geographiclib==2.0 # via geopy +geopy==2.4.0 # via -r requirements.in +gevent==23.9.1 # via -r requirements.in +greenlet==3.0.1 # via eventlet, gevent +h11==0.14.0 # via wsproto +idna==3.4 # via requests +imapclient==3.0.0 # via -r requirements.in +importlib-metadata==6.8.0 # via ax253, kiss3 +itsdangerous==2.1.2 # via flask +jinja2==3.1.2 # via click-completion, flask +kiss3==8.0.0 # via -r requirements.in +markupsafe==2.1.3 # via jinja2, werkzeug +marshmallow==3.20.1 # via dataclasses-json +mypy-extensions==1.0.0 # via typing-inspect +netaddr==0.9.0 # via oslo-config +oslo-config==9.2.0 # via -r requirements.in +oslo-i18n==6.2.0 # via oslo-config +packaging==23.2 # via marshmallow +pbr==6.0.0 # via -r requirements.in, oslo-i18n, stevedore +pluggy==1.3.0 # via -r requirements.in +plumbum==1.8.2 # via rpyc +pygments==2.16.1 # via rich +pyserial==3.5 # via pyserial-asyncio +pyserial-asyncio==0.6 # via kiss3 +python-engineio==4.8.0 # via python-socketio +python-socketio==5.10.0 # via -r requirements.in, flask-socketio +pytz==2023.3.post1 # via -r requirements.in +pyyaml==6.0.1 # via -r requirements.in, oslo-config +requests==2.31.0 # via -r requirements.in, oslo-config, update-checker +rfc3986==2.0.0 # via oslo-config +rich==12.6.0 # via -r requirements.in +rpyc==5.3.1 # via -r requirements.in +rush==2021.4.0 # via -r requirements.in +shellingham==1.5.4 # via -r requirements.in, click-completion +simple-websocket==1.0.0 # via python-engineio +six==1.16.0 # via -r requirements.in, click-completion, eventlet +soupsieve==2.5 # via beautifulsoup4 +stevedore==5.1.0 # via oslo-config +tabulate==0.9.0 # via -r requirements.in +thesmuggler==1.0.1 # via -r requirements.in +typing-extensions==4.8.0 # via typing-inspect +typing-inspect==0.9.0 # via dataclasses-json +update-checker==0.18.0 # via -r requirements.in +urllib3==2.1.0 # via requests +validators==0.20.0 # via click-params +werkzeug==3.0.1 # via -r requirements.in, flask +wrapt==1.16.0 # via -r requirements.in, debtcollector +wsproto==1.2.0 # via simple-websocket +zipp==3.17.0 # via importlib-metadata +zope-event==5.0 # via gevent +zope-interface==6.1 # via gevent # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/tox.ini b/tox.ini index c0fe0c1..e3cdffa 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ minversion = 2.9.0 skipdist = True skip_missing_interpreters = true -envlist = pep8,py{39,310} +envlist = pep8,py{39,310,311} #requires = tox-pipenv # pip==22.0.4 # pip-tools==5.4.0