[build-system]
-requires = ["pluginconf", "flit]
+requires = ["pluginconf", "flit"]
build-backend = "pluginconf.flit"
[project]
name = "foobar"
dynamic = ["*"]
Index: html/gui.html
==================================================================
--- html/gui.html
+++ html/gui.html
@@ -46,11 +46,11 @@
def plugin_layout(pmd_list, config, plugin_states, opt_label=False)
-craft list of widgets for each read plugin
+
def read_options(files)
@@ -63,32 +63,61 @@
Reads *.py files and crafts a settings dialog from meta data.
Where plugin_states{} is usually an entry in config{} itself. Depending on plugin
and option names, it might even be a flat/shared namespace for both. Per default you'd
set files=["plugins/*.py", __file__] to be read. But with files=[] it's possible to
provide a plugins=pluginconf.get_plugin_meta() or prepared plugin/options dict instead.
- Parameters
-
-config : dict 🔁
-- Config settings, updated after dialog completion
-plugin_states : dict 🔁
-- Plugin activation states, also input/output
-files : list
-- Glob list of *.py files to extract meta definitions from
-plugins : dict
-- Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected
-opt_label : bool
-- Show config name= as label
-theme : str
-- Set PSG window theme.
-**kwargs : dict
-- Other options are passed on to PySimpleGUI
-
- Returns
-
-True : if changed config{} values are to be saved (the dict will be updated in any case)
--
-
+
+
+
+Params |
+ |
+ |
+
+
+
+
+config |
+dict 🔁 |
+Config settings, updated after dialog completion |
+
+
+plugin_states |
+dict 🔁 |
+Plugin activation states, also input/output |
+
+
+files |
+list |
+Glob list of *.py files to extract meta definitions from |
+
+
+plugins |
+dict |
+Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected |
+
+
+opt_label |
+bool |
+Show config name= as label (instead of description) |
+
+
+theme |
+str |
+Set PSG window theme. |
+
+
+**kwargs |
+dict |
+Other options are passed on to PySimpleGUI |
+
+
+Returns |
+True |
+if updated config{} values should be [Saved] |
+
+
+
def wrap(text, width=50)
Index: html/index.html
==================================================================
--- html/index.html
+++ html/index.html
@@ -88,13 +88,12 @@
def add_plugin_defaults(conf_options, conf_plugins, meta, module='')
-
-
Utility function which collect defaults from plugin meta data to
-a config store. Which in the case of streamtuner2 is really just a
-dictionary conf{} and a plugin list in conf.plugins{} .
+ Utility function to collect defaults from plugin meta data to
+a config dict/store.
Parameters |
|
@@ -103,16 +102,16 @@
conf_options |
dict 🔁 |
-storage for amassed options |
+storage for amassed #config: options |
conf_plugins |
dict 🔁 |
-enable status based on plugin state/priority: |
+activation status derived from state/priority: |
meta |
dict |
input plugin meta data (invoke once per plugin) |
@@ -147,11 +146,11 @@
Returns |
dict |
-names to meta data dict |
+names to PluginMeta dict |
Index: html/setup.html
==================================================================
--- html/setup.html
+++ html/setup.html
@@ -47,19 +47,36 @@
def setup(filename=None, debug=False, **kwargs)
-
Wrapper around setuptools.setup() which adds some defaults
and plugin meta data import, with some shortcut parameters:
- Parameters
-
-filename : str
-- main file "pkg/main.py" (else deduced from primary package name)
-debug : bool
-- display collected options prior setuptools.setup() invocation
-long_description : str
-- e.g. "README.md", else comment block used
-
+
+
+
+Parameters |
+ |
+ |
+
+
+
+
+filename |
+str |
+main file "pkg/main.py" (else deduced from primary package name) |
+
+
+debug |
+bool |
+display collected options prior setuptools.setup() invocation |
+
+
+long_description |
+str |
+e.g. "README.md", else the comment block gets used |
+
+
+
Other setup() params work as usual, and are passed trough. Notably
entry_points= or data_files= can be used, even if they get augmented.
Index: pluginconf/__init__.py
==================================================================
--- pluginconf/__init__.py
+++ pluginconf/__init__.py
@@ -11,11 +11,11 @@
# suggests: python:flit, python:PySimpleGUI
# license: PD
# priority: core
# api-docs: https://fossil.include-once.org/pluginspec/doc/trunk/html/index.html
# docs: https://fossil.include-once.org/pluginspec/
-# url: http://fossil.include-once.org/streamtuner2/wiki/plugin+meta+data
+# url: https://fossil.include-once.org/pluginspec/wiki/pluginconf
# config: -
# format: off
# permissive: 0.75
# pylint: disable=invalid-name
# console-scripts: flit-pluginconf=pluginconf.flit:main
@@ -252,11 +252,11 @@
of available/installed plugins. It associates each plugin name
with a its meta{} fields.
| Parameters | | |
|-------------|---------|---------------------------------|
- | **Returns** | dict | names to meta data dict |
+ | **Returns** | dict | names to `PluginMeta` dict |
"""
return {
name: plugin_meta(module=name) for name in module_list()
}
@@ -557,18 +557,17 @@
# Add plugin defaults to conf.* store
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def add_plugin_defaults(conf_options, conf_plugins, meta, module=""):
"""
- Utility function which collect defaults from plugin meta data to
- a config store. Which in the case of streamtuner2 is really just a
- dictionary `conf{}` and a plugin list in `conf.plugins{}`.
+ Utility function to collect defaults from plugin meta data to
+ a config dict/store.
| Parameters | | |
|-------------|---------|--------------------------------------|
- | conf_options| dict 🔁 | storage for amassed options |
- | conf_plugins| dict 🔁 | enable status based on plugin state/priority: |
+ | conf_options| dict 🔁 | storage for amassed #config: options |
+ | conf_plugins| dict 🔁 | activation status derived from state/priority: |
| meta | dict | input plugin meta data (invoke once per plugin)|
| module | str | basename of meta: blocks plugin file |
| **Returns** | None | - |
"""
Index: pluginconf/bind.py
==================================================================
--- pluginconf/bind.py
+++ pluginconf/bind.py
@@ -7,18 +7,18 @@
# state: alpha
# priority: optional
#
# Most basic plugin management/loader. It unifies meta data retrieval
# and instantiation. It still doesn't dictate a plugin API or config
-# storage (using json in examples). And can be a simple setup:
+# storage (using json in examples). And can be a simpler setup:
#
# Create an empty plugins/__init__.py to use as package and for
# plugin discovery.
#
# Designate it as such:
#
-# import pluginconf.bind
+# import pluginconf.bind # (first import resets .plugin_base)
# pluginconf.bind.base("plugins")
#
# Set up a default conf={} in your application, with some presets,
# or updating it from a stored config file:
#
@@ -39,31 +39,46 @@
# Instantiate plugin modules based on load conf[plugins] state:
#
# for module in pluginconf.bind.load_enabled(conf):
# module.register(main_window)
#
-# Using a simple init function often suffices, if plugins don't
+# Electing a simple init function often suffices, if plugins don't
# register themselves. Alternatively use a class name aligned with
# the plugin basename to disover it, or dir(), or similar such.
+# (This is what "pluginconf imposes no API" means: you still have
+# to decide on a convention.)
#
# If you want users to update settings or plugin states, use the
# .window module:
#
# pluginconf.gui.window(conf, conf["plugins"], files=["plugins/*.py"])
# json.dump(conf, open("app.json", "w"))
#
-# Alternatively there's the load() for known plugin names, or find()
+# Alternatively there's load() for well-known plugin names, or find()
# to uncover them based on descriptors. Or isolated() to instantiate
# from a different set.
#
-# Notably the whole setup makes most sense if you have user-supplied
-# plugin and some repository to fetch/update new ones from. (Out of
-# scope here, but a zip/pyz download and extract might suffice). If
-# so, entangle the alternative directory or pyz to be scanned:
+# Notably the whole effort makes most sense if you have user-supplied
+# plugins and some repository to fetch/update new ones from. (Optional
+# meta descriptions are quite suitable to signify beta or experimental
+# extensions). If so, entangle the alternative directory to be scanned:
#
# pluginconf.bind.base("plugins", dir="~/.config/app/usrplugs/")
# pluginconf.bind.defaults(conf) # update
+#
+# A simpler user plugin mechanism might just download a zip:
+#
+# usr_pyz = f"{os.environ['HOME']}/.config/app/community.pyz"
+# with open(usr_pyz, "w") as write:
+# write.write(
+# requests.get("http://example.org/usr-plugins.zip").content
+# )
+#
+# And register that as pyz instead (on startup):
+#
+# if os.path.exists(usr_pyz):
+# pluginconf.bind.base("plugins", dir=usr_pyz)
#
# With PySimpleGUI, `conf` could be a psg.UserSettings("app.json") for
# automatic settings+update storage, btw.
#
@@ -95,11 +110,11 @@
### 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)
+ 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 `pluginconf.gui.window()` usage.)
@@ -109,10 +124,11 @@
"""
import os
import sys
import importlib
+import functools
import pluginconf
#-- reset pluginconf if .bind is used
pluginconf.plugin_base = []
@@ -150,11 +166,11 @@
| path | str | Bind directory or pyz/zip bundle to plugin_base. |
| **Returns** | None | - |
Module should be a package, as in a directory and init `plugins/__init__.py`.
Ideally this module was already imported in main. But parameter may be a string.
-
+
This could be invoked multiple times for the package name to append further
path= arguments (=./contrib/, =/usr/share/app/extenstions/, or a .pyz). Which
is functionally identical to delcaring `__path__` in the `package/__init__.py`.
"""
@@ -278,8 +294,22 @@
""" *return* defaults for isolated plugin structure 🧩 🧾 """
conf = {"plugins": {}}
defaults(conf)
return conf
+
+def _enable_cache(state=True):
+ """
+ Reduce plugin_meta() lookup costs, suitable for repeat find() calls
+ """
+ if hasattr(pluginconf.plugin_meta, "__wrapped__"):
+ if state:
+ return
+ pluginconf.plugin_meta = pluginconf.plugin_meta.__wrapped__
+ elif state:
+ decorator = functools.lru_cache(maxsize=None)
+ pluginconf.plugin_meta = decorator(pluginconf.plugin_meta)
+
def _dirname(file):
+ """ absolute dirname for file """
return os.path.dirname(os.path.realpath(file))
Index: pluginconf/depends.py
==================================================================
--- pluginconf/depends.py
+++ pluginconf/depends.py
@@ -58,18 +58,16 @@
While .depends() compares minimum versions against existing modules.
In practice there's little need for full-blown dependency resolving
for application-level modules.
- Attributes
- ----------
- api : list
- allowed api: identifiers for .valid() stream checks
- log : logging
- warning handler
- have : dict
- accumulated list of existing/virtual plugins
+ | Attributes | | |
+ |------------|---------|-----------------------------------------------------|
+ | api | list | allowed api: identifiers for .valid() stream checks |
+ | system_deps| bool | check `bin:app` or `python:package` dependencies |
+ | log | logging | warning handler |
+ | have | dict | accumulated list of existing/virtual plugins |
"""
# supported APIs
api = ["python", "streamtuner2"]
@@ -81,17 +79,14 @@
def __init__(self, add=None, core=["st2", "uikit", "config", "action"]):
"""
Prepare list of known plugins and versions in self.have={}
- Parameters
- ----------
- add : dict
- name to pmd list of existing/core/virtual plugins (can define
- versions or own dependencies)
- core : list
- name list of virtual plugins
+ | Parameters | | |
+ |------------|---------|------------------------------------------------------|
+ | add | dict | name→pmd of existing/core plugins (incl ver or deps) |
+ | core | list | name list of virtual plugins |
"""
self.have = {
"python": {"version": sys.version}
}
@@ -121,10 +116,15 @@
def valid(self, new_plugin):
"""
Plugin pre-screening from online repository stream.
Fields are $name, $file, $dist, api, id, depends, etc
Exclude installed or for newer-version presence.
+
+ | Parameters | | |
+ |-------------|---------|------------------------------------------------------|
+ | new_plugin | dict | online properties of available plugin |
+ | **Returns** | bool | is updatatable |
"""
if not "$name" in new_plugin:
self.log.warning(".valid() checks online plugin lists, requires $name")
name = new_plugin.get("$name", "__invalid")
have_ver = self.have.get(name, {}).get("version", "0")
@@ -139,11 +139,18 @@
else:
return True
return False
def depends(self, plugin):
- """ Verify depends: and breaks: against existing plugins/modules """
+ """
+ Verify depends: and breaks: against existing plugins/modules
+
+ | Parameters | | |
+ |-------------|---------|------------------------------------------------------|
+ | plugin | dict | plugin meta properties of (new?) plugin |
+ | **Returns** | bool | matches up with existing .have{} installation |
+ """
result = True
if plugin.get("depends"):
result &= self.and_or(self.split(plugin["depends"]), self.have)
if plugin.get("breaks"):
result &= self.neither(self.split(plugin["breaks"]), self.have)
Index: pluginconf/flit.py
==================================================================
--- pluginconf/flit.py
+++ pluginconf/flit.py
@@ -35,11 +35,11 @@
pyproject.toml |
foobar/__init__.py |
[build-system]
-requires = ["pluginconf", "flit]
+requires = ["pluginconf", "flit"]
build-backend = "pluginconf.flit"
[project]
name = "foobar"
dynamic = ["*"]
Index: pluginconf/gui.py
==================================================================
--- pluginconf/gui.py
+++ pluginconf/gui.py
@@ -50,30 +50,20 @@
Where `plugin_states{}` is usually an entry in `config{}` itself. Depending on plugin
and option names, it might even be a flat/shared namespace for both. Per default you'd
set `files=["plugins/*.py", __file__]` to be read. But with `files=[]` it's possible to
provide a `plugins=pluginconf.get_plugin_meta()` or prepared plugin/options dict instead.
- Parameters
- ----------
- config : dict 🔁
- Config settings, updated after dialog completion
- plugin_states : dict 🔁
- Plugin activation states, also input/output
- files : list
- Glob list of *.py files to extract meta definitions from
- plugins : dict
- Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected
- opt_label : bool
- Show config name= as label
- theme : str
- Set PSG window theme.
- **kwargs : dict
- Other options are passed on to PySimpleGUI
-
- Returns
- -------
- True : if changed config{} values are to be saved (the dict will be updated in any case)
+ | Params | | |
+ |---------------|---------|---------------------------------------------------------|
+ | config | dict 🔁 | Config settings, updated after dialog completion |
+ | plugin_states | dict 🔁 | Plugin activation states, also input/output |
+ | files | list | Glob list of *.py files to extract meta definitions from|
+ | plugins | dict | Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected |
+ | opt_label | bool | Show config name= as label (instead of description) |
+ | theme | str | Set PSG window theme. |
+ | **kwargs | dict | Other options are passed on to PySimpleGUI |
+ | **Returns** | True | if updated config{} values should be [Saved] |
"""
plugins = kwargs.get("plugins", {})
opt_label = kwargs.get("opt_label", False)
theme = kwargs.get("theme", "DefaultNoMoreNagging")
if theme:
@@ -112,11 +102,11 @@
return True
#print(config, plugin_states)
def plugin_layout(pmd_list, config, plugin_states, opt_label=False):
- """ craft list of widgets for each read plugin """
+ """ craft list of widgets: \*( `plugin_entry`, \*`option_entry` )"""
layout = []
for plg in pmd_list:
#print(plg.get("id"))
layout = layout + plugin_entry(plg, plugin_states)
for opt in plg["config"]:
Index: pluginconf/setup.py
==================================================================
--- pluginconf/setup.py
+++ pluginconf/setup.py
@@ -201,18 +201,15 @@
def setup(filename=None, debug=False, **kwargs):
"""
Wrapper around `setuptools.setup()` which adds some defaults
and plugin meta data import, with some shortcut parameters:
- Parameters
- ----------
- filename : str
- main file "pkg/main.py" (else deduced from primary package name)
- debug : bool
- display collected options prior setuptools.setup() invocation
- long_description : str
- e.g. "README.md", else comment block used
+ | Parameters | | |
+ |-------------|--------|------------------------------------------------|
+ | filename | str | main file "pkg/main.py" (else deduced from primary package name) |
+ | debug | bool | display collected options prior setuptools.setup() invocation |
+ |long_description| str | e.g. "README.md", else the comment block gets used |
Other setup() params work as usual, and are passed trough. Notably
entry_points= or data_files= can be used, even if they get augmented.
"""
|
|