mirror of
https://github.com/craigerl/aprsd.git
synced 2026-03-02 13:10:17 -05:00
Merge pull request #214 from craigerl/fix/cli-command-issues
Fix CLI command inconsistencies
This commit is contained in:
commit
bbc2ccd302
@ -1,16 +1,19 @@
|
||||
import click
|
||||
import click.shell_completion
|
||||
|
||||
from aprsd import cli_helper
|
||||
from aprsd.main import cli
|
||||
|
||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||
|
||||
|
||||
@cli.command()
|
||||
@cli_helper.add_options(cli_helper.common_options)
|
||||
@click.argument(
|
||||
'shell', type=click.Choice(list(click.shell_completion._available_shells))
|
||||
)
|
||||
def completion(shell):
|
||||
@cli_helper.process_standard_options_no_config
|
||||
def completion(ctx, shell):
|
||||
"""Show the shell completion code"""
|
||||
from click.utils import _detect_program_name
|
||||
|
||||
|
||||
@ -7,53 +7,11 @@ import click
|
||||
from aprsd import cli_helper
|
||||
from aprsd import plugin as aprsd_plugin
|
||||
from aprsd.main import cli
|
||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
||||
from aprsd.utils import package as aprsd_package
|
||||
|
||||
LOG = logging.getLogger('APRSD')
|
||||
|
||||
|
||||
def get_built_in_plugins():
|
||||
"""Discover all built-in APRSD plugins."""
|
||||
modules = [fortune, notify, ping, time, version, weather]
|
||||
plugins = []
|
||||
|
||||
for module in modules:
|
||||
entries = inspect.getmembers(module, inspect.isclass)
|
||||
for entry in entries:
|
||||
cls = entry[1]
|
||||
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
||||
plugin_info = {
|
||||
'package': 'aprsd',
|
||||
'class_name': cls.__qualname__,
|
||||
'path': f'{cls.__module__}.{cls.__qualname__}',
|
||||
'version': cls.version,
|
||||
'base_class_type': aprsd_package.plugin_type(cls),
|
||||
}
|
||||
|
||||
# If it's a regex command plugin, include the command_regex
|
||||
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||
# Try to get command_regex from the class
|
||||
# It's typically defined as a class attribute in plugin implementations
|
||||
try:
|
||||
# Check the MRO to find where command_regex is actually defined
|
||||
cmd_regex = None
|
||||
for base_cls in inspect.getmro(cls):
|
||||
if 'command_regex' in base_cls.__dict__:
|
||||
attr = base_cls.__dict__['command_regex']
|
||||
# If it's not a property descriptor, use it
|
||||
if not isinstance(attr, property):
|
||||
cmd_regex = attr
|
||||
break
|
||||
plugin_info['command_regex'] = cmd_regex
|
||||
except Exception:
|
||||
plugin_info['command_regex'] = None
|
||||
|
||||
plugins.append(plugin_info)
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
def get_installed_plugin_classes():
|
||||
"""Discover all installed 3rd party plugin classes, grouped by package."""
|
||||
installed_plugins = aprsd_package.get_installed_plugins()
|
||||
@ -117,7 +75,7 @@ def export_plugins(ctx):
|
||||
}
|
||||
|
||||
# Get built-in plugins
|
||||
built_in = get_built_in_plugins()
|
||||
built_in = aprsd_package.get_built_in_plugins()
|
||||
output['built_in_plugins'] = built_in
|
||||
|
||||
# Get installed 3rd party plugins (grouped by package)
|
||||
|
||||
@ -85,7 +85,7 @@ def healthcheck(ctx, timeout):
|
||||
|
||||
client_stats = stats.get('APRSClientStats')
|
||||
if not client_stats:
|
||||
console.log('No APRSClientStats')
|
||||
console.log('No APRSClientStats - Is the aprsd server running?')
|
||||
sys.exit(-1)
|
||||
else:
|
||||
aprsis_last_update = client_stats['connection_keepalive']
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
import click
|
||||
@ -7,39 +6,33 @@ from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from aprsd import cli_helper
|
||||
from aprsd import plugin as aprsd_plugin
|
||||
from aprsd.main import cli
|
||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
||||
from aprsd.utils import package as aprsd_package
|
||||
|
||||
LOG = logging.getLogger('APRSD')
|
||||
|
||||
|
||||
def show_built_in_plugins(console):
|
||||
modules = [fortune, notify, ping, time, version, weather]
|
||||
built_in = aprsd_package.get_built_in_plugins()
|
||||
plugins = []
|
||||
|
||||
for module in modules:
|
||||
entries = inspect.getmembers(module, inspect.isclass)
|
||||
for entry in entries:
|
||||
cls = entry[1]
|
||||
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
||||
info = {
|
||||
'name': cls.__qualname__,
|
||||
'path': f'{cls.__module__}.{cls.__qualname__}',
|
||||
'version': cls.version,
|
||||
'docstring': cls.__doc__,
|
||||
'short_desc': cls.short_description,
|
||||
}
|
||||
for plugin in built_in:
|
||||
info = {
|
||||
'name': plugin['class_name'],
|
||||
'path': plugin['path'],
|
||||
'version': plugin['version'],
|
||||
'short_desc': '',
|
||||
}
|
||||
|
||||
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||
info['command_regex'] = cls.command_regex
|
||||
info['type'] = 'RegexCommand'
|
||||
if plugin.get('command_regex'):
|
||||
info['command_regex'] = plugin['command_regex']
|
||||
info['type'] = 'RegexCommand'
|
||||
elif plugin['base_class_type'] == 'WatchList':
|
||||
info['type'] = 'WatchList'
|
||||
else:
|
||||
info['type'] = plugin['base_class_type']
|
||||
|
||||
if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
|
||||
info['type'] = 'WatchList'
|
||||
|
||||
plugins.append(info)
|
||||
plugins.append(info)
|
||||
|
||||
plugins = sorted(plugins, key=lambda i: i['name'])
|
||||
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import cProfile
|
||||
import datetime
|
||||
import logging
|
||||
import pstats
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
import click
|
||||
from loguru import logger
|
||||
@ -34,16 +30,9 @@ console = Console()
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
threads.APRSDThreadList().stop_all()
|
||||
if 'subprocess' not in str(frame):
|
||||
LOG.info(
|
||||
'Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}'.format(
|
||||
datetime.datetime.now(),
|
||||
),
|
||||
)
|
||||
time.sleep(5)
|
||||
# Last save to disk
|
||||
collector.Collector().collect()
|
||||
from aprsd import main as aprsd_main
|
||||
|
||||
aprsd_main.signal_handler(sig, frame)
|
||||
|
||||
|
||||
class APRSDListenProcessThread(rx.APRSDFilterThread):
|
||||
@ -154,12 +143,6 @@ class APRSDListenProcessThread(rx.APRSDFilterThread):
|
||||
default='http://localhost:8081',
|
||||
help='URL of the aprsd-exporter API to send stats to.',
|
||||
)
|
||||
@click.option(
|
||||
'--profile',
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help='Enable Python cProfile profiling to identify performance bottlenecks.',
|
||||
)
|
||||
@click.pass_context
|
||||
@cli_helper.process_standard_options
|
||||
def listen(
|
||||
@ -174,7 +157,6 @@ def listen(
|
||||
enable_packet_stats,
|
||||
export_stats,
|
||||
exporter_url,
|
||||
profile,
|
||||
):
|
||||
"""Listen to packets on the APRS-IS Network based on FILTER.
|
||||
|
||||
@ -186,12 +168,6 @@ def listen(
|
||||
o/obj1/obj2... - Object Filter Pass all objects with the exact name of obj1, obj2, ... (* wild card allowed)\n
|
||||
|
||||
"""
|
||||
# Initialize profiler if enabled
|
||||
profiler = None
|
||||
if profile:
|
||||
LOG.info('Starting Python cProfile profiling')
|
||||
profiler = cProfile.Profile()
|
||||
profiler.enable()
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
@ -315,25 +291,3 @@ def listen(
|
||||
stats.join()
|
||||
if stats_export:
|
||||
stats_export.join()
|
||||
|
||||
# Save profiling results if enabled
|
||||
if profiler:
|
||||
profiler.disable()
|
||||
profile_file = 'aprsd_listen_profile.prof'
|
||||
profiler.dump_stats(profile_file)
|
||||
LOG.info(f'Profile saved to {profile_file}')
|
||||
|
||||
# Print profiling summary
|
||||
LOG.info('Profile Summary (top 50 functions by cumulative time):')
|
||||
stats = pstats.Stats(profiler)
|
||||
stats.sort_stats('cumulative')
|
||||
|
||||
# Log the top functions
|
||||
LOG.info('-' * 80)
|
||||
for item in stats.get_stats().items()[:50]:
|
||||
func_info, stats_tuple = item
|
||||
cumulative = stats_tuple[3]
|
||||
total_calls = stats_tuple[0]
|
||||
LOG.info(
|
||||
f'{func_info} - Calls: {total_calls}, Cumulative: {cumulative:.4f}s'
|
||||
)
|
||||
|
||||
@ -2,6 +2,7 @@ import datetime
|
||||
import decimal
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import asdict, is_dataclass
|
||||
|
||||
from aprsd.packets import core
|
||||
|
||||
@ -63,6 +64,8 @@ class SimpleJSONEncoder(json.JSONEncoder):
|
||||
return str(obj)
|
||||
elif isinstance(obj, core.Packet):
|
||||
return obj.to_dict()
|
||||
elif is_dataclass(obj):
|
||||
return asdict(obj)
|
||||
else:
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import requests
|
||||
from thesmuggler import smuggle
|
||||
|
||||
from aprsd import plugin as aprsd_plugin
|
||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
||||
|
||||
# Handle importlib.metadata compatibility
|
||||
try:
|
||||
@ -245,3 +246,39 @@ def log_installed_extensions_and_plugins():
|
||||
|
||||
for plugin in plugins:
|
||||
LOG.info(f'Plugin: {plugin} version: {plugins[plugin][0]["version"]}')
|
||||
|
||||
|
||||
def get_built_in_plugins():
|
||||
"""Discover all built-in APRSD plugins."""
|
||||
modules = [fortune, notify, ping, time, version, weather]
|
||||
plugins = []
|
||||
|
||||
for module in modules:
|
||||
entries = inspect.getmembers(module, inspect.isclass)
|
||||
for entry in entries:
|
||||
cls = entry[1]
|
||||
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
||||
plugin_info = {
|
||||
'package': 'aprsd',
|
||||
'class_name': cls.__qualname__,
|
||||
'path': f'{cls.__module__}.{cls.__qualname__}',
|
||||
'version': cls.version,
|
||||
'base_class_type': plugin_type(cls),
|
||||
}
|
||||
|
||||
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
||||
try:
|
||||
cmd_regex = None
|
||||
for base_cls in inspect.getmro(cls):
|
||||
if 'command_regex' in base_cls.__dict__:
|
||||
attr = base_cls.__dict__['command_regex']
|
||||
if not isinstance(attr, property):
|
||||
cmd_regex = attr
|
||||
break
|
||||
plugin_info['command_regex'] = cmd_regex
|
||||
except Exception:
|
||||
plugin_info['command_regex'] = None
|
||||
|
||||
plugins.append(plugin_info)
|
||||
|
||||
return plugins
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user