Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1c6204e
migrate build system to hatchling from setuptools
emilykl Mar 16, 2026
77aeae4
remove unused keys from install.json
emilykl Mar 16, 2026
ef2359e
update version in js/package.json to match plotly.py version
emilykl Mar 16, 2026
0cfdc0d
add instruction to update js/package.json version when bumping plotly…
emilykl Mar 16, 2026
09d817a
update js build artifact
emilykl Mar 16, 2026
18c6b75
remove jupyter from dev_optional since we already have jupyterlab und…
emilykl Mar 16, 2026
3adab20
update uv lockfile
emilykl Mar 16, 2026
a650b15
install jupyterlab instead of jupyter
emilykl Mar 17, 2026
4e06ef0
print more debug info for jupyter labextension build
emilykl Mar 17, 2026
796a9b3
add CI step to check that versions match
emilykl Mar 17, 2026
0352c8a
fix issue introduced in check-js-build.yml
emilykl Mar 17, 2026
6a70d14
add webpack config for consistent builds across platforms
emilykl Mar 18, 2026
ed73ee3
remove some flags used for debugging
emilykl Mar 18, 2026
7c5c884
update package-lock.json to match package version in package.json
emilykl Mar 18, 2026
1a13239
try simplified check-js-build step
emilykl Mar 18, 2026
a98060d
add back readme key in metadata
emilykl Mar 19, 2026
754dfe7
change line endings from CRLF to LF
emilykl Mar 19, 2026
482a979
add bumpversion script in commands.py for updating version numbers
emilykl Mar 19, 2026
9717f12
better error handling: print failure message and exit 1 if version bu…
emilykl Mar 20, 2026
ac9e496
add documentation for commands.py to CONTRIBUTING.md
emilykl Mar 20, 2026
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
47 changes: 23 additions & 24 deletions .github/workflows/check-js-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,60 @@ on: push

jobs:
check-js-build:
name: Check JS build artifacts
name: Check JS version number and build artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"


- name: Check that version number for JS project matches version number for Python project
run: |
PYPROJECT_PATH="pyproject.toml"
PKGJSON_PATH="js/package.json"
PYPROJECT_VERSION=$(awk -F'"' '/^version/ {print $2; exit}' $PYPROJECT_PATH)
JSPROJECT_VERSION=$(cat $PKGJSON_PATH | jq -r '.version')
if [ "$PYPROJECT_VERSION" != "$JSPROJECT_VERSION" ]; then
echo "❌ Version number $JSPROJECT_VERSION in $PKGJSON_PATH does not match version number $PYPROJECT_VERSION in $PYPROJECT_PATH"
exit 1
else
echo "✅ Version number $JSPROJECT_VERSION in $PKGJSON_PATH matches version number $PYPROJECT_VERSION in $PYPROJECT_PATH"
fi
- name: Install Node
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Copy current files to a temporary directory
run: |
cp -R plotly/labextension/ plotly/labextension-tmp/
mv plotly/labextension/ plotly/labextension-tmp/

- name: Install dependencies and build
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv
source .venv/bin/activate
uv pip install jupyter
uv pip install jupyterlab
cd js
npm ci
npm run build
npm ls
- name: Check JupyterLab build artifacts
run: |
# 1. Hash contents of all static files, sort by content hash
find plotly/labextension/static -type f -exec sha256sum {} \; | awk '{print $1}' | sort > new_hashes.txt
find plotly/labextension-tmp/static -type f -exec sha256sum {} \; | awk '{print $1}' | sort > old_hashes.txt

# 2. Compare the sorted content hashes
diff old_hashes.txt new_hashes.txt > content_diff.txt

# Remove the "load" line from both package.json files before comparing
grep -v '"load": "static/' plotly/labextension/package.json > pkg1.json
grep -v '"load": "static/' plotly/labextension-tmp/package.json > pkg2.json

# Compare stripped versions
diff pkg1.json pkg2.json > package_json_diff.txt
# Compare the plotly/labextension and plotly/labextension-tmp directories
diff -r --brief plotly/labextension/ plotly/labextension-tmp/ > labextension_diff.txt

# 5. Final check
if [ -s content_diff.txt ] || [ -s package_json_diff.txt ]; then
# Check for differences
if [ -s labextension_diff.txt ]; then
echo "❌ Build artifacts differ:"
echo "--- Unexpected diffs ---"
cat content_diff.txt
echo "--- Unexpected package.json diffs ---"
cat package_json_diff.txt
cat labextension_diff.txt
echo "Please replace the 'plotly/labextension' directory with the artifacts of this CI run."
exit 1
else
echo "✅ Build artifacts match expected output (ignoring known 'load' hash in package.json)."
echo "✅ Build artifacts match expected output"
fi

