Documentation

name: Documentation

on:
  workflow_dispatch:
  workflow_call:

jobs:
  parse_commit_info:
    runs-on: ubuntu-latest
    outputs:
      can_deploy: ${{ steps.decide.outputs.can_deploy }}
      deploy_to: ${{ steps.decide.outputs.deploy_to }}

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Copy build utils
        run: |
          cp -r .github/utils ../utils

      - name: Decide Whether to Build and/or Release
        id: decide
        run: |
          set -xe
          CAN_DEPLOY=$(python ../utils/please.py can_i_deploy_documentation)
          DEPLOY_TO=$(python ../utils/please.py where_can_i_deploy_documentation)
          # The message string is directly substituted in before the command is run.
          # We use a HereDoc to avoid quotation issues if the message has quotes as well.
          TITLE=$(cat <<EOF | head -n 1
          ${{ github.event.head_commit.message }}
          EOF
          )

          echo "can_deploy=$CAN_DEPLOY" >> $GITHUB_OUTPUT
          echo "deploy_to=$DEPLOY_TO" >> $GITHUB_OUTPUT
          echo "commit_title='$TITLE'" >> $GITHUB_OUTPUT
          echo github.ref ${{ github.ref }}

  build-documentation:
    runs-on: ubuntu-latest
    needs: parse_commit_info

    strategy:
      matrix:
        python-version: [3.12]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Quarto
        uses: quarto-dev/quarto-actions/setup@v2
        with:
          version: pre-release

      - name: Install Package
        shell: bash
        run: |
          make doc-deps

      - name: Environment Information
        shell: bash
        run: |
          ls -la
          ls -la doc
          pip list

      - name: Get Commit Title
        run: |

      - name: Build docs
        shell: bash
        run: |
          pushd doc; make doc; popd

      - name: Environment Information
        shell: bash
        run: |
          ls -la doc
          cat doc/_variables.yml
          ls -la doc/reference

      - name: Deploy to Documentation to a Branch
        uses: JamesIves/github-pages-deploy-action@v4
        if: contains(needs.parse_commit_info.outputs.can_deploy, 'true')
        with:
          folder: doc/_site
          branch: ${{ needs.parse_commit_info.outputs.deploy_to }}
          commit-message: ${{ needs.parse_commit_info.outputs.commit_title }}


Release

name: Release

on:
  push:
    tags:
      - 'v[0-9]*'

jobs:
  run-tests:
    name: Run all tests
    uses: ./.github/workflows/testing.yml
    with:
      skip_codecov: true

  check-semver-tag:
    name: Check if the tag is in semantic version format
    needs: [run-tests]
    runs-on: ubuntu-latest
    outputs:
      tag_type: ${{ steps.check-tag.outputs.tag_type }}
      publish_on: ${{ steps.check-tag.outputs.publish_on }}

    strategy:
      matrix:
        python-version: ["3.12"]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Python
        with:
          python-version: ${{ matrix.python-version }}
        uses: actions/setup-python@v5

      - name: Check for semantic version tag
        id: check-tag
        run: python tools/gha_check_semver.py

      - name: See outputs
        shell: bash
        run: |
          echo "tag_type="${{ steps.check-tag.outputs.tag_type }}
          echo "publish_on="${{ steps.check-tag.outputs.publish_on }}

  # Ref: https://github.com/pypa/gh-action-pypi-publish
  publish:
    name: Build and publish Python 🐍 distributions 📦 to TestPyPI or PyPI
    needs: [check-semver-tag]
    runs-on: ubuntu-latest

    if: ${{ needs.check-semver-tag.outputs.publish_on != '' }}

    environment:
      name: release
      url: https://github.com/has2k1/plotnine

    permissions:
      id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing

    strategy:
      matrix:
        python-version: ["3.12"]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        run: python -m pip install build

      - name: Build a wheel and a source tarball
        run: make dist

      - name: Publish distribution 📦 to Test PyPI
        if: ${{ needs.check-semver-tag.outputs.publish_on == 'testpypi' }}
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: https://test.pypi.org/legacy/
          skip-existing: true

      - name: Publish distribution 📦 to PyPI
        if: ${{ needs.check-semver-tag.outputs.publish_on == 'pypi' }}
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true

      - name: Build Website (Dev)
        if: ${{ needs.check-semver-tag.outputs.publish_on == 'testpypi' }}
        uses: peter-evans/repository-dispatch@v3
        with:
          token: ${{ secrets.PAT_PLOTNINE_WEBSITE }}
          repository: has2k1/plotnine.org
          event-type: plotnine-pre-release

      - name: Build Website
        if: ${{ needs.check-semver-tag.outputs.publish_on == 'pypi' }}
        uses: peter-evans/repository-dispatch@v3
        with:
          token: ${{ secrets.PAT_PLOTNINE_WEBSITE }}
          repository: has2k1/plotnine.org
          event-type: plotnine-release


build

name: build

on:
  push:
    branches:
      - '*'
    tags-ignore:
      - 'v[0-9]*'
  pull_request:
  workflow_call:
    inputs:
      skip_codecov:
        type: boolean
        required: false
        default: false
jobs:
  unittests:
    runs-on: ubuntu-latest

    # We want to run on external PRs, but not on our own internal PRs as they'll be run
    # by the push to the branch.
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

    strategy:
      matrix:
        python-version: ["3.10", 3.12]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          # Install as an editable so that the coverage path
          # is predicable
          pip install -e ".[extra,test]"

      - name: Environment Information
        shell: bash
        run: |
          pip list

      - name: Run Tests
        shell: bash
        run: |
          coverage erase
          make test

      - name: List Directory
        shell: bash
        run: |
          ls -la

      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: result-images
          path: tests/result_images/
          if-no-files-found: ignore

      # To change secrets
      # https://app.codecov.io/github/has2k1/plotnine/settings
      # https://github.com/has2k1/plotnine/settings/secrets/actions
      - name: Upload coverage to Codecov
        if: ${{ !inputs.skip_codecov }}
        uses: codecov/codecov-action@v4
        with:
          name: "py${{ matrix.python-version }}"
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: true

  lint-and-format:
    runs-on: ubuntu-latest

    # We want to run on external PRs, but not on our own internal PRs as they'll be run
    # by the push to the branch.
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

    strategy:
      matrix:
        python-version: [3.12]
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install ruff

      - name: Environment Information
        shell: bash
        run: pip list

      - name: Check lint with Ruff
        shell: bash
        run: |
          make lint

      - name: Check format with Ruff
        shell: bash
        run: |
          make format

  typecheck:
    runs-on: ubuntu-latest

    # We want to run on external PRs, but not on our own internal PRs as they'll be run
    # by the push to the branch.
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

    strategy:
      matrix:
        python-version: [3.12]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install ".[extra, typing]"

      - name: Environment Information
        shell: bash
        run: pip list

      - name: Run Tests
        shell: bash
        run: make typecheck

  call-build-documentation:
    if: |
      github.event_name == 'push' ||
      github.event.pull_request.head.repo.full_name != github.repository
    uses: ./.github/workflows/documentation.yml

  build-website:
    # Requires all the previous jobs to pass
    needs: [unittests, lint-and-format, typecheck, call-build-documentation]
    if: |
      github.event_name == 'push' &&
      github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Build Website (Dev)
        uses: peter-evans/repository-dispatch@v3
        with:
          token: ${{ secrets.PAT_PLOTNINE_WEBSITE }}
          repository: has2k1/plotnine.org
          event-type: push-plotnine-main


Weekly tests

name: Weekly tests

on:
  push:
    branches: ['weekly-tests']
  schedule:
    # At 00:00 on Monday
    - cron: "40 06 * * WED"

jobs:
  plotnine_latest_matplotlib_latest:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: [3.12]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install --upgrade pip
          pip install ".[extra,test]"
          pip install --upgrade git+https://github.com/matplotlib/matplotlib

      - name: Environment Information
        shell: bash
        run: |
          pip list

      - name: Run Tests
        shell: bash
        run: |
          make test

      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: result-images
          path: result_images/
          if-no-files-found: ignore

  plotnine_latest_pandas_latest:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: [3.12]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install --upgrade pip
          pip install ".[extra,test]"
          pip install --upgrade git+https://github.com/pandas-dev/pandas

      - name: Environment Information
        shell: bash
        run: |
          pip list

      # to do: think about catching deprecations earlier
      - name: Run Tests
        shell: bash
        run: |
          make test

      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: result-images
          path: result_images/
          if-no-files-found: ignore

  plotnine_pypi_matplotlib_latest:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ["3.10"]

    steps:
      - name: Get latest release with tag from GitHub API
        id: latestrelease
        shell: bash
        run: |
          echo "::set-output name=releasetag::$(curl -s https://api.github.com/repos/has2k1/plotnine/releases/latest | jq '.tag_name' | sed 's/\"//g')"

      - name: Confirm Release Tag
        shell: bash
        run: |
          echo ${{ steps.latestrelease.outputs.latestrelease }}

      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          ref: ${{ steps.latestrelease.outputs.latestrelease }}

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install --upgrade pip
          pip install --upgrade git+https://github.com/matplotlib/matplotlib
          pip install ".[extra,test]"

      - name: Environment Information
        shell: bash
        run: |
          pip list

      - name: Run Tests
        shell: bash
        run: |
          make test

      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: result-images
          path: result_images/
          if-no-files-found: ignore

  plotnine_pypi_pandas_latest:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ["3.10"]

    steps:
      - name: Get latest release with tag from GitHub API
        id: latestrelease
        shell: bash
        run: |
          echo "::set-output name=releasetag::$(curl -s https://api.github.com/repos/has2k1/plotnine/releases/latest | jq '.tag_name' | sed 's/\"//g')"

      - name: Confirm Release Tag
        shell: bash
        run: |
          echo ${{ steps.latestrelease.outputs.latestrelease }}

      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          ref: ${{ steps.latestrelease.outputs.latestrelease }}

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install --upgrade pip
          pip install --upgrade git+https://github.com/pandas-dev/pandas
          pip install ".[extra,test]"

      - name: Environment Information
        shell: bash
        run: |
          pip list

      # to do: think about catching deprecations earlier
      - name: Run Tests
        shell: bash
        run: |
          make test

      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: result-images
          path: tests/result_images/
          if-no-files-found: ignore

  typecheck_plotnine_latest_matplotlib_latest:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: [3.11]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install --upgrade pip
          pip install ".[extra,typing]"
          pip install --upgrade git+https://github.com/matplotlib/matplotlib

      - name: Environment Information
        shell: bash
        run: |
          pip list

      - name: Run Tests
        shell: bash
        run: |
          make typecheck

  typecheck_plotnine_latest_pandas_latest:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: [3.11]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Packages
        shell: bash
        run: |
          pip install --upgrade pip
          pip install ".[extra,typing]"
          pip install --upgrade git+https://github.com/pandas-dev/pandas

      - name: Environment Information
        shell: bash
        run: |
          pip list

      # to do: think about catching deprecations earlier
      - name: Run Tests
        shell: bash
        run: |
          make test

      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: result-images
          path: result_images/
          if-no-files-found: ignore


