Index: README.md ================================================================== --- README.md +++ README.md @@ -47,11 +47,11 @@ # API Lookup configuration is currently just done through injection: plugin_base = [__package__, "myapp.plugins", "/usr/share/app/extensions"] - module_base = "pluginconf" # or any top-level app module + data_base = "pluginconf" # or any top-level app module Which 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 plain `plugin_meta(fn=)` extraction. @@ -85,11 +85,11 @@ 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.module_base = __name__ + pluginconf.data_base = __name__ pluginconf.plugin_base = [__package__] conf = { "defaults": "123", "plugins": {} # โ† stores the activation states @@ -185,13 +185,14 @@ or even `python -m build`. Field mapping isn't very robust yet, and mercilessly flaunts the `dynamic=` directive. -## other modules +# other modules * `pluginconf.depends` provides `Check` for .valid() and .depends() probing + * `pluginconf.bind` is a simpler interface and basic plugin loader * argparse_map() might also end up in a separate module. #### Caveats ADDED html/bind.html Index: html/bind.html ================================================================== --- html/bind.html +++ html/bind.html @@ -0,0 +1,239 @@ + + + + + + +pluginconf.bind API documentation + + + + + + + + + + + +
+
+
+

Module pluginconf.bind

+
+
+

Basic plugin loader implementation for flat namespaces. Ties together +pluginconf lookups and config management for plugin loading in apps. +It's rather basic, and subject to change. Does impose a config dict +format, but no storage still.

+

Usage example

+
# designate a plugins/*.py package as plugin_base
+import plugins
+import pluginconf.bind
+pluginconf.bind.base(plugins)
+
+# preset core app settings / load from json, add plugin options
+conf = {
+    "plugins": {
+    }
+}
+pluginconf.bind.defaults(conf)
+
+# load conf-enabled plugins, and register modules somehow
+for mod in pluginconf.bind.load_enabled(conf):
+    mod.init()
+
+

Find by type

+
for name, pmd in pluginconf.bind.find(type="effect").items():
+    mod = pluginconf.bind.load(name)
+    if pmd.category == "menu":
+        main_win.add_menu(mod.menu)
+
+

Note that this uses meta data extraction, so should best be confined +for app setup/initialization, not used recurringly. The config state +usage is the preferred method. (Only requires property loading +once, for installation or with window() usage.)

+
+

Method interactions: ๐Ÿš = import, ๐Ÿงฉ = meta, ๐Ÿงพ = config, ๐Ÿ›  += setup

+
+
+
+
+
+
+

Functions

+
+
+def base(module, path=None) +
+
+

Register module as package/plugin_base. Or expand its search path ๐Ÿ›  .

+

Parameters

+
+
module : module/str
+
The package basename to later load plugins from (must be a package, +like plugins/__init__.py, or be tied to a path= or zip). Ideally +this module was already imported in main. But parameter may be a string.
+
path : str
+
Add a directory or pyz/zip bundle to registered plugin_base. Could +be invoked multiple times =./contrib/, =/usr/share/app/extenstions/, +=~/.config/app/userplug/ (same as declaring the __path__ in the +base package/__init__.py.)
+
+
+
+def defaults(conf) +
+
+

Traverse installed plugins and expand config dict with presets ๐Ÿงฉ ๐Ÿงพ

+

Parameters

+
+
conf : dict ๐Ÿ”
+
Expands the top-level config dict with preset values from any plugins.
+
+
+
+def find(**kwargs) +
+
+

Find plugins by e.g. type= or category= ๐Ÿงฉ

+

Parameters

+
+
type : str
+
Usually you'd search on a designated plugin categorization, like type= +and api=, or slot=, or class= or whatever is most consistent. Multiple +attributes can be filtered on. (Version/title probably not useful here.)
+
+

Returns

+
+
dict : basename โ†’ PluginMeta dict
+
 
+
+
+
+def load(name) +
+
+

Import individual plugin from any of the base paths ๐Ÿš

+

Parameters

+
+
name : str
+
Plugin name in any of the registered plugin_baseยดs. (The whole +namespace is assumed to be flat, and identifiers to be unique.)
+
+
+
+def load_enabled(conf) +
+
+

Import modules that are enabled in conf[plugins]={name:True,โ€ฆ} ๐Ÿงพ ๐Ÿš

+

Parameters

+
+
conf : dict
+
Simple options-value dictionary, but with one conf["plugins"] = {} subdict, +which holds plugin activation states. The config dict doesn't have to match +the available plugin options (defaults can be added), but should focus on +essential presets. Differentiation only might become relevant for storage.
+
+
+
+
+
+

Classes

+
+
+class isolated +(package) +
+
+

