Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,47 @@
# Contributing a plugin to the catalog

Listing a plugin takes a public GitHub repository, a GitHub release holding the wheel, and a pull request adding one YAML file. No PyPI account is needed.
Listing a plugin takes a public GitHub repository, a GitHub release holding a wheel or a single Python file, and a pull request adding one YAML entry. No PyPI account is needed. Single-file artifacts are supported by the updated Desktop client only, not DataLab-Web.

## Requirements

- The source code is public on GitHub and licensed under an OSI-approved license, declared in the wheel metadata (`license = "..."` in `pyproject.toml` with setuptools 77 or later). Accepted licenses: 0BSD, AFL-3.0, Apache-2.0, BSD-2-Clause, BSD-3-Clause, BSL-1.0, CECILL-2.1, EPL-2.0, EUPL-1.2, GPL-2.0 and 3.0, ISC, LGPL-2.1 and 3.0, MIT, MPL-2.0, PSF-2.0, Unlicense, Zlib.
- The plugin is distributed as a single pure-Python wheel (`*-py3-none-any.whl`), without compiled code.
- The wheel declares its plugin class in the `datalab.plugins` entry-point group (DataLab desktop) and/or `datalab.web_plugins` (DataLab-Web).
- The source code is public on GitHub and licensed under an OSI-approved license. A wheel declares its license in its metadata (`license = "..."` in `pyproject.toml` with setuptools 77 or later); a module declares its SPDX license identifier in the catalog entry and must have corresponding source licensing. Accepted licenses: 0BSD, AFL-3.0, Apache-2.0, BSD-2-Clause, BSD-3-Clause, BSL-1.0, CECILL-2.1, EPL-2.0, EUPL-1.2, GPL-2.0 and 3.0, ISC, LGPL-2.1 and 3.0, MIT, MPL-2.0, PSF-2.0, Unlicense, Zlib.
- The artifact is either a pure-Python wheel (`*-py3-none-any.whl`) or one importable `datalab_<name>.py` source file (maximum 4 MiB), without compiled plugin code.
- A wheel declares its plugin class in the `datalab.plugins` (Desktop) and/or `datalab.web_plugins` (Web) entry-point group. A module uses Desktop's existing file-name convention and plugin API; it does not need an entry-point file or packaging metadata.
- Its requirements must be satisfied by DataLab (for example `datalab-platform`, `sigima`, `numpy`, `scipy`) or, for Desktop, by explicitly listed dependency wheels. Existing host package versions cannot be replaced. Additional wheels may contain native libraries but must cover the intended Python/ABI/platform; no compilation or system installation is performed. Web requirements remain restricted to packages supplied by the Web host.
- The plugin ID is the `id` of the plugin's `PluginInfo`. Prefer a reverse-domain name that you control, such as `io.github.<your-account>.<plugin>`. The `org.datalab.` prefix is reserved for DataLab-Platform plugins.
- The entry's ID agrees with a statically readable `PluginInfo.id` when present. For an unchanged historical module with unavailable metadata or several plugin classes, the entry identifies the entire module without rewriting its runtime identities. Prefer a reverse-domain name that you control, such as `io.github.<your-account>.<plugin>`. The `org.datalab.` prefix is reserved for DataLab-Platform plugins.

Projects created with `datalab-plugin create` already follow these rules.
The generators `datalab-plugin create` and `create --project` provide a starting point for the two source forms. Catalog metadata must still be supplied when it is not in the artifact.

## Source organization is not the distribution format

A plugin may be developed as a single `datalab_<name>.py` module or as a larger Python package. Both use the same plugin API and may expose the same functional capabilities. The layered project generated by `datalab-plugin create --project` is one convenient organization, not a catalog requirement.

The catalog accepts both wheels and raw single-file Desktop plugins. A wheel may contain just one module: packaging it does not require adopting the layered project. A raw `.py` entry avoids that packaging step entirely.

## Listing a single-file Desktop plugin

Attach the unchanged `datalab_<name>.py` file to a versioned GitHub release, then add a YAML entry such as:

```yaml
id: io.github.alice.spectrum-tools
name: Spectrum Tools
kind: module
summary: Spectral processing in one Python file
license: BSD-3-Clause
repository: https://github.com/alice/spectrum-tools
capabilities: [processing]
releases:
- version: "1.0.0"
filename: datalab_spectrum_tools.py
sha256: <SHA-256 of the actual file>
requires_python: ">=3.9"
```

