perspective-dev/perspective · error · Error

labextension is not present in data directory, please build…

Error message

labextension is not present in data directory, please build `perspective-jupyterlab`

What it means

This build script for the perspective-python wheel verifies that the JupyterLab labextension artifact (@perspective-dev/jupyterlab package.json) was copied into the wheel data directory. It throws because the file is absent, meaning the perspective-jupyterlab frontend package was never built or its output was not staged into data_dir before this check at build.mjs:130.

Solutions

  1. Build the jupyterlab extension first: run the perspective-jupyterlab package's build (e.g. `pnpm build` in packages/perspective-jupyterlab or the monorepo-wide build) before building perspective-python
  2. Run the full monorepo build so all frontend artifacts are produced and staged, then rebuild the python wheel
  3. Verify the labextension output exists at packages path and that build.mjs's copy step into data_dir ran (check for errors earlier in the build log)
  4. If the artifact exists but is misplaced, ensure you run the build from the repository root as expected (relative path `data_dir/data/share/jupyter/...` depends on cwd)

Example fix

# before
$ cd rust/perspective-python && node build.mjs
Error: labextension is not present in data directory...
// after
$ pnpm install && pnpm build   # builds all packages incl. perspective-jupyterlab
$ cd rust/perspective-python && node build.mjs  # proceeds past the check
Defensive patterns

Strategy: validation

Validate before calling

const labext = require('fs').existsSync('data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/package.json');
if (!labext) throw new Error('Build perspective-jupyterlab before building perspective-python');

Try / catch

try { await buildPython(); } catch (e) { if (/labextension is not present/.test(e.message)) { console.error('Run `pnpm build` in perspective-jupyterlab first'); process.exit(1); } throw e; }

Prevention

When it happens

Trigger: Running `node build.mjs` (or the Python build/pip install that invokes it) when `data_dir/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/package.json` does not exist — typically the perspective-jupyterlab package was not built first or its dist output was not copied into the data directory.

Common situations: Cloning the monorepo and building perspective-python without first building the jupyterlab extension; running a partial/clean build; CI caching that skips the frontend build; building only the python wheel target.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of perspective-dev/perspective@11c8238c0c (2026-09-09). Data as JSON: /api/errors/adc694b6c484836b. Report an issue: GitHub.

Appendix: source

Thrown at rust/perspective-python/build.mjs:130

if (build_sdist) {
    // `maturin sdist` has some issues with Cargo workspaces, so we assemble the sdist by hand here
    // Note that the resulting sdist is _not_ a Cargo workspace, it is rooted in this package.
    const cargo_toml = fs.readFileSync("./Cargo.toml").toString("utf-8");
    const pyproject_toml = fs
        .readFileSync("./pyproject.toml")
        .toString("utf-8");

    const cargo = toml.parse(cargo_toml);
    const pyproject = toml.parse(pyproject_toml);
    const version = cargo["package"]["version"];
    const data_dir = `perspective_python-${version}.data`;
    const testfile = path.join(
        data_dir,
        "data/share/jupyter/labextensions/@perspective-dev/jupyterlab/package.json",
    );

    if (!fs.existsSync(testfile)) {
        throw new Error(
            "labextension is not present in data directory, please build `perspective-jupyterlab`",
        );
    }

    if (
        !fs.existsSync("perspective/widget/static/perspective-anywidget.js")
    ) {
        throw new Error(
            "anywidget bundle is not present in perspective/widget/static, please build `@perspective-dev/anywidget`",
        );
    }

    const readme_md = fs.readFileSync("./README.md");
    const pkg_info = generatePkgInfo(pyproject, cargo, readme_md);
    fs.writeFileSync("./PKG-INFO", pkg_info);

    // Maturin finds extra license files in the root of the source directory,
    // then packages them into .dist-info in the wheel.  As of Nov 2024,

View on GitHub (pinned to 11c8238c0c)