Skip to content
Open
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
113 changes: 113 additions & 0 deletions skills/dpdata-plugin/SKILL.md
Original file line number Diff line number Diff line change
@@ -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,
Comment on lines +62 to +68
"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
Comment on lines +92 to +103
```
Comment on lines +94 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the current install/test snippet does not install the example plugin package.
fd -a SKILL.md | xargs rg -n -C2 "uv run --with dpdata --with numpy|Format.get_formats|uv pip install -e \."

Repository: deepmodeling/dpdata

Length of output: 803


🏁 Script executed:

# Find pyproject.toml or setup.py in the skills/dpdata-plugin directory
find skills/dpdata-plugin -maxdepth 2 -name "pyproject.toml" -o -name "setup.py"

Repository: deepmodeling/dpdata

Length of output: 45


🏁 Script executed:

# Check the overall structure of the skill directory
cd skills/dpdata-plugin && find . -maxdepth 2 -type f | head -20

Repository: deepmodeling/dpdata

Length of output: 73


🏁 Script executed:

# Search for 'random' format definition in the skills directory
rg -r "random" skills/dpdata-plugin/ | grep -i "format\|class"

Repository: deepmodeling/dpdata

Length of output: 45


🏁 Script executed:

# List the entire structure of skills/dpdata-plugin
cd skills/dpdata-plugin && ls -la

Repository: deepmodeling/dpdata

Length of output: 231


🏁 Script executed:

# Look for 'random' format implementation in the main dpdata codebase
rg -r "random" dpdata/ | grep -i "class.*Format\|def.*random" | head -10

Repository: deepmodeling/dpdata

Length of output: 45


🏁 Script executed:

# Check if there's a README or explanation about skills in the repo
find . -maxdepth 2 -name "README*" -o -name "AGENTS*"

Repository: deepmodeling/dpdata

Length of output: 113


🏁 Script executed:

# Check the AGENTS.md file to understand the context
cat AGENTS.md

Repository: deepmodeling/dpdata

Length of output: 7323


🏁 Script executed:

# Check the plugin_example directory
ls -la plugin_example/

Repository: deepmodeling/dpdata

Length of output: 359


🏁 Script executed:

# Read the full SKILL.md file to get complete context
cat skills/dpdata-plugin/SKILL.md

Repository: deepmodeling/dpdata

Length of output: 3238


Verification command must install the plugin package first

The test on Line 97 will print False instead of True because the example plugin package (dpdata_random) is never installed in the uv run environment. Without uv pip install -e ., the entry point is not registered and plugin discovery fails.

✅ Suggested fix
 ### 4) Install and test
 
 In a clean env (recommended via `uv`):
 
 ```bash
+uv pip install -e .
 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
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
In a clean env (recommended via `uv`):

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/dpdata-plugin/SKILL.md` around lines 94 - 104, The verification
snippet fails because the example plugin package isn't installed into the uv
environment, so Format.get_formats() won't include the "random" entry; fix the
SKILL.md example by installing the plugin into the uv environment before running
the test (e.g., run uv pip install -e . prior to uv run) so that dpdata's entry
points are registered and Format.get_formats() returns True.


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 ...`).
Loading