influxdata/influxdb · error · anyhow::Error

Plugin root has no parent directory

Error message

Plugin root has no parent directory

What it means

load_function_from_module treats plugin_root as a package directory whose parent is placed on sys.path; if the path has no parent (e.g. a bare relative component like Path('plugins') at filesystem root semantics, or an empty/root path), .parent() returns None and this anyhow error is raised. It means the plugin root path is malformed for module-style loading.

Solutions

  1. Pass an absolute path to the plugin directory, e.g. Path('/opt/influxdb3/plugins/my_plugin'), not just the directory name.
  2. Join the configured plugin base directory with the plugin name before calling: base_dir / plugin_name.
  3. Guard the path before loading: if not plugin_root.is_absolute(): plugin_root = base_dir / plugin_root.

Example fix

# before
root = Path(os.environ.get('PLUGIN_DIR', ''))  # empty -> no parent
# after
root = (Path('/opt/influxdb3/plugins') / plugin_name).resolve()
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
root = Path(plugin_name)
if not root.is_absolute():
    root = (Path('/opt/influxdb3/plugins') / plugin_name).resolve()

Try / catch

if not plugin_root.parent:
    plugin_root = base_dir / plugin_root  # normalize before calling the loader

Prevention

When it happens

Trigger: load_plugin_function given a plugin_root Path with no parent directory — e.g. a bare name like Path("my_plugin"), a root path like Path("/"), or an empty path — instead of a full directory path like /path/to/plugins/my_plugin.

Common situations: Passing just the plugin directory name read from config while the base directory wasn't joined; resolving plugin roots from env vars that are empty or '/'; incorrect working-directory assumptions making the path resolve to a bare component.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19). Data as JSON: /api/errors/d03f17c535f88764. Report an issue: GitHub.

Appendix: source

Thrown at influxdb3_py_api/src/system_py.rs:329

            .getattr("name")
            .ok()
            .and_then(|name| name.extract::<String>().ok())
            .is_some_and(|name| name == module_name)
}

/// Loads a Python function from a multi-file plugin module.
///
/// This function temporarily adds the plugin's parent directory to Python's sys.path,
/// imports the module, retrieves the function, and then cleans up sys.path.
fn load_function_from_module<'py>(
    py: Python<'py>,
    plugin_root: &Path,
    function_name: &str,
    missing_fn_error: ExecutePluginError,
) -> Result<Bound<'py, PyAny>, ExecutePluginError> {
    let parent_dir = plugin_root
        .parent()
        .ok_or_else(|| anyhow!("Plugin root has no parent directory"))?;
    let module_name = plugin_root
        .file_name()
        .and_then(|n| n.to_str())
        .ok_or_else(|| anyhow!("Invalid plugin directory name"))?;

    let sys = py.import("sys").map_err(anyhow::Error::from)?;
    let sys_path = sys.getattr("path").map_err(anyhow::Error::from)?;

    let parent_dir_str = parent_dir
        .to_str()
        .ok_or_else(|| anyhow!("Invalid UTF-8 in parent directory path"))?;

    // Acquire while attached to the Python runtime; `lock_py_attached` detaches
    // internally if the lock is contended, avoiding a GIL/lock deadlock.
    let _sys_path_guard = SYS_PATH_LOCK.lock_py_attached(py);

    // Add parent directory to sys.path
    sys_path

View on GitHub (pinned to 06200ef96b)