- name: Store the build artifacts from plotly/labextension
Expand Down
30 changes: 15 additions & 15 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Kruchten"
given-names: "Nicolas"
orcid: https://orcid.org/0000-0002-8416-789X
- family-names: "Seier"
given-names: "Andrew"
- family-names: "Parmer"
given-names: "Chris"
title: "An interactive, open-source, and browser-based graphing library for Python"
version: 6.6.0
doi: 10.5281/zenodo.14503524
date-released: 2026-03-02
url: "https://github.com/plotly/plotly.py"
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Kruchten"
given-names: "Nicolas"
orcid: https://orcid.org/0000-0002-8416-789X
- family-names: "Seier"
given-names: "Andrew"
- family-names: "Parmer"
given-names: "Chris"
title: "An interactive, open-source, and browser-based graphing library for Python"
version: 6.6.0
doi: 10.5281/zenodo.14503524
date-released: 2026-03-02
url: "https://github.com/plotly/plotly.py"
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,18 @@ You can then run the following command
```bash
python commands.py updateplotlyjsdev --local /path/to/your/plotly.js/
```

## Documentation for `commands.py`

`commands.py` serves as an entry point for utilities to help with plotly.py development.

Usage: `python commands.py <subcommand> <args>`

| Subcommand | Purpose |
|------------|---------|
| `codegen [--noformat]` | Regenerate Python files according to `plot-schema.json`.`--noformat` skips formatter step. |
| `lint` | Lint all Python code in `plotly/`. |
| `format` | Format all Python code in `plotly/`. |
| `updateplotlyjs` | Update `plotly.min.js` and `plot-schema.json` to match the `plotly.js` version specified in `js/package.json`. Then, run codegen to regenerate the Python files. |
| `updateplotlyjsdev [--devrepo REPONAME --devbranch BRANCHNAME] \| [--local PATH]` | Update `plot-schema.json` and `plotly.min.js` to match the version in the provided plotly.js repo name and branch name, OR local path. Then, run codegen to regenerate the Python files. |
| `bumpversion X.Y.Z` | Update the plotly.py version number to X.Y.Z across all files where it needs to be updated. |
3 changes: 0 additions & 3 deletions MANIFEST.in

This file was deleted.

31 changes: 20 additions & 11 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ This is the release process for releasing plotly.py version `X.Y.Z`, including c

### Finalize changelog

