From 22d22feaceeb472c8a394be0b428fe20e61a2761 Mon Sep 17 00:00:00 2001 From: Walter Boring Date: Fri, 23 Jan 2026 11:56:55 -0500 Subject: [PATCH] config export --- aprsd_stock_plugin/cli.py | 52 ++++++++++ aprsd_stock_plugin/conf/__init__.py | 6 ++ aprsd_stock_plugin/conf/main.py | 27 +++++ aprsd_stock_plugin/conf/opts.py | 146 ++++++++++++++++++++++++++++ pyproject.toml | 7 ++ 5 files changed, 238 insertions(+) create mode 100644 aprsd_stock_plugin/cli.py create mode 100644 aprsd_stock_plugin/conf/__init__.py create mode 100644 aprsd_stock_plugin/conf/main.py create mode 100644 aprsd_stock_plugin/conf/opts.py diff --git a/aprsd_stock_plugin/cli.py b/aprsd_stock_plugin/cli.py new file mode 100644 index 0000000..598779a --- /dev/null +++ b/aprsd_stock_plugin/cli.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +""" +CLI tool for aprsd-stock-plugin configuration export. +""" + +import json +import sys + + +def export_config_cmd(format="json"): + """Export plugin configuration options.""" + try: + from aprsd_stock_plugin.conf.opts import export_config + + result = export_config(format=format) + + if format == "json": + print(result) + else: + print(json.dumps(result, indent=2)) + + return 0 + except ImportError as e: + print(f"Error: {e}", file=sys.stderr) + print("\nTo export config, install oslo.config:", file=sys.stderr) + print(" pip install oslo.config", file=sys.stderr) + return 1 + except Exception as e: + print(f"Error exporting config: {e}", file=sys.stderr) + return 1 + + +def main(): + """Main entry point for CLI.""" + import argparse + + parser = argparse.ArgumentParser( + description="Export aprsd-stock-plugin configuration options", + ) + parser.add_argument( + "--format", + choices=["dict", "json"], + default="json", + help="Output format (default: json)", + ) + + args = parser.parse_args() + sys.exit(export_config_cmd(format=args.format)) + + +if __name__ == "__main__": + main() diff --git a/aprsd_stock_plugin/conf/__init__.py b/aprsd_stock_plugin/conf/__init__.py new file mode 100644 index 0000000..6331a21 --- /dev/null +++ b/aprsd_stock_plugin/conf/__init__.py @@ -0,0 +1,6 @@ +from oslo_config import cfg + +from aprsd_stock_plugin.conf import main + +CONF = cfg.CONF +main.register_opts(CONF) diff --git a/aprsd_stock_plugin/conf/main.py b/aprsd_stock_plugin/conf/main.py new file mode 100644 index 0000000..d8f8a8a --- /dev/null +++ b/aprsd_stock_plugin/conf/main.py @@ -0,0 +1,27 @@ +from oslo_config import cfg + +plugin_group = cfg.OptGroup( + name="aprsd_stock_plugin", + title="APRSD Stock Plugin settings", +) + +plugin_opts = [ + cfg.BoolOpt( + "enabled", + default=False, + help="Enable the plugin?", + ), +] + +ALL_OPTS = plugin_opts + + +def register_opts(cfg): + cfg.register_group(plugin_group) + cfg.register_opts(ALL_OPTS, group=plugin_group) + + +def list_opts(): + return { + plugin_group.name: plugin_opts, + } diff --git a/aprsd_stock_plugin/conf/opts.py b/aprsd_stock_plugin/conf/opts.py new file mode 100644 index 0000000..828e3a3 --- /dev/null +++ b/aprsd_stock_plugin/conf/opts.py @@ -0,0 +1,146 @@ +# Copyright 2015 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +This is the single point of entry to generate the sample configuration +file for Nova. It collects all the necessary info from the other modules +in this package. It is assumed that: + +* every other module in this package has a 'list_opts' function which + return a dict where + * the keys are strings which are the group names + * the value of each key is a list of config options for that group +* the nova.conf package doesn't have further packages with config options +* this module is only used in the context of sample file generation +""" + +import collections +import importlib +import importlib.util +import os +import pkgutil + +LIST_OPTS_FUNC_NAME = "list_opts" + + +def _tupleize(dct): + """Take the dict of options and convert to the 2-tuple format.""" + return [(key, val) for key, val in dct.items()] + + +def list_opts(): + opts = collections.defaultdict(list) + module_names = _list_module_names() + imported_modules = _import_modules(module_names) + _append_config_options(imported_modules, opts) + return _tupleize(opts) + + +def _list_module_names(): + module_names = [] + package_path = os.path.dirname(os.path.abspath(__file__)) + for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]): + if modname == "opts" or ispkg: + continue + else: + module_names.append(modname) + return module_names + + +def _import_modules(module_names): + imported_modules = [] + for modname in module_names: + mod = importlib.import_module("aprsd_stock_plugin.conf." + modname) + if not hasattr(mod, LIST_OPTS_FUNC_NAME): + msg = ( + f"The module 'aprsd_stock_plugin.conf.{modname}' should have a " + f"'{LIST_OPTS_FUNC_NAME}' function which returns the config options." + ) + raise Exception(msg) + else: + imported_modules.append(mod) + return imported_modules + + +def _append_config_options(imported_modules, config_options): + for mod in imported_modules: + configs = mod.list_opts() + for key, val in configs.items(): + config_options[key].extend(val) + + +def export_config(format="dict"): + """ + Export configuration options as a simple data structure. + + This function extracts configuration information from oslo_config + option objects and returns it in a simple dict or JSON format. + Works independently of aprsd installation. + + Args: + format: Output format - 'dict' (default) or 'json' + + Returns: + dict or JSON string containing all configuration options with: + - name: option name + - type: option type (StrOpt, BoolOpt, IntOpt, etc.) + - default: default value + - help: help text + - required: whether the option is required + - choices: list of valid choices (if applicable) + - secret: whether the option contains secret data (if applicable) + - min/max: min/max values for numeric types (if applicable) + + Raises: + ImportError: if oslo_config is not installed + """ + # Check if oslo_config is available + if importlib.util.find_spec("oslo_config") is None: + raise ImportError( + "oslo_config is required to export configuration. " + "Install it with: pip install oslo.config", + ) + + opts = list_opts() + result = {} + + for group_name, opt_list in opts: + result[group_name] = [] + for opt in opt_list: + opt_dict = { + "name": opt.name, + "type": type(opt).__name__, + "default": getattr(opt, "default", None), + "help": getattr(opt, "help", ""), + "required": not hasattr(opt, "default") or getattr(opt, "default", None) is None, + } + + # Add additional attributes if available + if hasattr(opt, "choices") and opt.choices: + opt_dict["choices"] = list(opt.choices) + if hasattr(opt, "secret") and opt.secret: + opt_dict["secret"] = True + if hasattr(opt, "min") and opt.min is not None: + opt_dict["min"] = opt.min + if hasattr(opt, "max") and opt.max is not None: + opt_dict["max"] = opt.max + + result[group_name].append(opt_dict) + + if format == "json": + import json + + return json.dumps(result, indent=2) + return result diff --git a/pyproject.toml b/pyproject.toml index eaa7ab2..f8f8bfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ classifiers = [ ] dependencies = [ "aprsd>=4.2.0", + "oslo-config", "yfinance", ] @@ -40,6 +41,12 @@ dev = [ "tox-uv", ] +[project.entry-points."oslo.config.opts"] + "aprsd_stock_plugin.conf" = "aprsd_stock_plugin.conf.opts:list_opts" + +[project.scripts] + "aprsd-stock-plugin-export-config" = "aprsd_stock_plugin.cli:main" + [tool.setuptools] packages = ["aprsd_stock_plugin"]