CI

name: CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches:
      - '**'

jobs:
  run_unittest_tests:
    name: Unittest
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-20.04]
        python-version:
            - "3.7"
            - "3.8"
            - "3.9"
            - "3.10"
            - "3.11"
            - "3.12"
        django-version:
            - "3.2"
            - "4.1"
            - "4.2"
            - "5.0"
            - "main"
        exclude:
            - python-version: "3.7"
              django-version: "4.1"
            - python-version: "3.12"
              django-version: "4.1"
            - python-version: "3.7"
              django-version: "4.2"
            - python-version: "3.12"
              django-version: "4.2"
            - python-version: "3.7"
              django-version: "5.0"
            - python-version: "3.8"
              django-version: "5.0"
            - python-version: "3.9"
              django-version: "5.0"
            - python-version: "3.7"
              django-version: "main"
            - python-version: "3.8"
              django-version: "main"
            - python-version: "3.9"
              django-version: "main"
            - python-version: "3.11"
              django-version: "3.2"
            - python-version: "3.12"
              django-version: "3.2"

    steps:
      - uses: actions/checkout@v4
      - name: setup python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Dependencies
        run: |
          pip install --upgrade setuptools
          pip install tox

      - name: Run unittest Python ${{ matrix.python-version }} -- Django ${{ matrix.django-version }}
        env:
          TOXENV: py${{ matrix.python-version }}-django${{ matrix.django-version }}
        run: tox

  run_quality_tests:
    name: Quality tests
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-20.04]
        python-version: ["3.7"]

    steps:
      - uses: actions/checkout@v4
      - name: setup python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Dependencies
        run: |
          pip install tox

      - name: Quality tests
        env:
          TOXENV: ruff
        run: |
          tox


OWTF CI

name: OWTF CI

on:
  push:
    branches:
      - develop
  pull_request:
    branches:
      - develop

jobs:
  build:
    runs-on: ubuntu-20.04

    strategy:
      matrix:
        python-version: [3.9]
        node-version: [15.0]
    services:
      db:
        image: postgres
        env:
          POSTGRES_USER: owtf_db_user
          POSTGRES_PASSWORD: ${{ secrets.OWTF_DB_PASSWORD }}
          POSTGRES_DB: owtf_db
          POSTGRES_HOST_AUTH_METHOD: trust
        ports:
          - 5432:5432
        options: >-
          --name=postgres
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v3

      - uses: actions/cache@v3
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }}
          restore-keys: |
            ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }}

      - name: before_install
        run: |
          sudo apt update
          sudo apt -y install libffi-dev golang postgresql-client libgnutls28-dev libyaml-dev python3-yaml python3-pip libcurl4-openssl-dev libssl-dev python3-venv
          pip3 install pyopenssl
      - name: install
        run: |
          echo "Running install base requirements"
          pip3 install -r requirements/base.txt
          echo "Running install test requirements"
          pip3 install -r requirements/test.txt
          echo "create virtual environment"
          python3 -m venv $HOME/.virtualenvs/owtf --clear
          echo "Activate virtual environment"
          source $HOME/.virtualenvs/owtf/bin/activate
          echo "Running setup.py"
          echo '\n\n' | python3 setup.py develop
      - name: before_script
        run: |
          source $HOME/.virtualenvs/owtf/bin/activate
          pip3 install mock pyHamcrest
          mkdir -p ~/.owtf/conf/profiles/plugin_web/
          mkdir -p ~/.owtf/conf/profiles/plugin_net/
          mkdir -p ~/.owtf/conf/profiles/plugin_aux/
          cp -f ./tests/fixtures/profiles/plugin_web/groups.cfg ~/.owtf/conf/profiles/plugin_web/groups.cfg
          cp -f ./tests/fixtures/profiles/plugin_net/groups.cfg ~/.owtf/conf/profiles/plugin_net/groups.cfg
          cp -f ./tests/fixtures/profiles/plugin_aux/groups.cfg ~/.owtf/conf/profiles/plugin_aux/groups.cfg
          git config --global user.email "tasty@mac.test"
          git config --global user.name "Tasty Test"
      - name: Python_tests_step
        run: |
          source $HOME/.virtualenvs/owtf/bin/activate
          python3 -m unittest tests.functional.cli.test_empty_run
          python3 -m unittest tests.functional.cli.test_list_plugins
          python3 -m unittest tests.functional.cli.test_nowebui
          python3 -m unittest tests.functional.plugins.web
        env:
          POSTGRES_HOST: localhost
          POSTGRES_PORT: 5432

      # - name: node_js
      #   if: "!contains(github.event.commits[0].message, '[skip ui-tests]')"
      #   working-directory: owtf/webapp
      #   run: yarn install --production=false && yarn test

      - name: slack - GitHub Actions Slack integration
        uses: act10ns/slack@v1.2.2
        with:
          status: ${{ job.status }}
          steps: ${{ toJson(steps) }}
          channel: "#owtf-notifications"
        if: always()
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}


CodeQL

# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  push:
    branches: [main]
  schedule:
    #        ┌───────────── minute (0 - 59)
    #        │  ┌───────────── hour (0 - 23)
    #        │  │  ┌───────────── day of the month (1 - 31)
    #        │  │  │ ┌───────────── month (1 - 12 or JAN-DEC)
    #        │  │  │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
    #        │  │  │ │ │
    #        │  │  │ │ │
    #        │  │  │ │ │
    #        *  *  * * *
    - cron: "35 18 * * 2"

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    permissions:
      actions: read
      contents: read
      security-events: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: python

      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
      # If this step fails, then you should remove it and run the build manually (see below)
      - name: Autobuild
        uses: github/codeql-action/autobuild@v2

      # ℹ Command-line programs to run using the OS shell.
      # 📚 https://git.io/JvXDl

      # ✏ If the Autobuild fails above, remove it and uncomment the following three lines
      #    and modify them (or add more) to build your code if your project
      #    uses a compiled language

      #- run: |
      #   make bootstrap
      #   make release

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2


Linters

name: Linters

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  flake8:
    name: flake8
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: python -m pip install flake8
      - name: flake8
        uses: liskin/gh-problem-matcher-wrap@v2
        with:
          linters: flake8
          run: flake8 neuralprophet scripts

  ruff:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: ruff
        uses: chartboost/ruff-action@v1

  isort:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: python -m pip install isort
      - name: isort
        uses: liskin/gh-problem-matcher-wrap@v2
        with:
          linters: isort
          run: isort --check --diff neuralprophet tests scripts

  black:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: black
        uses: psf/black@stable
        with:
          jupyter: true

  pyright:
    runs-on: ubuntu-latest
    env:
      POETRY_VIRTUALENVS_CREATE: false
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Cache poetry
        id: poetry-cache
        uses: actions/cache@v3
        with:
          path: ~/.local
          key: poetry
      - name: Setup poetry
        if: steps.poetry-cache.outputs.cache-hit != 'true'
        uses: snok/install-poetry@v1
      - name: Install dependencies
        run: poetry install --no-interaction --with=linters --without=dev,docs,pytest,metrics
      - name: pyright
        uses: jakebailey/pyright-action@v1


Measure Model Performance

