2 * python-plugin-provider.c
4 * Babeltrace Python plugin provider
6 * Copyright 2017 Philippe Proulx <pproulx@efficios.com>
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 #define BT_LOG_TAG "LIB/PLUGIN-PY"
29 #include "lib/logging.h"
30 #include "common/macros.h"
31 #include "compat/compiler.h"
32 #include <babeltrace2/plugin/plugin-const.h>
33 #include "lib/plugin/plugin.h"
34 #include <babeltrace2/graph/component-class.h>
35 #include <babeltrace2/current-thread.h>
36 #include "lib/graph/component-class.h"
37 #include "py-common/py-common.h"
44 #define PYTHON_PLUGIN_FILE_PREFIX "bt_plugin_"
45 #define PYTHON_PLUGIN_FILE_PREFIX_LEN (sizeof(PYTHON_PLUGIN_FILE_PREFIX) - 1)
46 #define PYTHON_PLUGIN_FILE_EXT ".py"
47 #define PYTHON_PLUGIN_FILE_EXT_LEN (sizeof(PYTHON_PLUGIN_FILE_EXT) - 1)
50 /* init_python() not called yet */
51 PYTHON_STATE_NOT_INITED
,
53 /* init_python() called once with success */
54 PYTHON_STATE_FULLY_INITIALIZED
,
56 /* init_python() called once without success */
57 PYTHON_STATE_CANNOT_INITIALIZE
,
60 * init_python() called, but environment variable asks the
61 * Python interpreter not to be loaded.
63 PYTHON_STATE_WONT_INITIALIZE
,
64 } python_state
= PYTHON_STATE_NOT_INITED
;
66 static PyObject
*py_try_load_plugin_module_func
= NULL
;
67 static bool python_was_initialized_by_us
;
70 void append_python_traceback_error_cause(void)
74 if (Py_IsInitialized() && PyErr_Occurred()) {
75 exc
= bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL
);
77 BT_LOGE_STR("Failed to format Python exception.");
81 (void) BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_UNKNOWN(
82 "Babeltrace library", "%s", exc
->str
);
87 g_string_free(exc
, TRUE
);
92 void log_python_traceback(int log_level
)
96 if (Py_IsInitialized() && PyErr_Occurred()) {
97 exc
= bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL
);
99 BT_LOGE_STR("Failed to format Python exception.");
103 BT_LOG_WRITE(log_level
, BT_LOG_TAG
,
104 "Exception occured: Python traceback:\n%s", exc
->str
);
109 g_string_free(exc
, TRUE
);
114 void pyerr_clear(void)
116 if (Py_IsInitialized()) {
122 int init_python(void)
124 int ret
= BT_FUNC_STATUS_OK
;
125 PyObject
*py_bt2_py_plugin_mod
= NULL
;
126 const char *dis_python_env
;
128 sig_t old_sigint
= signal(SIGINT
, SIG_DFL
);
131 switch (python_state
) {
132 case PYTHON_STATE_NOT_INITED
:
134 case PYTHON_STATE_FULLY_INITIALIZED
:
136 case PYTHON_STATE_WONT_INITIALIZE
:
137 ret
= BT_FUNC_STATUS_NOT_FOUND
;
139 case PYTHON_STATE_CANNOT_INITIALIZE
:
140 ret
= BT_FUNC_STATUS_ERROR
;
147 * User can disable Python plugin support with the
148 * `LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS` environment variable
151 dis_python_env
= getenv("LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS");
152 if (dis_python_env
&& strcmp(dis_python_env
, "1") == 0) {
153 BT_LOGI_STR("Python plugin support is disabled because the "
154 "`LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS` environment "
155 "variable is set to `1`.");
156 python_state
= PYTHON_STATE_WONT_INITIALIZE
;
157 ret
= BT_FUNC_STATUS_NOT_FOUND
;
161 if (!Py_IsInitialized()) {
162 BT_LOGI_STR("Python interpreter is not initialized: initializing Python interpreter.");
164 python_was_initialized_by_us
= true;
165 BT_LOGI("Initialized Python interpreter: version=\"%s\"",
168 BT_LOGI("Python interpreter is already initialized: version=\"%s\"",
172 py_bt2_py_plugin_mod
= PyImport_ImportModule("bt2.py_plugin");
173 if (!py_bt2_py_plugin_mod
) {
174 append_python_traceback_error_cause();
175 BT_LIB_LOGW_APPEND_CAUSE(
176 "Cannot import `bt2.py_plugin` Python module: "
177 "Python plugin support is disabled.");
178 python_state
= PYTHON_STATE_CANNOT_INITIALIZE
;
179 ret
= BT_FUNC_STATUS_ERROR
;
183 py_try_load_plugin_module_func
=
184 PyObject_GetAttrString(py_bt2_py_plugin_mod
, "_try_load_plugin_module");
185 if (!py_try_load_plugin_module_func
) {
186 append_python_traceback_error_cause();
187 BT_LIB_LOGW_APPEND_CAUSE(
188 "Cannot get `_try_load_plugin_module` attribute from `bt2.py_plugin` Python module: "
189 "Python plugin support is disabled.");
190 python_state
= PYTHON_STATE_CANNOT_INITIALIZE
;
191 ret
= BT_FUNC_STATUS_ERROR
;
195 python_state
= PYTHON_STATE_FULLY_INITIALIZED
;
199 if (old_sigint
!= SIG_ERR
) {
200 (void) signal(SIGINT
, old_sigint
);
204 log_python_traceback(ret
== BT_FUNC_STATUS_ERROR
?
205 BT_LOG_WARNING
: BT_LOG_INFO
);
207 Py_XDECREF(py_bt2_py_plugin_mod
);
211 __attribute__((destructor
)) static
212 void fini_python(void) {
213 if (Py_IsInitialized() && python_was_initialized_by_us
) {
214 if (py_try_load_plugin_module_func
) {
215 Py_DECREF(py_try_load_plugin_module_func
);
216 py_try_load_plugin_module_func
= NULL
;
220 BT_LOGI_STR("Finalized Python interpreter.");
223 python_state
= PYTHON_STATE_NOT_INITED
;
227 int bt_plugin_from_python_plugin_info(PyObject
*plugin_info
,
228 bool fail_on_load_error
, bt_plugin
**plugin_out
)
230 int status
= BT_FUNC_STATUS_OK
;
231 PyObject
*py_name
= NULL
;
232 PyObject
*py_author
= NULL
;
233 PyObject
*py_description
= NULL
;
234 PyObject
*py_license
= NULL
;
235 PyObject
*py_version
= NULL
;
236 PyObject
*py_comp_class_addrs
= NULL
;
237 const char *name
= NULL
;
238 const char *author
= NULL
;
239 const char *description
= NULL
;
240 const char *license
= NULL
;
241 unsigned int major
= 0, minor
= 0, patch
= 0;
242 const char *version_extra
= NULL
;
244 BT_ASSERT(plugin_out
);
246 BT_ASSERT(plugin_info
);
247 BT_ASSERT(python_state
== PYTHON_STATE_FULLY_INITIALIZED
);
248 py_name
= PyObject_GetAttrString(plugin_info
, "name");
250 if (fail_on_load_error
) {
251 append_python_traceback_error_cause();
252 BT_LIB_LOGW_APPEND_CAUSE(
253 "Cannot find `name` attribute in Python plugin info object: "
254 "py-plugin-info-addr=%p", plugin_info
);
255 status
= BT_FUNC_STATUS_ERROR
;
258 "Cannot find `name` attribute in Python plugin info object: "
259 "py-plugin-info-addr=%p", plugin_info
);
260 status
= BT_FUNC_STATUS_NOT_FOUND
;
266 py_author
= PyObject_GetAttrString(plugin_info
, "author");
268 if (fail_on_load_error
) {
269 append_python_traceback_error_cause();
270 BT_LIB_LOGW_APPEND_CAUSE(
271 "Cannot find `author` attribute in Python plugin info object: "
272 "py-plugin-info-addr=%p", plugin_info
);
273 status
= BT_FUNC_STATUS_ERROR
;
276 "Cannot find `author` attribute in Python plugin info object: "
277 "py-plugin-info-addr=%p", plugin_info
);
278 status
= BT_FUNC_STATUS_NOT_FOUND
;
284 py_description
= PyObject_GetAttrString(plugin_info
, "description");
285 if (!py_description
) {
286 if (fail_on_load_error
) {
287 append_python_traceback_error_cause();
288 BT_LIB_LOGW_APPEND_CAUSE(
289 "Cannot find `description` attribute in Python plugin info object: "
290 "py-plugin-info-addr=%p", plugin_info
);
291 status
= BT_FUNC_STATUS_ERROR
;
294 "Cannot find `description` attribute in Python plugin info object: "
295 "py-plugin-info-addr=%p", plugin_info
);
296 status
= BT_FUNC_STATUS_NOT_FOUND
;
302 py_license
= PyObject_GetAttrString(plugin_info
, "license");
304 if (fail_on_load_error
) {
305 append_python_traceback_error_cause();
306 BT_LIB_LOGW_APPEND_CAUSE(
307 "Cannot find `license` attribute in Python plugin info object: "
308 "py-plugin-info-addr=%p", plugin_info
);
309 status
= BT_FUNC_STATUS_ERROR
;
312 "Cannot find `license` attribute in Python plugin info object: "
313 "py-plugin-info-addr=%p", plugin_info
);
314 status
= BT_FUNC_STATUS_NOT_FOUND
;
320 py_version
= PyObject_GetAttrString(plugin_info
, "version");
322 if (fail_on_load_error
) {
323 append_python_traceback_error_cause();
324 BT_LIB_LOGW_APPEND_CAUSE(
325 "Cannot find `version` attribute in Python plugin info object: "
326 "py-plugin-info-addr=%p", plugin_info
);
327 status
= BT_FUNC_STATUS_ERROR
;
330 "Cannot find `version` attribute in Python plugin info object: "
331 "py-plugin-info-addr=%p", plugin_info
);
332 status
= BT_FUNC_STATUS_NOT_FOUND
;
338 py_comp_class_addrs
= PyObject_GetAttrString(plugin_info
,
340 if (!py_comp_class_addrs
) {
341 if (fail_on_load_error
) {
342 append_python_traceback_error_cause();
343 BT_LIB_LOGW_APPEND_CAUSE(
344 "Cannot find `comp_class_addrs` attribute in Python plugin info object: "
345 "py-plugin-info-addr=%p", plugin_info
);
346 status
= BT_FUNC_STATUS_ERROR
;
349 "Cannot find `comp_class_addrs` attribute in Python plugin info object: "
350 "py-plugin-info-addr=%p", plugin_info
);
351 status
= BT_FUNC_STATUS_NOT_FOUND
;
357 if (PyUnicode_Check(py_name
)) {
358 name
= PyUnicode_AsUTF8(py_name
);
360 if (fail_on_load_error
) {
361 append_python_traceback_error_cause();
362 BT_LIB_LOGW_APPEND_CAUSE(
363 "Cannot decode Python plugin name string: "
364 "py-plugin-info-addr=%p", plugin_info
);
365 status
= BT_FUNC_STATUS_ERROR
;
368 "Cannot decode Python plugin name string: "
369 "py-plugin-info-addr=%p", plugin_info
);
370 status
= BT_FUNC_STATUS_NOT_FOUND
;
376 /* Plugin name is mandatory */
377 if (fail_on_load_error
) {
378 append_python_traceback_error_cause();
379 BT_LIB_LOGW_APPEND_CAUSE(
380 "Plugin name is not a string: "
381 "py-plugin-info-addr=%p", plugin_info
);
382 status
= BT_FUNC_STATUS_ERROR
;
385 "Plugin name is not a string: "
386 "py-plugin-info-addr=%p", plugin_info
);
387 status
= BT_FUNC_STATUS_NOT_FOUND
;
393 if (PyUnicode_Check(py_author
)) {
394 author
= PyUnicode_AsUTF8(py_author
);
396 if (fail_on_load_error
) {
397 append_python_traceback_error_cause();
398 BT_LIB_LOGW_APPEND_CAUSE(
399 "Cannot decode Python plugin author string: "
400 "py-plugin-info-addr=%p", plugin_info
);
401 status
= BT_FUNC_STATUS_ERROR
;
404 "Cannot decode Python plugin author string: "
405 "py-plugin-info-addr=%p", plugin_info
);
406 status
= BT_FUNC_STATUS_NOT_FOUND
;
413 if (PyUnicode_Check(py_description
)) {
414 description
= PyUnicode_AsUTF8(py_description
);
416 if (fail_on_load_error
) {
417 append_python_traceback_error_cause();
418 BT_LIB_LOGW_APPEND_CAUSE(
419 "Cannot decode Python plugin description string: "
420 "py-plugin-info-addr=%p", plugin_info
);
421 status
= BT_FUNC_STATUS_ERROR
;
424 "Cannot decode Python plugin description string: "
425 "py-plugin-info-addr=%p", plugin_info
);
426 status
= BT_FUNC_STATUS_NOT_FOUND
;
433 if (PyUnicode_Check(py_license
)) {
434 license
= PyUnicode_AsUTF8(py_license
);
436 if (fail_on_load_error
) {
437 append_python_traceback_error_cause();
438 BT_LIB_LOGW_APPEND_CAUSE(
439 "Cannot decode Python plugin license string: "
440 "py-plugin-info-addr=%p", plugin_info
);
441 status
= BT_FUNC_STATUS_ERROR
;
444 "Cannot decode Python plugin license string: "
445 "py-plugin-info-addr=%p", plugin_info
);
446 status
= BT_FUNC_STATUS_NOT_FOUND
;
453 if (PyTuple_Check(py_version
)) {
454 if (PyTuple_Size(py_version
) >= 3) {
455 PyObject
*py_major
= PyTuple_GetItem(py_version
, 0);
456 PyObject
*py_minor
= PyTuple_GetItem(py_version
, 1);
457 PyObject
*py_patch
= PyTuple_GetItem(py_version
, 2);
463 if (PyLong_Check(py_major
)) {
464 major
= PyLong_AsUnsignedLong(py_major
);
467 if (PyLong_Check(py_minor
)) {
468 minor
= PyLong_AsUnsignedLong(py_minor
);
471 if (PyLong_Check(py_patch
)) {
472 patch
= PyLong_AsUnsignedLong(py_patch
);
475 if (PyErr_Occurred()) {
476 /* Overflow error, most probably */
477 if (fail_on_load_error
) {
478 append_python_traceback_error_cause();
479 BT_LIB_LOGW_APPEND_CAUSE(
480 "Invalid Python plugin version format: "
481 "py-plugin-info-addr=%p", plugin_info
);
482 status
= BT_FUNC_STATUS_ERROR
;
485 "Invalid Python plugin version format: "
486 "py-plugin-info-addr=%p", plugin_info
);
487 status
= BT_FUNC_STATUS_NOT_FOUND
;
494 if (PyTuple_Size(py_version
) >= 4) {
495 PyObject
*py_extra
= PyTuple_GetItem(py_version
, 3);
499 if (PyUnicode_Check(py_extra
)) {
500 version_extra
= PyUnicode_AsUTF8(py_extra
);
501 if (!version_extra
) {
502 if (fail_on_load_error
) {
503 append_python_traceback_error_cause();
504 BT_LIB_LOGW_APPEND_CAUSE(
505 "Cannot decode Python plugin version's extra string: "
506 "py-plugin-info-addr=%p", plugin_info
);
507 status
= BT_FUNC_STATUS_ERROR
;
510 "Cannot decode Python plugin version's extra string: "
511 "py-plugin-info-addr=%p", plugin_info
);
512 status
= BT_FUNC_STATUS_NOT_FOUND
;
521 *plugin_out
= bt_plugin_create_empty(BT_PLUGIN_TYPE_PYTHON
);
523 BT_LIB_LOGE_APPEND_CAUSE("Cannot create empty plugin object.");
524 status
= BT_FUNC_STATUS_MEMORY_ERROR
;
528 bt_plugin_set_name(*plugin_out
, name
);
531 bt_plugin_set_description(*plugin_out
, description
);
535 bt_plugin_set_author(*plugin_out
, author
);
539 bt_plugin_set_license(*plugin_out
, license
);
542 bt_plugin_set_version(*plugin_out
, major
, minor
, patch
, version_extra
);
544 if (PyList_Check(py_comp_class_addrs
)) {
547 for (i
= 0; i
< PyList_Size(py_comp_class_addrs
); i
++) {
548 bt_component_class
*comp_class
;
549 PyObject
*py_comp_class_addr
;
552 PyList_GetItem(py_comp_class_addrs
, i
);
553 BT_ASSERT(py_comp_class_addr
);
554 if (PyLong_Check(py_comp_class_addr
)) {
555 comp_class
= PyLong_AsVoidPtr(py_comp_class_addr
);
557 if (fail_on_load_error
) {
558 append_python_traceback_error_cause();
559 BT_LIB_LOGW_APPEND_CAUSE(
560 "Component class address is not an integer in Python plugin info object: "
561 "py-plugin-info-addr=%p, index=%zu",
563 status
= BT_FUNC_STATUS_ERROR
;
566 "Component class address is not an integer in Python plugin info object: "
567 "py-plugin-info-addr=%p, index=%zu",
569 status
= BT_FUNC_STATUS_NOT_FOUND
;
575 status
= bt_plugin_add_component_class(*plugin_out
,
578 BT_LIB_LOGE_APPEND_CAUSE(
579 "Cannot add component class to plugin: "
580 "py-plugin-info-addr=%p, "
581 "plugin-addr=%p, plugin-name=\"%s\", "
582 "comp-class-addr=%p, "
583 "comp-class-name=\"%s\", "
584 "comp-class-type=%s",
585 plugin_info
, *plugin_out
,
586 bt_plugin_get_name(*plugin_out
),
588 bt_component_class_get_name(comp_class
),
589 bt_component_class_type_string(
590 bt_component_class_get_type(comp_class
)));
599 BT_ASSERT(status
!= BT_FUNC_STATUS_OK
);
600 log_python_traceback(fail_on_load_error
? BT_LOG_WARNING
: BT_LOG_INFO
);
602 BT_OBJECT_PUT_REF_AND_RESET(*plugin_out
);
606 Py_XDECREF(py_author
);
607 Py_XDECREF(py_description
);
608 Py_XDECREF(py_license
);
609 Py_XDECREF(py_version
);
610 Py_XDECREF(py_comp_class_addrs
);
615 int bt_plugin_python_create_all_from_file(const char *path
,
616 bool fail_on_load_error
, struct bt_plugin_set
**plugin_set_out
)
618 bt_plugin
*plugin
= NULL
;
619 PyObject
*py_plugin_info
= NULL
;
620 gchar
*basename
= NULL
;
622 int status
= BT_FUNC_STATUS_OK
;
626 if (python_state
== PYTHON_STATE_CANNOT_INITIALIZE
) {
628 * We do not even care about the rest of the function
629 * here because we already know Python cannot be fully
632 BT_LIB_LOGE_APPEND_CAUSE(
633 "Python interpreter could not be initialized previously.");
634 status
= BT_FUNC_STATUS_ERROR
;
636 } else if (python_state
== PYTHON_STATE_WONT_INITIALIZE
) {
638 * This is not an error: the environment requires that
639 * Python plugins are disabled, so it's simply not
642 BT_LOGI_STR("Python plugin support was disabled previously "
643 "because the `LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS` "
644 "environment variable is set to `1`.");
645 status
= BT_FUNC_STATUS_NOT_FOUND
;
649 BT_LOGI("Trying to create all Python plugins from file: path=\"%s\"",
651 path_len
= strlen(path
);
653 /* File name ends with `.py` */
654 if (strncmp(path
+ path_len
- PYTHON_PLUGIN_FILE_EXT_LEN
,
655 PYTHON_PLUGIN_FILE_EXT
,
656 PYTHON_PLUGIN_FILE_EXT_LEN
) != 0) {
657 BT_LOGI("Skipping non-Python file: path=\"%s\"", path
);
658 status
= BT_FUNC_STATUS_NOT_FOUND
;
662 /* File name starts with `bt_plugin_` */
663 basename
= g_path_get_basename(path
);
665 BT_LIB_LOGE_APPEND_CAUSE(
666 "Cannot get path's basename: path=\"%s\"", path
);
667 status
= BT_FUNC_STATUS_ERROR
;
671 if (strncmp(basename
, PYTHON_PLUGIN_FILE_PREFIX
,
672 PYTHON_PLUGIN_FILE_PREFIX_LEN
) != 0) {
673 BT_LOGI("Skipping Python file not starting with `%s`: "
674 "path=\"%s\"", PYTHON_PLUGIN_FILE_PREFIX
, path
);
675 status
= BT_FUNC_STATUS_NOT_FOUND
;
680 * Initialize Python now.
682 * This is not done in the library contructor because the
683 * interpreter is somewhat slow to initialize. If you don't
684 * have any potential Python plugins, you don't need to endure
685 * this waiting time everytime you load the library.
687 status
= init_python();
688 if (status
!= BT_FUNC_STATUS_OK
) {
689 /* init_python() logs and append errors */
694 * Call bt2.py_plugin._try_load_plugin_module() with this path
695 * to get plugin info if the plugin is loadable and complete.
696 * This function returns None when there's an error, but just in
697 * case we also manually clear the last Python error state.
699 BT_LOGD_STR("Getting Python plugin info object from Python module.");
700 py_plugin_info
= PyObject_CallFunction(py_try_load_plugin_module_func
,
702 if (!py_plugin_info
|| py_plugin_info
== Py_None
) {
703 if (fail_on_load_error
) {
704 append_python_traceback_error_cause();
705 BT_LIB_LOGW_APPEND_CAUSE(
706 "Cannot load Python plugin: path=\"%s\"", path
);
707 status
= BT_FUNC_STATUS_ERROR
;
710 "Cannot load Python plugin: path=\"%s\"", path
);
711 status
= BT_FUNC_STATUS_NOT_FOUND
;
718 * Get bt_plugin from plugin info object.
721 status
= bt_plugin_from_python_plugin_info(py_plugin_info
,
722 fail_on_load_error
, &plugin
);
725 * bt_plugin_from_python_plugin_info() handles
726 * `fail_on_load_error`, so this is a "real" error.
728 BT_LIB_LOGW_APPEND_CAUSE(
729 "Cannot create plugin object from Python plugin info object: "
730 "path=\"%s\", py-plugin-info-addr=%p",
731 path
, py_plugin_info
);
734 } else if (status
== BT_FUNC_STATUS_NOT_FOUND
) {
739 BT_ASSERT(status
== BT_FUNC_STATUS_OK
);
741 bt_plugin_set_path(plugin
, path
);
742 *plugin_set_out
= bt_plugin_set_create();
743 if (!*plugin_set_out
) {
744 BT_LIB_LOGE_APPEND_CAUSE("Cannot create empty plugin set.");
745 status
= BT_FUNC_STATUS_MEMORY_ERROR
;
749 bt_plugin_set_add_plugin(*plugin_set_out
, plugin
);
750 BT_LOGD("Created all Python plugins from file: path=\"%s\", "
751 "plugin-addr=%p, plugin-name=\"%s\"",
752 path
, plugin
, bt_plugin_get_name(plugin
));
756 BT_ASSERT(status
!= BT_FUNC_STATUS_OK
);
757 log_python_traceback(BT_LOG_WARNING
);
759 BT_OBJECT_PUT_REF_AND_RESET(*plugin_set_out
);
762 bt_plugin_put_ref(plugin
);
763 Py_XDECREF(py_plugin_info
);