`filename` selects an exact release asset and stays stable between versions; do not add version numbers or hyphens that make the Python module name invalid. `summary`, `license` and a release version are required in the public entry. Literal source identities and versions, when readable, must agree with the entry. Unknown metadata is not evaluated: constructors, dynamic expressions and plugin code are never executed during inspection. A syntactically valid module is not thereby certified as a working or safe plugin.

Modules use already available Desktop libraries. They cannot declare dependency wheel installation, use PyPI as their artifact source or advertise the Web target. The catalog mirrors their exact bytes under `modules/<sha256>/`; copying that tree and `catalog.json` provides an offline mirror. Local catalogs may also scan `.py` and `.whl` files directly at the root, without an index.

For a local module whose version is unavailable, the generated index keeps an empty version. Desktop compares digests and asks for explicit replacement when content changes, rather than inventing a newer version. Local `catalog build/check/prepare` commands include modules, but only wheels participate in dependency preparation. Descriptive metadata supplied in an existing local JSON index is preserved when it is rebuilt.

## Additional Desktop dependencies

Expand All @@ -32,6 +63,8 @@ The CI checks and mirrors these artifacts without executing them. The Desktop cl

## Submitting a plugin

The following is the wheel workflow; single-file authors use the release asset and entry described above instead.

1. Build the wheel (`python -m build`) and attach it to a GitHub release of your repository, tagged `v<version>` (for example `v1.0.0`).
2. Fork this repository and add `plugins/<id>.yaml`:

Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ In DataLab 1.4 or later, open **Plugins > Configure plugins...**, select the **A

To install a downloaded file instead (also with the standalone version of DataLab):

1. Open <https://datalab-platform.com/plugins/> and download the wheel (`.whl`) of the plugin.
1. Open <https://datalab-platform.com/plugins/> and download the plugin artifact: a wheel (`.whl`) or, for a **Python file** entry, the unchanged `datalab_<name>.py` file.
2. In DataLab, open **Plugins > Configure plugins...**, go to the **Install plugins** tab and click **Install from file...**.

If the plugin lists **Additional Desktop packages**, download the wheels for your Python version and platform into a `dependencies` folder next to the plugin wheel. A complete local catalog with these files can be installed without Internet access, including with the standalone application. DataLab shows the additional packages before confirmation, never replaces existing package versions, and activates the added dependencies after a restart. Uninstalling a plugin keeps its shared dependencies.

A plugin runs with the same rights as DataLab: it may read and modify your files. Install only plugins from authors you trust. Being listed here does not mean that a plugin was audited.

Single-file plugins use libraries already available in Desktop and do not install additional packages. A module may be distributed without a packaging project; source organization does not determine its functional capabilities. Known versions can be updated; local scripts without a version use their digest and offer explicit replacement when changed.

## Publishing a plugin

Any author may list a plugin by opening a pull request that adds one small YAML file to the [plugins](plugins) folder. See [CONTRIBUTING.md](CONTRIBUTING.md).

## How the catalog works

- Each plugin is described by `plugins/<id>.yaml`, validated against [schema/plugin-entry.schema.json](schema/plugin-entry.schema.json).
- For each release, the CI downloads the wheel from the GitHub release (or from PyPI), checks its SHA-256 digest against the entry, and inspects it without running its code: pure-Python plugin, `datalab.plugins` (desktop) and/or `datalab.web_plugins` (web) entry points, declared requirements and OSI-approved license. Desktop releases may also list reviewed dependency wheels, including native wheels, which are verified and mirrored with the plugin. Web requirements must still be provided by its host. Python compatibility is checked per target: a desktop plugin must install on at least one Python version supported by DataLab (3.9 to 3.14), a web plugin on the Python of the Pyodide release used by DataLab-Web (3.12). DataLab resolves the complete dependency graph against the actual installation; publication metadata checks are not a functional certification of every platform.
- When a pull request is merged, the catalog is rebuilt and published: `catalog.json`, a copy of every wheel in `wheels/<sha256>/`, and a human-readable `index.html`. Wheels are downloaded again at each build and must still match the reviewed digests.
- Desktop entries may use `kind: module` with an exact `.py` GitHub release asset, summary, license and version. The source can remain unchanged: available metadata is read statically, and the entry supplies what cannot be determined without execution. The page labels these artifacts **Python file**, and does not include them in the Web filter.
- For each wheel release, the CI downloads the wheel from the GitHub release (or from PyPI), checks its SHA-256 digest against the entry, and inspects it without running its code: pure-Python plugin, `datalab.plugins` (desktop) and/or `datalab.web_plugins` (web) entry points, declared requirements and OSI-approved license. Desktop wheels may also list reviewed dependency wheels, including native wheels, which are verified and mirrored with the plugin. Web requirements must still be provided by its host. Python compatibility is checked per target: a desktop plugin must install on at least one Python version supported by DataLab (3.9 to 3.14), a web plugin on the Python of the Pyodide release used by DataLab-Web (3.12). DataLab resolves the complete dependency graph against the actual installation; publication metadata checks are not a functional certification of every platform.
- When a pull request is merged, the catalog is rebuilt and published: `catalog.json`, copies of wheels in `wheels/<sha256>/` and modules in `modules/<sha256>/`, and a human-readable `index.html`. All artifacts are downloaded again and must still match the reviewed digests.
- The page uses content-versioned URLs for its stylesheet and script, so browsers fetch updated assets instead of reusing an older cached version.
- Plugins from DataLab-Platform repositories are marked **official**, others **community**. IDs starting with `org.datalab.` are reserved for official plugins.

