aprsd 4.0.2__py3-none-any.whl → 4.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aprsd/cli_helper.py +36 -35
- aprsd/client/base.py +14 -11
- aprsd/client/drivers/aprsis.py +87 -35
- aprsd/client/drivers/kiss.py +28 -5
- aprsd/client/kiss.py +1 -0
- aprsd/cmds/listen.py +84 -91
- aprsd/cmds/send_message.py +30 -28
- aprsd/cmds/server.py +29 -64
- aprsd/conf/common.py +100 -101
- aprsd/conf/log.py +32 -22
- aprsd/log/log.py +31 -18
- aprsd/main.py +22 -22
- aprsd/packets/__init__.py +6 -0
- aprsd/packets/core.py +5 -2
- aprsd/packets/filter.py +58 -0
- aprsd/packets/filters/__init__.py +0 -0
- aprsd/packets/filters/dupe_filter.py +68 -0
- aprsd/packets/filters/packet_type.py +53 -0
- aprsd/packets/packet_list.py +33 -27
- aprsd/plugin.py +52 -52
- aprsd/plugin_utils.py +20 -21
- aprsd/plugins/weather.py +110 -109
- aprsd/threads/__init__.py +1 -2
- aprsd/threads/rx.py +83 -75
- aprsd/threads/service.py +42 -0
- aprsd/threads/stats.py +4 -9
- aprsd/utils/objectstore.py +12 -13
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/METADATA +22 -20
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/RECORD +34 -29
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/WHEEL +1 -1
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/AUTHORS +0 -0
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/LICENSE +0 -0
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/entry_points.txt +0 -0
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/top_level.txt +0 -0
aprsd/threads/rx.py
CHANGED
@@ -8,20 +8,32 @@ from oslo_config import cfg
|
|
8
8
|
|
9
9
|
from aprsd import packets, plugin
|
10
10
|
from aprsd.client import client_factory
|
11
|
-
from aprsd.packets import collector
|
11
|
+
from aprsd.packets import collector, filter
|
12
12
|
from aprsd.packets import log as packet_log
|
13
13
|
from aprsd.threads import APRSDThread, tx
|
14
|
-
from aprsd.utils import trace
|
15
14
|
|
16
15
|
CONF = cfg.CONF
|
17
|
-
LOG = logging.getLogger(
|
16
|
+
LOG = logging.getLogger('APRSD')
|
18
17
|
|
19
18
|
|
20
19
|
class APRSDRXThread(APRSDThread):
|
20
|
+
"""Main Class to connect to an APRS Client and recieve packets.
|
21
|
+
|
22
|
+
A packet is received in the main loop and then sent to the
|
23
|
+
process_packet method, which sends the packet through the collector
|
24
|
+
to track the packet for stats, and then put into the packet queue
|
25
|
+
for processing in a separate thread.
|
26
|
+
"""
|
27
|
+
|
21
28
|
_client = None
|
22
29
|
|
30
|
+
# This is the queue that packets are sent to for processing.
|
31
|
+
# We process packets in a separate thread to help prevent
|
32
|
+
# getting blocked by the APRS server trying to send us packets.
|
33
|
+
packet_queue = None
|
34
|
+
|
23
35
|
def __init__(self, packet_queue):
|
24
|
-
super().__init__(
|
36
|
+
super().__init__('RX_PKT')
|
25
37
|
self.packet_queue = packet_queue
|
26
38
|
|
27
39
|
def stop(self):
|
@@ -52,7 +64,7 @@ class APRSDRXThread(APRSDThread):
|
|
52
64
|
# kwargs. :(
|
53
65
|
# https://github.com/rossengeorgiev/aprs-python/pull/56
|
54
66
|
self._client.consumer(
|
55
|
-
self.
|
67
|
+
self.process_packet,
|
56
68
|
raw=False,
|
57
69
|
blocking=False,
|
58
70
|
)
|
@@ -60,7 +72,7 @@ class APRSDRXThread(APRSDThread):
|
|
60
72
|
aprslib.exceptions.ConnectionDrop,
|
61
73
|
aprslib.exceptions.ConnectionError,
|
62
74
|
):
|
63
|
-
LOG.error(
|
75
|
+
LOG.error('Connection dropped, reconnecting')
|
64
76
|
# Force the deletion of the client object connected to aprs
|
65
77
|
# This will cause a reconnect, next time client.get_client()
|
66
78
|
# is called
|
@@ -68,45 +80,18 @@ class APRSDRXThread(APRSDThread):
|
|
68
80
|
time.sleep(5)
|
69
81
|
except Exception:
|
70
82
|
# LOG.exception(ex)
|
71
|
-
LOG.error(
|
83
|
+
LOG.error('Resetting connection and trying again.')
|
72
84
|
self._client.reset()
|
73
85
|
time.sleep(5)
|
74
|
-
# Continue to loop
|
75
|
-
time.sleep(1)
|
76
86
|
return True
|
77
87
|
|
78
|
-
def _process_packet(self, *args, **kwargs):
|
79
|
-
"""Intermediate callback so we can update the keepalive time."""
|
80
|
-
# Now call the 'real' packet processing for a RX'x packet
|
81
|
-
self.process_packet(*args, **kwargs)
|
82
|
-
|
83
|
-
@abc.abstractmethod
|
84
88
|
def process_packet(self, *args, **kwargs):
|
85
|
-
pass
|
86
|
-
|
87
|
-
|
88
|
-
class APRSDDupeRXThread(APRSDRXThread):
|
89
|
-
"""Process received packets.
|
90
|
-
|
91
|
-
This is the main APRSD Server command thread that
|
92
|
-
receives packets and makes sure the packet
|
93
|
-
hasn't been seen previously before sending it on
|
94
|
-
to be processed.
|
95
|
-
"""
|
96
|
-
|
97
|
-
@trace.trace
|
98
|
-
def process_packet(self, *args, **kwargs):
|
99
|
-
"""This handles the processing of an inbound packet.
|
100
|
-
|
101
|
-
When a packet is received by the connected client object,
|
102
|
-
it sends the raw packet into this function. This function then
|
103
|
-
decodes the packet via the client, and then processes the packet.
|
104
|
-
Ack Packets are sent to the PluginProcessPacketThread for processing.
|
105
|
-
All other packets have to be checked as a dupe, and then only after
|
106
|
-
we haven't seen this packet before, do we send it to the
|
107
|
-
PluginProcessPacketThread for processing.
|
108
|
-
"""
|
109
89
|
packet = self._client.decode_packet(*args, **kwargs)
|
90
|
+
if not packet:
|
91
|
+
LOG.error(
|
92
|
+
'No packet received from decode_packet. Most likely a failure to parse'
|
93
|
+
)
|
94
|
+
return
|
110
95
|
packet_log.log(packet)
|
111
96
|
pkt_list = packets.PacketList()
|
112
97
|
|
@@ -140,26 +125,55 @@ class APRSDDupeRXThread(APRSDRXThread):
|
|
140
125
|
# If the packet came in within N seconds of the
|
141
126
|
# Last time seeing the packet, then we drop it as a dupe.
|
142
127
|
LOG.warning(
|
143
|
-
f
|
128
|
+
f'Packet {packet.from_call}:{packet.msgNo} already tracked, dropping.'
|
144
129
|
)
|
145
130
|
else:
|
146
131
|
LOG.warning(
|
147
|
-
f
|
148
|
-
f
|
132
|
+
f'Packet {packet.from_call}:{packet.msgNo} already tracked '
|
133
|
+
f'but older than {CONF.packet_dupe_timeout} seconds. processing.',
|
149
134
|
)
|
150
135
|
collector.PacketCollector().rx(packet)
|
151
136
|
self.packet_queue.put(packet)
|
152
137
|
|
153
138
|
|
154
|
-
class
|
155
|
-
|
139
|
+
class APRSDFilterThread(APRSDThread):
|
140
|
+
def __init__(self, thread_name, packet_queue):
|
141
|
+
super().__init__(thread_name)
|
142
|
+
self.packet_queue = packet_queue
|
156
143
|
|
157
|
-
|
158
|
-
|
144
|
+
def filter_packet(self, packet):
|
145
|
+
# Do any packet filtering prior to processing
|
146
|
+
if not filter.PacketFilter().filter(packet):
|
147
|
+
return None
|
148
|
+
return packet
|
159
149
|
|
150
|
+
def print_packet(self, packet):
|
151
|
+
"""Allow a child of this class to override this.
|
160
152
|
|
161
|
-
class
|
162
|
-
|
153
|
+
This is helpful if for whatever reason the child class
|
154
|
+
doesn't want to log packets.
|
155
|
+
|
156
|
+
"""
|
157
|
+
packet_log.log(packet)
|
158
|
+
|
159
|
+
def loop(self):
|
160
|
+
try:
|
161
|
+
packet = self.packet_queue.get(timeout=1)
|
162
|
+
self.print_packet(packet)
|
163
|
+
if packet:
|
164
|
+
if self.filter_packet(packet):
|
165
|
+
self.process_packet(packet)
|
166
|
+
except queue.Empty:
|
167
|
+
pass
|
168
|
+
return True
|
169
|
+
|
170
|
+
|
171
|
+
class APRSDProcessPacketThread(APRSDFilterThread):
|
172
|
+
"""Base class for processing received packets after they have been filtered.
|
173
|
+
|
174
|
+
Packets are received from the client, then filtered for dupes,
|
175
|
+
then sent to the packet queue. This thread pulls packets from
|
176
|
+
the packet queue for processing.
|
163
177
|
|
164
178
|
This is the base class for processing packets coming from
|
165
179
|
the consumer. This base class handles sending ack packets and
|
@@ -167,44 +181,38 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
167
181
|
for processing."""
|
168
182
|
|
169
183
|
def __init__(self, packet_queue):
|
170
|
-
|
171
|
-
super().__init__("ProcessPKT")
|
184
|
+
super().__init__('ProcessPKT', packet_queue=packet_queue)
|
172
185
|
if not CONF.enable_sending_ack_packets:
|
173
186
|
LOG.warning(
|
174
|
-
|
175
|
-
"will not be acknowledged.",
|
187
|
+
'Sending ack packets is disabled, messages will not be acknowledged.',
|
176
188
|
)
|
177
189
|
|
178
190
|
def process_ack_packet(self, packet):
|
179
191
|
"""We got an ack for a message, no need to resend it."""
|
180
192
|
ack_num = packet.msgNo
|
181
|
-
LOG.debug(f
|
193
|
+
LOG.debug(f'Got ack for message {ack_num}')
|
182
194
|
collector.PacketCollector().rx(packet)
|
183
195
|
|
184
196
|
def process_piggyback_ack(self, packet):
|
185
197
|
"""We got an ack embedded in a packet."""
|
186
198
|
ack_num = packet.ackMsgNo
|
187
|
-
LOG.debug(f
|
199
|
+
LOG.debug(f'Got PiggyBackAck for message {ack_num}')
|
188
200
|
collector.PacketCollector().rx(packet)
|
189
201
|
|
190
202
|
def process_reject_packet(self, packet):
|
191
203
|
"""We got a reject message for a packet. Stop sending the message."""
|
192
204
|
ack_num = packet.msgNo
|
193
|
-
LOG.debug(f
|
205
|
+
LOG.debug(f'Got REJECT for message {ack_num}')
|
194
206
|
collector.PacketCollector().rx(packet)
|
195
207
|
|
196
|
-
def loop(self):
|
197
|
-
try:
|
198
|
-
packet = self.packet_queue.get(timeout=1)
|
199
|
-
if packet:
|
200
|
-
self.process_packet(packet)
|
201
|
-
except queue.Empty:
|
202
|
-
pass
|
203
|
-
return True
|
204
|
-
|
205
208
|
def process_packet(self, packet):
|
206
209
|
"""Process a packet received from aprs-is server."""
|
207
|
-
LOG.debug(f
|
210
|
+
LOG.debug(f'ProcessPKT-LOOP {self.loop_count}')
|
211
|
+
|
212
|
+
# set this now as we are going to process it.
|
213
|
+
# This is used during dupe checking, so set it early
|
214
|
+
packet.processed = True
|
215
|
+
|
208
216
|
our_call = CONF.callsign.lower()
|
209
217
|
|
210
218
|
from_call = packet.from_call
|
@@ -227,7 +235,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
227
235
|
):
|
228
236
|
self.process_reject_packet(packet)
|
229
237
|
else:
|
230
|
-
if hasattr(packet,
|
238
|
+
if hasattr(packet, 'ackMsgNo') and packet.ackMsgNo:
|
231
239
|
# we got an ack embedded in this packet
|
232
240
|
# we need to handle the ack
|
233
241
|
self.process_piggyback_ack(packet)
|
@@ -267,7 +275,7 @@ class APRSDProcessPacketThread(APRSDThread):
|
|
267
275
|
if not for_us:
|
268
276
|
LOG.info("Got a packet meant for someone else '{packet.to_call}'")
|
269
277
|
else:
|
270
|
-
LOG.info(
|
278
|
+
LOG.info('Got a non AckPacket/MessagePacket')
|
271
279
|
|
272
280
|
|
273
281
|
class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
@@ -287,7 +295,7 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
|
287
295
|
tx.send(subreply)
|
288
296
|
else:
|
289
297
|
wl = CONF.watch_list
|
290
|
-
to_call = wl[
|
298
|
+
to_call = wl['alert_callsign']
|
291
299
|
tx.send(
|
292
300
|
packets.MessagePacket(
|
293
301
|
from_call=CONF.callsign,
|
@@ -299,7 +307,7 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
|
299
307
|
# We have a message based object.
|
300
308
|
tx.send(reply)
|
301
309
|
except Exception as ex:
|
302
|
-
LOG.error(
|
310
|
+
LOG.error('Plugin failed!!!')
|
303
311
|
LOG.exception(ex)
|
304
312
|
|
305
313
|
def process_our_message_packet(self, packet):
|
@@ -355,11 +363,11 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
|
355
363
|
if to_call == CONF.callsign and not replied:
|
356
364
|
# Tailor the messages accordingly
|
357
365
|
if CONF.load_help_plugin:
|
358
|
-
LOG.warning(
|
366
|
+
LOG.warning('Sending help!')
|
359
367
|
message_text = "Unknown command! Send 'help' message for help"
|
360
368
|
else:
|
361
|
-
LOG.warning(
|
362
|
-
message_text =
|
369
|
+
LOG.warning('Unknown command!')
|
370
|
+
message_text = 'Unknown command!'
|
363
371
|
|
364
372
|
tx.send(
|
365
373
|
packets.MessagePacket(
|
@@ -369,11 +377,11 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
|
369
377
|
),
|
370
378
|
)
|
371
379
|
except Exception as ex:
|
372
|
-
LOG.error(
|
380
|
+
LOG.error('Plugin failed!!!')
|
373
381
|
LOG.exception(ex)
|
374
382
|
# Do we need to send a reply?
|
375
383
|
if to_call == CONF.callsign:
|
376
|
-
reply =
|
384
|
+
reply = 'A Plugin failed! try again?'
|
377
385
|
tx.send(
|
378
386
|
packets.MessagePacket(
|
379
387
|
from_call=CONF.callsign,
|
@@ -382,4 +390,4 @@ class APRSDPluginProcessPacketThread(APRSDProcessPacketThread):
|
|
382
390
|
),
|
383
391
|
)
|
384
392
|
|
385
|
-
LOG.debug(
|
393
|
+
LOG.debug('Completed process_our_message_packet')
|
aprsd/threads/service.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# aprsd/aprsd/threads/service.py
|
2
|
+
#
|
3
|
+
# This module is used to register threads that the service command runs.
|
4
|
+
#
|
5
|
+
# The service command is used to start and stop the APRS service.
|
6
|
+
# This is a mechanism to register threads that the service or command
|
7
|
+
# needs to run, and then start stop them as needed.
|
8
|
+
|
9
|
+
from aprsd.threads import aprsd as aprsd_threads
|
10
|
+
from aprsd.utils import singleton
|
11
|
+
|
12
|
+
|
13
|
+
@singleton
|
14
|
+
class ServiceThreads:
|
15
|
+
"""Registry for threads that the service command runs.
|
16
|
+
|
17
|
+
This enables extensions to register a thread to run during
|
18
|
+
the service command.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
self.threads: list[aprsd_threads.APRSDThread] = []
|
23
|
+
|
24
|
+
def register(self, thread: aprsd_threads.APRSDThread):
|
25
|
+
if not isinstance(thread, aprsd_threads.APRSDThread):
|
26
|
+
raise TypeError(f'Thread {thread} is not an APRSDThread')
|
27
|
+
self.threads.append(thread)
|
28
|
+
|
29
|
+
def unregister(self, thread: aprsd_threads.APRSDThread):
|
30
|
+
if not isinstance(thread, aprsd_threads.APRSDThread):
|
31
|
+
raise TypeError(f'Thread {thread} is not an APRSDThread')
|
32
|
+
self.threads.remove(thread)
|
33
|
+
|
34
|
+
def start(self):
|
35
|
+
"""Start all threads in the list."""
|
36
|
+
for thread in self.threads:
|
37
|
+
thread.start()
|
38
|
+
|
39
|
+
def join(self):
|
40
|
+
"""Join all the threads in the list"""
|
41
|
+
for thread in self.threads:
|
42
|
+
thread.join()
|
aprsd/threads/stats.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
import logging
|
2
|
-
import threading
|
3
2
|
import time
|
4
3
|
|
5
|
-
import wrapt
|
6
4
|
from oslo_config import cfg
|
7
5
|
|
8
6
|
from aprsd.stats import collector
|
@@ -10,18 +8,15 @@ from aprsd.threads import APRSDThread
|
|
10
8
|
from aprsd.utils import objectstore
|
11
9
|
|
12
10
|
CONF = cfg.CONF
|
13
|
-
LOG = logging.getLogger(
|
11
|
+
LOG = logging.getLogger('APRSD')
|
14
12
|
|
15
13
|
|
16
14
|
class StatsStore(objectstore.ObjectStoreMixin):
|
17
15
|
"""Container to save the stats from the collector."""
|
18
16
|
|
19
|
-
lock = threading.Lock()
|
20
|
-
data = {}
|
21
|
-
|
22
|
-
@wrapt.synchronized(lock)
|
23
17
|
def add(self, stats: dict):
|
24
|
-
self.
|
18
|
+
with self.lock:
|
19
|
+
self.data = stats
|
25
20
|
|
26
21
|
|
27
22
|
class APRSDStatsStoreThread(APRSDThread):
|
@@ -31,7 +26,7 @@ class APRSDStatsStoreThread(APRSDThread):
|
|
31
26
|
save_interval = 10
|
32
27
|
|
33
28
|
def __init__(self):
|
34
|
-
super().__init__(
|
29
|
+
super().__init__('StatsStore')
|
35
30
|
|
36
31
|
def loop(self):
|
37
32
|
if self.loop_count % self.save_interval == 0:
|
aprsd/utils/objectstore.py
CHANGED
@@ -6,9 +6,8 @@ import threading
|
|
6
6
|
|
7
7
|
from oslo_config import cfg
|
8
8
|
|
9
|
-
|
10
9
|
CONF = cfg.CONF
|
11
|
-
LOG = logging.getLogger(
|
10
|
+
LOG = logging.getLogger('APRSD')
|
12
11
|
|
13
12
|
|
14
13
|
class ObjectStoreMixin:
|
@@ -63,7 +62,7 @@ class ObjectStoreMixin:
|
|
63
62
|
def _save_filename(self):
|
64
63
|
save_location = CONF.save_location
|
65
64
|
|
66
|
-
return
|
65
|
+
return '{}/{}.p'.format(
|
67
66
|
save_location,
|
68
67
|
self.__class__.__name__.lower(),
|
69
68
|
)
|
@@ -75,13 +74,13 @@ class ObjectStoreMixin:
|
|
75
74
|
self._init_store()
|
76
75
|
save_filename = self._save_filename()
|
77
76
|
if len(self) > 0:
|
78
|
-
LOG.
|
79
|
-
f
|
80
|
-
f
|
81
|
-
f
|
77
|
+
LOG.debug(
|
78
|
+
f'{self.__class__.__name__}::Saving'
|
79
|
+
f' {len(self)} entries to disk at '
|
80
|
+
f'{save_filename}',
|
82
81
|
)
|
83
82
|
with self.lock:
|
84
|
-
with open(save_filename,
|
83
|
+
with open(save_filename, 'wb+') as fp:
|
85
84
|
pickle.dump(self.data, fp)
|
86
85
|
else:
|
87
86
|
LOG.debug(
|
@@ -97,21 +96,21 @@ class ObjectStoreMixin:
|
|
97
96
|
return
|
98
97
|
if os.path.exists(self._save_filename()):
|
99
98
|
try:
|
100
|
-
with open(self._save_filename(),
|
99
|
+
with open(self._save_filename(), 'rb') as fp:
|
101
100
|
raw = pickle.load(fp)
|
102
101
|
if raw:
|
103
102
|
self.data = raw
|
104
103
|
LOG.debug(
|
105
|
-
f
|
104
|
+
f'{self.__class__.__name__}::Loaded {len(self)} entries from disk.',
|
106
105
|
)
|
107
106
|
else:
|
108
|
-
LOG.debug(f
|
107
|
+
LOG.debug(f'{self.__class__.__name__}::No data to load.')
|
109
108
|
except (pickle.UnpicklingError, Exception) as ex:
|
110
|
-
LOG.error(f
|
109
|
+
LOG.error(f'Failed to UnPickle {self._save_filename()}')
|
111
110
|
LOG.error(ex)
|
112
111
|
self.data = {}
|
113
112
|
else:
|
114
|
-
LOG.debug(f
|
113
|
+
LOG.debug(f'{self.__class__.__name__}::No save file found.')
|
115
114
|
|
116
115
|
def flush(self):
|
117
116
|
"""Nuke the old pickle file that stored the old results from last aprsd run."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: aprsd
|
3
|
-
Version: 4.
|
3
|
+
Version: 4.1.1
|
4
4
|
Summary: APRSd is a APRS-IS server that can be used to connect to APRS-IS and send and receive APRS packets.
|
5
5
|
Author-email: Craig Lamparter <craig@craiger.org>, "Walter A. Boring IV" <waboring@hemna.com>, Emre Saglam <emresaglam@gmail.com>, Jason Martin <jhmartin@toger.us>, John <johng42@users.noreply.github.com>, Martiros Shakhzadyan <vrzh@vrzh.net>, Zoe Moore <zoenb@mailbox.org>, ranguli <hello@joshmurphy.ca>
|
6
6
|
Maintainer-email: Craig Lamparter <craig@craiger.org>, "Walter A. Boring IV" <waboring@hemna.com>
|
@@ -202,13 +202,12 @@ Description-Content-Type: text/markdown
|
|
202
202
|
License-File: LICENSE
|
203
203
|
License-File: AUTHORS
|
204
204
|
Requires-Dist: aprslib==0.7.2
|
205
|
-
Requires-Dist: attrs==
|
205
|
+
Requires-Dist: attrs==25.1.0
|
206
206
|
Requires-Dist: ax253==0.1.5.post1
|
207
|
-
Requires-Dist: bitarray==3.
|
208
|
-
Requires-Dist: certifi==
|
207
|
+
Requires-Dist: bitarray==3.1.0
|
208
|
+
Requires-Dist: certifi==2025.1.31
|
209
209
|
Requires-Dist: charset-normalizer==3.4.1
|
210
210
|
Requires-Dist: click==8.1.8
|
211
|
-
Requires-Dist: commonmark==0.9.1
|
212
211
|
Requires-Dist: dataclasses-json==0.6.7
|
213
212
|
Requires-Dist: debtcollector==3.0.0
|
214
213
|
Requires-Dist: haversine==2.9.0
|
@@ -216,39 +215,42 @@ Requires-Dist: idna==3.10
|
|
216
215
|
Requires-Dist: importlib-metadata==8.6.1
|
217
216
|
Requires-Dist: kiss3==8.0.0
|
218
217
|
Requires-Dist: loguru==0.7.3
|
219
|
-
Requires-Dist:
|
218
|
+
Requires-Dist: markdown-it-py==3.0.0
|
219
|
+
Requires-Dist: marshmallow==3.26.1
|
220
|
+
Requires-Dist: mdurl==0.1.2
|
220
221
|
Requires-Dist: mypy-extensions==1.0.0
|
221
222
|
Requires-Dist: netaddr==1.3.0
|
222
|
-
Requires-Dist: oslo-config==9.7.
|
223
|
-
Requires-Dist: oslo-i18n==6.5.
|
223
|
+
Requires-Dist: oslo-config==9.7.1
|
224
|
+
Requires-Dist: oslo-i18n==6.5.1
|
224
225
|
Requires-Dist: packaging==24.2
|
225
|
-
Requires-Dist: pbr==6.1.
|
226
|
+
Requires-Dist: pbr==6.1.1
|
226
227
|
Requires-Dist: pluggy==1.5.0
|
227
228
|
Requires-Dist: pygments==2.19.1
|
228
229
|
Requires-Dist: pyserial==3.5
|
229
230
|
Requires-Dist: pyserial-asyncio==0.6
|
230
|
-
Requires-Dist: pytz==
|
231
|
+
Requires-Dist: pytz==2025.1
|
231
232
|
Requires-Dist: pyyaml==6.0.2
|
232
233
|
Requires-Dist: requests==2.32.3
|
233
234
|
Requires-Dist: rfc3986==2.0.0
|
234
|
-
Requires-Dist: rich==
|
235
|
+
Requires-Dist: rich==13.9.4
|
235
236
|
Requires-Dist: rush==2021.4.0
|
236
|
-
Requires-Dist:
|
237
|
+
Requires-Dist: setuptools==75.8.2
|
238
|
+
Requires-Dist: stevedore==5.4.1
|
237
239
|
Requires-Dist: thesmuggler==1.0.1
|
238
240
|
Requires-Dist: timeago==1.0.16
|
239
241
|
Requires-Dist: typing-extensions==4.12.2
|
240
242
|
Requires-Dist: typing-inspect==0.9.0
|
241
|
-
Requires-Dist: tzlocal==5.
|
243
|
+
Requires-Dist: tzlocal==5.3
|
242
244
|
Requires-Dist: update-checker==0.18.0
|
243
245
|
Requires-Dist: urllib3==2.3.0
|
244
246
|
Requires-Dist: wrapt==1.17.2
|
245
247
|
Requires-Dist: zipp==3.21.0
|
246
248
|
Provides-Extra: dev
|
247
249
|
Requires-Dist: alabaster==1.0.0; extra == "dev"
|
248
|
-
Requires-Dist: babel==2.
|
250
|
+
Requires-Dist: babel==2.17.0; extra == "dev"
|
249
251
|
Requires-Dist: build==1.2.2.post1; extra == "dev"
|
250
|
-
Requires-Dist: cachetools==5.5.
|
251
|
-
Requires-Dist: certifi==
|
252
|
+
Requires-Dist: cachetools==5.5.2; extra == "dev"
|
253
|
+
Requires-Dist: certifi==2025.1.31; extra == "dev"
|
252
254
|
Requires-Dist: cfgv==3.4.0; extra == "dev"
|
253
255
|
Requires-Dist: chardet==5.2.0; extra == "dev"
|
254
256
|
Requires-Dist: charset-normalizer==3.4.1; extra == "dev"
|
@@ -257,7 +259,7 @@ Requires-Dist: colorama==0.4.6; extra == "dev"
|
|
257
259
|
Requires-Dist: distlib==0.3.9; extra == "dev"
|
258
260
|
Requires-Dist: docutils==0.21.2; extra == "dev"
|
259
261
|
Requires-Dist: filelock==3.17.0; extra == "dev"
|
260
|
-
Requires-Dist: identify==2.6.
|
262
|
+
Requires-Dist: identify==2.6.8; extra == "dev"
|
261
263
|
Requires-Dist: idna==3.10; extra == "dev"
|
262
264
|
Requires-Dist: imagesize==1.4.1; extra == "dev"
|
263
265
|
Requires-Dist: jinja2==3.1.5; extra == "dev"
|
@@ -266,7 +268,7 @@ Requires-Dist: markupsafe==3.0.2; extra == "dev"
|
|
266
268
|
Requires-Dist: mistune==0.8.4; extra == "dev"
|
267
269
|
Requires-Dist: nodeenv==1.9.1; extra == "dev"
|
268
270
|
Requires-Dist: packaging==24.2; extra == "dev"
|
269
|
-
Requires-Dist: pip==
|
271
|
+
Requires-Dist: pip==25.0.1; extra == "dev"
|
270
272
|
Requires-Dist: pip-tools==7.4.1; extra == "dev"
|
271
273
|
Requires-Dist: platformdirs==4.3.6; extra == "dev"
|
272
274
|
Requires-Dist: pluggy==1.5.0; extra == "dev"
|
@@ -276,7 +278,7 @@ Requires-Dist: pyproject-api==1.9.0; extra == "dev"
|
|
276
278
|
Requires-Dist: pyproject-hooks==1.2.0; extra == "dev"
|
277
279
|
Requires-Dist: pyyaml==6.0.2; extra == "dev"
|
278
280
|
Requires-Dist: requests==2.32.3; extra == "dev"
|
279
|
-
Requires-Dist: setuptools==75.8.
|
281
|
+
Requires-Dist: setuptools==75.8.2; extra == "dev"
|
280
282
|
Requires-Dist: snowballstemmer==2.2.0; extra == "dev"
|
281
283
|
Requires-Dist: sphinx==8.1.3; extra == "dev"
|
282
284
|
Requires-Dist: sphinxcontrib-applehelp==2.0.0; extra == "dev"
|
@@ -289,7 +291,7 @@ Requires-Dist: tomli==2.2.1; extra == "dev"
|
|
289
291
|
Requires-Dist: tox==4.24.1; extra == "dev"
|
290
292
|
Requires-Dist: typing-extensions==4.12.2; extra == "dev"
|
291
293
|
Requires-Dist: urllib3==2.3.0; extra == "dev"
|
292
|
-
Requires-Dist: virtualenv==20.29.
|
294
|
+
Requires-Dist: virtualenv==20.29.2; extra == "dev"
|
293
295
|
Requires-Dist: wheel==0.45.1; extra == "dev"
|
294
296
|
|
295
297
|
# APRSD - Ham radio APRS-IS Message platform software
|