aprsd 4.1.0__py3-none-any.whl → 4.1.2__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/cmds/send_message.py +30 -28
- aprsd/cmds/server.py +28 -63
- aprsd/conf/log.py +5 -0
- aprsd/log/log.py +16 -10
- aprsd/main.py +22 -22
- aprsd/plugin.py +52 -52
- aprsd/plugin_utils.py +20 -21
- aprsd/plugins/weather.py +110 -109
- aprsd/threads/service.py +42 -0
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/METADATA +22 -20
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/RECORD +18 -17
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/WHEEL +1 -1
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/AUTHORS +0 -0
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/LICENSE +0 -0
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/entry_points.txt +0 -0
- {aprsd-4.1.0.dist-info → aprsd-4.1.2.dist-info}/top_level.txt +0 -0
aprsd/plugins/weather.py
CHANGED
@@ -9,7 +9,7 @@ from aprsd import plugin, plugin_utils
|
|
9
9
|
from aprsd.utils import trace
|
10
10
|
|
11
11
|
CONF = cfg.CONF
|
12
|
-
LOG = logging.getLogger(
|
12
|
+
LOG = logging.getLogger('APRSD')
|
13
13
|
|
14
14
|
|
15
15
|
class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
@@ -26,22 +26,22 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
|
|
26
26
|
"""
|
27
27
|
|
28
28
|
# command_regex = r"^([w][x]|[w][x]\s|weather)"
|
29
|
-
command_regex = r
|
29
|
+
command_regex = r'^[wW]'
|
30
30
|
|
31
|
-
command_name =
|
32
|
-
short_description =
|
31
|
+
command_name = 'USWeather'
|
32
|
+
short_description = 'Provide USA only weather of GPS Beacon location'
|
33
33
|
|
34
34
|
def setup(self):
|
35
35
|
self.ensure_aprs_fi_key()
|
36
36
|
|
37
37
|
@trace.trace
|
38
38
|
def process(self, packet):
|
39
|
-
LOG.info(
|
39
|
+
LOG.info('Weather Plugin')
|
40
40
|
fromcall = packet.from_call
|
41
|
-
message = packet.get(
|
41
|
+
message = packet.get('message_text', None)
|
42
42
|
# message = packet.get("message_text", None)
|
43
43
|
# ack = packet.get("msgNo", "0")
|
44
|
-
a = re.search(r
|
44
|
+
a = re.search(r'^.*\s+(.*)', message)
|
45
45
|
if a is not None:
|
46
46
|
searchcall = a.group(1)
|
47
47
|
searchcall = searchcall.upper()
|
@@ -51,34 +51,34 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin)
|
|
51
51
|
try:
|
52
52
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
53
53
|
except Exception as ex:
|
54
|
-
LOG.error(f
|
55
|
-
return
|
54
|
+
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
55
|
+
return 'Failed to fetch aprs.fi location'
|
56
56
|
|
57
|
-
LOG.debug(f
|
58
|
-
if not len(aprs_data[
|
57
|
+
LOG.debug(f'LocationPlugin: aprs_data = {aprs_data}')
|
58
|
+
if not len(aprs_data['entries']):
|
59
59
|
LOG.error("Didn't get any entries from aprs.fi")
|
60
|
-
return
|
60
|
+
return 'Failed to fetch aprs.fi location'
|
61
61
|
|
62
|
-
lat = aprs_data[
|
63
|
-
lon = aprs_data[
|
62
|
+
lat = aprs_data['entries'][0]['lat']
|
63
|
+
lon = aprs_data['entries'][0]['lng']
|
64
64
|
|
65
65
|
try:
|
66
66
|
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
67
67
|
except Exception as ex:
|
68
68
|
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
69
|
-
return
|
69
|
+
return 'Unable to get weather'
|
70
70
|
|
71
|
-
LOG.info(f
|
71
|
+
LOG.info(f'WX data {wx_data}')
|
72
72
|
|
73
73
|
reply = (
|
74
|
-
|
74
|
+
'%sF(%sF/%sF) %s. %s, %s.'
|
75
75
|
% (
|
76
|
-
wx_data[
|
77
|
-
wx_data[
|
78
|
-
wx_data[
|
79
|
-
wx_data[
|
80
|
-
wx_data[
|
81
|
-
wx_data[
|
76
|
+
wx_data['currentobservation']['Temp'],
|
77
|
+
wx_data['data']['temperature'][0],
|
78
|
+
wx_data['data']['temperature'][1],
|
79
|
+
wx_data['data']['weather'][0],
|
80
|
+
wx_data['time']['startPeriodName'][1],
|
81
|
+
wx_data['data']['weather'][1],
|
82
82
|
)
|
83
83
|
).rstrip()
|
84
84
|
LOG.debug(f"reply: '{reply}' ")
|
@@ -100,31 +100,31 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
|
100
100
|
|
101
101
|
"""
|
102
102
|
|
103
|
-
command_regex = r
|
104
|
-
command_name =
|
105
|
-
short_description =
|
103
|
+
command_regex = r'^([m]|[M]|[m]\s|metar)'
|
104
|
+
command_name = 'USMetar'
|
105
|
+
short_description = 'USA only METAR of GPS Beacon location'
|
106
106
|
|
107
107
|
def setup(self):
|
108
108
|
self.ensure_aprs_fi_key()
|
109
109
|
|
110
110
|
@trace.trace
|
111
111
|
def process(self, packet):
|
112
|
-
fromcall = packet.get(
|
113
|
-
message = packet.get(
|
112
|
+
fromcall = packet.get('from')
|
113
|
+
message = packet.get('message_text', None)
|
114
114
|
# ack = packet.get("msgNo", "0")
|
115
115
|
LOG.info(f"WX Plugin '{message}'")
|
116
|
-
a = re.search(r
|
116
|
+
a = re.search(r'^.*\s+(.*)', message)
|
117
117
|
if a is not None:
|
118
118
|
searchcall = a.group(1)
|
119
119
|
station = searchcall.upper()
|
120
120
|
try:
|
121
121
|
resp = plugin_utils.get_weather_gov_metar(station)
|
122
122
|
except Exception as e:
|
123
|
-
LOG.debug(f
|
124
|
-
reply =
|
123
|
+
LOG.debug(f'Weather failed with: {str(e)}')
|
124
|
+
reply = 'Unable to find station METAR'
|
125
125
|
else:
|
126
126
|
station_data = json.loads(resp.text)
|
127
|
-
reply = station_data[
|
127
|
+
reply = station_data['properties']['rawMessage']
|
128
128
|
|
129
129
|
return reply
|
130
130
|
else:
|
@@ -136,36 +136,36 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
|
|
136
136
|
try:
|
137
137
|
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
138
138
|
except Exception as ex:
|
139
|
-
LOG.error(f
|
140
|
-
return
|
139
|
+
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
140
|
+
return 'Failed to fetch aprs.fi location'
|
141
141
|
|
142
142
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
143
|
-
if not len(aprs_data[
|
144
|
-
LOG.error(
|
145
|
-
return
|
143
|
+
if not len(aprs_data['entries']):
|
144
|
+
LOG.error('Found no entries from aprs.fi!')
|
145
|
+
return 'Failed to fetch aprs.fi location'
|
146
146
|
|
147
|
-
lat = aprs_data[
|
148
|
-
lon = aprs_data[
|
147
|
+
lat = aprs_data['entries'][0]['lat']
|
148
|
+
lon = aprs_data['entries'][0]['lng']
|
149
149
|
|
150
150
|
try:
|
151
151
|
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
152
152
|
except Exception as ex:
|
153
153
|
LOG.error(f"Couldn't fetch forecast.weather.gov '{ex}'")
|
154
|
-
return
|
154
|
+
return 'Unable to metar find station.'
|
155
155
|
|
156
|
-
if wx_data[
|
157
|
-
station = wx_data[
|
156
|
+
if wx_data['location']['metar']:
|
157
|
+
station = wx_data['location']['metar']
|
158
158
|
try:
|
159
159
|
resp = plugin_utils.get_weather_gov_metar(station)
|
160
160
|
except Exception as e:
|
161
|
-
LOG.debug(f
|
162
|
-
reply =
|
161
|
+
LOG.debug(f'Weather failed with: {str(e)}')
|
162
|
+
reply = 'Failed to get Metar'
|
163
163
|
else:
|
164
164
|
station_data = json.loads(resp.text)
|
165
|
-
reply = station_data[
|
165
|
+
reply = station_data['properties']['rawMessage']
|
166
166
|
else:
|
167
167
|
# Couldn't find a station
|
168
|
-
reply =
|
168
|
+
reply = 'No Metar station found'
|
169
169
|
|
170
170
|
return reply
|
171
171
|
|
@@ -190,35 +190,36 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
190
190
|
"""
|
191
191
|
|
192
192
|
# command_regex = r"^([w][x]|[w][x]\s|weather)"
|
193
|
-
command_regex = r
|
193
|
+
command_regex = r'^[wW]'
|
194
194
|
|
195
|
-
command_name =
|
196
|
-
short_description =
|
195
|
+
command_name = 'OpenWeatherMap'
|
196
|
+
short_description = 'OpenWeatherMap weather of GPS Beacon location'
|
197
197
|
|
198
198
|
def setup(self):
|
199
199
|
if not CONF.owm_weather_plugin.apiKey:
|
200
|
-
LOG.error(
|
200
|
+
LOG.error('Config.owm_weather_plugin.apiKey is not set. Disabling')
|
201
201
|
self.enabled = False
|
202
202
|
else:
|
203
203
|
self.enabled = True
|
204
204
|
|
205
205
|
def help(self):
|
206
206
|
_help = [
|
207
|
-
|
207
|
+
'openweathermap: Send {} to get weather from your location'.format(
|
208
|
+
self.command_regex
|
209
|
+
),
|
210
|
+
'openweathermap: Send {} <callsign> to get weather from <callsign>'.format(
|
208
211
|
self.command_regex
|
209
212
|
),
|
210
|
-
"openweathermap: Send {} <callsign> to get "
|
211
|
-
"weather from <callsign>".format(self.command_regex),
|
212
213
|
]
|
213
214
|
return _help
|
214
215
|
|
215
216
|
@trace.trace
|
216
217
|
def process(self, packet):
|
217
|
-
fromcall = packet.get(
|
218
|
-
message = packet.get(
|
218
|
+
fromcall = packet.get('from_call')
|
219
|
+
message = packet.get('message_text', None)
|
219
220
|
# ack = packet.get("msgNo", "0")
|
220
221
|
LOG.info(f"OWMWeather Plugin '{message}'")
|
221
|
-
a = re.search(r
|
222
|
+
a = re.search(r'^.*\s+(.*)', message)
|
222
223
|
if a is not None:
|
223
224
|
searchcall = a.group(1)
|
224
225
|
searchcall = searchcall.upper()
|
@@ -230,16 +231,16 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
230
231
|
try:
|
231
232
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
232
233
|
except Exception as ex:
|
233
|
-
LOG.error(f
|
234
|
-
return
|
234
|
+
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
235
|
+
return 'Failed to fetch location'
|
235
236
|
|
236
237
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
237
|
-
if not len(aprs_data[
|
238
|
-
LOG.error(
|
239
|
-
return
|
238
|
+
if not len(aprs_data['entries']):
|
239
|
+
LOG.error('Found no entries from aprs.fi!')
|
240
|
+
return 'Failed to fetch location'
|
240
241
|
|
241
|
-
lat = aprs_data[
|
242
|
-
lon = aprs_data[
|
242
|
+
lat = aprs_data['entries'][0]['lat']
|
243
|
+
lon = aprs_data['entries'][0]['lng']
|
243
244
|
|
244
245
|
units = CONF.units
|
245
246
|
api_key = CONF.owm_weather_plugin.apiKey
|
@@ -249,40 +250,40 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
249
250
|
lat,
|
250
251
|
lon,
|
251
252
|
units=units,
|
252
|
-
exclude=
|
253
|
+
exclude='minutely,hourly',
|
253
254
|
)
|
254
255
|
except Exception as ex:
|
255
256
|
LOG.error(f"Couldn't fetch openweathermap api '{ex}'")
|
256
257
|
# default to UTC
|
257
|
-
return
|
258
|
+
return 'Unable to get weather'
|
258
259
|
|
259
|
-
if units ==
|
260
|
-
degree =
|
260
|
+
if units == 'metric':
|
261
|
+
degree = 'C'
|
261
262
|
else:
|
262
|
-
degree =
|
263
|
+
degree = 'F'
|
263
264
|
|
264
|
-
if
|
265
|
-
wind =
|
266
|
-
wx_data[
|
267
|
-
wx_data[
|
268
|
-
wx_data[
|
265
|
+
if 'wind_gust' in wx_data['current']:
|
266
|
+
wind = '{:.0f}@{}G{:.0f}'.format(
|
267
|
+
wx_data['current']['wind_speed'],
|
268
|
+
wx_data['current']['wind_deg'],
|
269
|
+
wx_data['current']['wind_gust'],
|
269
270
|
)
|
270
271
|
else:
|
271
|
-
wind =
|
272
|
-
wx_data[
|
273
|
-
wx_data[
|
272
|
+
wind = '{:.0f}@{}'.format(
|
273
|
+
wx_data['current']['wind_speed'],
|
274
|
+
wx_data['current']['wind_deg'],
|
274
275
|
)
|
275
276
|
|
276
277
|
# LOG.debug(wx_data["current"])
|
277
278
|
# LOG.debug(wx_data["daily"])
|
278
|
-
reply =
|
279
|
-
wx_data[
|
280
|
-
wx_data[
|
279
|
+
reply = '{} {:.1f}{}/{:.1f}{} Wind {} {}%'.format(
|
280
|
+
wx_data['current']['weather'][0]['description'],
|
281
|
+
wx_data['current']['temp'],
|
281
282
|
degree,
|
282
|
-
wx_data[
|
283
|
+
wx_data['current']['dew_point'],
|
283
284
|
degree,
|
284
285
|
wind,
|
285
|
-
wx_data[
|
286
|
+
wx_data['current']['humidity'],
|
286
287
|
)
|
287
288
|
|
288
289
|
return reply
|
@@ -311,26 +312,26 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
311
312
|
docker build -f Dockerfile -t avwx-api:master .
|
312
313
|
"""
|
313
314
|
|
314
|
-
command_regex = r
|
315
|
-
command_name =
|
316
|
-
short_description =
|
315
|
+
command_regex = r'^([m]|[m]|[m]\s|metar)'
|
316
|
+
command_name = 'AVWXWeather'
|
317
|
+
short_description = 'AVWX weather of GPS Beacon location'
|
317
318
|
|
318
319
|
def setup(self):
|
319
320
|
if not CONF.avwx_plugin.base_url:
|
320
|
-
LOG.error(
|
321
|
+
LOG.error('Config avwx_plugin.base_url not specified. Disabling')
|
321
322
|
return False
|
322
323
|
elif not CONF.avwx_plugin.apiKey:
|
323
|
-
LOG.error(
|
324
|
+
LOG.error('Config avwx_plugin.apiKey not specified. Disabling')
|
324
325
|
return False
|
325
326
|
else:
|
326
327
|
return True
|
327
328
|
|
328
329
|
def help(self):
|
329
330
|
_help = [
|
330
|
-
|
331
|
+
'avwxweather: Send {} to get weather from your location'.format(
|
331
332
|
self.command_regex
|
332
333
|
),
|
333
|
-
|
334
|
+
'avwxweather: Send {} <callsign> to get weather from <callsign>'.format(
|
334
335
|
self.command_regex
|
335
336
|
),
|
336
337
|
]
|
@@ -338,11 +339,11 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
338
339
|
|
339
340
|
@trace.trace
|
340
341
|
def process(self, packet):
|
341
|
-
fromcall = packet.get(
|
342
|
-
message = packet.get(
|
342
|
+
fromcall = packet.get('from')
|
343
|
+
message = packet.get('message_text', None)
|
343
344
|
# ack = packet.get("msgNo", "0")
|
344
345
|
LOG.info(f"AVWXWeather Plugin '{message}'")
|
345
|
-
a = re.search(r
|
346
|
+
a = re.search(r'^.*\s+(.*)', message)
|
346
347
|
if a is not None:
|
347
348
|
searchcall = a.group(1)
|
348
349
|
searchcall = searchcall.upper()
|
@@ -353,43 +354,43 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
353
354
|
try:
|
354
355
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
355
356
|
except Exception as ex:
|
356
|
-
LOG.error(f
|
357
|
-
return
|
357
|
+
LOG.error(f'Failed to fetch aprs.fi data {ex}')
|
358
|
+
return 'Failed to fetch location'
|
358
359
|
|
359
360
|
# LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
360
|
-
if not len(aprs_data[
|
361
|
-
LOG.error(
|
362
|
-
return
|
361
|
+
if not len(aprs_data['entries']):
|
362
|
+
LOG.error('Found no entries from aprs.fi!')
|
363
|
+
return 'Failed to fetch location'
|
363
364
|
|
364
|
-
lat = aprs_data[
|
365
|
-
lon = aprs_data[
|
365
|
+
lat = aprs_data['entries'][0]['lat']
|
366
|
+
lon = aprs_data['entries'][0]['lng']
|
366
367
|
|
367
368
|
api_key = CONF.avwx_plugin.apiKey
|
368
369
|
base_url = CONF.avwx_plugin.base_url
|
369
|
-
token = f
|
370
|
-
headers = {
|
370
|
+
token = f'TOKEN {api_key}'
|
371
|
+
headers = {'Authorization': token}
|
371
372
|
try:
|
372
|
-
coord = f
|
373
|
+
coord = f'{lat},{lon}'
|
373
374
|
url = (
|
374
|
-
|
375
|
-
|
375
|
+
'{}/api/station/near/{}?'
|
376
|
+
'n=1&airport=false&reporting=true&format=json'.format(base_url, coord)
|
376
377
|
)
|
377
378
|
|
378
379
|
LOG.debug(f"Get stations near me '{url}'")
|
379
380
|
response = requests.get(url, headers=headers)
|
380
381
|
except Exception as ex:
|
381
382
|
LOG.error(ex)
|
382
|
-
raise Exception(f"Failed to get the weather '{ex}'")
|
383
|
+
raise Exception(f"Failed to get the weather '{ex}'") from ex
|
383
384
|
else:
|
384
385
|
wx_data = json.loads(response.text)
|
385
386
|
|
386
387
|
# LOG.debug(wx_data)
|
387
|
-
station = wx_data[0][
|
388
|
+
station = wx_data[0]['station']['icao']
|
388
389
|
|
389
390
|
try:
|
390
391
|
url = (
|
391
|
-
|
392
|
-
|
392
|
+
'{}/api/metar/{}?options=info,translate,summary'
|
393
|
+
'&airport=true&reporting=true&format=json&onfail=cache'.format(
|
393
394
|
base_url,
|
394
395
|
station,
|
395
396
|
)
|
@@ -399,9 +400,9 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
399
400
|
response = requests.get(url, headers=headers)
|
400
401
|
except Exception as ex:
|
401
402
|
LOG.error(ex)
|
402
|
-
raise Exception(f
|
403
|
+
raise Exception(f'Failed to get metar {ex}') from ex
|
403
404
|
else:
|
404
405
|
metar_data = json.loads(response.text)
|
405
406
|
|
406
407
|
# LOG.debug(metar_data)
|
407
|
-
return metar_data[
|
408
|
+
return metar_data['raw']
|
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()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: aprsd
|
3
|
-
Version: 4.1.
|
3
|
+
Version: 4.1.2
|
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
|