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: cd360e316ff48b02a1776025bbf630c32b52d2da5a7380477c3bc1146476307b
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



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
<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>



<h2 id="parameters">Parameters</h2>


<dl>




<dt><strong><code>module</code></strong> :&ensp;<code>module/str</code></dt>
<dd>The package basename to later load plugins from (must be a package,













like <code>plugins/__init__.py</code>, or be tied to a path= or zip). Ideally
this module was already imported in main. But parameter may be a string.</dd>
<dt><strong><code>path</code></strong> :&ensp;<code>str</code></dt>
<dd>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 <code>__path__</code> in the
base <code>package/__init__.py</code>.)</dd>
</dl></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>



<h2 id="parameters">Parameters</h2>


<dl>




<dt><strong><code>conf</code></strong> :&ensp;<code>dict 🔁</code></dt>
<dd>Expands the top-level config dict with preset values from any plugins.</dd>







</dl></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 e.g. type= or category= 🧩</p>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>type</code></strong> :&ensp;<code>str</code></dt>









<dd>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.)</dd>
</dl>

<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>dict</code></strong> :&ensp;<code>basename → PluginMeta dict</code></dt>
<dd>&nbsp;</dd>

</dl></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 🚐</p>




<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>name</code></strong> :&ensp;<code>str</code></dt>

<dd>Plugin name in any of the registered plugin_base´s. (The whole
namespace is assumed to be flat, and identifiers to be unique.)</dd>







</dl></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>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>conf</code></strong> :&ensp;<code>dict</code></dt>

<dd>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.</dd>







</dl></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">







>
>
>
|
>
>
|
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
<
<
>
|
<
|
<






>
>
>
|
>
>
|
>
>
>
>
|
|
>
>
>
>
>
>
>
|





|
>
>
>
|
>
>
|
>
>
>
|
>
>
>
>
>
>
>
>
>
|
<
>
|
|
>
|
|
|
|
>
|





|
>
>
>
>
|
>
>
|
>
>
>
|
>
|
<
>
>
>
>
>
>
>
|






>
>
>
|
>
>
|
>
>
>
|
>
|
<
<
<
>
>
>
>
>
>
>
|







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
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>







|







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



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
<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>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>conf_options</code></strong> :&ensp;<code>dict : input/output</code></dt>

<dd>storage for amassed options</dd>


<dt><strong><code>conf_plugins</code></strong> :&ensp;<code>dict : input/output</code></dt>

<dd>enable status based on plugin state/priority:</dd>


<dt><strong><code>meta</code></strong> :&ensp;<code>dict</code></dt>

<dd>input plugin meta data (invoke once per plugin)</dd>


<dt><strong><code>module</code></strong> :&ensp;<code>str</code></dt>

<dd>basename of meta: blocks plugin file</dd>







</dl></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>










<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>dict</code></strong> :&ensp;<code>names to meta data dict</code></dt>
<dd>&nbsp;</dd>

</dl></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>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>filename</code></strong> :&ensp;<code> str</code></dt>

<dd>filename in pyz or bundle</dd>


<dt><strong><code>decode</code></strong> :&ensp;<code>bool</code></dt>

<dd>text file decoding utf-8</dd>


<dt><strong><code>gzip</code></strong> :&ensp;<code>bool</code></dt>

<dd>automatic gzdecode</dd>


<dt><strong><code>file_root</code></strong> :&ensp;<code>list</code></dt>

<dd>alternative base module (application or pyz root)</dd>
</dl>

<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>str</code></strong> :&ensp;<code>file contents</code></dt>
<dd>&nbsp;</dd>

</dl></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>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>extra_paths</code></strong> :&ensp;<code>list</code></dt>

<dd>in addition to plugin_base list</dd>
</dl>

<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>list</code></strong> :&ensp;<code>names</code> of <code>found plugins</code></dt>
<dd>&nbsp;</dd>

</dl></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>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>filename</code></strong> :&ensp;<code>str</code></dt>