Expand All @@ -33,6 +36,8 @@ Any author may list a plugin by opening a pull request that adds one small YAML

A release may additionally list `dependency_wheels`, each with `filename`, mirrored `url`, `sha256` and `size`, plus upstream and metadata information. The field is optional: existing catalogs and self-contained plugins keep their format and behavior. Dependency artifacts belong to Desktop only; this does not add a package-index installer to DataLab-Web.

For modules, a plugin record adds `kind: "module"` and its real import name in `module`, without a fictitious `distribution`. Its releases still use `filename`, `url`, `sha256`, `size`, version, optional Python constraint and the `desktop` target, but cannot include dependency wheels. Existing wheel records need no new fields. An older wheel-only client skips module artifacts as non-installable.

Plugins may also declare an optional `icon` HTTPS URL, used only by the web page. The image is loaded directly by the browser, not downloaded or mirrored by the catalog build. Initials remain the fallback if the image is absent or unavailable, including on a private mirror without access to the image host. See [CONTRIBUTING.md](CONTRIBUTING.md#optional-icon).

## Development
Expand All @@ -45,3 +50,5 @@ python tools/catalog.py build --output site
```

[tools/wheels.py](tools/wheels.py) is a verbatim copy of `datalab/plugins/wheels.py` from DataLab: update both together. A test compares them when a sibling DataLab checkout is available. Update `TARGET_PYTHONS` in [tools/catalog.py](tools/catalog.py) when DataLab supports a new Python version or DataLab-Web moves to another Pyodide release.

[tools/modules.py](tools/modules.py) is likewise copied from `datalab/plugins/modules.py`, with an equality test. The module inspector has no Qt dependency and never imports the artifact. The optional Desktop mirror integration test requires the updated client; the remaining publication tests also run against the latest published DataLab reference environment.
32 changes: 32 additions & 0 deletions schema/plugin-entry.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"minLength": 1,
"maxLength": 80
},
"kind": {
"description": "Distribution format, not a functional plugin category. Defaults to wheel.",
"enum": ["wheel", "module"]
},
"summary": {"type": "string", "minLength": 1},
"license": {"type": "string", "minLength": 1},
"repository": {
"description": "Public source repository, also used to find GitHub releases.",
"type": "string",
Expand Down Expand Up @@ -56,6 +62,23 @@
}
},
"allOf": [
{
"if": {"properties": {"kind": {"const": "module"}}, "required": ["kind"]},
"then": {
"required": ["summary", "license"],
"properties": {
"releases": {
"items": {
"required": ["filename"],
"properties": {
"source": {"const": "github"},
"dependency_wheels": {"maxItems": 0}
}
}
}
}
}
},
{
"if": {
"properties": {"status": {"enum": ["deprecated", "revoked"]}},
Expand All @@ -71,6 +94,15 @@
"required": ["version"],
"properties": {
"version": {"type": "string", "minLength": 1},
"filename": {
"description": "Exact GitHub release asset for a single-file module.",
"type": "string",
"pattern": "^datalab_[A-Za-z0-9_]+\\.py$"
},
"requires_python": {
"description": "Optional Python compatibility constraint for a module release.",
"type": "string"
},
"sha256": {
"description": "SHA-256 digest of the wheel. The catalog check prints it when missing.",
"type": "string",
Expand Down
Loading
Loading