Package: pluginconf
Provides meta data extraction and plugin basename lookup. And it’s meant for
in-application feature and option management.
Thedescriptor format(self-containedatop each script) is basically:# encoding: utf-8
# api: python
# type: handler
# category: io
# title: Plugin configuration
# description: Read meta data, pyz/package contents, module locating
# version: 0.5
# priority: core
# docs: https://fossil.include-once.org/pluginspec/
# config:
#    { name: xyz, value: 1, type: bool, description: "Sets..." }
#    { name: var, value: "init", description: "Another..." }
# license: MITL
#
# Documentation goes here...Thekey: valueformat is language-agnostic.  It’s basically YAML in the
topmost script comment.  For Python only # hash comments are used.  Defaults
to rather common field names, encourages a documentation block, and an
obviousconfig: { ..  } schemefor options and defaults.How it's used:import pluginconf
meta = pluginconf.plugin_meta(filename="./plugin/func.py")
print(meta)What it’s not:This is not another config reader/parser/storage class.Doesn’t impose a specific plugin API.Neither concerns itself with module/package loading. (Seepluginbaseor just__import__.)What for then?Separates code from meta data. Avoids keeping seldomly used descriptors in variables.Does away with externalized ini/json files for modules, yet simplifies use of external tooling.Minimizes premature module loading just to inspect meta information.APILookup configuration is currently just done through injection:plugin_base = [__package__, "myapp.plugins", "/usr/share/app/extensions"]
data_base = "pluginconf"  # or any top-level app moduleWhich declares module and plugin basenames, which get used for lookups by
just module= names in e.g.module_list(). (Works for literal setups
and within PYZ bundles).This is unnecessary for plainplugin_meta(fn=)extraction.plugin_meta( module= | filename= | src= | frame= )Returns a meta data dictionary for the given module name, file, source code, or caller frame:{
  "title": "Compound★",
  "description": "...",
  "version": "0.1",
  "type": "channel",
  "category": "virtual",
  "config": […],
  "doc": "The multiline comment \n following meta fields..."
  …
}And that’s already all it does.All other methods in pluginconf are mostly just for module lookup or data
retrieval.module_list()Returns basenames of available/installed plugins (from possible sources
inplugin_base).add_plugin_defaults()Populates your config_options{} and plugin_states{} list. Can be a classic dict,
or one of the hundreds of config parser/managers. You might want to combine
config options and plugin states in a single dict even:import pluginconf
pluginconf.data_base = __name__
pluginconf.plugin_base = [__package__]

conf = {
    "defaults": "123",
"plugins": {}       # ← stores the activation states
}

for module, meta in pluginconf.all_plugin_meta().items():
    pluginconf.add_plugin_defaults(conf, conf["plugins"], meta, module)
    #      share the same dict      ↑        ↑get_data( filename= )Is mostly an alias for pkgutil.get_data(). Abstracts usage within PYZ packages a little.argparse_map()Provides a simpler way to specify ugly argparse definitions. And allows to amass options from plugins.GUIThere's a Tkinter/PySimpleGUI variant of the option dialog fromstreamtuner2included:Thepluginconf.gui.window()implementation has a few less features, but
might suffice for common applications. It just lists a single pane of
settings, and doesn't even attempt to group by categories.Its main function performs the plugin lookup (*.pymeta reading) and
displays an editing window:import pluginconf.gui
 config = {
     "debug": 0, "verbose": 1, "temp_dir": "/tmp"
 }
 plugin_states = {
     "core": 1, "printing_ui": 0
 }
 pluginconf.gui.window(config, plugin_states, files=["./library/*.py"])Where bothconfigandplugin_statesget updated after invocation. The
function return value indicates whether save or cancel was pressed.plugin_states={}can be omitted/empty, but the GUI will still display
checkboxes for plugin files, even if they go unused.Supports only basic option types (bool, str, int, select), no table/dict.Type casting is somewhat basic (no integer regex).And doesn't support nested config names=app[module][var]yet.The config dict might be prefilled from either in-app defaults,
orjson.load(), and/or perpluginconf.add_plugin_defaults().It's still up to the application how/where to store the config{} dict (e.g.json.dumps()).And alternatively to the *.py glob list, you could inject a prepared
dictionary asplugins={}list (keys are unused) and leavefiles=None.Any PySimpleGUI options (title=, theme=, resizable=) are passed through to
the config window.Overall it's surprisingly short given the PySimpleGUI result set. It would
likely behave as well, if e.g. additional tabs or input widgets were added.setup.py wrapperAnother obvious use case for PMD is simplifying packaging. Asetup()script can become as short as:from pluginconf.setup import setup
 setup(
     filename="main/pkg.py"
 )Which will reuse version: and descriptors from the meta comment. For simple
one-module packages, you might get away with justsetup()and an all
automatic lookup. The non-standard PMD field# classifiers: x11, pythoncan be used to lookup trove categories (crude search on select topics).
All othersetup(fields=…)are passed on to distutils/setuptools as is.
-- Btw,setupmetais an even more
versatile wrapper with sensible defaults and source scanning.flit wrapperAlternatively, there'spluginconf.flitto utilize pyproject.toml for
building packages, while sourcing meta data from the primary package file.[build-system]
 requires = ["pluginconf", "flit"]
 build-backend = "pluginconf.flit"

 [project]
 name = "projectname"It can be invoked viaflit-pluginconf build/python -m pluginconf.flit buildor evenpython -m build. Field mapping isn't very robust yet, and mercilessly
flaunts thedynamic=directive.other modulespluginconf.dependsprovidesCheckfor .valid() and .depends() probingpluginconf.bindis a simpler interface and basic plugin loaderargparse_map() might also end up in a separate module.CaveatsIt’s mostly just an excerpt from streamtuner2.Might need customization prior use.The GUI implmentation is fairly simplistic.Doesn't bundle any plugin repo loader logic.So doesn't make use of the dependency class.The description fields can double as packaging source (setup.py). There's also a# pack: specifierfor fpm (deb/rpm/arch/exe/pyzw/pip generation), unused in thesetup.pywrapper here however.In Python# type:might neede doubled commenting (## pylint), or alternating to
other descriptors likeclass:orslot:. (The whole scheme is agnostic to
designators. Common keys are just recommended for potential interoparability.)The format incidentally mixes well with established comment markers like# format: offor# pylint: disable=…for instance.