diff --git a/.copier-answers.yml b/.copier-answers.yml
index b2bdcfa2..eeff466a 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,6 +1,6 @@
# WARNING: Do not edit this file manually.
# Any changes will be overwritten by Copier.
-_commit: v0.0.5
+_commit: v0.4.2
_src_path: gh:easyscience/templates
app_docs_url: https://easyscience.github.io/dynamics-app
app_doi: 10.5281/zenodo.18163581
diff --git a/.github/actions/publish-to-pypi/action.yml b/.github/actions/publish-to-pypi/action.yml
index 522e3a02..719928d9 100644
--- a/.github/actions/publish-to-pypi/action.yml
+++ b/.github/actions/publish-to-pypi/action.yml
@@ -1,13 +1,14 @@
name: 'Publish to PyPI'
-description: 'Publish a built distribution to PyPI using pypa/gh-action-pypi-publish'
+description: 'Publish dist/ to PyPI via Trusted Publishing (OIDC)'
inputs:
- password:
- description: 'PyPI API token (or password) for authentication'
- required: true
+ packages_dir:
+ description: 'Directory containing the built packages to upload'
+ required: false
+ default: 'dist'
runs:
using: 'composite'
steps:
- uses: pypa/gh-action-pypi-publish@release/v1
with:
- password: ${{ inputs.password }}
+ packages-dir: ${{ inputs.packages_dir }}
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 05fef7db..4056c1c0 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -107,11 +107,8 @@ jobs:
- name: Pre-build site step
run: pixi run python -c "import easydynamics"
- # Convert Python scripts in the docs/docs/tutorials/ directory to Jupyter
- # notebooks.
- # This step also strips any existing output from the notebooks and
- # prepares them for documentation.
- - name: Convert tutorial scripts to notebooks
+ # Prepare the Jupyter notebooks for documentation (strip output, etc.).
+ - name: Prepare notebooks
run: pixi run notebook-prepare
# Execute all Jupyter notebooks to generate output cells (plots, tables, etc.).
diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml
index 15a2c6ed..6e48e610 100644
--- a/.github/workflows/pypi-publish.yml
+++ b/.github/workflows/pypi-publish.yml
@@ -14,6 +14,10 @@ jobs:
pypi-publish:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
+
steps:
- name: Check-out repository
uses: actions/checkout@v5
@@ -23,10 +27,18 @@ jobs:
- name: Set up pixi
uses: ./.github/actions/setup-pixi
+ # Build the Python package (to dist/ folder)
- name: Create Python package
run: pixi run default-build
+ # Publish the package to PyPI (from dist/ folder)
+ # Instead of publishing with personal access token, we use
+ # GitHub Actions OIDC to get a short-lived token from PyPI.
+ # New publisher must be previously configured in PyPI at
+ # https://pypi.org/manage/project/easydynamics/settings/publishing/
+ # Use the following data:
+ # Owner: easyscience
+ # Repository name: dynamics-lib
+ # Workflow name: pypi-publish.yml
- name: Publish to PyPI
uses: ./.github/actions/publish-to-pypi
- with:
- password: ${{ secrets.PYPI_PASSWORD }}
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 1397f485..201dace4 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -79,6 +79,14 @@ jobs:
continue-on-error: true
shell: bash
run: pixi run nonpy-format-check
+ # Check formatting of Jupyter Notebooks in the tutorials folder
+ - name: Prepare notebooks and check formatting
+ id: check_notebooks_formatting
+ continue-on-error: true
+ shell: bash
+ run: |
+ pixi run notebook-prepare
+ pixi run notebook-format-check
# Add summary
- name: Add quality checks summary
diff --git a/.github/workflows/tutorial-tests.yml b/.github/workflows/tutorial-tests.yml
index a3454fe7..55998847 100644
--- a/.github/workflows/tutorial-tests.yml
+++ b/.github/workflows/tutorial-tests.yml
@@ -46,7 +46,7 @@ jobs:
shell: bash
run: pixi run script-tests
- - name: Convert tutorial scripts to notebooks
+ - name: Prepare notebooks
shell: bash
run: pixi run notebook-prepare
diff --git a/.gitignore b/.gitignore
index 7e0f2da3..f7ce4ac2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,11 @@ __pycache__/
.venv/
.coverage
+# PyInstaller
+dist/
+build/
+*.spec
+
# MkDocs
docs/site/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c3d471cd..007d2389 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,57 +1,54 @@
repos:
- repo: local
hooks:
- # -----------------
- # Pre-commit checks
- # -----------------
+ # -------------
+ # Manual checks
+ # -------------
- id: pixi-pyproject-check
name: pixi run pyproject-check
entry: pixi run pyproject-check
language: system
pass_filenames: false
- stages: [pre-commit]
+ stages: [manual]
- - id: pixi-py-lint-check-staged
- name: pixi run py-lint-check-staged
- entry: pixi run py-lint-check-pre
+ - id: pixi-py-lint-check
+ name: pixi run py-lint-check
+ entry: pixi run py-lint-check
language: system
pass_filenames: false
- stages: [pre-commit]
+ stages: [manual]
- - id: pixi-py-format-check-staged
- name: pixi run py-format-check-staged
- entry: pixi run py-format-check-pre
+ - id: pixi-py-format-check
+ name: pixi run py-format-check
+ entry: pixi run py-format-check
language: system
pass_filenames: false
- stages: [pre-commit]
+ stages: [manual]
- - id: pixi-nonpy-format-check-modified
- name: pixi run nonpy-format-check-modified
- entry: pixi run nonpy-format-check-modified
+ - id: pixi-nonpy-format-check
+ name: pixi run nonpy-format-check
+ entry: pixi run nonpy-format-check
language: system
pass_filenames: false
- stages: [pre-commit]
+ stages: [manual]
- id: pixi-docs-format-check
name: pixi run docs-format-check
entry: pixi run docs-format-check
language: system
pass_filenames: false
- stages: [pre-commit]
+ stages: [manual]
- # ----------------
- # Pre-push checks
- # ----------------
- - id: pixi-nonpy-format-check
- name: pixi run nonpy-format-check
- entry: pixi run nonpy-format-check
+ - id: pixi-notebook-format-check
+ name: pixi run notebook-format-check
+ entry: pixi run notebook-format-check
language: system
pass_filenames: false
- stages: [pre-push]
+ stages: [manual]
- id: pixi-unit-tests
name: pixi run unit-tests
entry: pixi run unit-tests
language: system
pass_filenames: false
- stages: [pre-push]
+ stages: [manual]
diff --git a/README.md b/README.md
index 2f55c56f..373d3828 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,18 @@
-
+
-
+
-
+
-**EasyDynamics** is a scientific software for plotting and fitting qens
-and ins powder data.
+**EasyDynamics** is a scientific software for plotting and fitting QENS
+and INS powder data.
+
+
**EasyDynamics** is available both as a Python library and as a
cross-platform desktop application.
diff --git a/docs/docs/assets/stylesheets/extra.css b/docs/docs/assets/stylesheets/extra.css
index 1c199950..a625be80 100644
--- a/docs/docs/assets/stylesheets/extra.css
+++ b/docs/docs/assets/stylesheets/extra.css
@@ -222,9 +222,27 @@ Adjust the margins and paddings to fit the defaults in MkDocs Material and do no
width: 100% !important;
display: flex !important;
}
+
.jp-Notebook {
padding: 0 !important;
margin-top: -3em !important;
+
+ /* Ensure notebook content stretches across the page */
+ width: 100% !important;
+ max-width: 100% !important;
+
+ /* mkdocs-material + some notebook HTML end up as flex */
+ align-items: stretch !important;
+}
+
+.jp-Notebook .jp-Cell {
+ /* Key: flex children often need min-width: 0 to prevent weird shrink */
+ width: 100% !important;
+ max-width: 100% !important;
+ min-width: 0 !important;
+
+ /* Removes jupyter cell paddings */
+ padding-left: 0 !important;
}
/* Removes jupyter cell prefixes, like In[123]: */
@@ -234,11 +252,6 @@ Adjust the margins and paddings to fit the defaults in MkDocs Material and do no
display: none !important;
}
-/* Removes jupyter cell paddings */
-.jp-Cell {
- padding-left: 0 !important;
-}
-
/* Removes jupyter output cell padding to align with input cell text */
.jp-RenderedText {
padding-left: 0.85em !important;
diff --git a/docs/docs/installation-and-setup/index.md b/docs/docs/installation-and-setup/index.md
index 3513f6e9..420ef07e 100644
--- a/docs/docs/installation-and-setup/index.md
+++ b/docs/docs/installation-and-setup/index.md
@@ -8,8 +8,8 @@ icon: material/cog-box
**Python 3.11** through **3.12**.
To install and set up EasyDynamics, we recommend using
-[**Pixi**](https://prefix.dev), a modern package manager for Windows,
-macOS, and Linux.
+[**Pixi**](https://pixi.prefix.dev), a modern package manager for
+Windows, macOS, and Linux.
!!! note "Main benefits of using Pixi"
@@ -46,16 +46,9 @@ This section describes the simplest way to set up EasyDynamics using
```txt
pixi add python=3.12
```
-- Add the GNU Scientific Library (GSL) dependency:
+- Add EasyDynamics to the Pixi environment from PyPI:
```txt
- pixi add gsl
- ```
-- Add EasyDynamics with the `visualization` extras, which include
- optional dependencies used for simplified visualization of charts and
- tables. This can be especially useful for running the Jupyter Notebook
- examples:
- ```txt
- pixi add --pypi "easydynamics[visualization]"
+ pixi add --pypi easydynamics
```
- Add a Pixi task to run EasyDynamics commands easily:
```txt
@@ -160,20 +153,7 @@ simply delete and recreate the environment.
### Installing from PyPI { #from-pypi }
EasyDynamics is available on **PyPI (Python Package Index)** and can be
-installed using `pip`.
-
-We recommend installing the latest release of EasyDynamics with the
-`visualization` extras, which include optional dependencies used for
-simplified visualization of charts and tables. This can be especially
-useful for running the Jupyter Notebook examples. To do so, use the
-following command:
-
-```txt
-pip install 'easydynamics[visualization]'
-```
-
-If only the core functionality is needed, the library can be installed
-simply with:
+installed using `pip`. To do so, use the following command:
```txt
pip install easydynamics
@@ -216,10 +196,10 @@ example:
pip install git+https://github.com/easyscience/dynamics-lib@develop
```
-To include extra dependencies (e.g., visualization):
+To include extra dependencies (e.g., dev):
```txt
-pip install 'easydynamics[visualization] @ git+https://github.com/easyscience/dynamics-lib@develop'
+pip install 'easydynamics[dev] @ git+https://github.com/easyscience/dynamics-lib@develop'
```
## How to Run Tutorials
diff --git a/docs/docs/introduction/index.md b/docs/docs/introduction/index.md
index 58240514..740d4b0d 100644
--- a/docs/docs/introduction/index.md
+++ b/docs/docs/introduction/index.md
@@ -6,8 +6,8 @@ icon: material/information-slab-circle
## Description
-**EasyDynamics** is a scientific software for plotting and fitting qens
-and ins powder data.
+**EasyDynamics** is a scientific software for plotting and fitting QENS
+and INS powder data.
**EasyDynamics** is available both as a Python library and as a
cross-platform desktop application.
diff --git a/docs/docs/tutorials/components.ipynb b/docs/docs/tutorials/components.ipynb
index 7815bcd4..83278fc4 100644
--- a/docs/docs/tutorials/components.ipynb
+++ b/docs/docs/tutorials/components.ipynb
@@ -21,6 +21,7 @@
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
+ "import scipp as sc\n",
"\n",
"from easydynamics.sample_model import DampedHarmonicOscillator\n",
"from easydynamics.sample_model import DeltaFunction\n",
@@ -105,8 +106,6 @@
"metadata": {},
"outputs": [],
"source": [
- "import scipp as sc\n",
- "\n",
"x1 = sc.linspace(dim='x', start=-2.0, stop=2.0, num=100, unit='meV')\n",
"x2 = sc.linspace(dim='x', start=-2.0 * 1e3, stop=2.0 * 1e3, num=101, unit='microeV')\n",
"\n",
diff --git a/docs/docs/tutorials/detailed_balance.ipynb b/docs/docs/tutorials/detailed_balance.ipynb
index d09a2546..135894d3 100644
--- a/docs/docs/tutorials/detailed_balance.ipynb
+++ b/docs/docs/tutorials/detailed_balance.ipynb
@@ -23,11 +23,11 @@
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
- "\n",
- "%matplotlib widget\n",
"import numpy as np\n",
"\n",
- "from easydynamics.utils import _detailed_balance_factor as detailed_balance_factor"
+ "from easydynamics.utils import _detailed_balance_factor as detailed_balance_factor\n",
+ "\n",
+ "%matplotlib widget"
]
},
{
diff --git a/docs/docs/tutorials/diffusion_model.ipynb b/docs/docs/tutorials/diffusion_model.ipynb
index be9ec8e1..f3d1571b 100644
--- a/docs/docs/tutorials/diffusion_model.ipynb
+++ b/docs/docs/tutorials/diffusion_model.ipynb
@@ -67,7 +67,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "a50c67ec",
+ "id": "3",
"metadata": {},
"outputs": [],
"source": [
diff --git a/docs/docs/tutorials/experiment.ipynb b/docs/docs/tutorials/experiment.ipynb
index 6319c61f..f6e185df 100644
--- a/docs/docs/tutorials/experiment.ipynb
+++ b/docs/docs/tutorials/experiment.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
- "id": "906b959a",
+ "id": "0",
"metadata": {},
"source": [
"# Experiment\n",
@@ -12,7 +12,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "c7d23add",
+ "id": "1",
"metadata": {},
"outputs": [],
"source": [
@@ -24,7 +24,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "2b7c5ca8",
+ "id": "2",
"metadata": {},
"outputs": [],
"source": [
@@ -38,7 +38,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "238ba6ee",
+ "id": "3",
"metadata": {},
"outputs": [],
"source": [
@@ -50,7 +50,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "bc32ab1f",
+ "id": "4",
"metadata": {},
"outputs": [],
"source": [
diff --git a/pixi.lock b/pixi.lock
index da8aee45..f51bc65b 100644
--- a/pixi.lock
+++ b/pixi.lock
@@ -5,8 +5,6 @@ environments:
- url: https://conda.anaconda.org/conda-forge/
indexes:
- https://pypi.org/simple
- options:
- pypi-prerelease-mode: if-necessary-or-explicit
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -80,7 +78,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -335,7 +333,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -590,7 +588,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -838,7 +836,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -1031,8 +1029,6 @@ environments:
- url: https://conda.anaconda.org/conda-forge/
indexes:
- https://pypi.org/simple
- options:
- pypi-prerelease-mode: if-necessary-or-explicit
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -1106,7 +1102,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -1362,7 +1358,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -1618,7 +1614,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -1867,7 +1863,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -2061,8 +2057,6 @@ environments:
- url: https://conda.anaconda.org/conda-forge/
indexes:
- https://pypi.org/simple
- options:
- pypi-prerelease-mode: if-necessary-or-explicit
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -2136,7 +2130,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -2391,7 +2385,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -2646,7 +2640,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -2894,7 +2888,7 @@ environments:
- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl
- pypi: https://files.pythonhosted.org/packages/b8/01/74922a1c552137c05a41fee0c61153753dddc9117d19c7c5902c146c25ab/copier-9.11.3-py3-none-any.whl
- - pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
- pypi: https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl
@@ -4091,7 +4085,7 @@ packages:
requires_python: '>=3.5'
- pypi: ./
name: easydynamics
- version: 0.1.1+devdirty2
+ version: 0.1.0+devdirty6
sha256: de299c914d4a865b9e2fdefa5e3947f37b1f26f73ff9087f7918ee417f3dd288
requires_dist:
- darkdetect
@@ -4134,7 +4128,8 @@ packages:
- validate-pyproject[all] ; extra == 'dev'
- versioningit ; extra == 'dev'
requires_python: '>=3.11'
-- pypi: git+https://github.com/easyscience/corelib.git#bd106537fcf522336aa0176aa6ccf215be8a5b86
+ editable: true
+- pypi: git+https://github.com/easyscience/corelib.git?rev=develop#bd106537fcf522336aa0176aa6ccf215be8a5b86
name: easyscience
version: 2.1.0
requires_dist:
diff --git a/pixi.toml b/pixi.toml
index baa0ea35..d280c259 100644
--- a/pixi.toml
+++ b/pixi.toml
@@ -76,41 +76,41 @@ default = { features = ['default', 'py-max'] }
[tasks]
+##################
# ๐งช Testing Tasks
-unit-tests = 'python -m pytest tests/unit/ --color=yes --cov= --cov-report='
+##################
+
+unit-tests = 'python -m pytest tests/unit/ --color=yes -v'
integration-tests = 'python -m pytest tests/integration/ --color=yes -n auto -v'
notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --color=yes -n auto -v'
script-tests = 'python -m pytest tools/test_scripts.py --color=yes -n auto -v'
test = { depends-on = ['unit-tests'] }
+###########
# โ๏ธ Checks
+###########
+
pyproject-check = 'python -m validate_pyproject pyproject.toml'
-py-lint-check-pre = "python -m ruff check"
-py-lint-check = 'pixi run py-lint-check-pre .'
-py-format-check-pre = "python -m ruff format --check"
-py-format-check = "pixi run py-format-check-pre ."
-nonpy-format-check-pre = "npx prettier --list-different --config=prettierrc.toml"
-nonpy-format-check-modified = "pixi run nonpy-format-check-pre $(git diff --diff-filter=d --name-only HEAD | grep -E '\\.(json|ya?ml|toml|md|css|html)$' || echo .)"
-nonpy-format-check = "pixi run nonpy-format-check-pre ."
+docs-format-check = 'docformatter --check src/ docs/docs/tutorials/'
notebook-format-check = 'nbqa ruff docs/docs/tutorials/'
-docs-format-check = 'docformatter src/ docs/docs/tutorials/ --check'
+py-lint-check = 'ruff check src/ tests/ docs/docs/tutorials/'
+py-format-check = "ruff format --check src/ tests/ docs/docs/tutorials/"
+nonpy-format-check = "npx prettier --list-different --config=prettierrc.toml --ignore-unknown ."
+nonpy-format-check-modified = "python tools/nonpy_prettier_modified.py"
-check = { depends-on = [
- 'docs-format-check',
- 'py-format-check',
- 'py-lint-check',
- 'nonpy-format-check-modified',
-] }
+check = 'pre-commit run --hook-stage manual --all-files'
+##########
# ๐ ๏ธ Fixes
-py-lint-fix = 'pixi run py-lint-check --fix'
-#py-format-fix = "python -m ruff format $(git diff --cached --name-only -- '*.py')"
-py-format-fix = "python -m ruff format"
-nonpy-format-fix = 'pixi run nonpy-format-check --write'
-nonpy-format-fix-modified = "pixi run nonpy-format-check-modified --write"
-notebook-format-fix = 'pixi run notebook-format-check --fix'
-docs-format-fix = 'docformatter src/ docs/docs/tutorials/ --in-place'
+##########
+
+docs-format-fix = 'docformatter --in-place src/ docs/docs/tutorials/'
+notebook-format-fix = 'nbqa ruff --fix docs/docs/tutorials/'
+py-lint-fix = 'ruff check --fix src/ tests/ docs/docs/tutorials/'
+py-format-fix = "ruff format src/ tests/ docs/docs/tutorials/"
+nonpy-format-fix = 'npx prettier --write --list-different --config=prettierrc.toml --ignore-unknown .'
+nonpy-format-fix-modified = "python tools/nonpy_prettier_modified.py --write"
success-message-fix = 'echo "โ
All code auto-formatting steps have been applied."'
fix = { depends-on = [
@@ -118,10 +118,14 @@ fix = { depends-on = [
'docs-format-fix',
'py-lint-fix',
'nonpy-format-fix',
+ 'notebook-format-fix',
'success-message-fix',
] }
+####################
# ๐งฎ Code Complexity
+####################
+
complexity-check = 'radon cc -s src/'
complexity-check-json = 'radon cc -s -j src/'
maintainability-check = 'radon mi src/'
@@ -129,8 +133,11 @@ maintainability-check-json = 'radon mi -j src/'
raw-metrics = 'radon raw -s src/'
raw-metrics-json = 'radon raw -s -j src/'
+#############
# ๐ Coverage
-unit-tests-coverage = 'python -m pytest tests/unit/ --color=yes --cov=src/easydynamics --cov-report=term-missing'
+#############
+
+unit-tests-coverage = 'pixi run unit-tests --cov=src/easydynamics --cov-report=term-missing'
integration-tests-coverage = 'pixi run integration-tests --cov=src/easydynamics --cov-report=term-missing'
docstring-coverage = 'interrogate -c pyproject.toml src/easydynamics'
@@ -140,19 +147,25 @@ cov = { depends-on = [
'integration-tests-coverage',
] }
+########################
# ๐ Notebook Management
+########################
+
notebook-convert = 'jupytext docs/docs/tutorials/*.py --from py:percent --to ipynb'
notebook-strip = 'nbstripout docs/docs/tutorials/*.ipynb'
notebook-tweak = 'python tools/tweak_notebooks.py tutorials/'
notebook-exec = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=600 --overwrite --color=yes -n auto -v'
notebook-prepare = { depends-on = [
- ###'notebook-convert',
+ #'notebook-convert',
'notebook-strip',
- ###'notebook-tweak',
+ #'notebook-tweak',
] }
+########################
# ๐ Documentation Tasks
+########################
+
docs-vars = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning'"
docs-pre = "pixi run docs-vars python -m mkdocs"
docs-serve = "pixi run docs-pre serve -f docs/mkdocs.yml"
@@ -163,29 +176,19 @@ docs-build-local = "pixi run docs-build --no-directory-urls"
docs-deploy-pre = 'mike deploy -F docs/mkdocs.yml --push --branch gh-pages --update-aliases --alias-type redirect'
docs-set-default-pre = 'mike set-default -F docs/mkdocs.yml --push --branch gh-pages'
-docs-update-assets = 'pixi run python tools/update_docs_assets.py'
+docs-update-assets = 'python tools/update_docs_assets.py'
+##############################
# ๐ฆ Template Management Tasks
-copier-copy = "pixi run copier copy gh:easyscience/templates . --data-file ../dynamics/.copier-answers.yml --data template_type=lib"
-copier-recopy = "pixi run copier recopy --data-file ../dynamics/.copier-answers.yml --data template_type=lib"
-copier-update = "pixi run copier update --data-file ../dynamics/.copier-answers.yml --data template_type=lib"
+##############################
-# ๐ Development & Build Tasks
-default-build = 'python -m build'
-dist-build = 'python -m build --wheel --outdir dist'
+copier-copy = "copier copy gh:easyscience/templates . --data-file ../dynamics/.copier-answers.yml --data template_type=lib"
+copier-recopy = "copier recopy --data-file ../dynamics/.copier-answers.yml --data template_type=lib"
+copier-update = "copier update --data-file ../dynamics/.copier-answers.yml --data template_type=lib"
-npm-config = 'npm config set registry https://registry.npmjs.org/'
-prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml'
-
-clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +"
-spdx-update = 'python tools/update_spdx.py'
-
-# Run like a real commit: staged files only (almost)
-pre-commit-check = 'pre-commit run --hook-stage pre-commit'
-# CI check: lint/format everything
-pre-commit-check-all = 'pre-commit run --all-files --hook-stage pre-commit'
-# Pre-push check: lint/format everything
-pre-push-check = 'pre-commit run --all-files --hook-stage pre-push'
+#####################
+# ๐ช Pre-commit Hooks
+#####################
pre-commit-clean = 'pre-commit clean'
pre-commit-install = 'pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite'
@@ -196,11 +199,28 @@ pre-commit-setup = { depends-on = [
'pre-commit-install',
] }
+####################################
+# ๐ Other Development & Build Tasks
+####################################
+
+github-labels = 'python tools/update_github_labels.py'
+
+default-build = 'python -m build'
+dist-build = 'python -m build --wheel --outdir dist'
+
+npm-config = 'npm config set registry https://registry.npmjs.org/'
+prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml'
+
+clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +"
+spdx-update = 'python tools/update_spdx.py'
+
post-install = { depends-on = [
'npm-config',
'prettier-install',
- 'pre-commit-setup',
+ #'pre-commit-setup',
] }
+##########################
# ๐ Main Package Shortcut
+##########################
easydynamics = 'python -m easydynamics'
diff --git a/tools/update_github_labels.py b/tools/update_github_labels.py
new file mode 100644
index 00000000..a18043d0
--- /dev/null
+++ b/tools/update_github_labels.py
@@ -0,0 +1,254 @@
+"""
+Set/update GitHub labels for current or specified easyscience
+repository.
+
+Requires:
+ - gh CLI installed
+ - gh auth login completed
+
+Usage:
+ python update_github_labels.py
+ python update_github_labels.py --dry-run
+ python update_github_labels.py --repo easyscience/my-repo
+ python update_github_labels.py --repo easyscience/my-repo --dry-run
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import shlex
+import subprocess
+import sys
+from dataclasses import dataclass
+from typing import Iterable
+
+
+EASYSCIENCE_ORG = 'easyscience'
+
+
+# --- Label definitions -----------------------------------------------------------
+
+BASIC_GITHUB_LABELS = [
+ 'bug',
+ 'documentation',
+ 'duplicate',
+ 'enhancement',
+ 'good first issue',
+ 'help wanted',
+ 'invalid',
+ 'question',
+ 'wontfix',
+]
+
+NEW_BASIC_LABEL_NAMES = [
+ '[scope] bug',
+ '[scope] documentation',
+ '[maintainer] duplicate',
+ '[scope] enhancement',
+ '[maintainer] good first issue',
+ '[maintainer] help wanted',
+ '[maintainer] invalid',
+ '[maintainer] question',
+ '[maintainer] wontfix',
+]
+
+SCOPE_LABELS = [
+ ('bug', 'Bug report or fix (major.minor.PATCH)'),
+ ('documentation', 'Documentation only changes (major.minor.patch.POST)'),
+ ('enhancement', 'Adds/improves features (major.MINOR.patch)'),
+ ('maintenance', 'Code/tooling cleanup, no feature or bugfix (major.minor.PATCH)'),
+ ('significant', 'Breaking or major changes (MAJOR.minor.patch)'),
+ ('โ ๏ธ label needed', 'Automatically added to issues and PRs without a [scope] label'),
+]
+
+MAINTAINER_LABELS = [
+ ('duplicate', 'Already reported or submitted'),
+ ('good first issue', 'Good entry-level issue for newcomers'),
+ ('help wanted', 'Needs additional help to resolve or implement'),
+ ('invalid', 'Invalid, incorrect or outdated'),
+ ('question', 'Needs clarification, discussion, or more information'),
+ ('wontfix', 'Will not be fixed or continued'),
+]
+
+PRIORITY_LABELS = [
+ ('lowest', 'Very low urgency'),
+ ('low', 'Low importance'),
+ ('medium', 'Normal/default priority'),
+ ('high', 'Should be prioritized soon'),
+ ('highest', 'Urgent. Needs attention ASAP'),
+ ('โ ๏ธ label needed', 'Automatically added to issues without a [priority] label'),
+]
+
+BOT_LABEL = (
+ '[bot] pull request',
+ 'Automated release PR. Excluded from changelog/versioning',
+)
+
+COLORS = {
+ 'scope': 'd73a4a',
+ 'maintainer': '0e8a16',
+ 'priority': 'fbca04',
+ 'bot': '5319e7',
+}
+
+
+# --- Helpers --------------------------------------------------------------------
+
+
+@dataclass(frozen=True)
+class CmdResult:
+ returncode: int
+ stdout: str
+ stderr: str
+
+
+def run_cmd(args: list[str], *, dry_run: bool, check: bool = True) -> CmdResult:
+ """Run a command (or print it in dry-run mode)."""
+ cmd_str = ' '.join(shlex.quote(a) for a in args)
+
+ if dry_run:
+ print(f'{cmd_str}')
+ return CmdResult(0, '', '')
+
+ proc = subprocess.run(
+ args,
+ text=True,
+ capture_output=True,
+ )
+ res = CmdResult(proc.returncode, proc.stdout.strip(), proc.stderr.strip())
+
+ if check and proc.returncode != 0:
+ raise RuntimeError(f'Command failed ({proc.returncode}): {cmd_str}\n{res.stderr}')
+
+ return res
+
+
+def get_current_repo_name_with_owner() -> str:
+ res = subprocess.run(
+ ['gh', 'repo', 'view', '--json', 'nameWithOwner'],
+ text=True,
+ capture_output=True,
+ check=True,
+ )
+ data = json.loads(res.stdout)
+ nwo = data.get('nameWithOwner')
+ if not nwo or '/' not in nwo:
+ raise RuntimeError('Could not determine current repository name')
+ return nwo
+
+
+def try_rename_label(repo: str, old: str, new: str, *, dry_run: bool) -> None:
+ try:
+ run_cmd(
+ ['gh', 'label', 'edit', old, '--name', new, '--repo', repo],
+ dry_run=dry_run,
+ )
+ print(f'Rename: {old!r} โ {new!r}')
+ except Exception:
+ print(f'Skip rename (label not found): {old!r}')
+
+
+def upsert_label(
+ repo: str,
+ name: str,
+ color: str,
+ description: str,
+ *,
+ dry_run: bool,
+) -> None:
+ run_cmd(
+ [
+ 'gh',
+ 'label',
+ 'create',
+ name,
+ '--color',
+ color,
+ '--description',
+ description,
+ '--force',
+ '--repo',
+ repo,
+ ],
+ dry_run=dry_run,
+ )
+ print(f'Upsert label: {name!r}')
+
+
+def upsert_group(
+ repo: str,
+ prefix: str,
+ color: str,
+ items: Iterable[tuple[str, str]],
+ *,
+ dry_run: bool,
+) -> None:
+ for short, desc in items:
+ upsert_label(
+ repo,
+ f'[{prefix}] {short}',
+ color,
+ desc,
+ dry_run=dry_run,
+ )
+
+
+# --- Main -----------------------------------------------------------------------
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description='Sync GitHub labels for easyscience repos')
+ parser.add_argument(
+ '--repo',
+ help='Target repository in the form easyscience/',
+ )
+ parser.add_argument(
+ '--dry-run',
+ action='store_true',
+ help='Print actions without applying changes',
+ )
+ args = parser.parse_args()
+
+ if args.repo:
+ repo = args.repo
+ else:
+ repo = get_current_repo_name_with_owner()
+
+ org, _ = repo.split('/', 1)
+
+ if org.lower() != EASYSCIENCE_ORG:
+ print(
+ f"Refusing to run: repository {repo!r} is not under '{EASYSCIENCE_ORG}'.",
+ file=sys.stderr,
+ )
+ return 2
+
+ print(f'Target repository: {repo}')
+ if args.dry_run:
+ print('Running in DRY-RUN mode (no changes will be made)\n')
+
+ # 1) Rename basic labels
+ for old, new in zip(BASIC_GITHUB_LABELS, NEW_BASIC_LABEL_NAMES, strict=True):
+ try_rename_label(repo, old, new, dry_run=args.dry_run)
+
+ # 2) Scope / Maintainer / Priority groups
+ upsert_group(repo, 'scope', COLORS['scope'], SCOPE_LABELS, dry_run=args.dry_run)
+ upsert_group(repo, 'maintainer', COLORS['maintainer'], MAINTAINER_LABELS, dry_run=args.dry_run)
+ upsert_group(repo, 'priority', COLORS['priority'], PRIORITY_LABELS, dry_run=args.dry_run)
+
+ # 3) Bot label
+ upsert_label(
+ repo,
+ BOT_LABEL[0],
+ COLORS['bot'],
+ BOT_LABEL[1],
+ dry_run=args.dry_run,
+ )
+
+ print('\nDone.')
+ return 0
+
+
+if __name__ == '__main__':
+ raise SystemExit(main())