<dd>Read literal files, or .pyz contents.</dd>


<dt><strong><code>src</code></strong> :&ensp;<code>str</code></dt>

<dd>From already uncovered script code.</dd>


<dt><strong><code>module</code></strong> :&ensp;<code>str</code></dt>

<dd>Lookup per pkgutil, from plugin_base or top-level modules.</dd>


<dt><strong><code>frame</code></strong> :&ensp;<code>int</code></dt>

<dd>Extract comment header of caller (default).</dd>


<dt><strong><code>extra_base</code></strong> :&ensp;<code>list</code></dt>

<dd>Additional search directories.</dd>


<dt><strong><code>max_length</code></strong> :&ensp;<code>list</code></dt>

<dd>Maximum size to read from files (6K default).</dd>
</dl>

<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>dict</code></strong> :&ensp;<code>Extracted comment fields, with config: preparsed</code></dt>
<dd>&nbsp;</dd>
</dl>

<p>The result dictionary has fields accessible as e.g. <code>pmd["title"]</code>
or <code>pmd.version</code>. The documentation block after all fields: is called
["doc"]<code>. And </code>pmd.config` already parsed as a list 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">







>
>
>
|
>
>
|
>
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
>
>
>
>
>
|








>
>
>
>
>
>
>
>
>
>
|
|
|
|
>
|









>
>
>
|
>
>
|
>
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
|
>
|
|
|
|
>
|







>
>
>
|
>
>
|
>
>
>
|
>
|
|
>
|
|
|
|
>
|






>
>
>
|
>
>
|
>
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
|
>
|
|
|
|
|
>
|

|







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
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







|







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
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
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.error("get_data() didn't find '%s' in '%s'", filename, file_root)
        pass


# Plugin name lookup
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def module_list(extra_paths=None):
    """
    Search through ./plugins/ (and other configured plugin_base
    names → paths) and get module basenames.

    Parameters
    ----------
    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.realname(
                    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.

    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, from plugin_base or top-level modules.
    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 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.
    """

    # Try via pkgutil first,
    # find any plugins.* modules, or main packages
    if module:
        filename = module
        for base in plugin_base + kwargs.get("extra_base", []):







|
|
<
|
<
|
<
|
<
|
<
<
<
|









|
<









|
|
<
|
<
<
<
|









|


















|
|
|













|
|
<
|
<
|
<
|
<
|
<
|
<
|
<
<
<
|

|

|







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
355
356
357
358
359
360
361
362

363
364
365
366
367
368
369
@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

    """

    # Defaults
    meta = {
        "id": os.path.splitext(os.path.basename(filename or ""))[0],
        "fn": filename,
        "api": "python",







|
|
<
|
<
|
<
|
>







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
385
386
387
388
389
390
391
392
    }

    # Extract coherent comment block
    src = src.replace("\r", "")
    if not literal:
        src = rx.comment.search(src)
        if not src:
            log.warn("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)







|







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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
    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,







|
|
<
|
<
<
<
|







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
587
588
589
590
591
592
593
594
595
596

597
598
599
600
601
602
603
604
605
606
607
608
609
610
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
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 : input/output
        storage for amassed options
    conf_plugins : dict : input/output
        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

    """

    # 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", "")
        _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")







|
|
<
|
<
|
<
|
<
|
>






|







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
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
#-- reset pluginconf if .bind is used
pluginconf.plugin_base = []


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.)
    """

    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
        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`.)
    """

    # 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 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 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
        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.

    """
    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 top-level config dict with preset values from any plugins.

    """
    for name, pmd in pluginconf.all_plugin_meta().items():
        pluginconf.add_plugin_defaults(conf, conf["plugins"], pmd, name)


# pylint: disable=invalid-name
class isolated():







>

|
|
<
|
|


















|
|
<
|
>
>
|
>
|
|
<
>
|
<
|



















|

|
|
|
|
<
<
|
<
<
|


















|
|
<
<
|
<
<
>













|
|
<
|
>







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
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(reset):
#    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():







|







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():