Review the contents of `CHANGELOG.md`. We try to follow
Review the contents of `CHANGELOG.md` under the **Unreleased** header. We try to follow
the [keepachangelog](https://keepachangelog.com/en/1.0.0/) guidelines.
Make sure the changelog includes the version being published at the top, along
with the expected publication date.

**Note: You don't need to update the header itself with the new version number,
as that will be done automatically as part of the next step.**

Use the `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, and `Security`
labels for all changes to plotly.py. If the version of plotly.js has
Expand All @@ -22,16 +23,24 @@ a link to the plotly.js CHANGELOG.

**Create a release branch `git checkout -b release-X.Y.Z` _from the tip of `origin/main`_.**

- Manually update the versions to `X.Y.Z` in the files specified below:
- Ensure that you have `npm` and `uv` installed in your environment

- Run the command `python commands.py bumpversion X.Y.Z`, which will update the version to X.Y.Z in the following places
- `pyproject.toml`
- update version
- `CHANGELOG.md`
- update version and release date
- finalize changelog entries according to instructions above
- `uv.lock`
- `js/package.json`
- `js/package-lock.json`
- `CHANGELOG.md` (Adds a new header for X.Y.Z above the unreleased items)
- `CITATION.cff`
- update version and release date
- Run `uv lock` to update the version number in the `uv.lock` file (do not update manually)
- Commit and push your changes to the release branch:

- Run `git diff` and ensure the above files were all updated correctly.
- Note: The current date is used as the release date in `CHANGELOG.md` and `CITATION.cff`. If you want to use a different date, edit these files manually afterward.
- If the bumpversion command failed for any reason, you can update the versions yourself by doing the following:
- Manually update the version number (and release date, as needed) in `pyproject.toml`, `CHANGELOG.md` and `CITATION.cff`
- Run `npm version X.Y.Z` to update `js/package.json` and `js/package-lock.json`
- Run `uv lock` to update `uv.lock`

- Commit and push the changed files to the release branch:
```sh
$ git add -u
$ git commit -m "version changes for vX.Y.Z"
Expand Down
180 changes: 180 additions & 0 deletions commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import json
import os
import platform
import re
import requests
import shutil
import subprocess
from subprocess import check_call
import sys
import time
Expand Down Expand Up @@ -296,6 +298,177 @@ def update_plotlyjs_dev(args, outdir):
perform_codegen(outdir)


def bump_version_pyproject_toml(new_version):
"""
Bump the version in pyproject.toml to new_version
Returns True on success, False on failure
"""
pyproject_toml_path = "pyproject.toml"
pattern = r'(^\s*version\s*=\s*")([\w.]+)"(\s*$)'
with open(pyproject_toml_path, "r") as f:
content = f.read()
new_content, n_subs = re.subn(
pattern,
rf'\g<1>{new_version}"\g<3>',
content,
count=1, # replace only the first match
flags=re.MULTILINE,
)
if n_subs < 1:
print(
f"FAILED to update version in {pyproject_toml_path}.",
"Please update manually and then run `uv lock`.",
)
return False

with open(pyproject_toml_path, "w") as f:
f.write(new_content)

# Run `uv lock` to update the version number in the `uv.lock` file (do not update manually)
subprocess.run(["uv", "lock"], check=True)

print(
f"Updated version in {pyproject_toml_path} to {new_version},",
"and updated uv lockfile",
)
return True


def bump_version_package_json(new_version):
"""
Bump the version in package.json to new_version
Returns True on success, False on failure
"""
js_dir = "js"
subprocess.run(
["npm", "version", new_version, "--no-git-tag-version", "--allow-same-version"],
cwd=js_dir,
check=True,
)
print(
f"Updated version in {js_dir}/package.json and {js_dir}/package-lock.json to {new_version}"
)
return True


def bump_version_citation_cff(new_version, new_date):
"""
Bump the version in CITATION.cff to new_version and date-released to new_date
Returns True on success, False on failure
"""
citation_cff_path = "CITATION.cff"
pattern_version = r"(^\s*version\s*:\s*)([\w.]+)(\s*$)"
pattern_date = r"(^\s*date-released\s*:\s*)([0-9\-]+)(\s*$)"

with open(citation_cff_path, "r") as f:
content = f.read()
new_content, n_subs = re.subn(
pattern_version,
rf"\g<1>{new_version}\g<3>",
content,
count=1, # replace only the first match
flags=re.MULTILINE,
)
if n_subs < 1:
print(
f"FAILED to update version in {citation_cff_path}.",
"Please update manually.",
)
return False
new_content, n_subs = re.subn(
pattern_date,
rf"\g<1>{new_date}\g<3>",
new_content,
count=1, # replace only the first match
flags=re.MULTILINE,
)
if n_subs < 1:
print(
f"FAILED to update date-released in {citation_cff_path}.",
"Please update manually.",
)
return False

with open(citation_cff_path, "w") as f:
f.write(new_content)
print(
f"Updated version in {citation_cff_path} to {new_version}",
f"and date-released to {new_date}",
)
return True


def bump_version_changelog_md(new_version, new_date):
"""
Bump the version in CHANGELOG.md to new_version and date to new_date
Returns True on success, False on failure
"""
changelog_md_path = "CHANGELOG.md"
pattern = r"(^\s*##\s*Unreleased\s*$)"
new_header = f"\n\n## [{new_version}] - {new_date}\n"

with open(changelog_md_path, "r") as f:
content = f.read()

# Check if the header already exists, so that we don't add a double header
already_exists_pattern = rf"(^\s*##\s*\[ *{re.escape(new_version)} *\])"
if re.search(already_exists_pattern, content, flags=re.MULTILINE):
print(
f"Header for version {new_version} already exists ",
f"in {changelog_md_path}.",
)
return True

new_content, n_subs = re.subn(
pattern,
rf"\g<1>{new_header}",
content,
count=1, # replace only the first match
flags=re.MULTILINE,
)
if n_subs < 1:
print(
f"FAILED to update version in {changelog_md_path}.",
"Please update manually.",
)
return False

with open(changelog_md_path, "w") as f:
f.write(new_content)
print(
f"Added header in {changelog_md_path} with version {new_version}",
f"and release date {new_date}",
)
return True


def bump_version(args):
"""Bump the version of plotly.py everywhere it needs to be updated."""
new_version = args.version
new_date = time.strftime("%Y-%m-%d")

success = True

success = success and bump_version_citation_cff(new_version, new_date)
success = success and bump_version_changelog_md(new_version, new_date)
success = success and bump_version_package_json(new_version)
# Do this one last since it's the most visible,
# so that if one of the above commands fails it will be easier to notice
success = success and bump_version_pyproject_toml(new_version)

if not success:
print(
"\n\n*** FAILED to update version for at least one file.",
"Please check the output above for details. ***\n\n",
)
exit(1)

print(
f"\n\n*** SUCCESSFULLY updated version to {new_version}.",
"Please verify the result using `git diff`. ***\n\n",
)


def make_parser():
"""Make argument parser."""

Expand All @@ -322,6 +495,10 @@ def make_parser():

subparsers.add_parser("updateplotlyjs", help="update plotly.js")

p_bump_version = subparsers.add_parser("bumpversion", help="bump plotly.py version")
# Add a positional argument for the version
p_bump_version.add_argument("version", help="version number")

return parser


Expand Down Expand Up @@ -356,6 +533,9 @@ def main():
print(version)
update_plotlyjs(version, outdir)

elif args.cmd == "bumpversion":
bump_version(args)

elif args.cmd is None:
parser.print_help()
sys.exit(1)
Expand Down
Loading
Loading