aprsd 4.1.2__py3-none-any.whl → 4.2.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.
Files changed (43) hide show
  1. aprsd/client/__init__.py +5 -13
  2. aprsd/client/client.py +156 -0
  3. aprsd/client/drivers/__init__.py +10 -0
  4. aprsd/client/drivers/aprsis.py +174 -255
  5. aprsd/client/drivers/fake.py +59 -11
  6. aprsd/client/drivers/lib/__init__.py +0 -0
  7. aprsd/client/drivers/lib/aprslib.py +296 -0
  8. aprsd/client/drivers/registry.py +86 -0
  9. aprsd/client/drivers/tcpkiss.py +423 -0
  10. aprsd/client/stats.py +2 -2
  11. aprsd/cmds/dev.py +6 -4
  12. aprsd/cmds/fetch_stats.py +2 -0
  13. aprsd/cmds/list_plugins.py +6 -133
  14. aprsd/cmds/listen.py +5 -3
  15. aprsd/cmds/send_message.py +8 -5
  16. aprsd/cmds/server.py +7 -11
  17. aprsd/conf/common.py +7 -1
  18. aprsd/exception.py +7 -0
  19. aprsd/log/log.py +1 -1
  20. aprsd/main.py +0 -7
  21. aprsd/packets/core.py +168 -169
  22. aprsd/packets/log.py +69 -59
  23. aprsd/plugin.py +3 -2
  24. aprsd/plugin_utils.py +2 -2
  25. aprsd/plugins/weather.py +2 -2
  26. aprsd/stats/collector.py +5 -4
  27. aprsd/threads/rx.py +13 -11
  28. aprsd/threads/tx.py +32 -31
  29. aprsd/utils/keepalive_collector.py +7 -5
  30. aprsd/utils/package.py +176 -0
  31. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/METADATA +48 -48
  32. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/RECORD +37 -37
  33. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/WHEEL +1 -1
  34. aprsd/client/aprsis.py +0 -183
  35. aprsd/client/base.py +0 -156
  36. aprsd/client/drivers/kiss.py +0 -144
  37. aprsd/client/factory.py +0 -91
  38. aprsd/client/fake.py +0 -49
  39. aprsd/client/kiss.py +0 -143
  40. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/entry_points.txt +0 -0
  41. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info/licenses}/AUTHORS +0 -0
  42. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info/licenses}/LICENSE +0 -0
  43. {aprsd-4.1.2.dist-info → aprsd-4.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,423 @@
1
+ """
2
+ APRSD KISS Client Driver using native KISS implementation.
3
+
4
+ This module provides a KISS client driver for APRSD using the new
5
+ non-asyncio KISSInterface implementation.
6
+ """
7
+
8
+ import datetime
9
+ import logging
10
+ import select
11
+ import socket
12
+ import time
13
+ from typing import Any, Callable, Dict
14
+
15
+ import aprslib
16
+ from ax253 import frame as ax25frame
17
+ from kiss import constants as kiss_constants
18
+ from kiss import util as kissutil
19
+ from kiss.kiss import Command
20
+ from oslo_config import cfg
21
+
22
+ from aprsd import ( # noqa
23
+ client,
24
+ conf, # noqa
25
+ exception,
26
+ )
27
+ from aprsd.packets import core
28
+
29
+ CONF = cfg.CONF
30
+ LOG = logging.getLogger('APRSD')
31
+
32
+
33
+ def handle_fend(buffer: bytes, strip_df_start: bool = True) -> bytes:
34
+ """
35
+ Handle FEND (end of frame) encountered in a KISS data stream.
36
+
37
+ :param buffer: the buffer containing the frame
38
+ :param strip_df_start: remove leading null byte (DATA_FRAME opcode)
39
+ :return: the bytes of the frame without escape characters or frame
40
+ end markers (FEND)
41
+ """
42
+ frame = kissutil.recover_special_codes(kissutil.strip_nmea(bytes(buffer)))
43
+ if strip_df_start:
44
+ frame = kissutil.strip_df_start(frame)
45
+ LOG.warning(f'handle_fend {" ".join(f"{b:02X}" for b in bytes(frame))}')
46
+ return bytes(frame)
47
+
48
+
49
+ # class TCPKISSDriver(metaclass=trace.TraceWrapperMetaclass):
50
+ class TCPKISSDriver:
51
+ """APRSD client driver for TCP KISS connections."""
52
+
53
+ # Class level attributes required by Client protocol
54
+ packets_received = 0
55
+ packets_sent = 0
56
+ last_packet_sent = None
57
+ last_packet_received = None
58
+ keepalive = None
59
+ client_name = None
60
+ socket = None
61
+ # timeout in seconds
62
+ select_timeout = 1
63
+ path = None
64
+
65
+ def __init__(self):
66
+ """Initialize the KISS client.
67
+
68
+ Args:
69
+ client_name: Name of the client instance
70
+ """
71
+ super().__init__()
72
+ self._connected = False
73
+ self.keepalive = datetime.datetime.now()
74
+ self._running = False
75
+ # This is initialized in setup_connection()
76
+ self.socket = None
77
+
78
+ @property
79
+ def transport(self) -> str:
80
+ return client.TRANSPORT_TCPKISS
81
+
82
+ @staticmethod
83
+ def is_enabled() -> bool:
84
+ """Check if KISS is enabled in configuration.
85
+
86
+ Returns:
87
+ bool: True if either TCP is enabled
88
+ """
89
+ return CONF.kiss_tcp.enabled
90
+
91
+ @staticmethod
92
+ def is_configured():
93
+ # Ensure that the config vars are correctly set
94
+ if TCPKISSDriver.is_enabled():
95
+ if not CONF.kiss_tcp.host:
96
+ LOG.error('KISS TCP enabled, but no host is set.')
97
+ raise exception.MissingConfigOptionException(
98
+ 'kiss_tcp.host is not set.',
99
+ )
100
+ return True
101
+ return False
102
+
103
+ @property
104
+ def is_alive(self) -> bool:
105
+ """Check if the client is connected.
106
+
107
+ Returns:
108
+ bool: True if connected to KISS TNC, False otherwise
109
+ """
110
+ return self._connected
111
+
112
+ def close(self):
113
+ """Close the connection."""
114
+ self.stop()
115
+
116
+ def send(self, packet: core.Packet):
117
+ """Send an APRS packet.
118
+
119
+ Args:
120
+ packet: APRS packet to send (Packet or Message object)
121
+
122
+ Raises:
123
+ Exception: If not connected or send fails
124
+ """
125
+ if not self.socket:
126
+ raise Exception('KISS interface not initialized')
127
+
128
+ payload = None
129
+ path = self.path
130
+ packet.prepare()
131
+ payload = packet.payload.encode('US-ASCII')
132
+ if packet.path:
133
+ path = packet.path
134
+
135
+ LOG.debug(
136
+ f"KISS Send '{payload}' TO '{packet.to_call}' From "
137
+ f"'{packet.from_call}' with PATH '{path}'",
138
+ )
139
+ frame = ax25frame.Frame.ui(
140
+ destination='APZ100',
141
+ # destination=packet.to_call,
142
+ source=packet.from_call,
143
+ path=path,
144
+ info=payload,
145
+ )
146
+
147
+ # now escape the frame special characters
148
+ frame_escaped = kissutil.escape_special_codes(bytes(frame))
149
+ # and finally wrap the frame in KISS protocol
150
+ command = Command.DATA_FRAME
151
+ frame_kiss = b''.join(
152
+ [kiss_constants.FEND, command.value, frame_escaped, kiss_constants.FEND]
153
+ )
154
+ self.socket.send(frame_kiss)
155
+ # Update last packet sent time
156
+ self.last_packet_sent = datetime.datetime.now()
157
+ # Increment packets sent counter
158
+ self.packets_sent += 1
159
+
160
+ def setup_connection(self):
161
+ """Set up the KISS interface."""
162
+ if not self.is_enabled():
163
+ LOG.error('KISS is not enabled in configuration')
164
+ return
165
+
166
+ try:
167
+ # Configure for TCP KISS
168
+ if self.is_enabled():
169
+ LOG.info(
170
+ f'KISS TCP Connection to {CONF.kiss_tcp.host}:{CONF.kiss_tcp.port}'
171
+ )
172
+ self.path = CONF.kiss_tcp.path
173
+ self.connect()
174
+ if self._connected:
175
+ LOG.info('KISS interface initialized')
176
+ else:
177
+ LOG.error('Failed to connect to KISS interface')
178
+
179
+ except Exception as ex:
180
+ LOG.error('Failed to initialize KISS interface')
181
+ LOG.exception(ex)
182
+ self._connected = False
183
+
184
+ def set_filter(self, filter_text: str):
185
+ """Set packet filter (not implemented for KISS).
186
+
187
+ Args:
188
+ filter_text: Filter specification (ignored for KISS)
189
+ """
190
+ # KISS doesn't support filtering at the TNC level
191
+ pass
192
+
193
+ @property
194
+ def filter(self) -> str:
195
+ """Get packet filter (not implemented for KISS).
196
+ Returns:
197
+ str: Empty string (not implemented for KISS)
198
+ """
199
+ return ''
200
+
201
+ def login_success(self) -> bool:
202
+ """There is no login for KISS."""
203
+ if not self._connected:
204
+ return False
205
+ return True
206
+
207
+ def login_failure(self) -> str:
208
+ """There is no login for KISS."""
209
+ return 'Login successful'
210
+
211
+ def consumer(self, callback: Callable, raw: bool = False):
212
+ """Start consuming frames with the given callback.
213
+
214
+ Args:
215
+ callback: Function to call with received packets
216
+
217
+ Raises:
218
+ Exception: If not connected to KISS TNC
219
+ """
220
+ self._running = True
221
+ while self._running:
222
+ # Ensure connection
223
+ if not self._connected:
224
+ if not self.connect():
225
+ time.sleep(1)
226
+ continue
227
+
228
+ # Read frame
229
+ frame = self.read_frame()
230
+ if frame:
231
+ LOG.warning(f'GOT FRAME: {frame} calling {callback}')
232
+ kwargs = {
233
+ 'frame': frame,
234
+ }
235
+ callback(**kwargs)
236
+
237
+ def decode_packet(self, *args, **kwargs) -> core.Packet:
238
+ """Decode a packet from an AX.25 frame.
239
+
240
+ Args:
241
+ frame: Received AX.25 frame
242
+ """
243
+ frame = kwargs.get('frame')
244
+ if not frame:
245
+ LOG.warning('No frame received to decode?!?!')
246
+ return None
247
+
248
+ LOG.warning(f'FRAME: {str(frame)}')
249
+ try:
250
+ aprslib_frame = aprslib.parse(str(frame))
251
+ packet = core.factory(aprslib_frame)
252
+ if isinstance(packet, core.ThirdPartyPacket):
253
+ return packet.subpacket
254
+ else:
255
+ return packet
256
+ except Exception as e:
257
+ LOG.error(f'Error decoding packet: {e}')
258
+ return None
259
+
260
+ def stop(self):
261
+ """Stop the KISS interface."""
262
+ self._running = False
263
+ self._connected = False
264
+ if self.socket:
265
+ try:
266
+ self.socket.close()
267
+ except Exception:
268
+ pass
269
+
270
+ def stats(self, serializable: bool = False) -> Dict[str, Any]:
271
+ """Get client statistics.
272
+
273
+ Returns:
274
+ Dict containing client statistics
275
+ """
276
+ if serializable:
277
+ keepalive = self.keepalive.isoformat()
278
+ if self.last_packet_sent:
279
+ last_packet_sent = self.last_packet_sent.isoformat()
280
+ else:
281
+ last_packet_sent = 'None'
282
+ if self.last_packet_received:
283
+ last_packet_received = self.last_packet_received.isoformat()
284
+ else:
285
+ last_packet_received = 'None'
286
+ else:
287
+ keepalive = self.keepalive
288
+ last_packet_sent = self.last_packet_sent
289
+ last_packet_received = self.last_packet_received
290
+
291
+ stats = {
292
+ 'client': self.__class__.__name__,
293
+ 'transport': self.transport,
294
+ 'connected': self._connected,
295
+ 'path': self.path,
296
+ 'packets_sent': self.packets_sent,
297
+ 'packets_received': self.packets_received,
298
+ 'last_packet_sent': last_packet_sent,
299
+ 'last_packet_received': last_packet_received,
300
+ 'connection_keepalive': keepalive,
301
+ 'host': CONF.kiss_tcp.host,
302
+ 'port': CONF.kiss_tcp.port,
303
+ }
304
+
305
+ return stats
306
+
307
+ def connect(self) -> bool:
308
+ """Establish TCP connection to the KISS host.
309
+
310
+ Returns:
311
+ bool: True if connection successful, False otherwise
312
+ """
313
+ try:
314
+ if self.socket:
315
+ try:
316
+ self.socket.close()
317
+ except Exception:
318
+ pass
319
+
320
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
321
+ self.socket.settimeout(5.0) # 5 second timeout for connection
322
+ self.socket.connect((CONF.kiss_tcp.host, CONF.kiss_tcp.port))
323
+ self.socket.settimeout(0.1) # Reset to shorter timeout for reads
324
+ self._connected = True
325
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
326
+ # MACOS doesn't have TCP_KEEPIDLE
327
+ if hasattr(socket, 'TCP_KEEPIDLE'):
328
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
329
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
330
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
331
+ return True
332
+
333
+ except ConnectionError as e:
334
+ LOG.error(
335
+ f'Failed to connect to {CONF.kiss_tcp.host}:{CONF.kiss_tcp.port} - {str(e)}'
336
+ )
337
+ self._connected = False
338
+ return False
339
+
340
+ except Exception as e:
341
+ LOG.error(
342
+ f'Failed to connect to {CONF.kiss_tcp.host}:{CONF.kiss_tcp.port} - {str(e)}'
343
+ )
344
+ self._connected = False
345
+ return False
346
+
347
+ def fix_raw_frame(self, raw_frame: bytes) -> bytes:
348
+ """Fix the raw frame by recalculating the FCS."""
349
+ ax25_data = raw_frame[2:-1] # Remove KISS markers
350
+ return handle_fend(ax25_data)
351
+
352
+ def read_frame(self, blocking=False):
353
+ """
354
+ Generator for complete lines, received from the server
355
+ """
356
+ try:
357
+ self.socket.setblocking(0)
358
+ except OSError as e:
359
+ LOG.error(f'socket error when setblocking(0): {str(e)}')
360
+ raise aprslib.ConnectionDrop('connection dropped') from e
361
+
362
+ while self._running:
363
+ short_buf = b''
364
+
365
+ try:
366
+ readable, _, _ = select.select(
367
+ [self.socket],
368
+ [],
369
+ [],
370
+ self.select_timeout,
371
+ )
372
+ if not readable:
373
+ if not blocking:
374
+ break
375
+ else:
376
+ continue
377
+ except Exception as e:
378
+ LOG.error(f'Error in read loop: {e}')
379
+ self._connected = False
380
+ break
381
+
382
+ try:
383
+ print('reading from socket')
384
+ short_buf = self.socket.recv(1024)
385
+ print(f'short_buf: {short_buf}')
386
+ # sock.recv returns empty if the connection drops
387
+ if not short_buf:
388
+ if not blocking:
389
+ # We could just not be blocking, so empty is expected
390
+ continue
391
+ else:
392
+ self.logger.error('socket.recv(): returned empty')
393
+ raise aprslib.ConnectionDrop('connection dropped')
394
+
395
+ raw_frame = self.fix_raw_frame(short_buf)
396
+ return ax25frame.Frame.from_bytes(raw_frame)
397
+ except OSError as e:
398
+ # self.logger.error("socket error on recv(): %s" % str(e))
399
+ if 'Resource temporarily unavailable' in str(e):
400
+ if not blocking:
401
+ if len(short_buf) == 0:
402
+ break
403
+ except socket.timeout:
404
+ continue
405
+ except (KeyboardInterrupt, SystemExit):
406
+ raise
407
+ except ConnectionError:
408
+ self.close()
409
+ if not self.auto_reconnect:
410
+ raise
411
+ else:
412
+ self.connect()
413
+ continue
414
+ except StopIteration:
415
+ break
416
+ except IOError:
417
+ LOG.error('IOError')
418
+ break
419
+ except Exception as e:
420
+ LOG.error(f'Error in read loop: {e}')
421
+ self._connected = False
422
+ if not self.auto_reconnect:
423
+ break
aprsd/client/stats.py CHANGED
@@ -3,7 +3,7 @@ import threading
3
3
  import wrapt
4
4
  from oslo_config import cfg
5
5
 
6
- from aprsd import client
6
+ from aprsd.client.client import APRSDClient
7
7
  from aprsd.utils import singleton
8
8
 
9
9
  CONF = cfg.CONF
@@ -15,4 +15,4 @@ class APRSClientStats:
15
15
 
16
16
  @wrapt.synchronized(lock)
17
17
  def stats(self, serializable=False):
18
- return client.client_factory.create().stats(serializable=serializable)
18
+ return APRSDClient().stats(serializable=serializable)
aprsd/cmds/dev.py CHANGED
@@ -4,14 +4,15 @@
4
4
  #
5
5
  # python included libs
6
6
  import logging
7
+ import sys
7
8
 
8
9
  import click
9
10
  from oslo_config import cfg
10
11
 
11
- from aprsd import cli_helper, conf, packets, plugin
12
+ import aprsd
13
+ from aprsd import cli_helper, conf, packets, plugin, utils
12
14
 
13
15
  # local imports here
14
- from aprsd.client import base
15
16
  from aprsd.main import cli
16
17
  from aprsd.utils import trace
17
18
 
@@ -72,6 +73,9 @@ def test_plugin(
72
73
  ):
73
74
  """Test an individual APRSD plugin given a python path."""
74
75
 
76
+ LOG.info(f'Python version: {sys.version}')
77
+ LOG.info(f'APRSD DEV Started version: {aprsd.__version__}')
78
+ utils.package.log_installed_extensions_and_plugins()
75
79
  CONF.log_opt_values(LOG, logging.DEBUG)
76
80
 
77
81
  if not aprs_login:
@@ -97,8 +101,6 @@ def test_plugin(
97
101
  if CONF.trace_enabled:
98
102
  trace.setup_tracing(['method', 'api'])
99
103
 
100
- base.APRSClient()
101
-
102
104
  pm = plugin.PluginManager()
103
105
  if load_all:
104
106
  pm.setup_plugins(load_help_plugin=CONF.load_help_plugin)
aprsd/cmds/fetch_stats.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # Fetch active stats from a remote running instance of aprsd admin web interface.
2
2
  import logging
3
+ import sys
3
4
 
4
5
  import click
5
6
  import requests
@@ -38,6 +39,7 @@ CONF = cfg.CONF
38
39
  def fetch_stats(ctx, host, port):
39
40
  """Fetch stats from a APRSD admin web interface."""
40
41
  console = Console()
42
+ console.print(f'Python version: {sys.version}')
41
43
  console.print(f'APRSD Fetch-Stats started version: {aprsd.__version__}')
42
44
 
43
45
  CONF.log_opt_values(LOG, logging.DEBUG)
@@ -1,119 +1,18 @@
1
- import fnmatch
2
- import importlib
3
1
  import inspect
4
2
  import logging
5
- import os
6
- import pkgutil
7
- import sys
8
- from traceback import print_tb
9
3
 
10
4
  import click
11
- import requests
12
5
  from rich.console import Console
13
6
  from rich.table import Table
14
7
  from rich.text import Text
15
- from thesmuggler import smuggle
16
8
 
17
9
  from aprsd import cli_helper
18
10
  from aprsd import plugin as aprsd_plugin
19
11
  from aprsd.main import cli
20
12
  from aprsd.plugins import fortune, notify, ping, time, version, weather
13
+ from aprsd.utils import package as aprsd_package
21
14
 
22
15
  LOG = logging.getLogger('APRSD')
23
- PYPI_URL = 'https://pypi.org/search/'
24
-
25
-
26
- def onerror(name):
27
- print(f'Error importing module {name}')
28
- type, value, traceback = sys.exc_info()
29
- print_tb(traceback)
30
-
31
-
32
- def is_plugin(obj):
33
- for c in inspect.getmro(obj):
34
- if issubclass(c, aprsd_plugin.APRSDPluginBase):
35
- return True
36
-
37
- return False
38
-
39
-
40
- def plugin_type(obj):
41
- for c in inspect.getmro(obj):
42
- if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
43
- return 'RegexCommand'
44
- if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
45
- return 'WatchList'
46
- if issubclass(c, aprsd_plugin.APRSDPluginBase):
47
- return 'APRSDPluginBase'
48
-
49
- return 'Unknown'
50
-
51
-
52
- def walk_package(package):
53
- return pkgutil.walk_packages(
54
- package.__path__,
55
- package.__name__ + '.',
56
- onerror=onerror,
57
- )
58
-
59
-
60
- def get_module_info(package_name, module_name, module_path):
61
- if not os.path.exists(module_path):
62
- return None
63
-
64
- dir_path = os.path.realpath(module_path)
65
- pattern = '*.py'
66
-
67
- obj_list = []
68
-
69
- for path, _subdirs, files in os.walk(dir_path):
70
- for name in files:
71
- if fnmatch.fnmatch(name, pattern):
72
- module = smuggle(f'{path}/{name}')
73
- for mem_name, obj in inspect.getmembers(module):
74
- if inspect.isclass(obj) and is_plugin(obj):
75
- obj_list.append(
76
- {
77
- 'package': package_name,
78
- 'name': mem_name,
79
- 'obj': obj,
80
- 'version': obj.version,
81
- 'path': f'{".".join([module_name, obj.__name__])}',
82
- },
83
- )
84
-
85
- return obj_list
86
-
87
-
88
- def _get_installed_aprsd_items():
89
- # installed plugins
90
- plugins = {}
91
- extensions = {}
92
- for _finder, name, ispkg in pkgutil.iter_modules():
93
- if ispkg and name.startswith('aprsd_'):
94
- module = importlib.import_module(name)
95
- pkgs = walk_package(module)
96
- for pkg in pkgs:
97
- pkg_info = get_module_info(
98
- module.__name__, pkg.name, module.__path__[0]
99
- )
100
- if 'plugin' in name:
101
- plugins[name] = pkg_info
102
- elif 'extension' in name:
103
- extensions[name] = pkg_info
104
- return plugins, extensions
105
-
106
-
107
- def get_installed_plugins():
108
- # installed plugins
109
- plugins, extensions = _get_installed_aprsd_items()
110
- return plugins
111
-
112
-
113
- def get_installed_extensions():
114
- # installed plugins
115
- plugins, extensions = _get_installed_aprsd_items()
116
- return extensions
117
16
 
118
17
 
119
18
  def show_built_in_plugins(console):
@@ -157,34 +56,8 @@ def show_built_in_plugins(console):
157
56
  console.print(table)
158
57
 
159
58
 
160
- def _get_pypi_packages():
161
- if simple_r := requests.get(
162
- 'https://pypi.org/simple',
163
- headers={'Accept': 'application/vnd.pypi.simple.v1+json'},
164
- ):
165
- simple_response = simple_r.json()
166
- else:
167
- simple_response = {}
168
-
169
- key = 'aprsd'
170
- matches = [
171
- p['name'] for p in simple_response['projects'] if p['name'].startswith(key)
172
- ]
173
-
174
- packages = []
175
- for pkg in matches:
176
- # Get info for first match
177
- if r := requests.get(
178
- f'https://pypi.org/pypi/{pkg}/json',
179
- headers={'Accept': 'application/json'},
180
- ):
181
- packages.append(r.json())
182
-
183
- return packages
184
-
185
-
186
59
  def show_pypi_plugins(installed_plugins, console):
187
- packages = _get_pypi_packages()
60
+ packages = aprsd_package.get_pypi_packages()
188
61
 
189
62
  title = Text.assemble(
190
63
  ('Pypi.org APRSD Installable Plugin Packages\n\n', 'bold magenta'),
@@ -225,7 +98,7 @@ def show_pypi_plugins(installed_plugins, console):
225
98
 
226
99
 
227
100
  def show_pypi_extensions(installed_extensions, console):
228
- packages = _get_pypi_packages()
101
+ packages = aprsd_package.get_pypi_packages()
229
102
 
230
103
  title = Text.assemble(
231
104
  ('Pypi.org APRSD Installable Extension Packages\n\n', 'bold magenta'),
@@ -282,7 +155,7 @@ def show_installed_plugins(installed_plugins, console):
282
155
  name.replace('_', '-'),
283
156
  plugin['name'],
284
157
  plugin['version'],
285
- plugin_type(plugin['obj']),
158
+ aprsd_package.plugin_type(plugin['obj']),
286
159
  plugin['path'],
287
160
  )
288
161
 
@@ -302,7 +175,7 @@ def list_plugins(ctx):
302
175
  show_built_in_plugins(console)
303
176
 
304
177
  status.update('Fetching pypi.org plugins')
305
- installed_plugins = get_installed_plugins()
178
+ installed_plugins = aprsd_package.get_installed_plugins()
306
179
  show_pypi_plugins(installed_plugins, console)
307
180
 
308
181
  status.update('Looking for installed APRSD plugins')
@@ -321,5 +194,5 @@ def list_extensions(ctx):
321
194
  status.update('Fetching pypi.org APRSD Extensions')
322
195
 
323
196
  status.update('Looking for installed APRSD Extensions')
324
- installed_extensions = get_installed_extensions()
197
+ installed_extensions = aprsd_package.get_installed_extensions()
325
198
  show_pypi_extensions(installed_extensions, console)