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 + EasyDynamics

-**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())