From c281b948ccf160354d852e22993d2a15464721c0 Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Sun, 15 Mar 2026 04:22:30 +0000 Subject: [PATCH 1/4] docs(skills): add dpdata plugin skill --- .gitignore | 4 ++ skills/dpdata-plugin/SKILL.md | 113 ++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 skills/dpdata-plugin/SKILL.md diff --git a/.gitignore b/.gitignore index 7fd27471..645b6210 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,7 @@ tests/data_*.h5 tests/data_*/ tests/tmp.* tests/.coverage + +# local dev artifact +uv.lock +.venv/ diff --git a/skills/dpdata-plugin/SKILL.md b/skills/dpdata-plugin/SKILL.md new file mode 100644 index 00000000..ef9dba2f --- /dev/null +++ b/skills/dpdata-plugin/SKILL.md @@ -0,0 +1,113 @@ +--- +name: dpdata-plugin +description: Create and install dpdata plugins (especially custom Format readers/writers) using Format.register(...) and pyproject.toml entry_points under 'dpdata.plugins'. Use when extending dpdata with new formats or distributing plugins as separate Python packages. +--- + +# dpdata-plugin + +dpdata loads plugins in two ways: + +1. **Built-in plugins** in `dpdata.plugins.*` (imported automatically) +1. **External plugins** exposed via Python package entry points: `dpdata.plugins` + +This skill focuses on **external plugin packages**, the recommended way to add new formats without modifying dpdata itself. + +## What can be extended? + +Most commonly: add a new **Format** (file reader/writer) via: + +```python +from dpdata.format import Format + + +@Format.register("myfmt") +class MyFormat(Format): ... +``` + +## How dpdata discovers plugins + +dpdata imports `dpdata.plugins` during normal use (e.g. `dpdata.system` imports it). That module: + +- imports every built-in module in `dpdata/plugins/*.py` +- then loads all **entry points** in group `dpdata.plugins` + +So an external plugin package only needs to ensure that importing the entry-point target triggers the `@Format.register(...)` side effects. + +## Minimal external plugin package (based on plugin_example/) + +### 1) Create a new Python package + +Example layout: + +```text +dpdata_random/ + pyproject.toml + dpdata_random/ + __init__.py +``` + +### 2) Implement and register your Format + +In `dpdata_random/__init__.py` (shortened example): + +```python +from __future__ import annotations + +import numpy as np +from dpdata.format import Format + + +@Format.register("random") +class RandomFormat(Format): + def from_system(self, N, **kwargs): + return { + "atom_numbs": [20], + "atom_names": ["X"], + "atom_types": np.zeros(20, dtype=int), + "cells": np.repeat(np.eye(3)[None, ...], N, axis=0) * 100.0, + "coords": np.random.rand(N, 20, 3) * 100.0, + "orig": np.zeros(3), + "nopbc": False, + } +``` + +Return dicts must match dpdata’s expected schema (cells/coords/atom_names/atom_types/...). + +### 3) Expose an entry point + +In `pyproject.toml`: + +```toml +[project] +name = "dpdata_random" +version = "0.0.0" +dependencies = ["numpy", "dpdata"] + +[project.entry-points.'dpdata.plugins'] +random = "dpdata_random:RandomFormat" +``` + +Any importable target works; this pattern points directly at the class. + +### 4) Install and test + +In a clean env (recommended via `uv`): + +```bash +uv run --with dpdata --with numpy python3 - <<'PY' +import dpdata +from dpdata.format import Format + +# importing dpdata will load entry points (dpdata.plugins) +print('random' in Format.get_formats()) +PY +``` + +If it prints `True`, your plugin was discovered. + +## Debug checklist + +- Did you install the plugin package into the same environment where you run dpdata? +- Does `pyproject.toml` contain `[project.entry-points.'dpdata.plugins']`? +- Does importing the entry point module/class execute the `@Format.register(...)` decorator? +- If using `uv run`, remember each command runs in its own environment unless you’re in a `uv` project (or you rely on `uv run --with ...`). From 285cbdd150b225060e64307cf3669d8557820f2f Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Sun, 15 Mar 2026 05:35:56 +0000 Subject: [PATCH 2/4] chore: revert gitignore changes --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index 645b6210..7fd27471 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,3 @@ tests/data_*.h5 tests/data_*/ tests/tmp.* tests/.coverage - -# local dev artifact -uv.lock -.venv/ From f251724d8e189f00ad4367adf2465bcf796ee264 Mon Sep 17 00:00:00 2001 From: "njzjz-bot (driven by OpenClaw (model: gpt-5.4))[bot]" <48687836+njzjz-bot@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:15:03 +0000 Subject: [PATCH 3/4] docs(dpdata-plugin): address AI review suggestions Install the example plugin into the same environment before verifying registration so the entry point is actually discovered. Also add a debug checklist item reminding users to avoid collisions with existing format keys. Authored by OpenClaw (model: gpt-5.4) --- skills/dpdata-plugin/SKILL.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/skills/dpdata-plugin/SKILL.md b/skills/dpdata-plugin/SKILL.md index ef9dba2f..2125525c 100644 --- a/skills/dpdata-plugin/SKILL.md +++ b/skills/dpdata-plugin/SKILL.md @@ -94,7 +94,10 @@ Any importable target works; this pattern points directly at the class. In a clean env (recommended via `uv`): ```bash -uv run --with dpdata --with numpy python3 - <<'PY' +uv venv +. .venv/bin/activate +uv pip install -e . dpdata numpy +python - <<'PY' import dpdata from dpdata.format import Format @@ -110,4 +113,5 @@ If it prints `True`, your plugin was discovered. - Did you install the plugin package into the same environment where you run dpdata? - Does `pyproject.toml` contain `[project.entry-points.'dpdata.plugins']`? - Does importing the entry point module/class execute the `@Format.register(...)` decorator? +- Before choosing a key like `random`, check `Format.get_formats()` to avoid colliding with an existing built-in or previously loaded plugin key. - If using `uv run`, remember each command runs in its own environment unless you’re in a `uv` project (or you rely on `uv run --with ...`). From 3807cb11612d19e4cdd1f0f6d9aa08ab7f94ea37 Mon Sep 17 00:00:00 2001 From: njzjz-bot <48687836+njzjz-bot@users.noreply.github.com> Date: Mon, 23 Mar 2026 07:12:28 +0000 Subject: [PATCH 4/4] docs(dpdata-plugin): clarify from_system example Use the real parameter name in the example and explain that this sample interprets it as an integer frame count for generating random frames.\n\nAuthored by OpenClaw (model: gpt-5.4) --- skills/dpdata-plugin/SKILL.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/skills/dpdata-plugin/SKILL.md b/skills/dpdata-plugin/SKILL.md index 2125525c..818cc8f9 100644 --- a/skills/dpdata-plugin/SKILL.md +++ b/skills/dpdata-plugin/SKILL.md @@ -59,19 +59,21 @@ from dpdata.format import Format @Format.register("random") class RandomFormat(Format): - def from_system(self, N, **kwargs): + def from_system(self, file_name, **kwargs): + nframes = int(file_name) return { "atom_numbs": [20], "atom_names": ["X"], "atom_types": np.zeros(20, dtype=int), - "cells": np.repeat(np.eye(3)[None, ...], N, axis=0) * 100.0, - "coords": np.random.rand(N, 20, 3) * 100.0, + "cells": np.repeat(np.eye(3)[None, ...], nframes, axis=0) * 100.0, + "coords": np.random.rand(nframes, 20, 3) * 100.0, "orig": np.zeros(3), "nopbc": False, } ``` Return dicts must match dpdata’s expected schema (cells/coords/atom_names/atom_types/...). +Here `file_name` is the argument dpdata passes into `from_system`; this example interprets it as an integer frame count. ### 3) Expose an entry point