name: Measure Model Performance
on:
  push:
    paths: # Run only when files under neuralprophet/ are modified
      - neuralprophet/**
    branches: # Run only on these branches
      - main
      - develop
  pull_request: # Run on pull requests
    branches:
      - main
      - develop
  workflow_dispatch:
jobs:
  metrics:
    runs-on: ubuntu-latest # container: docker://ghcr.io/iterative/cml:0-dvc2-base1
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Install Python 3.12
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Setup NodeJS (for CML)
        uses: actions/setup-node@v3 # For CML
        with:
          node-version: '16'
      - name: Setup CML
        uses: iterative/setup-cml@v1
      - name: Install Poetry
        uses: snok/install-poetry@v1
      - name: Install Dependencies
        run: poetry install --no-interaction --no-root --with=pytest,metrics --without=dev,docs,linters
      - name: Install Project
        run: poetry install --no-interaction --with=pytest,metrics --without=dev,docs,linters
      - name: Train model
        run: poetry run pytest tests/test_model_performance.py -n 1 --durations=0
      - name: Download metrics from main
        uses: dawidd6/action-download-artifact@v2
        with:
          repo: ourownstory/neural_prophet
          branch: main
          name: metrics
          path: tests/metrics-main/
          if_no_artifact_found: warn
      - name: Open Benchmark Report
        run: echo "## Model Benchmark" >> report.md
      - name: Write Benchmark Report
        run: poetry run python tests/metrics/compareMetrics.py >> report.md
      - name: Publish Report with CML
        env:
          REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo "\nModel training plots\n" >> report.md
          echo "## Model Training" >> report.md
          echo "### PeytonManning" >> report.md
          cml asset publish tests/metrics/PeytonManning.svg --md >> report.md
          echo "### YosemiteTemps" >> report.md
          cml asset publish tests/metrics/YosemiteTemps.svg --md >> report.md
          echo "### AirPassengers" >> report.md
          cml asset publish tests/metrics/AirPassengers.svg --md >> report.md
          echo "### EnergyPriceDaily" >> report.md
          cml asset publish tests/metrics/EnergyPriceDaily.svg --md >> report.md
          echo "\n" >> report.md
          cml comment update --target=pr report.md # Post reports as comments in GitHub PRs
          cml check create --title=ModelReport report.md # update status of check in PR
      - name: Upload metrics if on main
        uses: actions/upload-artifact@v3
        with:
          name: metrics
          path: tests/metrics/


Test build docs

name: Test build docs
on:
  push:
    branches:
      - main
      - website
  pull_request:
    branches:
      - main
      - website

defaults:
  run:
    shell: bash

jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.12"
      - name: Install Poetry
        uses: abatilo/actions-poetry@v2
      - name: Setup Pandoc
        uses: r-lib/actions/setup-pandoc@v2
      - name: Setup Requirements
        run: poetry install  --no-interaction --with=docs --without=dev,pytest,metrics,linters
      - name: Build with Sphinx
        run: poetry run sphinx-build docs/source _site


Pytest and Coverage

name: Pytest and Coverage
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

defaults:
  run:
    shell: bash

jobs:
  tests:
    runs-on: ${{matrix.os}}
    strategy:
      matrix:
        os: [ubuntu-latest]
        python-version: ["3.9", "3.12"]
        include:
          - os: "ubuntu-22.04"
            python-version: "3.10"
    env:
      POETRY_VIRTUALENVS_CREATE: false
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Poetry
        uses: snok/install-poetry@v1
      - name: Install dependencies
        run: poetry install --no-root --no-interaction --with=dev,pytest --without=docs,metrics,linters
      - name: Install Project
        run: poetry install --no-interaction --with=dev,pytest --without=docs,metrics,linters
      - name: Pytest
        run: poetry run pytest tests -v -n auto --durations=0 --cov=./ --cov-report=xml
      - name: Upload coverage to codecov
        uses: codecov/codecov-action@v3
        with:
          env_vars: OS,PYTHON
          files: ./coverage.xml
          name: Coverage Report with codecov overview
          verbose: true


Deploy website with GitHub Pages

name: Deploy website with GitHub Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches:
      - website

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow one concurrent deployment
concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Pages
        uses: actions/configure-pages@v2
      - name: Setup Pandoc
        uses: r-lib/actions/setup-pandoc@v2
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.10"
      - name: Setup Poetry
        uses: snok/install-poetry@v1
      - name: Setup Requirements
        run: poetry install --with=docs --without=dev,pytest,metrics,linters
      - name: Build with Sphinx
        run: poetry run sphinx-build docs/source _site
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v1

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1


Build wheels, optionally deploy to PyPI

name: Build wheels, optionally deploy to PyPI

on:
  workflow_dispatch:
    inputs:
      confirm_ref:
        description: "Confirm chosen branch name to deploy to PyPI (optional):"
        default: ""
      override_version:
        description: "Override version number (optional):"
        default: ""

jobs:
  # The deploy_test job is part of the test of whether we should deploy to PyPI
  # or test.PyPI. The job will succeed if either the confirmation reference is
  # empty, 'test' or if the confirmation is the selected branch or tag name.
  # It will fail if it is nonempty and does not match.  All later jobs depend
  # on this job, so that they will be immediately cancelled if the confirmation
  # is bad.  The dependency is currently necessary (2021-03) because GitHub
  # Actions does not have a simpler method of cancelling an entire workflow---
  # the normal use-case expects to try and run as much as possible despite one
  # or two failures.
  deploy_test:
    name: Verify PyPI deployment confirmation
    runs-on: ubuntu-latest
    env:
      GITHUB_REF: ${{ github.ref }}
      CONFIRM_REF: ${{ github.event.inputs.confirm_ref }}
    steps:
      - name: Compare confirmation to current reference
        shell: bash
        run: |
          [[ -z $CONFIRM_REF || $GITHUB_REF =~ ^refs/(heads|tags)/$CONFIRM_REF$ || $CONFIRM_REF == "test" ]]
          if [[ $CONFIRM_REF == "test" ]]; then
            echo "Build and deploy to test.pypi.org."
          elif [[ -z $CONFIRM_REF ]]; then
            echo "Build only.  Nothing will be uploaded to PyPI."
          else
            echo "Full build and deploy.  Wheels and source will be uploaded to PyPI."
          fi

  build_sdist:
    name: Build sdist on Ubuntu
    needs: deploy_test
    runs-on: ubuntu-latest
    env:
      OVERRIDE_VERSION: ${{ github.event.inputs.override_version }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v4
        name: Install Python
        with:
          # For the sdist we should be as conservative as possible with our
          # Python version.  This should be the lowest supported version.  This
          # means that no unsupported syntax can sneak through.
          python-version: '3.10'

      - name: Install pip build
        run: |
          python -m pip install 'build'

      - name: Build sdist tarball
        shell: bash
        run: |
          if [[ ! -z "$OVERRIDE_VERSION" ]]; then echo "$OVERRIDE_VERSION" > VERSION; fi
          # The build package is the reference PEP 517 package builder.  All
          # dependencies are specified by our setup code.
          python -m build --sdist .

      # Zip files are not part of PEP 517, so we need to make our own.
      - name: Create zipfile from tarball
        shell: bash
        working-directory: dist
        run: |
          # First assert that there is exactly one tarball, and find its name.
          shopt -s failglob
          tarball_pattern="*.tar.gz"
          tarballs=($tarball_pattern)
          [[ ${#tarballs[@]} == 1 ]]
          tarball="${tarballs[0]}"
          # Get the stem and make the zipfile name.
          stem="${tarball%.tar.gz}"
          zipfile="${stem}.zip"
          # Extract the tarball and rezip it.
          tar -xzf "$tarball"
          zip "$zipfile" -r "$stem"
          rm -r "$stem"

      - uses: actions/upload-artifact@v4
        with:
          name: sdist
          path: |
            dist/*.tar.gz
            dist/*.zip
          if-no-files-found: error

  build_wheels:
    name: Build wheels on ${{ matrix.os }}
    needs: deploy_test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    env:
      # Set up wheels matrix.  This is CPython 3.10--3.12 for all OS targets.
      CIBW_BUILD: "cp3{10,11,12}-*"
      # Numpy and SciPy do not supply wheels for i686 or win32 for
      # Python 3.10+, so we skip those:
      CIBW_SKIP: "*-musllinux* cp3{10,11,12}-manylinux_i686 cp3{10,11,12}-win32"
      OVERRIDE_VERSION: ${{ github.event.inputs.override_version }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v4
        name: Install Python
        with:
          # This is about the build environment, not the released wheel version.
          python-version: '3.10'

      - name: Install cibuildwheel
        run: |
          # cibuildwheel does the heavy lifting for us. Originally tested on
          # 2.11.3, but should be fine at least up to any minor new release.
          python -m pip install 'cibuildwheel==2.11.*'

      - name: Build wheels
        shell: bash
        run: |
          # If the version override was specified, then write it the VERSION
          # file with it.
          if [[ ! -z "$OVERRIDE_VERSION" ]]; then echo "$OVERRIDE_VERSION" > VERSION; fi
          python -m cibuildwheel --output-dir wheelhouse

      - uses: actions/upload-artifact@v4
        with:
          name: wheels
          path: ./wheelhouse/*.whl

  deploy:
    name: "Deploy to PyPI if desired"
    # The confirmation is tested explicitly in `deploy_test`, so we know it is
    # either a missing confirmation (so we shouldn't run this job), 'test' or a
    # valid confirmation.  We don't need to retest the value of the
    # confirmation, beyond checking that one existed.
    if: ${{ github.event.inputs.confirm_ref != '' && github.event.inputs.confirm_ref != 'test' }}
    needs: [deploy_test, build_sdist, build_wheels]
    runs-on: ubuntu-latest
    env:
      TWINE_USERNAME: __token__
      TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
      TWINE_NON_INTERACTIVE: 1
      TWINE_REPOSITORY: pypi

    steps:
      - name: Download build artifacts to local runner
        uses: actions/download-artifact@v4

      - uses: actions/setup-python@v4
        name: Install Python
        with:
          python-version: '3.10'

      - name: Verify this is not a dev version
        shell: bash
        run: |
          python -m pip install wheels/*-cp310-cp310-manylinux*.whl
          python -c 'import qutip; print(qutip.__version__); assert "dev" not in qutip.__version__; assert "+" not in qutip.__version__'

      # We built the zipfile for convenience distributing to Windows users on
      # our end, but PyPI only needs the tarball.
      - name: Upload sdist and wheels to PyPI
        run: |
          python -m pip install "twine"
          python -m twine upload --verbose wheels/*.whl sdist/*.tar.gz

  deploy_testpypi:
    name: "Deploy to TestPyPI if desired"
    if: ${{ github.event.inputs.confirm_ref == 'test' }}
    needs: [deploy_test, build_sdist, build_wheels]
    runs-on: ubuntu-latest
    env:
      TWINE_USERNAME: __token__
      TWINE_PASSWORD: ${{ secrets.TESTPYPI_TOKEN }}
      TWINE_NON_INTERACTIVE: 1

    steps:
      - name: Download build artifacts to local runner
        uses: actions/download-artifact@v4

      - uses: actions/setup-python@v4
        name: Install Python
        with:
          python-version: '3.10'

      - name: Verify this is not a dev version
        shell: bash
        run: |
          python -m pip install wheels/*-cp310-cp310-manylinux*.whl
          python -c 'import qutip; print(qutip.__version__); assert "dev" not in qutip.__version__; assert "+" not in qutip.__version__'

      # We built the zipfile for convenience distributing to Windows users on
      # our end, but PyPI only needs the tarball.
      - name: Upload sdist and wheels to TestPyPI
        run: |
          python -m pip install "twine"
          python -m twine upload --repository testpypi --verbose wheels/*.whl sdist/*.tar.gz


Build documentation

name: Build documentation

on:
  [push, pull_request]

jobs:
  build:
    name: Build documentation
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v4
        name: Install Python
        with:
          python-version: '3.11'

      - name: Install documentation dependencies
        run: |
          pip install pip --upgrade
          python -mpip install -r doc/requirements.txt
          sudo apt-get update
          sudo apt-get install texlive-full

      - name: Install QuTiP from GitHub
        run: |
          # Build without build isolation so that we use the build
          # dependencies already installed from doc/requirements.txt.
          python -m pip install -e .[full] --no-build-isolation --config-settings editable_mode=compat
          # Install in editable mode so it doesn't matter if we import from
          # inside the installation directory, otherwise we can get some errors
          # because we're importing from the wrong location.
          python -c 'import qutip; qutip.about()'

      - name: Build PDF documentation
        working-directory: doc
        run: |
          make latexpdf SPHINXOPTS="-W --keep-going -T"
          # Above flags are:
          #   -W : turn warnings into errors
          #   --keep-going : do not stop after the first error
          #   -T : display a full traceback if a Python exception occurs

      - name: Upload built PDF files
        uses: actions/upload-artifact@v4
        with:
          name: qutip_pdf_docs
          path: doc/_build/latex/*
          if-no-files-found: error

      - name: Build HTML documentation
        working-directory: doc
        run: |
          make html SPHINXOPTS="-W --keep-going -T"
          # Above flags are:
          #   -W : turn warnings into errors
          #   --keep-going : do not stop after the first error
          #   -T : display a full traceback if a Python exception occurs

      - name: Upload built HTML files
        uses: actions/upload-artifact@v4
        with:
          name: qutip_html_docs
          path: doc/_build/html/*
          if-no-files-found: error


Tests

# The name is short because we mostly care how it appears in the pull request
# "checks" dialogue box - it looks like
#     Tests / ubuntu-latest, python-3.9, defaults
# or similar.
name: Tests

on:
    [push, pull_request]

defaults:
  run:
    # The slightly odd shell call is to force bash to read .bashrc, which is
    # necessary for having conda behave sensibly.  We use bash as the shell even
    # on Windows, since we don't run anything much complicated, and it makes
    # things much simpler.
    shell: bash -l -e {0}

jobs:
  cases:
    name: ${{ matrix.os }}, python${{ matrix.python-version }}, ${{ matrix.case-name }}
    runs-on: ${{ matrix.os }}
    env:
      MPLBACKEND: Agg  # Explicitly define matplotlib backend for Windows tests
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        # Test other versions of Python in special cases to avoid exploding the
        # matrix size; make sure to test all supported versions in some form.
        python-version: ["3.11"]
        case-name: [defaults]
        # Version 2 not yet available on conda's default channel
        condaforge: [1]
        numpy-build: [""]
        numpy-requirement: [""]
        scipy-requirement: [">=1.9"]
        coverage-requirement: ["==6.5"]
        # Extra special cases.  In these, the new variable defined should always
        # be a truth-y value (hence 'nomkl: 1' rather than 'mkl: 0'), because
        # the lack of a variable is _always_ false-y, and the defaults lack all
        # the special cases.
        include:
          - case-name: p312 numpy 2
            os: ubuntu-latest
            python-version: "3.12"
            numpy-build: ">=2.0.0"
            numpy-requirement: ">=2.0.0"
            pypi: 1

          - case-name: p310 numpy 1.22
            os: ubuntu-latest
            python-version: "3.10"
            numpy-build: ">=1.22.0,<1.23.0"
            numpy-requirement: ">=1.22.0,<1.23.0"
            scipy-requirement: ">=1.10,<1.11"
            semidefinite: 1
            oldcython: 1
            pypi: 1
            pytest-extra-options: "-W ignore:dep_util:DeprecationWarning -W \"ignore:The 'renderer' parameter of do_3d_projection\""

          # Python 3.12 and latest numpy
          # Use conda-forge to provide Python 3.11 and latest numpy
          - case-name: p312, numpy fallback
            os: ubuntu-latest
            python-version: "3.12"
            numpy-requirement: ">=1.26,<1.27"
            scipy-requirement: ">=1.11,<1.12"
            condaforge: 1
            # Install mpi4py to test mpi_pmap
            # Should be enough to include this in one of the runs
            includempi: 1

          # Python 3.10, no mkl, scipy 1.9, numpy 1.23
          # Scipy 1.9 did not support cython 3.0 yet.
          # cython#17234
          - case-name: p310 no mkl
            os: ubuntu-latest
            python-version: "3.10"
            numpy-requirement: ">=1.23,<1.24"
            scipy-requirement: ">=1.9,<1.10"
            semidefinite: 1
            condaforge: 1
            oldcython: 1
            nomkl: 1
            pytest-extra-options: "-W ignore:dep_util:DeprecationWarning -W \"ignore:The 'renderer' parameter of do_3d_projection\""

          # Mac
          # Mac has issues with MKL since september 2022.
          - case-name: macos
            # setup-miniconda not compatible with macos-latest presently.
            # https://github.com/conda-incubator/setup-miniconda/issues/344
            os: macos-12
            python-version: "3.12"
            numpy-build: ">=2.0.0"
            numpy-requirement: ">=2.0.0"
            condaforge: 1
            nomkl: 1

          - case-name: macos - numpy fallback
            os: macos-12
            python-version: "3.11"
            numpy-build: ">=2.0.0"
            numpy-requirement: ">=1.25,<1.26"
            condaforge: 1
            nomkl: 1

          - case-name: Windows
            os: windows-latest
            python-version: "3.11"
            numpy-build: ">=2.0.0"
            numpy-requirement: ">=2.0.0"
            pypi: 1

          - case-name: Windows - numpy fallback
            os: windows-latest
            python-version: "3.10"
            numpy-build: ">=2.0.0"
            numpy-requirement: ">=1.24,<1.25"
            semidefinite: 1
            oldcython: 1
            nocython: 1
            condaforge: 1
            pytest-extra-options: "-W ignore:dep_util:DeprecationWarning -W \"ignore:The 'renderer' parameter of do_3d_projection\""

    steps:
      - uses: actions/checkout@v4
      - uses: conda-incubator/setup-miniconda@v3
        with:
          auto-update-conda: true
          python-version: ${{ matrix.python-version }}
          channels: ${{ matrix.condaforge == 1 && 'conda-forge' || 'defaults' }}

      - name: Install QuTiP and dependencies
        # In the run, first we handle any special cases.  We do this in bash
        # rather than in the GitHub Actions file directly, because bash gives us
        # a proper programming language to use.
        # We install without build isolation so qutip is compiled with the
        # version of cython, scipy, numpy in the test matrix, not a temporary
        # version use in the installation virtual environment.
        run: |
          # Install the extra requirement
          python -m pip install pytest>=5.2 pytest-rerunfailures  # tests
          python -m pip install ipython  # ipython
          python -m pip install loky tqdm  # extras
          python -m pip install "coverage${{ matrix.coverage-requirement }}" chardet
          python -m pip install pytest-cov coveralls pytest-fail-slow

          if [[ "${{ matrix.pypi }}" ]]; then
            pip install "numpy${{ matrix.numpy-build }}"
            pip install "scipy${{ matrix.scipy-requirement }}"
          elif [[ -z "${{ matrix.nomkl }}" ]]; then
            conda install blas=*=mkl "numpy${{ matrix.numpy-build }}" "scipy${{ matrix.scipy-requirement }}"
          elif [[ "${{ matrix.os }}" =~ ^windows.*$ ]]; then
            # Conda doesn't supply forced nomkl builds on Windows, so we rely on
            # pip not automatically linking to MKL.
            pip install "numpy${{ matrix.numpy-build }}" "scipy${{ matrix.scipy-requirement }}"
          else
            conda install nomkl "numpy${{ matrix.numpy-build }}" "scipy${{ matrix.scipy-requirement }}"
          fi
          if [[ -n "${{ matrix.conda-extra-pkgs }}" ]]; then
            conda install "${{ matrix.conda-extra-pkgs }}"
          fi
          if [[ "${{ matrix.includempi }}" ]]; then
            # Use openmpi because mpich causes problems. Note, environment variable names change in v5
            conda install "openmpi<5" mpi4py
          fi
          if [[ "${{ matrix.oldcython }}" ]]; then
            python -m pip install cython==0.29.36 filelock matplotlib==3.5
          else
            python -m pip install cython filelock
          fi

          python -m pip install -e . -v --no-build-isolation

          if [[ "${{ matrix.nocython }}" ]]; then
            python -m pip uninstall cython -y
          fi

          if [[ "${{ matrix.pypi }}" ]]; then
            python -m pip install "numpy${{ matrix.numpy-requirement }}"
          elif [[ -z "${{ matrix.nomkl }}" ]]; then
            conda install "numpy${{ matrix.numpy-requirement }}"
          elif [[ "${{ matrix.os }}" =~ ^windows.*$ ]]; then
            python -m pip install "numpy${{ matrix.numpy-requirement }}"
          else
            conda install nomkl "numpy${{ matrix.numpy-requirement }}"
          fi
          if [[ -n "${{ matrix.semidefinite }}" ]]; then
            python -m pip install cvxpy>=1.0 cvxopt
          fi
          python -m pip install matplotlib>=1.2.1  # graphics

      - name: Package information
        run: |
          conda list
          python -c "import qutip; qutip.about()"
          python -c "import qutip; print(qutip.settings)"

      - name: Environment information
        run: |
          uname -a
          if [[ "ubuntu-latest" == "${{ matrix.os }}" ]]; then
              hostnamectl
              lscpu
              free -h
          fi

      - name: Run tests
        # If our tests are running for longer than an hour, _something_ is wrong
        # somewhere.  The GitHub default is 6 hours, which is a bit long to wait
        # to see if something hung.
        timeout-minutes: 60
        run: |
          if [[ -n "${{ matrix.openmp }}" ]]; then
            # Force OpenMP runs to use more threads, even if there aren't
            # actually that many CPUs.  We have to check any dispatch code is
            # truly being executed.
            export QUTIP_NUM_PROCESSES=2
          fi
          if [[ "${{ matrix.includempi }}" ]]; then
            # By default, the max. number of allowed worker processes in openmpi is
            # (number of physical cpu cores) - 1.
            # We only have 2 physical cores, but we want to test mpi_pmap with 2 workers.
            export OMPI_MCA_rmaps_base_oversubscribe=true
          fi
          pytest -Werror --strict-config --strict-markers --fail-slow=300 --durations=0 --durations-min=1.0 --verbosity=1 --cov=qutip --cov-report= --color=yes ${{ matrix.pytest-extra-options }} qutip/tests
          # Above flags are:
          #  -Werror
          #     treat warnings as errors
          #  --strict-config
          #     error out if the configuration file is not parseable
          #  --strict-markers
          #     error out if a marker is used but not defined in the
          #     configuration file
          #  --timeout=300
          #     error any individual test that goes longer than the given time
          #  --durations=0 --durations-min=1.0
          #     at the end, show a list of all the tests that took longer than a
          #     second to run
          #  --verbosity=1
          #     turn the verbosity up so pytest prints the names of the tests
          #     it's currently working on
          #  --cov=qutip
          #     limit coverage reporting to code that's within the qutip package
          #  --cov-report=
          #     don't print the coverage report to the terminal---it just adds
          #     cruft, and we're going to upload the .coverage file to Coveralls
          #  --color=yes
          #     force coloured output in the terminal

      - name: Upload to Coveralls
        env:
          GITHUB_TOKEN: ${{ secrets.github_token }}
          COVERALLS_FLAG_NAME: ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.case-name }}
          COVERALLS_PARALLEL: true
        run: coveralls --service=github

  towncrier-check:
    name: Verify Towncrier entry added
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Towncrier
        run: |
          python -m pip install towncrier

      - name: Verify Towncrier entry added
        if: github.event_name == 'pull_request'
        env:
          BASE_BRANCH: ${{ github.base_ref }}
        run: |
          # Fetch the pull request' base branch so towncrier will be able to
          # compare the current branch with the base branch.
          # Source: https://github.com/actions/checkout/#fetch-all-branches.
          git fetch --no-tags origin +refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH}
          towncrier check --compare-with origin/${BASE_BRANCH}
          towncrier build --version "$(cat VERSION)" --draft

  finalise:
    name: Finalise coverage reporting
    needs: cases
    runs-on: ubuntu-latest
    container: python:3-slim
    steps:
      - name: Finalise coverage reporting
        env:
          GITHUB_TOKEN: ${{ secrets.github_token }}
        run: |
          python -m pip install coveralls
          coveralls --service=github --finish


Build

name: Build
on:
  - push
  - pull_request
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: linux-3.12
            python-version: "3.12"
            os: ubuntu-latest
          - name: windows-3.12
            python-version: "3.12"
            os: windows-latest
          - name: macos-3.12-skip-exe
            python-version: "3.12"
            os: macos-latest
          - name: linux-3.12-lint-update-help-skip-exe
            python-version: "3.12"
            os: ubuntu-latest
          - name: linux-3.11-pytype-skip-exe
            python-version: "3.11"
            os: ubuntu-latest
          - name: linux-3.10-skip-exe
            python-version: "3.10"
            os: ubuntu-latest
          - name: linux-3.9-skip-exe
            python-version: "3.9"
            os: ubuntu-latest
          - name: linux-3.8-skip-exe
            python-version: "3.8"
            os: ubuntu-latest
          - name: linux-pypy-3.10-skip-exe
            python-version: pypy-3.10
            os: ubuntu-latest
          - name: linux-pypy-3.9-skip-exe
            python-version: pypy-3.9
            os: ubuntu-latest
          - name: linux-pypy-3.8-skip-exe
            python-version: pypy-3.8
            os: ubuntu-latest
    steps:
      - name: Checkout source repo
        uses: actions/checkout@v4
      - name: Apt install system dependencies (linux unless -skip-exe)
        if: ${{ startsWith(matrix.name, 'linux-') && !contains(matrix.name, '-skip-exe') }}
        run: |
          echo "::group::Run sudo apt-get update"
          sudo apt-get update
          echo "::endgroup::"
          echo "::group::Run sudo apt-get install graphviz"
          sudo apt-get install graphviz
          echo "::endgroup::"
          echo "::group::Run dot -V"
          DOT_VERSION="$(dot -V 2>&1)"
          echo $DOT_VERSION;
          echo "::endgroup::"
          echo "::notice::Apt installed ${DOT_VERSION#dot - }"
        shell: bash
      - name: Brew install system dependencies (macos unless -skip-exe)
        if: ${{ startsWith(matrix.name, 'macos-') && !contains(matrix.name, '-skip-exe') }}
        run: |
          echo "::group::Run brew update --preinstall"
          brew update --preinstall
          echo "::endgroup::"
          echo "::group::Run brew install graphviz"
          brew install graphviz
          echo "::endgroup::"
          echo "::group::Run dot -V"
          DOT_VERSION="$(dot -V 2>&1)"
          echo $DOT_VERSION
          echo "::endgroup::"
          echo "::notice::Brew installed ${DOT_VERSION#dot - }"
        shell: bash
      - name: Choco install system dependencies (windows unless -skip-exe)
        if: ${{ startsWith(matrix.name, 'windows-') && !contains(matrix.name, '-skip-exe') }}
        run: |
          echo "::group::Run choco install --no-progress graphviz"
          choco install --no-progress graphviz
          echo "::endgroup::"
          echo "::group::Run dot -V"
          DOT_VERSION="$(dot -V 2>&1)"
          echo $DOT_VERSION
          echo "::endgroup::"
          echo "::notice::Choco installed ${DOT_VERSION#dot - }"
        shell: bash
      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Pip install dependencies
        run: |
          echo "::group::Run python -m pip install --upgrade pip setuptools wheel"
          python -m pip install --upgrade pip setuptools wheel
          echo "::endgroup::"
          echo "::group::Run pip install .[test] flake8"
          pip install .[test] flake8
          echo "::endgroup::"
      - name: Run full tests (unless -skip-exe)
        if: ${{ !contains(matrix.name, '-skip-exe') }}
        run: python run-tests.py
      - name: Run tests with --only-exe (unless -skip-exe)
        if: ${{ !contains(matrix.name, '-skip-exe') }}
        run: python run-tests.py --only-exe --cov-append
      - name: Run tests with --skip-exe (always included)
        run: python run-tests.py --skip-exe --cov-append
      - name: Upload test coverage
        uses: codecov/codecov-action@v4
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
      - name: Check test coverage
        run: |
          echo "::group::Run coverage report --fail-under=100 --show-missing --skip-covered"
          FAILED=0
          coverage report --fail-under=100 --show-missing --skip-covered || FAILED=$?
          echo "::endgroup::"
          [ $FAILED -eq 0 ] || echo "::warning::coverage report --fail-under=100 failed with exit code $FAILED"
        shell: bash
      - name: Run build-docs.py -b doctest (unless -skip-exe)
        if: ${{ !contains(matrix.name, '-skip-exe') }}
        run: |
          echo "::group::Run pip install .[docs]"
          pip install .[docs]
          echo "::endgroup::"
          echo "::group::Run build-docs.py -b doctest"
          FAILED=0
          python build-docs.py -b doctest || FAILED=$?
          echo ::endgroup::
          [ $FAILED -eq 0 ] || echo "::warning::build-docs.py -b doctest failed with exit code $FAILED"
        shell: bash
      - name: Run try-examples.py (unless -skip-exe)
        if: ${{ !contains(matrix.name, '-skip-exe') }}
        run: |
          echo "::group::Run try-examples.py"
          FAILED=0
          python try-examples.py || FAILED=$?
          echo "::endgroup::"
          SEVERITY=warning
          ${{ startsWith(matrix.name, 'windows-') }} && SEVERITY=notice
          [ $FAILED -eq 0 ] || echo "::$SEVERITY::try-examples.py failed with exit code $FAILED (XFAIL 'Graphviz not built with triangulation library' on Windows cbuild)"
        shell: bash
      - name: Run lint-code.py (if -lint)
        if: ${{ contains(matrix.name, '-lint') }}
        run: |
          echo "::group::Run lint-code.py"
          FAILED=0
          python lint-code.py || FAILED=$?
          echo "::endgroup::"
          [ $FAILED -eq 0 ] || echo "::warning::lint-code.py failed with exit code $FAILED"
        shell: bash
      - name: Run update-help.py (if -update-help)
        if: ${{ contains(matrix.name, '-update-help') }}
        run: |
          echo "::group::Run update-help.py"
          FAILED=0
          python update-help.py || FAILED=$?
          echo "::endgroup::"
          [ $FAILED -eq 0 ] || echo "::warning::update-help.py failed with exit code $FAILED"
      - name: Run pytype (if -pytype)
        if: ${{ contains(matrix.name, '-pytype') }}
        run: |
          echo "::group::Run pip install pytype"
          pip install pytype
          echo "::endgroup::"
          echo "::group::Run pytype"
          FAILED=0
          pytype || FAILED=$?
          echo "::endgroup::"
          [ $FAILED -eq 0 ] || echo "::warning::pytype failed with exit code $FAILED"
        shell: bash
      - name: Upload ${{ matrix.name }} artifacts
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.name }}
          path: |
            test-log.txt
            doctest-output/
            examples/*.gv
            examples/*.pdf


Build doc

name: Build doc

on:
  workflow_dispatch:
  pull_request:
  push:
    branches:
      - 'master'

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    # Standard drop-in approach that should work for most people.

    - name: Set up Python 3.10
      uses: actions/setup-python@v5
      with:
        python-version: "3.10"

    - name: Get Python running
      run: |
        python -m pip install --user --upgrade --progress-bar off pip
        python -m pip install --user --upgrade --progress-bar off -r requirements_all.txt
        python -m pip install --user --upgrade --progress-bar off -r docs/requirements.txt
        python -m pip install --user --upgrade --progress-bar off ipython "https://api.github.com/repos/sphinx-gallery/sphinx-gallery/zipball/master" memory_profiler
        python -m pip install --user -e .
    # Look at what we have and fail early if there is some library conflict
    - name: Check installation
      run: |
        which python
        python -c "import ot"
    # Build docs
    - name: Generate HTML docs
      uses: rickstaa/sphinx-action@master
      with:
        docs-folder: "docs/"
    - uses: actions/upload-artifact@v1
      with:
        name: Documentation
        path: docs/build/html/


Tests

name: Tests

on:
  workflow_dispatch:
  pull_request:
    branches:
      - 'master'
  push:
    branches:
      - 'master'
  create:
    branches:
      - 'master'
    tags:
      - '**'

jobs:
  linux:

    runs-on: ubuntu-latest
    if: "!contains(github.event.head_commit.message, 'no ci')"
    strategy:
      max-parallel: 4
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11"]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install POT
      run: |
        pip install -e .
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements_all.txt
        pip install pytest pytest-cov
    - name: Run tests
      run: |
        python -m pytest --durations=20 -v test/ ot/ --doctest-modules --color=yes --cov=./ --cov-report=xml
    - name: Upload coverage reports to Codecov with GitHub Action
      uses: codecov/codecov-action@v3

  pep8:
    runs-on: ubuntu-latest
    if: "!contains(github.event.head_commit.message, 'no pep8')"
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: "3.10"
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 examples/ ot/ test/ --count --max-line-length=127 --statistics

  linux-minimal-deps:

    runs-on: ubuntu-latest
    if: "!contains(github.event.head_commit.message, 'no ci')"
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: "3.10"
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pytest pytest-cov
    - name: Install POT
      run: |
        pip install -e .
    - name: Run tests
      run: |
        python -m pytest --durations=20 -v test/ ot/ --color=yes --cov=./ --cov-report=xml
    - name: Upload coverage reports to Codecov with GitHub Action
      uses: codecov/codecov-action@v3

  macos:
     runs-on: ${{ matrix.os }}
     if: "!contains(github.event.head_commit.message, 'no ci')"
     strategy:
       max-parallel: 4
       matrix:
         os: [macos-latest, macos-13]
         python-version: ["3.11"]

     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install POT
       run: |
         pip install -e .
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
         pip install -r requirements_all.txt
         pip install pytest
     - name: Run tests
       run: |
         python -m pytest --durations=20  -v test/ ot/ --color=yes

  windows:
    runs-on: windows-latest
    if: "!contains(github.event.head_commit.message, 'no ci')"
    strategy:
      max-parallel: 4
      matrix:
        python-version: ["3.11"]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
    - name: RC.exe
      run: |
        function Invoke-VSDevEnvironment {
        $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
            $installationPath = & $vswhere -prerelease -legacy -latest -property installationPath
            $Command = Join-Path $installationPath "Common7\Tools\vsdevcmd.bat"
          & "${env:COMSPEC}" /s /c "`"$Command`" -no_logo && set" | Foreach-Object {
                if ($_ -match '^([^=]+)=(.*)') {
                    [System.Environment]::SetEnvironmentVariable($matches[1], $matches[2])
                }
            }
        }
        Invoke-VSDevEnvironment
        Get-Command rc.exe | Format-Table -AutoSize
    - name: Update pip
      run : |
        python -m pip install --upgrade pip setuptools
        python -m pip install cython
    - name: Install POT
      run: |
        python -m pip install -e .
    - name: Install dependencies
      run: |
        python -m pip install -r .github/requirements_test_windows.txt
        python -m pip3 install torch torchvision torchaudio
        python -m pip install pytest
    - name: Run tests
      run: |
        python -m pytest --durations=20  -v test/ ot/ --color=yes


Tests CUDA

name: Tests CUDA

on:
  workflow_dispatch:
  pull_request_review:
    types: [submitted]

jobs:
  linux-cuda:

    runs-on: pc-cuda
    if: github.event.review.state == 'approved' || github.event_name ==     'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/master')

    steps:
    - uses: actions/checkout@v4
    - name: Install POT
      run: |
        python3.10 -m pip install  --ignore-installed -e .
    - name: Run tests
      run: |
        python3.10 -m pytest --durations=20 -v test/ ot/ --doctest-modules --color=yes --ignore=test/test_dr.py --ignore=ot.dr --ignore=ot.plot


Build wheels

name: Build wheels

on:
  workflow_dispatch:
  release:
  pull_request:
  push:
    branches:
      - "*"

jobs:
  build_wheels:
    name: ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    if: "contains(github.event.head_commit.message, 'build wheels')"
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.10
      uses: actions/setup-python@v5
      with:
        python-version: "3.10"

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip

    - name: Install cibuildwheel
      run: |
        python -m pip install cibuildwheel==2.16.2

    - name: Build wheels
      env:
        CIBW_SKIP: "pp*-win* pp*-macosx* cp2* pp* cp36* cp*musl* *i686" # remove pypy on mac and win (wrong version)
      run: |
        python -m cibuildwheel --output-dir wheelhouse

    - uses: actions/upload-artifact@v1
      with:
        name: wheels
        path: ./wheelhouse

  build_all_wheels:
    name: ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    if: "contains(github.event.head_commit.message, 'build all wheels')"
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.10
      uses: actions/setup-python@v5
      with:
        python-version: "3.10"

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip

    - name: Install cibuildwheel
      run: |
        python -m pip install cibuildwheel==2.16.2

    - name: Set up QEMU
      if: runner.os == 'Linux'
      uses: docker/setup-qemu-action@v1
      with:
        platforms: all

    - name: Build wheels
      env:
        CIBW_SKIP: "pp*-win* pp*-macosx* cp2* pp* cp*musl* *i686" # remove pypy on mac and win (wrong version)
        CIBW_ARCHS_LINUX: auto aarch64 # force aarch64 with QEMU
        CIBW_ARCHS_MACOS: x86_64 universal2 arm64
      run: |
        python -m cibuildwheel --output-dir wheelhouse

    - uses: actions/upload-artifact@v1
      with:
        name: wheels
        path: ./wheelhouse


Build all wheels

name: Build all wheels

on:
  workflow_dispatch:
  schedule:
    - cron: '30 0 * * 1'
  push:
    branches:
      - "master"

jobs:
  build_wheels:
    name: ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.10
      uses: actions/setup-python@v5
      with:
        python-version: "3.10"

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip

    - name: Install cibuildwheel
      run: |
        python -m pip install cibuildwheel==2.16.2

    - name: Set up QEMU
      if: runner.os == 'Linux'
      uses: docker/setup-qemu-action@v1
      with:
        platforms: all

    - name: Build wheels
      env:
        CIBW_SKIP: "pp*-win* pp*-macosx* cp2* pp* cp*musl* cp36* *i686" # remove pypy on mac and win (wrong version)
        CIBW_ARCHS_LINUX: auto aarch64 # force aarch64 with QEMU
        CIBW_ARCHS_MACOS: x86_64 universal2 arm64
      run: |
        python -m cibuildwheel --output-dir wheelhouse

    - uses: actions/upload-artifact@v1
      with:
        name: wheels
        path: ./wheelhouse


circleci-redirector

name: circleci-redirector
on: [status]
jobs:
  circleci_artifacts_redirector_job:
    runs-on: ubuntu-latest
    if: "${{ github.event.context == 'ci/circleci: build_docs' }}"
    name: Run CircleCI artifacts redirector
    steps:
      - name: GitHub Action step
        uses: larsoner/circleci-artifacts-redirector-action@master
        with:
          api-token: ${{ secrets.CIRCLE_TOKEN }}
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          artifact-path: 0/dev/index.html
          circleci-jobs: build_docs


main

name: main

on:
  push:
    branches: [main, test-me-*]
    tags: '*'
  pull_request:

jobs:
  main-windows:
    uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0
    with:
      env: '["py39"]'
      os: windows-latest
  main-linux:
    uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0
    with:
      env: '["py39", "py310", "py311", "py312"]'
      os: ubuntu-latest


Main

name: Main

on: [push, pull_request]

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          pip install --upgrade pip setuptools
          pip install --upgrade tox-gh-actions coveralls

      - name: Run tests
        run: |
          tox

      - name: Send coverage report
        run: |
          coveralls
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COVERALLS_FLAG_NAME: ${{ matrix.python-version }}
          COVERALLS_PARALLEL: true
          COVERALLS_SERVICE_NAME: github

  coveralls:
    name: Finish Coveralls
    needs: test
    runs-on: ubuntu-latest
    container: python:3-slim

    steps:
      - name: Finished
        run: |
          pip install --upgrade coveralls
          coveralls --finish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


Test

name: Test

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
        dj-version: ["3.2.*", "4.0.*", "4.1.*", "4.2.*"]
        drf-version: ["3.13.*", "3.14.*", "3.15.*"]
        exclude:
        - python-version: 3.7
          dj-version: '4.0.*'
        - python-version: 3.7
          dj-version: '4.1.*'
        - python-version: 3.7
          dj-version: '4.2.*'
        - dj-version: '4.2.*'
          drf-version: '3.13.*'

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install 'django==${{ matrix.dj-version }}' 'djangorestframework==${{ matrix.drf-version }}'

    - name: Run Tests
      run: |
        ./runtests.py --fast --coverage -rw

  lint:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Set up Python39
      uses: actions/setup-python@v2
      with:
        python-version: 3.9

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8==5.0.4

    - name: Lint Code
      run: |
        flake8 dynamic_rest tests


benchmarks-report

# Post any reports generated by benchmarks_run.yml .
# Separated for security:
#  https://securitylab.github.com/research/github-actions-preventing-pwn-requests/

name: benchmarks-report
run-name: Report benchmark results

on:
  workflow_run:
    workflows: [benchmarks-run]
    types:
      - completed

jobs:
  download:
    runs-on: ubuntu-latest
    outputs:
      reports_exist: ${{ steps.unzip.outputs.reports_exist }}
    steps:
      - name: Download artifact
        id: download-artifact
        # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow
        uses: actions/github-script@v7
        with:
          script: |
            let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
               owner: context.repo.owner,
               repo: context.repo.repo,
               run_id: context.payload.workflow_run.id,
            });
            let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
              return artifact.name == "benchmark_reports"
            })[0];
            if (typeof matchArtifact != 'undefined') {
              let download = await github.rest.actions.downloadArtifact({
                owner: context.repo.owner,
                repo: context.repo.repo,
                artifact_id: matchArtifact.id,
                archive_format: 'zip',
              });
              let fs = require('fs');
              fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/benchmark_reports.zip`, Buffer.from(download.data));
            };

      - name: Unzip artifact
        id: unzip
        run: |
          if test -f "benchmark_reports.zip"; then
            reports_exist=1
            unzip benchmark_reports.zip -d benchmark_reports
          else
            reports_exist=0
          fi
          echo "reports_exist=$reports_exist" >> "$GITHUB_OUTPUT"

      - name: Store artifact
        uses: actions/upload-artifact@v4
        with:
          name: benchmark_reports
          path: benchmark_reports

  post_reports:
    runs-on: ubuntu-latest
    needs: download
    if: needs.download.outputs.reports_exist == 1
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: benchmark_reports
          path: .github/workflows/benchmark_reports

      - name: Set up Python
        # benchmarks/bm_runner.py only needs builtins to run.
        uses: actions/setup-python@v5

      - name: Post reports
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: python benchmarks/bm_runner.py _gh_post


benchmarks-run

# Use ASV to check for performance regressions, either:
#  - In the last 24 hours' commits.
#  - Introduced by this pull request.

name: benchmarks-run
run-name: Run benchmarks

on:
  schedule:
    # Runs every day at 23:00.
    - cron: "0 23 * * *"
  workflow_dispatch:
    inputs:
      first_commit:
        description: "First commit to benchmark (see bm_runner.py > Overnight)."
        required: false
        type: string
  pull_request:
    # Add the `labeled` type to the default list.
    types: [labeled, opened, synchronize, reopened]

jobs:
  pre-checks:
    runs-on: ubuntu-latest
    if: github.repository == 'SciTools/iris'
    outputs:
      overnight: ${{ steps.overnight.outputs.check }}
      branch: ${{ steps.branch.outputs.check }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - id: files-changed
        uses: marceloprado/has-changed-path@df1b7a3161b8fb9fd8c90403c66a9e66dfde50cb
        with:
          # SEE ALSO .github/labeler.yml .
          paths: requirements/locks/*.lock setup.py
      - id: overnight
        if: github.event_name != 'pull_request'
        run: echo "check=true" >> "$GITHUB_OUTPUT"
      - id: branch
        if: >
          github.event_name == 'pull_request'
          &&
          (
            steps.files-changed.outputs.changed == 'true'
            ||
            github.event.label.name == 'benchmark_this'
          )
        run: echo "check=true" >> "$GITHUB_OUTPUT"

  benchmark:
    runs-on: ubuntu-latest
    needs: pre-checks
    if: >
      needs.pre-checks.outputs.overnight == 'true' ||
      needs.pre-checks.outputs.branch == 'true'

    env:
      IRIS_TEST_DATA_LOC_PATH: benchmarks
      IRIS_TEST_DATA_PATH: benchmarks/iris-test-data
      IRIS_TEST_DATA_VERSION: "2.22"
      # Lets us manually bump the cache to rebuild
      ENV_CACHE_BUILD: "0"
      TEST_DATA_CACHE_BUILD: "2"

    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Nox
        run: |
          pip install nox

      - name: Cache environment directories
        id: cache-env-dir
        uses: actions/cache@v4
        with:
          path: |
            .nox
            benchmarks/.asv/env
            $CONDA/pkgs
          key: ${{ runner.os }}-${{ hashFiles('requirements/') }}-${{ env.ENV_CACHE_BUILD }}

      - name: Cache test data directory
        id: cache-test-data
        uses: actions/cache@v4
        with:
          path: |
            ${{ env.IRIS_TEST_DATA_PATH }}
          key:
            test-data-${{ env.IRIS_TEST_DATA_VERSION }}-${{ env.TEST_DATA_CACHE_BUILD }}

      - name: Fetch the test data
        if: steps.cache-test-data.outputs.cache-hit != 'true'
        run: |
          wget --quiet https://github.com/SciTools/iris-test-data/archive/v${IRIS_TEST_DATA_VERSION}.zip -O iris-test-data.zip
          unzip -q iris-test-data.zip
          mkdir --parents ${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_LOC_PATH}
          mv iris-test-data-${IRIS_TEST_DATA_VERSION} ${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_PATH}

      - name: Set test data var
        run: |
          echo "OVERRIDE_TEST_DATA_REPOSITORY=${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_PATH}/test_data" >> $GITHUB_ENV

      - name: Benchmark this pull request
        if: needs.pre-checks.outputs.branch == 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.number }}
        run: |
          nox -s benchmarks -- branch origin/${{ github.base_ref }}

      - name: Run overnight benchmarks
        id: overnight
        if: needs.pre-checks.outputs.overnight == 'true'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          first_commit=${{ inputs.first_commit }}
          if [ "$first_commit" == "" ]
          then
            first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1)
          fi

          if [ "$first_commit" != "" ]
          then
            nox -s benchmarks -- overnight $first_commit
          fi

      - name: Warn of failure
        if: >
          failure() &&
          steps.overnight.outcome == 'failure'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          title="Overnight benchmark workflow failed: \`${{ github.run_id }}\`"
          body="Generated by GHA run [\`${{github.run_id}}\`](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}})"
          gh issue create --title "$title" --body "$body" --label "Bot" --label "Type: Performance" --repo $GITHUB_REPOSITORY

      - name: Upload any benchmark reports
        if: success() || steps.overnight.outcome == 'failure'
        uses: actions/upload-artifact@v4
        with:
          name: benchmark_reports
          path: .github/workflows/benchmark_reports

      - name: Archive asv results
        if: ${{ always() }}
        uses: actions/upload-artifact@v4
        with:
          name: asv-raw-results
          path: benchmarks/.asv/results


ci-citation

name: ci-citation

on:
  pull_request:
    paths:
      - "CITATION.cff"

  push:
    paths:
      - "CITATION.cff"

  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  validate:
    name: "validate"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: "check CITATION.cff"
        uses: citation-file-format/cffconvert-github-action@4cf11baa70a673bfdf9dad0acc7ee33b3f4b6084
        with:
          args: "--validate"


ci-manifest

# Reference
#    - https://github.com/actions/checkout

name: ci-manifest

on:
  pull_request:
    branches:
      - "*"

  push:
    branches-ignore:
      - "auto-update-lockfiles"
      - "pre-commit-ci-update-config"
      - "dependabot/*"

  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  manifest:
    name: "check-manifest"
    uses: scitools/workflows/.github/workflows/ci-manifest.yml@2024.06.4


ci-tests

# reference:
#   - https://github.com/actions/cache
#   - https://github.com/actions/checkout
#   - https://github.com/marketplace/actions/setup-miniconda

name: ci-tests

on:
  push:
    branches:
      - "main"
      - "v*x"
    tags:
      - "v*"
  pull_request:
    branches:
      - "*"
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  tests:
    name: "${{ matrix.session }} (py${{ matrix.python-version }} ${{ matrix.os }})"

    runs-on: ${{ matrix.os }}

    defaults:
      run:
        shell: bash -l {0}

    strategy:
      fail-fast: false
      matrix:
        os: ["ubuntu-latest"]
        python-version: ["3.12"]
        session: ["doctest", "gallery", "linkcheck"]
        include:
          - os: "ubuntu-latest"
            python-version: "3.12"
            session: "tests"
            coverage: "--coverage"
          - os: "ubuntu-latest"
            python-version: "3.11"
            session: "tests"
          - os: "ubuntu-latest"
            python-version: "3.10"
            session: "tests"

    env:
      IRIS_TEST_DATA_VERSION: "2.22"
      ENV_NAME: "ci-tests"

    steps:
      - name: "checkout"
        uses: actions/checkout@v4

      - name: "environment configure"
        env:
          # Maximum cache period (in weeks) before forcing a cache refresh.
          CACHE_WEEKS: 2
        run: |
          echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV}
          echo "LOCK_FILE=requirements/locks/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV}

      - name: "data cache"
        uses: ./.github/workflows/composite/iris-data-cache
        with:
          cache_build: 0
          env_name: ${{ env.ENV_NAME }}
          version: ${{ env.IRIS_TEST_DATA_VERSION }}

      - name: "conda package cache"
        uses: ./.github/workflows/composite/conda-pkg-cache
        with:
          cache_build: 0
          cache_period: ${{ env.CACHE_PERIOD }}
          env_name: ${{ env.ENV_NAME }}

      - name: "conda install"
        uses: conda-incubator/setup-miniconda@v3
        with:
          miniforge-version: latest
          channels: conda-forge,defaults
          activate-environment: ${{ env.ENV_NAME }}
          auto-update-conda: false
          use-only-tar-bz2: true

      - name: "conda environment cache"
        uses: ./.github/workflows/composite/conda-env-cache
        with:
          cache_build: 0
          cache_period: ${{ env.CACHE_PERIOD }}
          env_name: ${{ env.ENV_NAME }}
          install_packages: "cartopy nox pip"

      - name: "conda info"
        run: |
          conda info
          conda list

      - name: "cartopy cache"
        uses: ./.github/workflows/composite/cartopy-cache
        with:
          cache_build: 0
          cache_period: ${{ env.CACHE_PERIOD }}
          env_name: ${{ env.ENV_NAME }}

      - name: "nox cache"
        uses: ./.github/workflows/composite/nox-cache
        with:
          cache_build: 2
          env_name: ${{ env.ENV_NAME }}
          lock_file: ${{ env.LOCK_FILE }}

      # TODO: drop use of site.cfg and explicit use of mplrc
      - name: "iris configure"
        env:
          SITE_CFG: lib/iris/etc/site.cfg
          MPL_RC: ${HOME}/.config/matplotlib/matplotlibrc
        run: |
          mkdir -p $(dirname ${SITE_CFG})
          echo ${SITE_CFG}
          echo "[Resources]" >> ${SITE_CFG}
          echo "test_data_dir = ${HOME}/iris-test-data/test_data" >> ${SITE_CFG}
          echo "doc_dir = ${GITHUB_WORKSPACE}/docs" >> ${SITE_CFG}
          cat ${SITE_CFG}
          mkdir -p $(dirname ${MPL_RC})
          echo ${MPL_RC}
          echo "backend : agg" >> ${MPL_RC}
          echo "image.cmap : viridis" >> ${MPL_RC}
          cat ${MPL_RC}

      - name: "iris ${{ matrix.session }}"
        env:
          PY_VER: ${{ matrix.python-version }}
          # Force coloured output on GitHub Actions.
          PY_COLORS: "1"
        run: |
          nox --session ${{ matrix.session }} -- --verbose ${{ matrix.coverage }}

      - name: "upload coverage report"
        if: ${{ matrix.coverage }}
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}


ci-wheels

# Reference:
#   - https://github.com/actions/checkout
#   - https://github.com/actions/download-artifact
#   - https://github.com/actions/upload-artifact
#   - https://github.com/pypa/build
#   - https://github.com/pypa/gh-action-pypi-publish
#   - https://test.pypi.org/help/#apitoken

name: ci-wheels

on:
  pull_request:

  push:
    tags:
      - "v*"
    branches-ignore:
      - "auto-update-lockfiles"
      - "pre-commit-ci-update-config"
      - "dependabot/*"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    name: "build sdist & wheel"
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: "building"
      shell: bash
      run: |
        pipx run build

    - uses: actions/upload-artifact@v4
      with:
        name: pypi-artifacts
        path: ${{ github.workspace }}/dist/*

  test-wheel:
    needs: build
    name: "test wheel (py${{ matrix.python-version }})"
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash -l {0}
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
        session: ["wheel"]
    env:
      ENV_NAME: "ci-wheels"
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - uses: actions/download-artifact@v4
      with:
        name: pypi-artifacts
        path: ${{ github.workspace }}/dist

    - name: "environment configure"
      env:
        # Maximum cache period (in weeks) before forcing a cache refresh.
          CACHE_WEEKS: 2
      run: |
        echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV}
        echo "LOCK_FILE=requirements/locks/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV}

    - name: "conda package cache"
      uses: ./.github/workflows/composite/conda-pkg-cache
      with:
        cache_build: 0
        cache_period: ${{ env.CACHE_PERIOD }}
        env_name: ${{ env.ENV_NAME }}

    - name: "conda install"
      uses: conda-incubator/setup-miniconda@v3
      with:
        miniforge-version: latest
        channels: conda-forge,defaults
        activate-environment: ${{ env.ENV_NAME }}
        auto-update-conda: false
        use-only-tar-bz2: true

    - name: "conda environment cache"
      uses: ./.github/workflows/composite/conda-env-cache
      with:
        cache_build: 0
        cache_period: ${{ env.CACHE_PERIOD }}
        env_name: ${{ env.ENV_NAME }}
        install_packages: "nox pip"

    - name: "nox cache"
      uses: ./.github/workflows/composite/nox-cache
      with:
        cache_build: 1
        env_name: ${{ env.ENV_NAME }}
        lock_file: ${{ env.LOCK_FILE }}

    - name: "nox install and test wheel"
      env:
        PY_VER: ${{ matrix.python-version }}
      run: |
        nox --session ${{ matrix.session }} -- --verbose

  show-artifacts:
    needs: build
    name: "show artifacts"
    runs-on: ubuntu-latest
    steps:
    - uses: actions/download-artifact@v4
      with:
        name: pypi-artifacts
        path: ${{ github.workspace }}/dist

    - shell: bash
      run: |
        ls -l ${{ github.workspace }}/dist

  publish-artifacts-test-pypi:
    needs: test-wheel
    name: "publish to test.pypi"
    runs-on: ubuntu-latest
    # upload to Test PyPI for every commit on main branch
    # and check for the SciTools repo
    if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && github.repository_owner == 'SciTools'
    steps:
    - uses: actions/download-artifact@v4
      with:
        name: pypi-artifacts
        path: ${{ github.workspace }}/dist

    - uses: pypa/gh-action-pypi-publish@release/v1
      with:
        user: __token__
        password: ${{ secrets.TEST_PYPI_API_TOKEN }}
        repository_url: https://test.pypi.org/legacy/
        skip_existing: true
        print_hash: true

  publish-artifacts-pypi:
    needs: test-wheel
    name: "publish to pypi"
    runs-on: ubuntu-latest
    # upload to PyPI for every tag starting with 'v'
    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && github.repository_owner == 'SciTools'
    steps:
    - uses: actions/download-artifact@v4
      with:
        name: pypi-artifacts
        path: ${{ github.workspace }}/dist

    - uses: pypa/gh-action-pypi-publish@release/v1
      with:
        user: __token__
        password: ${{ secrets.PYPI_API_TOKEN }}
        print_hash: true


Pull Request Labeler

# Reference
#   - https://github.com/actions/labeler

name: "Pull Request Labeler"
on:
- pull_request_target

jobs:
  labeler:
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
    - uses: actions/labeler@v5


Refresh Lockfiles

# Updates the environment lock files. See the called workflow in the
# scitools/workflows repo for more details.

name: Refresh Lockfiles

on:
  workflow_dispatch:
  schedule:
    # Run once a week on a Saturday night
    # N.B. "should" be quoted, according to
    # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule
    - cron: "1 0 * * 6"

jobs:
  refresh_lockfiles:
    uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2024.06.4
    secrets: inherit


Stale issues and pull-requests

# See https://github.com/actions/stale

name: Stale issues and pull-requests

on:
  schedule:
    # Run once a day
    # N.B. "should" be quoted, according to
    # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule
    - cron: "0 0 * * *"

jobs:
  stale:
    if: "github.repository == 'SciTools/iris'"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v9
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

          # Idle number of days before marking issues/prs stale.
          days-before-stale: 500

          # Idle number of days before closing stale issues/prs.
          days-before-close: 28

          # Comment on the staled issues.
          stale-issue-message: |
            In order to maintain a backlog of relevant issues, we automatically label them as stale after 500 days of inactivity.

            If this issue is still important to you, then please comment on this issue and the stale label will be removed.

            Otherwise this issue will be automatically closed in 28 days time.

          # Comment on the staled prs.
          stale-pr-message: |
            In order to maintain a backlog of relevant PRs, we automatically label them as stale after 500 days of inactivity.

            If this PR is still important to you, then please comment on this PR and the stale label will be removed.

            Otherwise this PR will be automatically closed in 28 days time.

          # Comment on the staled issues while closed.
          close-issue-message: |
            This stale issue has been automatically closed due to a lack of community activity.

            If you still care about this issue, then please either:
              * Re-open this issue, if you have sufficient permissions, or
              * Add a comment stating that this is still relevant and someone will re-open it on your behalf.

          # Comment on the staled prs while closed.
          close-pr-message: |
            This stale PR has been automatically closed due to a lack of community activity.

            If you still care about this PR, then please either:
              * Re-open this PR, if you have sufficient permissions, or
              * Add a comment pinging `@SciTools/iris-devs` who will re-open on your behalf.

          # Label to apply on staled issues.
          stale-issue-label: Stale

          # Label to apply on staled prs.
          stale-pr-label: Stale

          # Labels on issues exempted from stale.
          exempt-issue-labels:
            "Status: Blocked,Status: Decision Required,Peloton 🚴♂,Good First Issue, Dragon 🐉, Dragon Sub-Task 🦎, Release: Major"

          # Labels on prs exempted from stale.
          exempt-pr-labels:
            "Status: Blocked,Status: Decision Required,Peloton 🚴♂,Good First Issue, Dragon 🐉, Dragon Sub-Task 🦎, Release: Major"

          # Max number of operations per run.
          operations-per-run: 300

          # Remove stale label from issues/prs on updates/comments.
          remove-stale-when-updated: true

          # Order to get issues/PRs.
          ascending: true

          # Exempt all issues/prs with milestones from stale.
          exempt-all-milestones: true