Context manager for isolated plugin structures. +๐Ÿ›  +This is a shallow alternative to pluginbase and library-level plugins. +Temporarily swaps global settings, thus maps most static functions.

+
with pluginconf.bind.isolated("libplugins") as bound:
+    bound.modules2.init()
+    print(
+        bound.find(api="library")
+    )
+
+

Static methods

+
+
+def defaults() +
+
+

return defaults for isolated plugin structure ๐Ÿงฉ ๐Ÿงพ

+
+
+def find(**kwargs) +
+
+

find by meta attributes ๐Ÿงฉ

+
+
+def load(name) +
+
+

load module from wrapped package ๐Ÿš

+
+
+

Methods

+
+
+def get_data(self, *args, **kwargs) +
+
+

get file relative to encapsulated plugins ๐Ÿš

+
+
+
+
+
+
+ +
+ + + Index: html/flit.html ================================================================== --- html/flit.html +++ html/flit.html @@ -26,28 +26,28 @@

monkeypatches flit to use pluginconf sources for packaging with a pyproject.toml like:

- - + +
pyproject.toml foobar/__init__.py
+
 [build-system]
 requires = ["pluginconf", "flit]
 build-backend = "pluginconf.flit"
 
 [project]
 name = "foobar"
 dynamic = ["*"]
-
+
 # title: foobar
 # description: package summary
 # version: 2.5.0
 # depends: python:requests >= 2.25
 # license: MITL
 # classifiers: backend, text
 # url: http;//example.org
-

Can be invoked per flit-pluginconf build or python -m build.

flit - can't believe it's not setup.py!!

Index: html/index.html ================================================================== --- html/index.html +++ html/index.html @@ -20,23 +20,32 @@

Package pluginconf

-

Plugin meta extraction and module lookup

-
    -
  • Main function plugin_meta(filename=โ€ฆ) unpacks -meta fields -into dictionaries.
  • -
  • Other utility code is about module location, but requires -some initialization.
  • -
-

#

+

Plugin meta extraction and module lookup.

+ +
+ + +
  • Main function plugin_meta() unpacks meta fields +into dictionaries. +
  • Other utility code is about module listing, relative to +plugin_base anchors. +
  • //pypi.org/project/pluginconf/ +
  • //fossil.include-once.org/pluginspec/ +
  • Sub-modules

    +
    pluginconf.bind
    +
    +

    Basic plugin loader implementation for flat namespaces. Ties together +pluginconf lookups and config management for plugin loading in apps. +It's rather โ€ฆ

    +
    pluginconf.depends

    Dependency validation and consistency checker for updates

    pluginconf.flit
    @@ -48,15 +57,33 @@

    PySimpleGUI window to populate config dict via plugin options โ€ฆ

    pluginconf.setup
    -

    Simulates setuptools.setup()

    +

    Expands setuptools.setup() with automatic package description lookup

    +

    Global variables

    +
    +
    var config_opt_type_map
    +
    +

    normalize config type: names to str, text, bool, int, select, dict

    +
    +
    var data_root
    +
    +

    File lookup relation for get_data(), should name a top-level package. +(Equivalent to PluginBase(package=โ€ฆ))

    +
    +
    var plugin_base
    +
    +

    Package/module names (or directories) for module_list() and plugin_meta() +lookups. Associated paths (__path__) will be scanned for module/plugin +basenames. (Similar to PluginBase(searchpath=โ€ฆ))

    +
    +

    Functions

    @@ -90,27 +117,27 @@
    dict : names to meta data dict
     
    -def get_data(filename, decode=False, gzip=False, file_base=None) +def get_data(filename, decode=False, gzip=False, file_root=None)

    Fetches file content from install path or from within PYZ archive. This is just an alias and convenience wrapper for pkgutil.get_data(). -Utilizes the module_base / plugin_base as top-level reference.

    +Utilizes the data_root as top-level reference.

    Parameters

    filename :  str
    filename in pyz or bundle
    decode : bool
    text file decoding utf-8
    gzip : bool
    automatic gzdecode
    -
    file_base : list
    -
    alternative base module reference
    +
    file_root : list
    +
    alternative base module (application or pyz root)

    Returns

    str : file contents
     
    @@ -135,11 +162,11 @@
    def plugin_meta(filename=None, src=None, module=None, frame=1, **kwargs)
    -

    Extract plugin meta data block from different sources:

    +

    Extract plugin meta data block from specified source.

    Parameters

    filename : str
    Read literal files, or .pyz contents.
    src : str
    @@ -149,31 +176,45 @@
    frame : int
    Extract comment header of caller (default).
    extra_base : list
    Additional search directories.
    max_length : list
    -
    Maximum size to read from files.
    +
    Maximum size to read from files (6K default).

    Returns

    dict : Extracted comment fields, with config: preparsed
     
    -
    + +

    The result dictionary has fields accessible as e.g. pmd["title"] +or pmd.version. The documentation block after all fields: is called +["doc"]. And pmd.config` already parsed as a list of dictionaries.

    Classes

    +
    +class OptionList +(*args, **kwargs) +
    +
    +

    List of config: options, with alernative .name access (lookup by name= from option entry).

    +

    Ancestors

    +
      +
    • builtins.list
    • +
    +
    class PluginMeta (*args, **kwargs)
    -

    Plugin meta data, as dictionary with alternative .property access. -Returned for each plugin_meta() result, and config: options. -Non-existent .fieldnames just resolve to "".

    +

    Plugin meta data as dictionary{}, or alternatively .property access. +Returned for each plugin_meta() result, and individual config: options. +Absent .field access resolves to "".

    Ancestors

    • builtins.dict
    @@ -186,15 +227,23 @@
      • Sub-modules

        +
      • +
      • Global variables

        +
      • Functions

      • Classes

      • Index: html/setup.html ================================================================== --- html/setup.html +++ html/setup.html @@ -3,11 +3,11 @@ pluginconf.setup API documentation - + @@ -20,11 +20,11 @@

        Module pluginconf.setup

        -

        Simulates setuptools.setup()

        +

        Expands setuptools.setup() with automatic package description lookup

        @@ -69,11 +69,11 @@
        class MetaUtils (*args, **kwargs)
        -

        convenience access to PMD fields

        +

        Convenience access to PMD fields and conversion functions

        Ancestors

        • builtins.dict

        Static methods

        Index: pluginconf/gui.py ================================================================== --- pluginconf/gui.py +++ pluginconf/gui.py @@ -23,11 +23,11 @@ # """ PySimpleGUI window to populate config dict via plugin options -![](http://fossil.include-once.org/streamtuner2/raw/st2-plugins.png?name=946c0e32c868a22facd7498250451723a50ab00a) +![config window](/pluginspec/doc/tip/html/config.png) """ #import os import re Index: pluginconf/setup.py ================================================================== --- pluginconf/setup.py +++ pluginconf/setup.py @@ -42,11 +42,11 @@ # Classifiers and license matching is very crude, just for # the most common cases. Type:, Category: and Classifiers: # or Keywords: are also scanned for trove classifers. # -""" Simulates setuptools.setup() """ +""" Expands setuptools.setup() with automatic package description lookup """ import os import re import glob @@ -76,11 +76,11 @@ "long_description_content_type": "text/plain", } class MetaUtils(dict): - """ convenience access to PMD fields """ + """ Convenience access to PMD fields and conversion functions """ def __getattr__(self, name): """ dict into properties """ return self.get(name, "") Index: test/pyz.py ================================================================== --- test/pyz.py +++ test/pyz.py @@ -15,13 +15,13 @@ sys.path.insert(0, f"{os.path.dirname(__file__)}/.pyz.pyz") #os.chdir(os.path.dirname(__file__)) @pytest.fixture def init(): - print(pluginconf.module_base) - pluginconf.module_base = ["config"] # must be one of the .pyz-contained modules (should be a dir/submodule for real uses) - pluginconf.plugin_base = ["inner"] # relative, must declare __path__, can't be __main__.py + print(pluginconf.plugin_base) + pluginconf.data_root = ["config"] # must be one of the .pyz-contained modules (should be a dir/submodule for real uses) + pluginconf.plugin_base = ["inner"] # relative, must declare __path__, can't be __main__.py @pytest.fixture def pmd(): return pluginconf.plugin_meta(module="inner") @@ -37,9 +37,10 @@ assert pmd["category"] == "complex" assert pmd["title"] == "pyz module" assert pmd["config"][0]["name"] == "relation" assert pmd["state"] == "alpha" -#def tearDown(): +def tearDown(reset): + pass # pass#pluginconf.plugin_base = [".", "test", os.path.dirname(__file__), "."] # #pluginconf.module_base = "all_plugin_meta" # sys.path.pop(0) Index: test/setup.py ================================================================== --- test/setup.py +++ test/setup.py @@ -23,14 +23,15 @@ def attributes(mocker, pmd): stop = mocker.patch('setuptools.setup', _record) pluginconf.setup.setup( fn="pluginconf/__init__.py", + packages = ["pluginconf"], ) assert attr["classifiers"] assert attr["project_urls"] - assert attr["packages"] == ['pluginconf', 'test'] + assert attr["packages"] == ['pluginconf'] assert attr["long_description_content_type"] == 'text/markdown' assert attr["license"] == 'PD' assert attr["keywords"] == 'config' assert attr["long_description"].find("meta data") > 0