{"record":{"id":"3346010afe1c8410","repo":"influxdata/influxdb","slug":"failed-to-load-function-from-plugin-module","errorCode":null,"errorMessage":"Failed to load function '{}' from plugin module '{}': {}","messagePattern":"Failed to load function '(.+?)' from plugin module '(.+?)': (.+?)","errorType":"exception","errorClass":"ExecutePluginError","httpStatus":null,"severity":"error","filePath":"influxdb3_py_api/src/system_py.rs","lineNumber":387,"sourceCode":"    let _ = sys_path.call_method1(\"pop\", (0,));\n\n    // Any import failure is the real load error and must not be masked.\n    let module = import_result.map_err(|e| {\n        ExecutePluginError::PluginError(anyhow!(\n            \"Failed to import plugin module '{}': {}\\n\\\n             Hint: Check for syntax errors or missing dependencies in Python files.\",\n            module_name,\n            e\n        ))\n    })?;\n\n    module.getattr(function_name).map_err(|e| {\n        // Only a missing attribute on the imported module means the entry-point\n        // function is absent; surface anything else as the real cause.\n        if e.is_instance_of::<PyAttributeError>(py) {\n            missing_fn_error\n        } else {\n            ExecutePluginError::PluginError(anyhow!(\n                \"Failed to load function '{}' from plugin module '{}': {}\",\n                function_name,\n                module_name,\n                e\n            ))\n        }\n    })\n}\n\n/// Execute a WAL flush trigger plugin.\n#[allow(clippy::too_many_arguments)]\npub fn execute_wal_flush_trigger(\n    code: &str,\n    wal_data: &[WalFlushElement],\n    schema: Arc<DatabaseSchema>,\n    query_endpoint: Arc<dyn QueryEndpoint>,\n    write_endpoint: Arc<dyn WriteEndpoint>,\n    logger: PluginLogger,","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/influxdata/influxdb/blob/d28e26e048401c53cbb98cf2d6ab0cf1e98048ca/influxdb3_py_api/src/system_py.rs#L369-L405","documentation":"The InfluxDB 3 Python plugin host imports each plugin module and resolves the configured entry-point function (e.g. process_request for request plugins) via getattr. A plain AttributeError is mapped to a dedicated MissingProcessRequestFunction error; this PluginError is only produced when the attribute lookup fails with some other exception. The message shows the requested function name, module name, and the underlying Python exception, which is the real cause.","triggerScenarios":"The plugin module defines a module-level __getattr__ (PEP 562) that raises for the entry-point name; the entry point is a descriptor/property whose access raises; or a package __init__ was left partially initialized after a swallowed import error, so getattr(process_request) raises something other than AttributeError.","commonSituations":"Plugins using metaprogramming (lazy imports, generated attributes), plugin code written against a newer/older SDK whose module shape changed, or stale __pycache__ from a different Python version causing attribute access to fail.","solutions":["Read the trailing Python exception in the message - it names the actual failing code in the plugin module","Reproduce outside the server: run python -c \"import my_plugin; my_plugin.process_request\" from the plugin directory","Make the entry point a plain module-level def and remove module-level __getattr__/descriptors that intercept the lookup","Delete stale __pycache__ directories under the plugin root and reload the plugin"],"exampleFix":"# before (plugin.py)\n_ENTRY_POINTS = {}\ndef __getattr__(name):\n    if name not in _ENTRY_POINTS:\n        raise RuntimeError(f'unknown entry point: {name}')  # becomes PluginError\n    return _ENTRY_POINTS[name]\n\n# after\ndef process_request(influxdb, query_params, request_params, body, args):\n    ...\n","handlingStrategy":"validation","validationCode":"# preflight the plugin before deploying it\nimport importlib\nmod = importlib.import_module('my_plugin')\ntry:\n    fn = getattr(mod, 'process_request')\nexcept AttributeError:\n    raise SystemExit('entry point missing')\nexcept Exception as e:\n    raise SystemExit(f'entry-point lookup itself raised: {e}')  # this is the PluginError path\nassert callable(fn)\n","typeGuard":"def entry_point_ok(module_name: str, fn_name: str = 'process_request') -> bool:\n    try:\n        return callable(getattr(importlib.import_module(module_name), fn_name))\n    except Exception:\n        return False\n","tryCatchPattern":"// Rust: distinguish load failure from missing function\nmatch execute_plugin(...) {\n    Err(ExecutePluginError::PluginError(e)) => log::error!(\"plugin load failed: {e:#}\"),\n    Err(ExecutePluginError::MissingProcessRequestFunction) => log::error!(\"process_request not defined\"),\n    Ok(v) => { /* ... */ }\n}\n","preventionTips":["Keep plugin entry points as plain module-level def functions","Add a CI step that imports each plugin and asserts callable(getattr(mod, 'process_request'))","Avoid module-level __getattr__ and lazy-attribute machinery in plugin modules","Clear __pycache__ under the plugin root after upgrading the server's Python"],"tags":["python-plugin","influxdb3","plugin-loader","getattr"],"backgroundTag":"python-plugin-entrypoint-load-failure","analyzedSha":"d28e26e048401c53cbb98cf2d6ab0cf1e98048ca","analyzedAt":"2026-08-16T19:53:34.623Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}