mirror of
https://github.com/craigerl/aprsd.git
synced 2026-05-01 04:03:59 -04: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
|
||||||
import click.shell_completion
|
import click.shell_completion
|
||||||
|
|
||||||
|
from aprsd import cli_helper
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
|
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
@click.argument(
|
@click.argument(
|
||||||
'shell', type=click.Choice(list(click.shell_completion._available_shells))
|
'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"""
|
"""Show the shell completion code"""
|
||||||
from click.utils import _detect_program_name
|
from click.utils import _detect_program_name
|
||||||
|
|
||||||
|
|||||||
@ -7,53 +7,11 @@ import click
|
|||||||
from aprsd import cli_helper
|
from aprsd import cli_helper
|
||||||
from aprsd import plugin as aprsd_plugin
|
from aprsd import plugin as aprsd_plugin
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
|
||||||
from aprsd.utils import package as aprsd_package
|
from aprsd.utils import package as aprsd_package
|
||||||
|
|
||||||
LOG = logging.getLogger('APRSD')
|
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():
|
def get_installed_plugin_classes():
|
||||||
"""Discover all installed 3rd party plugin classes, grouped by package."""
|
"""Discover all installed 3rd party plugin classes, grouped by package."""
|
||||||
installed_plugins = aprsd_package.get_installed_plugins()
|
installed_plugins = aprsd_package.get_installed_plugins()
|
||||||
@ -117,7 +75,7 @@ def export_plugins(ctx):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Get built-in plugins
|
# Get built-in plugins
|
||||||
built_in = get_built_in_plugins()
|
built_in = aprsd_package.get_built_in_plugins()
|
||||||
output['built_in_plugins'] = built_in
|
output['built_in_plugins'] = built_in
|
||||||
|
|
||||||
# Get installed 3rd party plugins (grouped by package)
|
# Get installed 3rd party plugins (grouped by package)
|
||||||
|
|||||||
@ -85,7 +85,7 @@ def healthcheck(ctx, timeout):
|
|||||||
|
|
||||||
client_stats = stats.get('APRSClientStats')
|
client_stats = stats.get('APRSClientStats')
|
||||||
if not client_stats:
|
if not client_stats:
|
||||||
console.log('No APRSClientStats')
|
console.log('No APRSClientStats - Is the aprsd server running?')
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
else:
|
else:
|
||||||
aprsis_last_update = client_stats['connection_keepalive']
|
aprsis_last_update = client_stats['connection_keepalive']
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import inspect
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@ -7,39 +6,33 @@ from rich.table import Table
|
|||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from aprsd import cli_helper
|
from aprsd import cli_helper
|
||||||
from aprsd import plugin as aprsd_plugin
|
|
||||||
from aprsd.main import cli
|
from aprsd.main import cli
|
||||||
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
|
||||||
from aprsd.utils import package as aprsd_package
|
from aprsd.utils import package as aprsd_package
|
||||||
|
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger('APRSD')
|
||||||
|
|
||||||
|
|
||||||
def show_built_in_plugins(console):
|
def show_built_in_plugins(console):
|
||||||
modules = [fortune, notify, ping, time, version, weather]
|
built_in = aprsd_package.get_built_in_plugins()
|
||||||
plugins = []
|
plugins = []
|
||||||
|
|
||||||
for module in modules:
|
for plugin in built_in:
|
||||||
entries = inspect.getmembers(module, inspect.isclass)
|
info = {
|
||||||
for entry in entries:
|
'name': plugin['class_name'],
|
||||||
cls = entry[1]
|
'path': plugin['path'],
|
||||||
if issubclass(cls, aprsd_plugin.APRSDPluginBase):
|
'version': plugin['version'],
|
||||||
info = {
|
'short_desc': '',
|
||||||
'name': cls.__qualname__,
|
}
|
||||||
'path': f'{cls.__module__}.{cls.__qualname__}',
|
|
||||||
'version': cls.version,
|
|
||||||
'docstring': cls.__doc__,
|
|
||||||
'short_desc': cls.short_description,
|
|
||||||
}
|
|
||||||
|
|
||||||
if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
|
if plugin.get('command_regex'):
|
||||||
info['command_regex'] = cls.command_regex
|
info['command_regex'] = plugin['command_regex']
|
||||||
info['type'] = 'RegexCommand'
|
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):
|
plugins.append(info)
|
||||||
info['type'] = 'WatchList'
|
|
||||||
|
|
||||||
plugins.append(info)
|
|
||||||
|
|
||||||
plugins = sorted(plugins, key=lambda i: i['name'])
|
plugins = sorted(plugins, key=lambda i: i['name'])
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import cProfile
|
|
||||||
import datetime
|
|
||||||
import logging
|
import logging
|
||||||
import pstats
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@ -34,16 +30,9 @@ console = Console()
|
|||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
threads.APRSDThreadList().stop_all()
|
from aprsd import main as aprsd_main
|
||||||
if 'subprocess' not in str(frame):
|
|
||||||
LOG.info(
|
aprsd_main.signal_handler(sig, frame)
|
||||||
'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()
|
|
||||||
|
|
||||||
|
|
||||||
class APRSDListenProcessThread(rx.APRSDFilterThread):
|
class APRSDListenProcessThread(rx.APRSDFilterThread):
|
||||||
@ -154,12 +143,6 @@ class APRSDListenProcessThread(rx.APRSDFilterThread):
|
|||||||
default='http://localhost:8081',
|
default='http://localhost:8081',
|
||||||
help='URL of the aprsd-exporter API to send stats to.',
|
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
|
@click.pass_context
|
||||||
@cli_helper.process_standard_options
|
@cli_helper.process_standard_options
|
||||||
def listen(
|
def listen(
|
||||||
@ -174,7 +157,6 @@ def listen(
|
|||||||
enable_packet_stats,
|
enable_packet_stats,
|
||||||
export_stats,
|
export_stats,
|
||||||
exporter_url,
|
exporter_url,
|
||||||
profile,
|
|
||||||
):
|
):
|
||||||
"""Listen to packets on the APRS-IS Network based on FILTER.
|
"""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
|
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.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
@ -315,25 +291,3 @@ def listen(
|
|||||||
stats.join()
|
stats.join()
|
||||||
if stats_export:
|
if stats_export:
|
||||||
stats_export.join()
|
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 decimal
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
from dataclasses import asdict, is_dataclass
|
||||||
|
|
||||||
from aprsd.packets import core
|
from aprsd.packets import core
|
||||||
|
|
||||||
@ -63,6 +64,8 @@ class SimpleJSONEncoder(json.JSONEncoder):
|
|||||||
return str(obj)
|
return str(obj)
|
||||||
elif isinstance(obj, core.Packet):
|
elif isinstance(obj, core.Packet):
|
||||||
return obj.to_dict()
|
return obj.to_dict()
|
||||||
|
elif is_dataclass(obj):
|
||||||
|
return asdict(obj)
|
||||||
else:
|
else:
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import requests
|
|||||||
from thesmuggler import smuggle
|
from thesmuggler import smuggle
|
||||||
|
|
||||||
from aprsd import plugin as aprsd_plugin
|
from aprsd import plugin as aprsd_plugin
|
||||||
|
from aprsd.plugins import fortune, notify, ping, time, version, weather
|
||||||
|
|
||||||
# Handle importlib.metadata compatibility
|
# Handle importlib.metadata compatibility
|
||||||
try:
|
try:
|
||||||
@ -245,3 +246,39 @@ def log_installed_extensions_and_plugins():
|
|||||||
|
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
LOG.info(f'Plugin: {plugin} version: {plugins[plugin][0]["version"]}')
|
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