Overview
Comment: | use table markup for api docs |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
cd360e316ff48b02a1776025bbf630c3 |
User & Date: | mario on 2022-10-30 15:03:17 |
Other Links: | manifest | tags |
Context
2022-10-30
| ||
15:54 | update template (RTD-like) check-in: feb199bddf user: mario tags: trunk | |
15:03 | use table markup for api docs check-in: cd360e316f user: mario tags: trunk | |
09:13 | doc updates, minor test changes (reset/tearDown) check-in: d8c4b46a36 user: mario tags: trunk | |
Changes
Modified html/bind.html from [f4a4553477] to [6d1581117d].
︙ | ︙ | |||
67 68 69 70 71 72 73 | <h2 class="section-title" id="header-functions">Functions</h2> <dl> <dt id="pluginconf.bind.base"><code class="name flex"> <span>def <span class="ident">base</span></span>(<span>module, path=None)</span> </code></dt> <dd> <div class="desc"><p>Register module as package/plugin_base. Or expand its search path 🛠 .</p> | > > > | > > | > > > > | | > > > > > > > > > > > > > | | < < > | < | < > > > | > > | > > > > | | > > > > > > > | | > > > | > > | > > > | > > > > > > > > > | < > | | > | | | | > | | > > > > | > > | > > > | > | < > > > > > > > | > > > | > > | > > > | > | < < < > > > > > > > | | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | <h2 class="section-title" id="header-functions">Functions</h2> <dl> <dt id="pluginconf.bind.base"><code class="name flex"> <span>def <span class="ident">base</span></span>(<span>module, path=None)</span> </code></dt> <dd> <div class="desc"><p>Register module as package/plugin_base. Or expand its search path 🛠 .</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>module</td> <td>module/str</td> <td>Package basename to later load plugins from</td> </tr> <tr> <td>path</td> <td>str</td> <td>Bind directory or pyz/zip bundle to plugin_base.</td> </tr> <tr> <td><strong>Returns</strong></td> <td>None</td> <td>-</td> </tr> </tbody> </table> <p>Module should be a package, as in a directory and init <code>plugins/__init__.py</code>. Ideally this module was already imported in main. But parameter may be a string.</p> <p>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 <code>__path__</code> in the <code>package/__init__.py</code>.</p></div> </dd> <dt id="pluginconf.bind.defaults"><code class="name flex"> <span>def <span class="ident">defaults</span></span>(<span>conf)</span> </code></dt> <dd> <div class="desc"><p>Traverse installed plugins and expand config dict with presets 🧩 🧾</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>conf</td> <td>dict 🔁</td> <td>Expands the conf dict with preset values from any plugins.</td> </tr> <tr> <td><strong>Returns</strong></td> <td>None</td> <td>-</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.bind.find"><code class="name flex"> <span>def <span class="ident">find</span></span>(<span>**kwargs)</span> </code></dt> <dd> <div class="desc"><p>Find plugins by any combination of e.g. type= or category= 🧩</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>type</td> <td>str</td> <td>Search by type: in plugins</td> </tr> <tr> <td>api</td> <td>str</td> <td>Matching api: designator</td> </tr> <tr> <td>category</td> <td>str</td> <td>Or a menu/category or other attributes</td> </tr> <tr> <td><strong>Returns</strong></td> <td>dict</td> <td>basename → <code>PluginMeta</code> dict of matches</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.bind.load"><code class="name flex"> <span>def <span class="ident">load</span></span>(<span>name)</span> </code></dt> <dd> <div class="desc"><p>Import individual plugin from any of the base paths 🚐 (The whole namespace is assumed to be flat, and identifiers to be unique.)</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>name</td> <td>str</td> <td>Plugin name in any of the registered plugin_base´s.</td> </tr> <tr> <td><strong>Returns</strong></td> <td>module</td> <td>Imported module</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.bind.load_enabled"><code class="name flex"> <span>def <span class="ident">load_enabled</span></span>(<span>conf)</span> </code></dt> <dd> <div class="desc"><p>Import modules that are enabled in conf[plugins]={name:True,…} 🧾 🚐</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>conf</td> <td>dict</td> <td>Should contain conf["plugins"] activation states</td> </tr> <tr> <td><strong>Returns</strong></td> <td>generator</td> <td>Of loaded modules</td> </tr> </tbody> </table></div> </dd> </dl> </section> <section> <h2 class="section-title" id="header-classes">Classes</h2> <dl> <dt id="pluginconf.bind.isolated"><code class="flex name class"> |
︙ | ︙ |
Modified html/index.html from [9345b17570] to [0bfaf66345].
︙ | ︙ | |||
28 29 30 31 32 33 34 | </td> <td> <li> Main function <a href="#pluginconf.plugin_meta">plugin_meta()</a> unpacks meta fields into dictionaries. <li> Other utility code is about module listing, relative to <a href="#pluginconf.plugin_base">plugin_base</a> anchors. <li> <a href="https://pypi.org/project/pluginconf/">//pypi.org/project/pluginconf/</a> | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | </td> <td> <li> Main function <a href="#pluginconf.plugin_meta">plugin_meta()</a> unpacks meta fields into dictionaries. <li> Other utility code is about module listing, relative to <a href="#pluginconf.plugin_base">plugin_base</a> anchors. <li> <a href="https://pypi.org/project/pluginconf/">//pypi.org/project/pluginconf/</a> <li><a href="https://fossil.include-once.org/pluginspec/">//fossil.include-once.org/pluginspec/</a> </td></tr></table> </section> <section> <h2 class="section-title" id="header-submodules">Sub-modules</h2> <dl> <dt><code class="name"><a title="pluginconf.bind" href="bind.html">pluginconf.bind</a></code></dt> <dd> |
︙ | ︙ | |||
89 90 91 92 93 94 95 | <dt id="pluginconf.add_plugin_defaults"><code class="name flex"> <span>def <span class="ident">add_plugin_defaults</span></span>(<span>conf_options, conf_plugins, meta, module='')</span> </code></dt> <dd> <div class="desc"><p>Utility function which collect defaults from plugin meta data to a config store. Which in the case of streamtuner2 is really just a dictionary <code>conf{}</code> and a plugin list in <code>conf.plugins{}</code>.</p> | > > > | > > | > > > | > | > > | > | > > | > | > > | > | > > > > > > > | > > > > > > > > > > | | | | > | > > > | > > | > > > | > | > > | > | > > | > | > > | > | | > | | | | > | > > > | > > | > > > | > | | > | | | | > | > > > | > > | > > > | > | > > | > | > > | > | > > | > | > > | > | > > | > | | > | | | | | > | | | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | <dt id="pluginconf.add_plugin_defaults"><code class="name flex"> <span>def <span class="ident">add_plugin_defaults</span></span>(<span>conf_options, conf_plugins, meta, module='')</span> </code></dt> <dd> <div class="desc"><p>Utility function which collect defaults from plugin meta data to a config store. Which in the case of streamtuner2 is really just a dictionary <code>conf{}</code> and a plugin list in <code>conf.plugins{}</code>.</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>conf_options</td> <td>dict 🔁</td> <td>storage for amassed options</td> </tr> <tr> <td>conf_plugins</td> <td>dict 🔁</td> <td>enable status based on plugin state/priority:</td> </tr> <tr> <td>meta</td> <td>dict</td> <td>input plugin meta data (invoke once per plugin)</td> </tr> <tr> <td>module</td> <td>str</td> <td>basename of meta: blocks plugin file</td> </tr> <tr> <td><strong>Returns</strong></td> <td>None</td> <td>-</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.all_plugin_meta"><code class="name flex"> <span>def <span class="ident">all_plugin_meta</span></span>(<span>)</span> </code></dt> <dd> <div class="desc"><p>This is a trivial wrapper to assemble a complete dictionary of available/installed plugins. It associates each plugin name with a its meta{} fields.</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><strong>Returns</strong></td> <td>dict</td> <td>names to meta data dict</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.get_data"><code class="name flex"> <span>def <span class="ident">get_data</span></span>(<span>filename, decode=False, gzip=False, file_root=None)</span> </code></dt> <dd> <div class="desc"><p>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 data_root as top-level reference.</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>filename</td> <td>str</td> <td>filename in pyz or bundle</td> </tr> <tr> <td>decode</td> <td>bool</td> <td>text file decoding utf-8</td> </tr> <tr> <td>gzip</td> <td>bool</td> <td>automatic gzdecode</td> </tr> <tr> <td>file_root</td> <td>list</td> <td>alternative base module (application or pyz root)</td> </tr> <tr> <td><strong>Returns</strong></td> <td>str</td> <td>file contents</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.module_list"><code class="name flex"> <span>def <span class="ident">module_list</span></span>(<span>extra_paths=None)</span> </code></dt> <dd> <div class="desc"><p>Search through ./plugins/ (and other configured plugin_base names → paths) and get module basenames.</p> <table> <thead> <tr> <th>Parameter</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>extra_paths</td> <td>list</td> <td>in addition to plugin_base list</td> </tr> <tr> <td><strong>Returns</strong></td> <td>list</td> <td>names of found plugins</td> </tr> </tbody> </table></div> </dd> <dt id="pluginconf.plugin_meta"><code class="name flex"> <span>def <span class="ident">plugin_meta</span></span>(<span>filename=None, src=None, module=None, frame=1, **kwargs)</span> </code></dt> <dd> <div class="desc"><p>Extract plugin meta data block from specified source.</p> <table> <thead> <tr> <th>Parameters</th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>filename</td> <td>str</td> <td>Read literal files, or .pyz contents.</td> </tr> <tr> <td>src</td> <td>str</td> <td>From already uncovered script code.</td> </tr> <tr> <td>module</td> <td>str</td> <td>Lookup per pkgutil, relative to plugin_base</td> </tr> <tr> <td>frame</td> <td>int</td> <td>Extract comment header of caller (default).</td> </tr> <tr> <td>extra_base</td> <td>list</td> <td>Additional search directories.</td> </tr> <tr> <td>max_length</td> <td>list</td> <td>Maximum size to read from files (6K default).</td> </tr> <tr> <td><strong>Returns</strong></td> <td>dict</td> <td>Extracted comment fields, with config: preparsed</td> </tr> </tbody> </table> <p>The result dictionary (<code><a title="pluginconf.PluginMeta" href="#pluginconf.PluginMeta">PluginMeta</a></code>) has fields accessible as e.g. <code>pmd["title"]</code> or <code>pmd.version</code>. The documentation block after all fields: is called <code>["doc"]</code>. And <code>pmd.config</code> already parsed as a list (<code><a title="pluginconf.OptionList" href="#pluginconf.OptionList">OptionList</a></code>) of dictionaries.</p></div> </dd> </dl> </section> <section> <h2 class="section-title" id="header-classes">Classes</h2> <dl> <dt id="pluginconf.OptionList"><code class="flex name class"> |
︙ | ︙ |
Modified pluginconf/__init__.py from [5fc579b1c8] to [9584d270b3].
︙ | ︙ | |||
97 98 99 100 101 102 103 | </td> <td> <li> Main function <a href="#pluginconf.plugin_meta">plugin_meta()</a> unpacks meta fields into dictionaries. <li> Other utility code is about module listing, relative to <a href="#pluginconf.plugin_base">plugin_base</a> anchors. <li> <a href="https://pypi.org/project/pluginconf/">//pypi.org/project/pluginconf/</a> | | | 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | </td> <td> <li> Main function <a href="#pluginconf.plugin_meta">plugin_meta()</a> unpacks meta fields into dictionaries. <li> Other utility code is about module listing, relative to <a href="#pluginconf.plugin_base">plugin_base</a> anchors. <li> <a href="https://pypi.org/project/pluginconf/">//pypi.org/project/pluginconf/</a> <li><a href="https://fossil.include-once.org/pluginspec/">//fossil.include-once.org/pluginspec/</a> </td></tr></table> """ import sys import os import os.path |
︙ | ︙ | |||
189 190 191 192 193 194 195 | 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 data_root as top-level reference. | | | < | < | < | < | < < < | | < | | < | < < < | | | | | | | < | < | < | < | < | < | < < < | | | | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | 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 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_root | list | alternative base module (application or pyz root) | | **Returns** | str | file contents | """ try: data = pkgutil.get_data(file_root or data_root, filename) if gzip: data = gzip_decode(data) if decode: return data.decode("utf-8", errors='ignore') return str(data) except: #(FileNotFoundError, IOError, OSError, ImportError, gzip.BadGzipFile): log.warning("get_data() didn't find '%s' in '%s'", filename, file_root) # Plugin name lookup # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ def module_list(extra_paths=None): """ Search through ./plugins/ (and other configured plugin_base names → paths) and get module basenames. | Parameter | | | |-------------|---------|---------------------------------| | extra_paths | list | in addition to plugin_base list | | **Returns** | list | names of found plugins | """ # Convert plugin_base package names into paths for iter_modules paths = [] for module_or_path in plugin_base: if sys.modules.get(module_or_path): try: paths += sys.modules[module_or_path].__path__ except AttributeError: paths += os.path.dirname(os.path.realpath( sys.modules[module_or_path] )) elif os.path.exists(module_or_path): paths.append(module_or_path) # Should list plugins within zips as well as local paths dirs = pkgutil.iter_modules(paths + (extra_paths or [])) return [name for loader, name, ispkg in dirs] # Plugin => meta dict # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ def all_plugin_meta(): """ This is a trivial wrapper to assemble a complete dictionary of available/installed plugins. It associates each plugin name with a its meta{} fields. | Parameters | | | |-------------|---------|---------------------------------| | **Returns** | dict | names to meta data dict | """ return { name: plugin_meta(module=name) for name in module_list() } # Plugin meta data extraction # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ @renamed_arguments({"fn": "filename"}) def plugin_meta(filename=None, src=None, module=None, frame=1, **kwargs): """ Extract plugin meta data block from specified source. | Parameters | | | |-------------|---------|-------------------------------------------------| | filename | str | Read literal files, or .pyz contents. | | src | str | From already uncovered script code. | | module | str | Lookup per pkgutil, relative to plugin_base | | frame | int | Extract comment header of caller (default). | | extra_base | list | Additional search directories. | | max_length | list | Maximum size to read from files (6K default). | | **Returns** | dict | Extracted comment fields, with config: preparsed| The result dictionary (`PluginMeta`) 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 (`OptionList`) of dictionaries. """ # Try via pkgutil first, # find any plugins.* modules, or main packages if module: filename = module for base in plugin_base + kwargs.get("extra_base", []): |
︙ | ︙ | |||
348 349 350 351 352 353 354 | @renamed_arguments({"fn": "filename"}) def plugin_meta_extract(src="", filename=None, literal=False): """ Finds the first comment block. Splits key:value header fields from comment. Turns everything into an dict, with some stub fields if absent. Dashes substituted for underscores. | | | < | < | < | > | 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | @renamed_arguments({"fn": "filename"}) def plugin_meta_extract(src="", filename=None, literal=False): """ Finds the first comment block. Splits key:value header fields from comment. Turns everything into an dict, with some stub fields if absent. Dashes substituted for underscores. | Parameters | | | |-------------|---------|---------------------------------| | src | str | from existing source code | | filename | str | set filename attribute | | literls | bool | just split comment from doc | | **Returns** | dict | fields | """ # Defaults meta = { "id": os.path.splitext(os.path.basename(filename or ""))[0], "fn": filename, "api": "python", |
︙ | ︙ | |||
378 379 380 381 382 383 384 | } # Extract coherent comment block src = src.replace("\r", "") if not literal: src = rx.comment.search(src) if not src: | | | 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | } # Extract coherent comment block src = src.replace("\r", "") if not literal: src = rx.comment.search(src) if not src: log.warning("Couldn't read source meta information: %s", filename) return meta src = src.group(0) src = rx.hash.sub("", src).strip() # Split comment block if src.find("\n\n") > 0: src, meta["doc"] = src.split("\n\n", 1) |
︙ | ︙ | |||
439 440 441 442 443 444 445 | Creates an array from JSON/YAML option lists. Stubs out name, value, type, description if absent. # config: { name: 'var1', type: text, value: "default, ..." } { name=option2, type=boolean, $value=1, etc. } | | | < | < < < | | 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 | Creates an array from JSON/YAML option lists. Stubs out name, value, type, description if absent. # config: { name: 'var1', type: text, value: "default, ..." } { name=option2, type=boolean, $value=1, etc. } | Parameters | | | |-------------|---------|--------------------------------------| | src | str | unprocessed config: field | | **Returns** | list | of option dictionaries | """ config = [] for entry in rx.config.findall(src): entry = entry[0] or entry[1] opt = { "type": None, |
︙ | ︙ | |||
580 581 582 583 584 585 586 | # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 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{}`. | | | < | < | < | < | > | | 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 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{}`. | Parameters | | | |-------------|---------|--------------------------------------| |conf_options | dict 🔁 | storage for amassed options | |conf_plugins | dict 🔁 | enable status based on plugin state/priority: | |meta | dict | input plugin meta data (invoke once per plugin)| |module | str | basename of meta: blocks plugin file | | **Returns** | None | - | """ # Option defaults, if not yet defined for opt in meta.get("config", []): if "name" not in opt or "value" not in opt: continue _value = opt.get("value") or "" _name = opt.get("name") _type = opt.get("type") if _name in conf_options: continue # typemap if _type == "bool": val = _value.lower() in ("1", "true", "yes", "on") |
︙ | ︙ |
Modified pluginconf/bind.py from [8927125c8f] to [ce733ed382].
︙ | ︙ | |||
116 117 118 119 120 121 122 123 | #-- reset pluginconf if .bind is used pluginconf.plugin_base = [] def load(name): """ Import individual plugin from any of the base paths 🚐 | > | | < | | | | < | > > | > | | < > | < | | | | | | < < | < < | | | < < | < < > | | < | > | 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | #-- reset pluginconf if .bind is used pluginconf.plugin_base = [] def load(name): """ Import individual plugin from any of the base paths 🚐 (The whole namespace is assumed to be flat, and identifiers to be unique.) | Parameters | | | |-------------|--------|-------------------------------| | name | str | Plugin name in any of the registered plugin_base´s. | | **Returns** | module | Imported module | """ for package in pluginconf.plugin_base: if package in ("", "."): continue module_name = package + "." + name if module_name in sys.modules: return sys.modules[module_name] try: return importlib.import_module(module_name) except ImportError: pass def base(module, path=None): """ Register module as package/plugin_base. Or expand its search path 🛠 . | Parameters | | | |-------------|------------|-------------------------------| | module | module/str | Package basename to later load plugins from | | 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`. """ # if supplied as string, instead of active module if isinstance(module, str): module = sys.modules.get(module) or __import__(module) # add to search list if module.__name__ not in pluginconf.plugin_base: pluginconf.plugin_base.append(module.__name__) # enjoin dir or pyz if not hasattr(module, "__path__"): module.__path__ = [_dirname(module.__file__)] if path: module.__path__.append(_dirname(path)) def find(**kwargs): """ Find plugins by any combination of e.g. type= or category= 🧩 | Parameters | | | |-------------|-----------|-------------------------------------------| | type | str | Search by type: in plugins | | api | str | Matching api: designator | | category | str | Or a menu/category or other attributes | | **Returns** | dict | basename → `PluginMeta` dict of matches | """ def has_all(pmd): for key, dep in kwargs.items(): if not pmd.get(key) == dep: break else: return True return pluginconf.PluginMeta({ name: pmd for name, pmd in pluginconf.all_plugin_meta().items() if has_all(pmd) }) def load_enabled(conf): """ Import modules that are enabled in conf[plugins]={name:True,…} 🧾 🚐 | Parameters | | | |-------------|-----------|-------------------------------------------| | conf | dict | Should contain conf["plugins"] activation states | | **Returns** | generator | Of loaded modules | """ for name, state in conf.get("plugins", conf).items(): if not state: continue if name.startswith("_"): continue yield load(name) def defaults(conf): """ Traverse installed plugins and expand config dict with presets 🧩 🧾 | Parameters | | | |-------------|-----------|-------------------------------------------| | conf | dict 🔁 | Expands the conf dict with preset values from any plugins. | | **Returns** | None | - | """ for name, pmd in pluginconf.all_plugin_meta().items(): pluginconf.add_plugin_defaults(conf, conf["plugins"], pmd, name) # pylint: disable=invalid-name class isolated(): |
︙ | ︙ |
Modified test/bind.py from [bdb0475b8c] to [02e2635fa3].
︙ | ︙ | |||
10 11 12 13 14 15 16 | import logging logging.basicConfig(level=logging.DEBUG) import pluginconf print(pluginconf.plugin_base) import pluginconf.bind | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import logging logging.basicConfig(level=logging.DEBUG) import pluginconf print(pluginconf.plugin_base) import pluginconf.bind def init(): # pluginconf.plugin_base = [] import test.plugins pluginconf.bind.base(test.plugins) assert "test.plugins" in pluginconf.plugin_base assert test.plugins.__path__ != [] def test_first(): |
︙ | ︙ |