ci

name: ci

on:
  push:
    branches:
      - main
    tags:
      - '**'
  pull_request: {}

env:
  COLUMNS: 150

jobs:
  coverage:
    runs-on: ubuntu-20.04

    steps:
      - uses: actions/checkout@v4

      - name: install rust nightly
        uses: dtolnay/rust-toolchain@nightly

      - id: cache-rust
        name: cache rust
        uses: Swatinem/rust-cache@v2

      - run: cargo install rustfilt coverage-prepare
        if: steps.cache-rust.outputs.cache-hit != 'true'

      - run: rustup component add llvm-tools-preview

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - run: pip install -r tests/requirements.txt

      - run: rustc --version --verbose

      - run: pip install -e .
        env:
          RUST_BACKTRACE: 1
          RUSTFLAGS: '-C instrument-coverage'

      - run: pip freeze

      - run: coverage run -m pytest

      - run: ls -lha
      - run: coverage xml

      - run: coverage-prepare lcov python/pydantic_core/*.so

      - uses: codecov/codecov-action@v4

  # See https://github.com/PyO3/pyo3/discussions/2781
  # tests intermittently segfault with pypy and cpython 3.7 when using `coverage run ...`, hence separate job
  test-python:
    name: test ${{ matrix.python-version }}
    strategy:
      fail-fast: false
      matrix:
        python-version:
          - '3.8'
          - '3.9'
          - '3.10'
          - '3.11'
          - '3.12'
          - 'pypy3.9'
          - 'pypy3.10'

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: install rust stable
        uses: dtolnay/rust-toolchain@stable

      - name: cache rust
        uses: Swatinem/rust-cache@v2
        with:
          key: test-v3

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

      - run: pip install -r tests/requirements.txt

      - run: pip install -e .
        env:
          RUST_BACKTRACE: 1

      - run: pip freeze

      - run: pytest
        env:
          HYPOTHESIS_PROFILE: slow

  test-os:
    name: test on ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]

    runs-on: ${{ matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4

      - name: install rust stable
        uses: dtolnay/rust-toolchain@stable

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - run: pip install -r tests/requirements.txt

      - run: pip install -e .
        env:
          RUST_BACKTRACE: 1

      - run: pip freeze

      - run: pytest

      - run: cargo test

  test-msrv:
    name: test MSRV

    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: resolve MSRV
        id: resolve-msrv
        run:
          echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT

      - name: install rust MSRV
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ steps.resolve-msrv.outputs.MSRV }}

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - run: pip install -r tests/requirements.txt

      - run: pip install -e .
        env:
          RUST_BACKTRACE: 1

      - run: pip freeze

      - run: pytest

      - run: cargo test

  # test with a debug build as it picks up errors which optimised release builds do not
  test-debug:
    name: test-debug ${{ matrix.python-version }}
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        python-version:
          - '3.11'
          - 'pypy3.10'

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

      - name: install rust stable
        uses: dtolnay/rust-toolchain@stable

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - run: pip install -r tests/requirements.txt
      - run: make build-dev

      - run: pip freeze
      - run: pytest

  test-pydantic-integration:
    runs-on: ubuntu-latest
    continue-on-error: true

    steps:
      - uses: actions/checkout@v4
        with:
          repository: pydantic/pydantic
          path: pydantic

      - uses: actions/checkout@v4
        with:
          path: pydantic-core

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: install rust stable
        uses: dtolnay/rust-toolchain@stable

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - name: install deps
        run: |
          pip install pdm maturin
          pdm venv create --with-pip
          pdm install -G testing -G email
          pdm run pip install maturin
          pdm run bash -c 'cd ../pydantic-core && make build-dev'
        working-directory: pydantic

      - run: pdm info && pdm list
        working-directory: pydantic
        # Run pytest with lax xfail because we often add tests to pydantic
        # which xfail on a pending release of pydantic-core
      - run: pdm run pytest --override-ini=xfail_strict=False
        working-directory: pydantic

  lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: install rust stable
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      # used to lint js code
      - uses: actions/setup-node@v4
        with:
          node-version: '18'

      - uses: actions/cache@v4
        id: cache-py
        name: cache python
        with:
          path: ${{ env.pythonLocation }}
          key: >
            py
            ${{ env.pythonLocation }}
            ${{ hashFiles('tests/requirements-linting.txt') }}
            ${{ hashFiles('pyproject.toml') }}

      - run: pip install -r tests/requirements-linting.txt
        if: steps.cache-py.outputs.cache-hit != 'true'

      - run: make build-dev

      - run: pip freeze

      - run: make lint
      - run: make pyright

      - run: npm install
      - run: npm run lint

  bench:
    name: rust benchmarks
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: install rust nightly
        uses: dtolnay/rust-toolchain@nightly

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - run: pip install typing_extensions

      - run: cargo bench

  build-wasm-emscripten:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: set up python
        id: setup-python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: install rust nightly
        uses: dtolnay/rust-toolchain@nightly
        with:
          components: rust-src
          targets: wasm32-unknown-emscripten

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - uses: mymindstorm/setup-emsdk@v14
        with:
          # NOTE!: as per https://github.com/pydantic/pydantic-core/pull/149 this version needs to match the version
          # in node_modules/pyodide/repodata.json, to get the version, run:
          # `cat node_modules/pyodide/repodata.json | python -m json.tool | rg platform`
          version: '3.1.46'
          actions-cache-folder: emsdk-cache

      - run: pip install 'maturin>=1,<2' 'ruff==0.1.3' typing_extensions

      - name: build wheels
        run: make build-wasm

      - uses: actions/setup-node@v4
        with:
          node-version: '18'

      - run: npm install

      - run: npm run test

      - run: |
         ls -lh dist/
         ls -l dist/

      - uses: actions/upload-artifact@v4
        with:
          name: wasm_wheels
          path: dist

  # https://github.com/marketplace/actions/alls-green#why used for branch protection checks
  check:
    if: always()
    needs: [coverage, test-python, test-os, test-debug, lint, bench, build-wasm-emscripten]
    runs-on: ubuntu-latest
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}
          allowed-failures: coverage

  build-sdist:
    name: build sdist
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist
          rust-toolchain: stable
      - uses: actions/upload-artifact@v4
        with:
          name: pypi_files_sdist
          path: dist

  build:
    name: build on ${{ matrix.os }} (${{ matrix.target }} - ${{ matrix.interpreter || 'all' }}${{ matrix.os == 'linux' && format(' - {0}', matrix.manylinux == 'auto' && 'manylinux' || matrix.manylinux) || '' }})
    # only run on push to main and on release
    if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build')
    strategy:
      fail-fast: false
      matrix:
        os: [linux, macos, windows]
        target: [x86_64, aarch64]
        manylinux: [auto]
        include:
          # manylinux for various platforms, plus x86_64 pypy
          - os: linux
            manylinux: auto
            target: i686
          - os: linux
            manylinux: auto
            target: aarch64
          - os: linux
            manylinux: auto
            target: armv7
            interpreter: 3.8 3.9 3.10 3.11 3.12
          - os: linux
            manylinux: auto
            target: ppc64le
            interpreter: 3.8 3.9 3.10 3.11 3.12
          - os: linux
            manylinux: auto
            target: s390x
            interpreter: 3.8 3.9 3.10 3.11 3.12
          - os: linux
            manylinux: auto
            target: x86_64
            interpreter: pypy3.9 pypy3.10

          # musllinux
          - os: linux
            manylinux: musllinux_1_1
            target: x86_64
          - os: linux
            manylinux: musllinux_1_1
            target: aarch64

          # macos;
          # all versions x86_64
          # arm pypy and older pythons which can't be run on the arm hardware for PGO
          - os: macos
            target: x86_64
          - os: macos
            target: aarch64
            interpreter: 3.8 3.9 pypy3.9 pypy3.10

          # windows;
          # x86_64 pypy builds are not PGO optimized
          # i686 not supported by pypy
          # aarch64 only 3.11 and up, also not PGO optimized
          - os: windows
            target: x86_64
            interpreter: pypy3.9 pypy3.10
          - os: windows
            target: i686
            python-architecture: x86
            interpreter: 3.8 3.9 3.10 3.11 3.12
          # FIXME aarch64 builds broken, see https://github.com/PyO3/maturin/issues/2110
          # - os: windows
          #   target: aarch64
          #   interpreter: 3.11 3.12

        exclude:
          # See above; disabled for now.
          - os: windows
            target: aarch64

    runs-on: ${{ (matrix.os == 'linux' && 'ubuntu') || matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          architecture: ${{ matrix.python-architecture || 'x64' }}

      - run: pip install -U twine 'ruff==0.1.3' typing_extensions

      # generate self-schema now, so we don't have to do so inside docker in maturin build
      - run: python generate_self_schema.py

      - name: build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux }}
          args: --release --out dist --interpreter ${{ matrix.interpreter || '3.8 3.9 3.10 3.11 3.12 pypy3.9 pypy3.10' }}
          rust-toolchain: stable
          docker-options: -e CI

      - run: ${{ (matrix.os == 'windows' && 'dir') || 'ls -lh' }} dist/

      - run: twine check --strict dist/*

      - uses: actions/upload-artifact@v4
        with:
          name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }}
          path: dist

  build-pgo:
    name: build pgo-optimized on ${{ matrix.os }} / ${{ matrix.interpreter }}
    # only run on push to main and on release
    if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Full Build')
    strategy:
      fail-fast: false
      matrix:
        os: [linux, windows, macos]
        interpreter: ['3.8', '3.9', '3.10', '3.11', '3.12']
        include:
          # standard runners with override for macos arm
          - os: linux
            runs-on: ubuntu-latest
          - os: windows
            ls: dir
            runs-on: windows-latest
          - os: macos
            runs-on: macos-latest-xlarge
        exclude:
          # macos arm only supported from 3.10 and up
          - os: macos
            interpreter: '3.8'
          - os: macos
            interpreter: '3.9'

    runs-on: ${{ matrix.runs-on }}
    steps:
      - uses: actions/checkout@v4

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

      - name: install rust stable
        id: rust-toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools

      - run: pip install -U 'ruff==0.1.3' typing_extensions

      # generate self-schema now, so we don't have to do so inside docker in maturin build
      - run: python generate_self_schema.py

      - run: rustc --version --verbose

      - name: build initial wheel
        uses: PyO3/maturin-action@v1
        with:
          manylinux: auto
          args: >
            --release
            --out pgo-wheel
            --interpreter ${{ matrix.interpreter }}
          rust-toolchain: stable
          docker-options: -e CI
        env:
          RUSTFLAGS: "-Cprofile-generate=${{ github.workspace }}/profdata"

      - name: detect rust host
        run: echo RUST_HOST=$(rustc -Vv | grep host | cut -d ' ' -f 2) >> "$GITHUB_ENV"
        shell: bash

      - name: generate pgo data
        run: |
          pip install -U pip
          pip install -r tests/requirements.txt
          pip install pydantic-core --no-index --no-deps --find-links pgo-wheel --force-reinstall
          pytest tests/benchmarks
          rustup run stable bash -c 'echo LLVM_PROFDATA=$RUSTUP_HOME/toolchains/$RUSTUP_TOOLCHAIN/lib/rustlib/${{ env.RUST_HOST }}/bin/llvm-profdata >> "$GITHUB_ENV"'

      - name: merge pgo data
        run: ${{ env.LLVM_PROFDATA }} merge -o ${{ github.workspace }}/merged.profdata ${{ github.workspace }}/profdata

      - name: build pgo-optimized wheel
        uses: PyO3/maturin-action@v1
        with:
          manylinux: auto
          args: >
            --release
            --out dist
            --interpreter ${{ matrix.interpreter }}
          rust-toolchain: stable
          docker-options: -e CI
        env:
          RUSTFLAGS: "-Cprofile-use=${{ github.workspace }}/merged.profdata"

      - run: ${{ matrix.ls || 'ls -lh' }} dist/

      - uses: actions/upload-artifact@v4
        with:
          name: pypi_files_${{ matrix.os }}_${{ matrix.interpreter }}
          path: dist

  inspect-pypi-assets:
    needs: [build, build-sdist, build-pgo]
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - name: list dist files
        run: |
         ls -lh dist/
         ls -l dist/
         echo "`ls dist | wc -l` files"

      - name: extract and list sdist file
        run: |
         mkdir sdist-files
         tar -xvf dist/*.tar.gz -C sdist-files
         tree -a sdist-files

      - name: extract and list wheel file
        run: |
         ls dist/*cp310-manylinux*x86_64.whl | head -n 1
         python -m zipfile --list `ls dist/*cp310-manylinux*x86_64.whl | head -n 1`

  test-builds-arch:
    name: test build on ${{ matrix.target }}-${{ matrix.distro }}
    needs: [build]
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        target: [aarch64, armv7, s390x, ppc64le]
        distro: ['ubuntu22.04']
        include:
          - target: aarch64
            distro: alpine_latest

    steps:
      - uses: actions/checkout@v4

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_linux_*
          merge-multiple: true
          path: dist

      - uses: uraimo/run-on-arch-action@v2.7.2
        name: install & test
        with:
          arch: ${{ matrix.target }}
          distro: ${{ matrix.distro }}
          githubToken: ${{ github.token }}
          install: |
            set -x
            if command -v apt-get &> /dev/null; then
              echo "installing python & pip with apt-get..."
              apt-get update
              apt-get install -y --no-install-recommends python3 python3-pip python3-venv git
            else
              echo "installing python & pip with apk..."
              apk update
              apk add python3 py3-pip git
            fi
          run: |
            set -x
            python3 -m venv venv
            source venv/bin/activate
            python3 -m pip install -r tests/requirements.txt
            python3 -m pip install pydantic-core --no-index --no-deps --find-links dist --force-reinstall
            python3 -m pytest --ignore=tests/test_docstrings.py
            python3 -c 'import pydantic_core._pydantic_core; print(pydantic_core._pydantic_core.__version__)'

  test-builds-os:
    name: test build on ${{ matrix.os }}
    needs: [build, build-pgo]

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]

    runs-on: ${{ matrix.os }}-latest
    steps:
      - uses: actions/checkout@v4

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - run: pip install -r tests/requirements.txt
      - run: pip install pydantic-core --no-index --no-deps --find-links dist --force-reinstall
      - run: pytest --ignore=tests/test_docstrings.py

  release:
    needs: [test-builds-arch, test-builds-os, build-sdist, check]
    if: success() && startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - run: pip install -U twine

      - name: check package version
        run: python .github/check_version.py

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - run: twine check --strict dist/*

      - name: upload to pypi
        run: twine upload dist/*
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.pypi_token }}

      - name: get wasm dist artifacts
        uses: actions/download-artifact@v4
        with:
          name: wasm_wheels
          path: wasm

      - name: upload to github release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            wasm/*.whl
          prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}


codspeed

name: codspeed

on:
  push:
    branches:
      - main
  pull_request:
  # `workflow_dispatch` allows CodSpeed to trigger backtest
  # performance analysis in order to generate initial data.
  workflow_dispatch:

jobs:
  benchmarks:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - uses: actions/cache@v4
        id: cache-py
        name: cache python
        with:
          path: ${{ env.pythonLocation }}
          key: >
            py-v2
            ${{ env.pythonLocation }}
            ${{ hashFiles('tests/requirements.txt') }}
            ${{ hashFiles('pyproject.toml') }}

      - run: pip install -r tests/requirements.txt
        if: steps.cache-py.outputs.cache-hit != 'true'

      - run: pip uninstall pytest-speed -y
        if: steps.cache-py.outputs.cache-hit != 'true'

      - run: pip install pytest-benchmark==4.0.0
        if: steps.cache-py.outputs.cache-hit != 'true'

      - name: install rust stable
        id: rust-toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools

      - name: cache rust
        uses: Swatinem/rust-cache@v2

      - name: Compile pydantic-core for profiling
        run: make build-profiling
        env:
          CONST_RANDOM_SEED: 0 # Fix the compile time RNG seed
          RUSTFLAGS: "-Cprofile-generate=${{ github.workspace }}/profdata"

      - name: Gather pgo data
        run: pytest tests/benchmarks

      - name: Prepare merged pgo data
        run: rustup run stable bash -c '$RUSTUP_HOME/toolchains/$RUSTUP_TOOLCHAIN/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata merge -o ${{ github.workspace }}/merged.profdata ${{ github.workspace }}/profdata'

      - name: Compile pydantic-core for benchmarking
        run: make build-profiling
        env:
          CONST_RANDOM_SEED: 0 # Fix the compile time RNG seed
          RUSTFLAGS: "-Cprofile-use=${{ github.workspace }}/merged.profdata"

      - name: Run CodSpeed benchmarks
        uses: CodSpeedHQ/action@v2
        with:
          run: pytest tests/benchmarks/ --codspeed


Test fhempy FHEM Modules

# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Test fhempy FHEM Modules

on:
  pull_request:
      paths:
      - 'FHEM/**.pm'
      - 't/**'
      - '.github/workflows/fhem_test.yml'

  push:
    branches: [ master ]

  workflow_dispatch:

jobs:
  update_controls:
    needs: [runtest]
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repostory
      uses: actions/checkout@v4
      with:
        repository: ${{ github.event.pull_request.head.repo.full_name }}
        ref: ${{ github.event.pull_request.head.ref }}
        fetch-depth: 0

    - name: update controls file for FHEM Module
      uses: fhem/fhem-controls-actions@v2.3.0
      with:
        filename: controls_pythonbinding.txt

    - name: update controls file for python binary
      uses: fhem/fhem-controls-actions@v2.3.0
      with:
        filename: controls_pythonbinding.txt
        directory: FHEM/bindings/python/bin
        writemode: a
        extension: ''

    - name: Commit changes
      uses: EndBug/add-and-commit@v9
      with:
        default_author: github_actions
        commit: --signoff
        message: "action: auto update controls"
        push: true
        add: controls_pythonbinding.txt

  runtest:
    strategy:
      matrix:
        os: ['ubuntu-latest']
        perl: [ '5.28', '5.32' ]

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

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

    - uses: shogo82148/actions-setup-perl@v1.29.0
      with:
        perl-version: ${{ matrix.perl }}
        install-modules-with: cpanm
        multi-thread: true
        install-modules: |
            Test2::Suite
            JSON
            Protocol::WebSocket::Frame
            File::Basename
            Devel::Cover

    - name: Install FHEM via debian nightly
      uses: fhem/setup-fhem@v1.0.1

    - name: fix permissions
      run: |
        sudo chown -R --reference=controls_pythonbinding.txt /opt/fhem

    - name: run prove on fhem with perl ${{ matrix.perl }}
      run: |
        cp -R ${GITHUB_WORKSPACE}/FHEM/*.pm ${FHEM_DIR}/FHEM/
        prove -j1 --exec 'perl -MDevel::Cover=-silent,1 fhem.pl -t' -I FHEM -vv -r ${GITHUB_WORKSPACE}/t/FHEM/
      shell: bash
      working-directory: /opt/fhem/
      env:
        FHEM_DIR: /opt/fhem


release

# This is a basic workflow to help you get started with Actions

name: release

# Controls when the action will run.
on:
  #release:
  #  types: [created]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  release:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    concurrency: release
    permissions:
      id-token: write
      contents: write

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - name: Checkout
        uses: actions/checkout@v4
        with:
          ref: "master"
          fetch-depth: 0

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Python Semantic Release
        id: release
        uses: python-semantic-release/python-semantic-release@v9.6.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          force: "patch"
          push: "true"

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        # NOTE: DO NOT wrap the conditional in ${{ }} as it will always evaluate to true.
        # See https://github.com/actions/runner/issues/1173
        if: steps.release.outputs.released == 'true'

      - name: Publish package distributions to GitHub Releases
        uses: python-semantic-release/upload-to-gh-release@main
        if: steps.release.outputs.released == 'true'


Mark stale issues and pull requests

name: Mark stale issues and pull requests

on:
  schedule:
  - cron: "30 1 * * *"

permissions:
  issues: write

jobs:
  stale:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/stale@v9
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        stale-issue-label: 'no-issue-activity'
        stale-pr-label: 'no-pr-activity'
        days-before-stale: 60
        close-issue-message: Please reopen the issue if the issue still occurs.


Test fhempy

name: Test fhempy

on:
  push:
    branches: [master]
  workflow_dispatch:
  workflow_call:

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest]
        python-version: ["3.10", "3.11"]

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

    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 apt depencies
      run: |
        sudo apt update
        sudo apt install python3-dev libffi-dev libssl-dev libjpeg-dev zlib1g-dev autoconf build-essential libglib2.0-dev libdbus-1-dev bluez libbluetooth-dev
    - name: Install Poetry
      uses: snok/install-poetry@v1
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        python -m pip install pytest tox-gh-actions tox
    - name: Test with tox
      run: |
        tox


Test update manifest

name: Test update manifest

on:
  pull_request:
  workflow_dispatch:

permissions:
  contents: write

jobs:
  call_tests:
    needs: [update_manifest]
    uses: ./.github/workflows/tests.yml
    secrets: inherit

  update_manifest:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repostory
      uses: actions/checkout@v4
      with:
        repository: ${{ github.event.pull_request.head.repo.full_name }}
        ref: ${{ github.event.pull_request.head.ref }}
        fetch-depth: 0

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'

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

    - name: Update dependencies in manifest.json
      run: |
        python3 pyproject2manifest.py

    - name: Commit changes
      uses: EndBug/add-and-commit@v9
      with:
        default_author: github_actions
        commit: --signoff
        message: "action: auto update manifest.json"
        push: true


Downstream

name: Downstream
on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:

jobs:
  test_covasim:
    name: Covasim tests
    timeout-minutes: 10
    strategy:
      fail-fast: false
      max-parallel: 8
      matrix:
        os: ['ubuntu-latest']
        python-version: ['3.11']
    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout sources
        uses: actions/checkout@v2
      - uses: actions/setup-python@master
        with:
          python-version: ${{ matrix.python-version }}
          architecture: x64

      - name: Install Sciris
        run: pip install -e .

      - name: Check out Covasim
        uses: actions/checkout@v2
        with:
          repository: 'institutefordiseasemodeling/covasim'
          path: 'covasim'

      - name: Install Covasim
        working-directory: ./covasim
        run: pip install -e .

      - name: Install Covasim tests
        working-directory: ./covasim/tests
        run: pip install -r requirements_test.txt

      - name: Run Covasim tests
        working-directory: ./covasim/tests
        run: pytest --durations=0 --junitxml=covasim-test-results.xml # Run actual tests
      - name: Upload test results
        uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: test_results
          path: covasim/tests/covasim-test-results.xml

  test_starsim:
    name: Starsim tests
    timeout-minutes: 10
    strategy:
      fail-fast: false
      max-parallel: 8
      matrix:
        os: ['ubuntu-latest']
        python-version: ['3.11']
    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout sources
        uses: actions/checkout@v2
      - uses: actions/setup-python@master
        with:
          python-version: ${{ matrix.python-version }}
          architecture: x64

      - name: Install Sciris
        run: pip install -e .

      - name: Check out Starsim
        uses: actions/checkout@v2
        with:
          repository: 'starsimhub/starsim'
          path: 'starsim'

      - name: Install Starsim
        working-directory: ./starsim
        run: pip install -e .

      - name: Install Starsim tests
        working-directory: ./starsim/tests
        run: pip install -r requirements.txt

      - name: Run Starsim tests
        working-directory: ./starsim/tests
        run: pytest --durations=0 --junitxml=starsim-test-results.xml # Run actual tests
      - name: Upload test results
        uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: test_results
          path: starsim/tests/starsim-test-results.xml

  test_atomica:
    name: Atomica tests
    timeout-minutes: 10
    strategy:
      fail-fast: false
      max-parallel: 8
      matrix:
        os: ['ubuntu-latest']
        python-version: ['3.11']
    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout sources
        uses: actions/checkout@v2
      - uses: actions/setup-python@master
        with:
          python-version: ${{ matrix.python-version }}
          architecture: x64

      - name: Install Sciris
        run: pip install -e .

      - name: Check out Atomica
        uses: actions/checkout@v2
        with:
          repository: 'atomicateam/atomica'
          path: 'atomica'

      - name: Install Atomica
        working-directory: ./atomica
        run: pip install -e .

      - name: Install Atomica tests
        run: pip install pytest pytest-xdist pytest-cov pytest-env

      - name: Run Atomica tox tests
        working-directory: ./atomica/tests
        run: pytest test_tox_*.py --durations=0 --junitxml=starsim-test-results.xml # Run actual tests
      - name: Upload test results
        uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: test_results
          path: starsim/tests/starsim-test-results.xml


Sciris

name: Sciris
on:
  workflow_dispatch:
  pull_request:
  schedule:
    - cron: "0 0 * * *"

jobs:
  install_and_test:
    timeout-minutes: 8
    strategy:
      fail-fast: false
      max-parallel: 8
      matrix:
        include:
          - os: ubuntu-latest
            python-version: '3.9'
          - os: ubuntu-latest
            python-version: '3.10'
          - os: ubuntu-latest
            python-version: '3.11'
          - os: ubuntu-latest
            python-version: '3.12'
          - os: windows-latest
            python-version: '3.12'
          - os: macos-latest
            python-version: '3.12'
    runs-on: ${{ matrix.os }}
    name: CI tests
    steps:
      - name: Checkout sources
        uses: actions/checkout@v2
      - uses: actions/setup-python@master
        with:
          python-version: ${{ matrix.python-version }}
          architecture: x64
      - name: Install Sciris
        run: pip install -e .
      - name: Install tests
        working-directory: ./tests
        run: pip install -r requirements.txt
      - name: Run API tests
        working-directory: ./tests
        run: pytest --durations=0 --junitxml=test-results.xml # Run actual tests
      - name: Upload test results
        uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: test_results
          path: tests/test-results.xml


Auto Labels PR

name: Auto Labels PR

# how usage: https://github.com/marketplace/actions/labeler

on:
- pull_request_target

jobs:
  triage:
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v4
        with:
          repo-token: "${{ secrets.GITHUB_TOKEN }}"
          configuration-path: .github/labeler.yml # update role in this file


Size labeler

name: Size labeler

on:
- pull_request_target

jobs:
  size-labeler:
    permissions:
      issues: write
      pull-requests: write
    runs-on: ubuntu-latest
    name: Label the PR size
    steps:
      - uses: codelytv/pr-size-labeler@v1
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          xs_label: "size/xs"
          xs_max_size: "9"
          s_label: "size/s"
          s_max_size: "29"
          m_label: "size/m"
          m_max_size: "99"
          l_label: "size/l"
          l_max_size: "499"
          xl_label: "size/xl"
          xl_max_size: "999"
          xll_label: "size/xll"
          xll_max_size: "1999"
          fail_if_xll: "false"
          message_if_xll: >
            This PR exceeds the recommended size of 2000 lines.
            Please make sure you are NOT addressing multiple issues with one PR.
            Note this PR might be rejected due to its size.


find corrupted hyperlink

# This is a basic workflow to help you get started with Actions

name: find corrupted hyperlink

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build-test:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0 # Required for mkdocs to be able to display pages last update info
      - uses: actions/setup-python@v2
        with:
          python-version: 3.x

      # Runs a single command using the runners shell
      - run: pip install mkdocs-material \
              mkdocs-git-revision-date-plugin \
              mkdocs-mermaid2-plugin \
              mkdocs-rss-plugin \
              mkdocs-minify-plugin \
              mkdocs-macros-plugin \
              mkdocs-git-revision-date-localized-plugin \
              mkdocs-awesome-pages-plugin \
              mkdocs-redirects \
              mkdocs-print-site-plugin \
              mkdocs-swagger-ui-tag \
              pyyaml

      # add custom plugin with pdf support
      - run: pip install git+https://github.com/SAMZONG/mkdocs-with-pdf-support-material-v8

      # check corrupted hyperlink
      - run: pwd
      - run: mkdocs build -f docs/zh/mkdocs.yml -d ../../public/  >> zh_build.log
      - run: mkdocs build -f docs/en/mkdocs.yml -d ../../public/en/ >> en_build.log
      - run: ls -lha
      - run: echo "# zh_build.log" >> issue-body.txt
      - run: grep -e 'WARNING.*\.md.*contains a link to.*not found' zh_build.log >> issue-body.txt
      - run: echo "# en_build.log" >> issue-body.txt
      - run: grep -e 'WARNING.*\.md.*contains a link to.*not found' en_build.log >> issue-body.txt

      # Create an issue if there are any warnings
      - name: Create issue
        uses: peter-evans/create-issue-from-file@v2
        with:
          title: 'Corrupted document hyperlink'
          body: |
            ${{ steps.get_issue_body.outputs.body }}
          labels: |
            bug
            corrupted-hyperlink
          assignees: |
            windsonsea
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ISSUE_BODY_PATH: ./issue-body.txt
        id: get_issue_body


deploy-for-main-path

# This is a basic workflow to help you get started with Actions

name: deploy-for-main-path

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  push:
    branches: [ main ]

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

env:
  UCLOUD_PUBLICKEY : ${{ secrets.UCLOUD_PUBLICKEY }}
  UCLOUD_PRIVATEKEY : ${{ secrets.UCLOUD_PRIVATEKEY }}
  UCLOUD_REGION : ${{ secrets.UCLOUD_REGION }}
  UCLOUD_BUCKET : ${{ secrets.UCLOUD_BUCKET }}
  CI: 1

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

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  deploy:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0 # Required for mkdocs to be able to display pages last update info

      # Clone
      - name: Checkout public repository
        uses: actions/checkout@v2
        with:
          repository: daocloud/daocloud-api-docs
          path: dao-openapi
          fetch-depth: 0 # Required for mkdocs to be able to display pages last update info

      - uses: actions/setup-python@v2
#       - uses: actions/setup-python@v4
        with:
          python-version: 3.x
#           cache: 'pip'

      # Add ssh private key
      - name: Setup SSH
        uses: MrSquaare/ssh-setup-action@v1
        with:
          host: github.com
          private-key: ${{ secrets.SSH_PRIVATE_KEY }}

#       - run: pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

      # Install mkdocs-material-insiders
      - run: git clone git@github.com:DaoCloud/mkdocs-material-insiders.git mkdocs-material
      - run: pip install -e mkdocs-material

      # Runs a single command using the runners shell
      - run: pip install mkdocs-git-revision-date-plugin \
              mkdocs-mermaid2-plugin \
              mkdocs-rss-plugin \
              mkdocs-minify-plugin \
              mkdocs-macros-plugin \
              mkdocs-git-revision-date-localized-plugin \
              mkdocs-awesome-pages-plugin \
              mkdocs-redirects \
              mkdocs-print-site-plugin \
              mkdocs-swagger-ui-tag \
              pyyaml

      # add custom plugin with pdf support
      - run: pip install git+https://github.com/SAMZONG/mkdocs-with-pdf-support-material-v8

      # merged openapi docs
      - run: cp -av dao-openapi/docs/openapi docs/zh/docs/
      - run: python scripts/merged_nav.py

      # build docs
      - run: pwd
      - run: mkdocs build -f docs/zh/mkdocs.path.yaml -d ../../public/
      - run: mkdocs build -f docs/en/mkdocs.path.yaml -d ../../public/en/

      # upload to ucloud bucket
      - run: cd public && pwd && python ../scripts/upload-ucloud.py \
                public_key=$UCLOUD_PUBLICKEY \
                private_key=$UCLOUD_PRIVATEKEY \
                region=$UCLOUD_REGION \
                bucket=$UCLOUD_BUCKET

      # refresh docs site cdn cache
      - run: python scripts/refresh_cdn_cache.py \
            publickey=$UCLOUD_PUBLICKEY \
            privatekey=$UCLOUD_PRIVATEKEY


deploy-for-main

# This is a basic workflow to help you get started with Actions

name: deploy-for-main

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  push:
    branches: [ main ]

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

env:
  UCLOUD_PUBLICKEY : ${{ secrets.UCLOUD_PUBLICKEY }}
  UCLOUD_PRIVATEKEY : ${{ secrets.UCLOUD_PRIVATEKEY }}
  UCLOUD_REGION : ${{ secrets.UCLOUD_REGION }}
  UCLOUD_BUCKET : ${{ secrets.UCLOUD_BUCKET }}
  CI: 1

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

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  deploy:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0 # Required for mkdocs to be able to display pages last update info

      # Clone
      - name: Checkout public repository
        uses: actions/checkout@v2
        with:
          repository: daocloud/daocloud-api-docs
          path: dao-openapi
          fetch-depth: 0 # Required for mkdocs to be able to display pages last update info

      - uses: actions/setup-python@v2
#       - uses: actions/setup-python@v4
        with:
          python-version: 3.x
#           cache: 'pip'

      # Add ssh private key
      - name: Setup SSH
        uses: MrSquaare/ssh-setup-action@v1
        with:
          host: github.com
          private-key: ${{ secrets.SSH_PRIVATE_KEY }}

#       - run: pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

      # Install mkdocs-material-insiders
      - run: git clone git@github.com:DaoCloud/mkdocs-material-insiders.git mkdocs-material
      - run: pip install -e mkdocs-material

      # Runs a single command using the runners shell
      - run: pip install mkdocs-git-revision-date-plugin \
              mkdocs-mermaid2-plugin \
              mkdocs-rss-plugin \
              mkdocs-minify-plugin \
              mkdocs-macros-plugin \
              mkdocs-git-revision-date-localized-plugin \
              mkdocs-awesome-pages-plugin \
              mkdocs-redirects \
              mkdocs-print-site-plugin \
              mkdocs-swagger-ui-tag \
              pyyaml

      # add custom plugin with pdf support
      - run: pip install git+https://github.com/SAMZONG/mkdocs-with-pdf-support-material-v8

      # merged openapi docs
      - run: cp -av dao-openapi/docs/openapi docs/zh/docs/
      - run: python scripts/merged_nav.py

      # build docs
      - run: pwd
      - run: mkdocs build -f docs/zh/mkdocs.yml -d ../../public/
      - run: mkdocs build -f docs/en/mkdocs.yml -d ../../public/en/

      # upload to ucloud bucket
      - run: cd public && pwd && python ../scripts/upload-ucloud.py \
                public_key=$UCLOUD_PUBLICKEY \
                private_key=$UCLOUD_PRIVATEKEY \
                region=$UCLOUD_REGION \
                bucket=$UCLOUD_BUCKET

      # refresh docs site cdn cache
      - run: python scripts/refresh_cdn_cache.py \
            publickey=$UCLOUD_PUBLICKEY \
            privatekey=$UCLOUD_PRIVATEKEY


pull-request-test

# This is a basic workflow to help you get started with Actions

name: pull-request-test

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  pull_request:
    branches:
      - main

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

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build-test:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0 # Required for mkdocs to be able to display pages last update info
      - uses: actions/setup-python@v2
        with:
          python-version: 3.x

#       # Add ssh private key
#       - name: Setup SSH
#         uses: MrSquaare/ssh-setup-action@v1
#         with:
#           host: github.com
#           private-key: ${{ secrets.SSH_PRIVATE_KEY }}

#       # Install mkdocs-material-insiders
#       - run: git clone git@github.com:DaoCloud/mkdocs-material-insiders.git mkdocs-material
#       - run: pip install -e mkdocs-material

      # Runs a single command using the runners shell
      - run: pip install mkdocs-git-revision-date-plugin \
              mkdocs-mermaid2-plugin \
              mkdocs-rss-plugin \
              mkdocs-minify-plugin \
              mkdocs-macros-plugin \
              mkdocs-git-revision-date-localized-plugin \
              mkdocs-awesome-pages-plugin \
              mkdocs-redirects \
              mkdocs-print-site-plugin \
              mkdocs-swagger-ui-tag \
              pyyaml

      # add custom plugin with pdf support
      - run: pip install git+https://github.com/SAMZONG/mkdocs-with-pdf-support-material-v8

      # build docs
      - run: pwd
      - run: mkdocs build -f docs/zh/mkdocs.yml -d ../../public/
      - run: mkdocs build -f docs/en/mkdocs.yml -d ../../public/en/


Upstream Sync

name: Upstream Sync

permissions:
  contents: write

on:
  schedule:
    - cron: "0 * * * *" # every hour
  workflow_dispatch:

jobs:
  sync_latest_from_upstream:
    name: Sync latest commits from upstream repo
    runs-on: ubuntu-latest
    if: ${{ github.event.repository.fork }}

    steps:
      # Step 1: run a standard checkout action
      - name: Checkout target repo
        uses: actions/checkout@v3

      # Step 2: run the sync action
      - name: Sync upstream changes
        id: sync
        uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
        with:
          # set your upstream repo and branch
          upstream_sync_repo: DaoCloud/DaoCloud-docs
          upstream_sync_branch: main
          target_sync_branch: main
          target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set

          # Set test_mode true to run tests instead of the true action!!
          test_mode: false

      - name: Sync check
        if: failure()
        run: |
          echo "::error::由于权限不足，导致同步失败（这是预期的行为），请前往仓库首页手动执行[Sync fork]。"
          echo "::error::Due to insufficient permissions, synchronization failed (as expected). Please go to the repository homepage and manually perform [Sync fork]."
          exit 1


🫸 Bot comment

name: 🫸 Bot comment

on:
  pull_request_target:
    types: [synchronize, reopened, opened, ready_for_review]
    branches:
    - main
    paths:
    - "gqlauth/**"
    - ".github/workflows/bot_comment.yml"
    - "RELEASE.md"

jobs:
  bot_comment:
    name:  Create or update bot status comment
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: 3.11
      - name: setup poetry
        run: |
          pip install poetry
          poetry install
      - name: upload comment
        run:
          cd .github && poetry run python -m workflows.management bot_comment
        env:
          PR_NUMBER: ${{ github.event.number }}
          BOT_TOKEN: ${{ secrets.BOT_TOKEN }}


version: 2
updates:
  - package-ecosystem: github-actions
    directory: "/"
    schedule:
      interval: daily
  - package-ecosystem: pip
    directory: "/.github/workflows"
    schedule:
      interval: daily
  - package-ecosystem: pip
    directory: "/"
    schedule:
      interval: daily


docs deploy

name: docs deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v5
        with:
          python-version: 3.12
      - run: |
          pip install poetry
          poetry install
          make deploy-docs


🦺 MyPy

name: 🦺 MyPy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
    paths:
      - "gqlauth/**"
      - "tests/**"
      - ".github/workflows/mypy.yml"
env:
  IS_GITHUB_ACTION: 1

jobs:
  mypy:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        python-version: ["3.10"]

    steps:

      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      #----------------------------------------------
      #        load pip cache if cache exists
      #----------------------------------------------
      - uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip
          restore-keys: ${{ runner.os }}-pip

      #----------------------------------------------
      #  -----  install & configure poetry  -----
      #----------------------------------------------
      - name: Install Poetry
        uses: abatilo/actions-poetry@v2

      #----------------------------------------------
      #       load cached venv if cache exists
      #----------------------------------------------
      - name: Load cached venv
        id: cached-poetry-dependencies
        uses: actions/cache@v4
        with:
          path: .venv
          key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}

      #----------------------------------------------
      # install dependencies if cache does not exist
      #----------------------------------------------
      - name: Install dependencies
        if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
        run: poetry install --all-extras

      - name: mypy checks
        run: |
          poetry run mypy ./gqlauth


🆙 Release

name: 🆙 Release

on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Deploy
    permissions: write-all
    environment: Deployment
    runs-on: ubuntu-latest
    if: ${{ github.ref=='refs/heads/main' && github.event_name!='pull_request' }}

    steps:
        - uses: actions/checkout@v3
        - name: Set up Python
          uses: actions/setup-python@v4
          with:
            python-version: 3.11
        - name: Install deps
          run: |
            python -m pip install poetry
            poetry install
            poetry run pip install githubrelease

        - name: Check if we should release
          id: check_release
          run: cd .github && poetry run python -m workflows.management check-release
        - name: Publish
          if: steps.check_release.outputs.status == ''
          env:
            BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
            POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
          run: |
            git remote set-url origin https://${{ secrets.BOT_TOKEN }}@github.com/${{ github.repository }}
            cd .github && poetry run python -m workflows.management release
            poetry publish --username __token__


Run Tests

---
name: Run Tests

on:
  push:
    branches: [master, main]
  pull_request:
    branches: [master, main]
  release:
    types: [released]

jobs:
  tests:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        django-version: ["3.2.0", "4.1.3", "4.2.8", "5.0"]
        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
        exclude:
          # excludes Python 3.8 and 3.9 for Django 5.0.
          - django-version: "5.0"
            python-version: "3.8"
          - django-version: "5.0"
            python-version: "3.9"

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

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

      - name: Install poetry
        uses: snok/install-poetry@v1
        with:
          virtualenvs-create: true
          virtualenvs-in-project: true

      - name: Load cached venv
        id: cached-poetry
        uses: actions/cache@v4
        with:
          path: .venv
          key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
      - name: Run tests
        run: |
          poetry install -E captcha
          poetry run pip install "Django~=${{ matrix.django-version }}"
          make test

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4


Cherry Pick

name: Cherry Pick

on:
  pull_request_target:
    branches:
      - main
    types:
      - closed

jobs:
  cherry_pick:
    runs-on: ubuntu-latest

    name: Cherry pick into 1.10.X-fixes

    if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'cherry-pick-v1')

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

      - name: Cherry pick into 1.10.X-fixes
        uses: carloscastrojumo/github-cherry-pick-action@v1.0.10
        with:
          branch: 1.10.X-fixes
          labels: from-cherry-pick
          token: ${{ secrets.GITHUB_TOKEN }}
          title: '[cherry-pick] {old_title}'
          body: 'Cherry picking #{old_pull_request_id} onto this branch'


CI

name: CI

on:
  push:
    branches:
      - main
    tags:
      - '**'
  pull_request: {}

env:
  COLUMNS: 150
  PDM_DEPS: 'urllib3<2'
  FINDPYTHON_GET_VERSION_TIMEOUT: 30

jobs:
  lint:
    runs-on: ubuntu-latest
    name: lint ${{ matrix.python-version }}
    strategy:
      fail-fast: false
      matrix:
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
    steps:
      - uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: true
          allow-python-prereleases: true

      - name: install
        run: |
          pdm venv create --with-pip --force $PYTHON
          pdm install -G linting -G email

      - uses: pre-commit/action@v3.0.1
        with:
          extra_args: --all-files --verbose
        env:
          SKIP: no-commit-to-branch

  docs-build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - uses: pdm-project/setup-pdm@v4
      with:
        # cloudflare pages build containers run 3.8, hence using it here
        python-version: '3.8'

    - name: install dependencies
      # Unlike the docs build, we don't use mkdocs_material-insiders
      # Because the secret for accessing the library is not accessible from forks, but we still want to run
      # this job on public CI runs.
      run: |
        pdm venv create --with-pip --force $PYTHON
        pdm install -G docs

    - run: pdm run python -c 'import docs.plugins.main'

    # Adding local symlinks gets nice source locations like
    #   pydantic_core/core_schema.py
    # instead of
    #   .venv/lib/python3.10/site-packages/pydantic_core/core_schema.py
    - name: prepare shortcuts for extra modules
      run: |
        ln -s .venv/lib/python*/site-packages/pydantic_core pydantic_core
        ln -s .venv/lib/python*/site-packages/pydantic_settings pydantic_settings
        ln -s .venv/lib/python*/site-packages/pydantic_extra_types pydantic_extra_types

    - run: pdm run mkdocs build

  test-memray:
    name: test memray
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.12'
          cache: true

      - name: install deps
        run: |
          pdm venv create --with-pip --force $PYTHON
          pdm install -G testing -G testing-extra -G email -G memray
          pdm add pytest-memray

      - name: test
        run: pdm run pytest --ignore=tests/mypy/ --ignore=tests/test_docs.py --memray

  test:
    name: test ${{ matrix.os }} / ${{ matrix.python-version }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-13, macos-latest, windows-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
        include:
          # no pydantic-core binaries for pypy on windows, so tests take absolute ages
          # macos tests with pypy take ages (>10mins) since pypy is very slow
          # so we only test pypy on ubuntu
          - os: ubuntu-latest
            python-version: 'pypy3.9'
          - os: ubuntu-latest
            python-version: 'pypy3.10'
        exclude:
          # Python 3.8 and 3.9 are not available on macOS 14
          - os: macos-13
            python-version: '3.10'
          - os: macos-13
            python-version: '3.11'
          - os: macos-13
            python-version: '3.12'
          - os: macos-latest
            python-version: '3.8'
          - os: macos-latest
            python-version: '3.9'

    env:
      OS: ${{ matrix.os }}
      DEPS: yes

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

    steps:
    - uses: actions/checkout@v4

    - uses: pdm-project/setup-pdm@v4
      with:
        python-version: ${{ matrix.python-version }}
        cache: true
        allow-python-prereleases: true

    - name: install deps
      run: |
        pdm venv create --with-pip --force $PYTHON
        pdm install -G testing -G email

    - run: pdm info && pdm list

    - run: 'pdm run python -c "import pydantic.version; print(pydantic.version.version_info())"'

    - run: mkdir coverage

    - name: test without deps
      # speed up by skipping this step on pypy
      if: "!startsWith(matrix.python-version, 'pypy')"
      run: make test
      env:
        COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-without-deps
        CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-without-deps

    - name: install extra deps
      run: pdm install -G testing-extra -G email

    - name: test with deps
      run: make test
      env:
        COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-with-deps
        CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-with-deps

    - name: store coverage files
      uses: actions/upload-artifact@v4
      with:
        name: coverage-${{ matrix.os }}-${{ matrix.python-version }}
        path: coverage

  test-fastapi:
    # If some tests start failing due to out-of-date schemas/validation errors/etc.,
    # update the `tests/test_fastapi.sh` script to exclude tests that have known-acceptable failures.
    name: test fastapi
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: test
        run: make test-fastapi

  test-plugin:
    name: test pydantic plugin
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.12'
          cache: true

      - name: install deps
        run: |
          pdm venv create --with-pip --force $PYTHON
          pdm install -G testing

      - name: install example plugin
        run: pdm add ./tests/plugin

      - run: pdm run pytest ./tests/plugin
        env:
          TEST_PLUGIN: 1

      # uninstalling is necessary to avoid creating a pdm cache shared with other jobs
      # which includes the plugin
      - name: uninstall example plugin
        run: pdm remove ./tests/plugin

  test-mypy:
    name: mypy ${{ matrix.mypy-version }} / ${{ matrix.python-version }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # test recent mypy versions on 3.10 and mypy 1.0.1 on all supported python versions
        # if you change the value in the lockfile add the old value to this list
        mypy-version: ['1.0.1', 'lockfile', '1.2.0', '1.4.1', '1.5.1']
        python-version: ['3.10']
        include:
          - mypy-version: 'lockfile'
            python-version: '3.8'
          - mypy-version: 'lockfile'
            python-version: '3.9'
          - mypy-version: 'lockfile'
            python-version: '3.11'
          # TODO: Once Python 3.7 is removed, and mypy is bumped on the lockfile, we can add 'lockfile' again.
          - mypy-version: '1.5.1'
            python-version: '3.12'

    steps:
    - uses: actions/checkout@v4

    - uses: pdm-project/setup-pdm@v4
      with:
        python-version: ${{ matrix.python-version }}
        cache: true
        allow-python-prereleases: true

    - name: install deps
      run: |
        pdm venv create --with-pip --force $PYTHON
        pdm install -G testing -G mypy

    - name: install mypy
      if: steps.cache.outputs.cache-hit != 'true'
      run: |
        if [ ${{ matrix.mypy-version }} != 'lockfile' ]; then
          pdm remove -G linting mypy && pdm remove -G mypy mypy && pdm add mypy==${{ matrix.mypy-version }}
        fi
        pdm list

    - run: mkdir coverage

    - name: run mypy tests
      run: pdm run coverage run -m pytest tests/mypy --test-mypy
      env:
        COVERAGE_FILE: coverage/.coverage.linux-py${{ matrix.python-version }}-mypy${{ matrix.mypy-version }}
        CONTEXT: linux-py${{ matrix.python-version }}-mypy${{ matrix.mypy-version }}

    - name: install node for pyright
      uses: actions/setup-node@v4
      with:
        node-version: '18'

    - name: install pyright
      run: npm install -g pyright@1.1.367  # try to keep this in sync with .pre-commit-config.yaml

    - name: run pyright tests
      run: make test-pyright
      env:
        COVERAGE_FILE: coverage/.coverage.linux-py${{ matrix.python-version }}-pyright
        CONTEXT: linux-py${{ matrix.python-version }}-pyright

    - name: store coverage files
      uses: actions/upload-artifact@v4
      with:
        name: coverage-${{ matrix.os }}-${{ matrix.python-version }}-mypy${{ matrix.mypy-version }}
        path: coverage

  coverage-combine:
    needs: [test, test-mypy]
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: get coverage files
        uses: actions/download-artifact@v4
        with:
          merge-multiple: true
          pattern: coverage-*
          path: coverage

      - run: pip install coverage[toml]

      - run: ls -la coverage
      - run: coverage combine coverage
      - run: coverage report
      - run: coverage html --show-contexts --title "pydantic coverage for ${{ github.sha }}"

      - name: Store coverage html
        uses: actions/upload-artifact@v4
        with:
          name: coverage-html
          path: htmlcov

  test-typing-extensions:
    name: test typing-extensions ${{ matrix.typing-extensions-version }} on Python ${{ matrix.python-version }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # test the oldest supported version and main
        typing-extensions-version: ['4.6.1', 'main']
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

    steps:
    - uses: actions/checkout@v4

    - uses: pdm-project/setup-pdm@v4
      with:
        python-version: ${{ matrix.python-version }}
        cache: true
        allow-python-prereleases: true

    - name: install deps
      run: |
        pdm venv create --with-pip --force $PYTHON
        pdm install -G testing -G email

    - name: install typing-extensions
      run: |
        if [ ${{ matrix.typing-extensions-version }} == 'main' ]; then
          pdm remove typing-extensions && pdm add 'typing-extensions @ git+https://github.com/python/typing_extensions.git'
        else
          pdm remove typing-extensions && pdm add 'typing-extensions==${{ matrix.typing-extensions-version }}'
        fi
        pdm list
    - name: test
      run: make test

  # https://github.com/marketplace/actions/alls-green
  check:  # This job does nothing and is only used for the branch protection

    if: always()

    outputs:
      result: ${{ steps.all-green.outputs.result }}

    needs:
      - lint
      - docs-build
      - test
      - test-memray
      - test-mypy
      - test-fastapi
      - test-plugin

    runs-on: ubuntu-latest

    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        id: all-green
        with:
          jobs: ${{ toJSON(needs) }}

  release:
    needs: [check]
    if: needs.check.outputs.result == 'success' && startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    environment: release

    permissions:
      id-token: write

    outputs:
      pydantic-version: ${{ steps.check-tag.outputs.VERSION }}

    steps:
      - uses: actions/checkout@v4

      - name: set up python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: install
        run: pip install -U build

      - name: check version
        id: check-tag
        uses: samuelcolvin/check-python-version@v4.1
        with:
          version_file_path: pydantic/version.py

      - name: build
        run: python -m build

      - name: Upload package to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

  send-tweet:
    name: Send tweet
    needs: [release]
    if: needs.release.result == 'success'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: pip install tweepy==4.14.0
      - name: Send tweet
        shell: python
        run: |
          import os
          import tweepy

          client = tweepy.Client(
              access_token=os.getenv("TWITTER_ACCESS_TOKEN"),
              access_token_secret=os.getenv("TWITTER_ACCESS_TOKEN_SECRET"),
              consumer_key=os.getenv("TWITTER_CONSUMER_KEY"),
              consumer_secret=os.getenv("TWITTER_CONSUMER_SECRET"),
          )
          version = os.getenv("VERSION").strip('"')
          tweet = os.getenv("TWEET").format(version=version)
          client.create_tweet(text=tweet)
        env:
          VERSION: ${{ needs.release.outputs.pydantic-version }}
          TWEET: |
            Pydantic version {version} is out! 🎉

            https://github.com/pydantic/pydantic/releases/tag/v{version}
          TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }}
          TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }}
          TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
          TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}


codspeed

name: codspeed

on:
  push:
    branches:
      - main
  pull_request:
  # `workflow_dispatch` allows CodSpeed to trigger backtest
  # performance analysis in order to generate initial data.
  workflow_dispatch:

jobs:
  codspeed-profiling:
    name: codspeed profiling
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.12'
          cache: true

      - name: install deps
        run: |
          pdm venv create --with-pip --force $PYTHON
          pdm install -G testing -G testing-extra -G email

      - name: Run CodSpeed benchmarks
        uses: CodSpeedHQ/action@v2
        with:
          run: pdm run pytest ./tests/benchmarks --codspeed


Dependencies Check

name: Dependencies Check

on:
  schedule:
  - cron: '43 3 * * 6,3'
  workflow_dispatch: {}

jobs:
  find_dependency_cases:
    runs-on: ubuntu-latest

    outputs:
      PYTHON_DEPENDENCY_CASES: ${{ steps.list-python-dependencies.outputs.PYTHON_DEPENDENCY_CASES }}

    steps:
    - uses: actions/checkout@v4
    - uses: samuelcolvin/list-python-dependencies@main
      id: list-python-dependencies
      with:
        mode: first-last

  test:
    name: test py${{ matrix.python-version }} on ${{ matrix.PYTHON_DEPENDENCY_CASE }}

    needs:
    - find_dependency_cases

    strategy:
      fail-fast: true
      matrix:
        python-version: ['3.8', '3.11']
        PYTHON_DEPENDENCY_CASE: ${{ fromJSON(needs.find_dependency_cases.outputs.PYTHON_DEPENDENCY_CASES) }}

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

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

    - run: pip install pdm && pdm install --no-isolation

    - run: pdm run pip install ${{ matrix.PYTHON_DEPENDENCY_CASE }}

    - run: pdm run pip freeze

    - run: make test


Publish Documentation

name: Publish Documentation

on:
  push:
    branches:
      - main
      - docs-update
    tags:
      - '**'

env:
  COLUMNS: 150
  PDM_DEPS: 'urllib3<2'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.11'
          cache: true

      - name: install
        run: pdm install -G linting -G email

      - uses: pre-commit/action@v3.0.1
        with:
          extra_args: --all-files --verbose
        env:
          SKIP: no-commit-to-branch

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.11'
          cache: true

      - name: install deps
        run: pdm install -G testing -G email

      - run: pdm info && pdm list

      - run: 'pdm run python -c "import pydantic.version; print(pydantic.version.version_info())"'

      - run: make test

  publish:
    # Compare with the docs-build job in .github/workflows/ci.yml
    needs: [lint, test]
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: checkout docs-site
        uses: actions/checkout@v4
        with:
          ref: docs-site

      - name: checkout current branch
        uses: actions/checkout@v4

      - uses: pdm-project/setup-pdm@v4
        with:
          python-version: '3.10'

      - name: install
        run: |
          pdm venv create --with-pip --force $PYTHON
          pdm install -G docs
          pdm run python -m pip install https://files.scolvin.com/${MKDOCS_TOKEN}/mkdocs_material-9.5.17+insiders.4.53.5-py3-none-any.whl
        env:
          MKDOCS_TOKEN: ${{ secrets.MKDOCS_TOKEN }}

      - run: pdm run python -c 'import docs.plugins.main'

      # Adding local symlinks gets nice source locations like
      #   pydantic_core/core_schema.py
      # instead of
      #   .venv/lib/python3.10/site-packages/pydantic_core/core_schema.py
      - name: prepare shortcuts for extra modules
        run: |
          ln -s .venv/lib/python*/site-packages/pydantic_core pydantic_core
          ln -s .venv/lib/python*/site-packages/pydantic_settings pydantic_settings
          ln -s .venv/lib/python*/site-packages/pydantic_extra_types pydantic_extra_types

      - name: Set git credentials
        run: |
          git config --global user.name "${{ github.actor }}"
          git config --global user.email "${{ github.actor }}@users.noreply.github.com"

      - run: pdm run mike deploy -b docs-site dev --push
        if: "github.ref == 'refs/heads/main'"

      - if: "github.ref == 'refs/heads/docs-update' || startsWith(github.ref, 'refs/tags/')"
        id: check-version
        uses: samuelcolvin/check-python-version@v4.1
        with:
          version_file_path: 'pydantic/version.py'
          skip_env_check: true

      - run: pdm run mike deploy -b docs-site ${{ steps.check-version.outputs.VERSION_MAJOR_MINOR }} latest --update-aliases --push
        if: "(github.ref == 'refs/heads/docs-update' || startsWith(github.ref, 'refs/tags/')) && !fromJSON(steps.check-version.outputs.IS_PRERELEASE)"
        env:
          PYDANTIC_VERSION: v${{ steps.check-version.outputs.VERSION }}


Pydantic Family Integration Tests

name: Pydantic Family Integration Tests

on:
  schedule:
  - cron: '21 3 * * 1,2,3,4,5'
  workflow_dispatch: {}

jobs:
  test-pydantic-settings:
    name: test pydantic settings
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: test
      run: make test-pydantic-settings

  test-pydantic-extra-types:
    name: test pydantic extra types
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: test
      run: make test-pydantic-extra-types


release notes

name: release notes

on:
  pull_request_target:
    types: [opened]

jobs:
  auto-labeler:
    name: auto-labeler
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - id: label-PR-by-branch-name
        uses: actions/labeler@v5
        with:
          configuration-path: '.github/labels/first_pass.yml'

      - id: add-default-if-no-labels
        if: ${{ !contains(steps.label-PR-by-branch-name.outputs.all-labels, 'relnotes') || !contains(steps.label-PR-by-branch-name.outputs.all-labels, 'documentation') }}
        uses: actions/labeler@v5
        with:
          configuration-path: '.github/labels/default_pass.yml'


Pydantic People Update

name: Pydantic People Update

on:
  schedule:
    - cron: "0 12 1 * *"
  workflow_dispatch: {}

jobs:
  pydantic-people:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/people
        with:
          token: ${{ secrets.GITHUB_TOKEN }}


Upload Previews

name: Upload Previews

on:
  workflow_run:
    workflows: [CI]
    types: [completed]

permissions:
  statuses: write

jobs:
  upload-previews:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - run: pip install smokeshow

      - uses: dawidd6/action-download-artifact@v3
        with:
          workflow: ci.yml
          commit: ${{ github.event.workflow_run.head_sha }}

      - run: smokeshow upload coverage-html
        env:
          SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
          # 5 is set here while V2 is in development and coverage is far from complete
          SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 91
          SMOKESHOW_GITHUB_CONTEXT: coverage
          SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
          SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}


Tests

name: Tests

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

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.11]

    steps:
      - uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install --upgrade pytest
          python -m pip install flake8 pytest pytest-cov
          python -m pip install codecov
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          if [ -f test_requirements.txt ]; then pip install -r test_requirements.txt; fi
          python -m pip install .
      #- 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 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
      - name: Test with pytest
        run: |
          pytest --cov=./ --cov-report=xml
          codecov

      - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@v3
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        with:
          files: ./coverage.xml

#      - name: Coveralls
#        env:
#          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
#        run: |
#          coveralls --service=github

      # from this source for coveralls: https://github.com/marketplace/actions/coveralls-github-action
      #- uses: actions/checkout@v1

      #- name: Use Node.js 10.x
      #  uses: actions/setup-node@v1
      #  with:
      #    node-version: 10.x

      #- name: npm install, make test-coverage
      #  run: |
      #    npm install
      #    make test-coverage

      #- name: Coveralls
      #  uses: coverallsapp/github-action@master
      #  with:
      #    github-token: ${{ secrets.GITHUB_TOKEN }}


Bandit

name: Bandit

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

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: 3.9
      - name: Install
        run: |
          python -m pip install --upgrade pip
          python -m pip install bandit
      - name: Bandit
        run: bandit -r -lll -ii .


CFN Nag

name: CFN Nag

on:
  workflow_dispatch:
  push:
    paths:
    - "test_infra/**"
    branches:
    - main
  pull_request:
    paths:
    - "test_infra/**"
    branches:
    - main

permissions:
  contents: read

env:
  CDK_DEFAULT_ACCOUNT: 111111111111
  CDK_DEFAULT_REGION: us-east-1

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 16
      - name: Cache Node.js modules
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.OS }}-node-
            ${{ runner.OS }}-
      - name: Install CDK
        run: |
          npm install -g aws-cdk
          cdk --version
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: 3.11
      - name: Install Requirements
        run: |
          cd test_infra
          python -m pip install --upgrade pip
          python -m pip install poetry
          poetry env use python
          poetry env info
          source $(poetry env info --path)/bin/activate
          poetry install -vvv
      - name: Set up cdk.json
        run: |
          cd test_infra
          cat <<EOT >> cdk.context.json
          {
            "availability-zones:account=111111111111:region=us-east-1": [
              "us-east-1a",
              "us-east-1b",
              "us-east-1c",
              "us-east-1d",
              "us-east-1e",
              "us-east-1f"
            ]
          }
          EOT
          cat cdk.json | jq -r '.context.databases.neptune = true' | jq -r '.context.databases.oracle = true' | jq -r '.context.databases.sqlserver = true' > overwrite.cdk.json
          rm cdk.json && mv overwrite.cdk.json cdk.json
      - name: CDK Synth
        run: |
          cd test_infra
          source $(poetry env info --path)/bin/activate
          cdk synth
      - uses: stelligent/cfn_nag@master
        with:
          input_path: test_infra/cdk.out
          extra_args: --ignore-fatal


Check Tests for Unspecific XFails

name: Check Tests for Unspecific XFails

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

permissions:
  contents: read

jobs:
  Check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: check xfails
        run: if grep -ro "@pytest.mark.xfail()" tests/; then echo "xfails must catch a specific error, e.g. '@pytest.mark.xfail(raises=NotImplementedError)'" && exit 1; else echo "success" && exit 0; fi


Dependabot Pull Request Metadata

name: Dependabot Pull Request Metadata
on: pull_request_target
jobs:
  build:
    permissions:
      pull-requests: read
    runs-on: ubuntu-latest
    if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}
    steps:
    - name: Fetch Dependabot metadata
      id: dependabot-metadata
      uses: dependabot/fetch-metadata@v2
      with:
        alert-lookup: true
        compat-lookup: true
        github-token: ${{ secrets.GITHUB_TOKEN }}
    - name: Add a label for all PRs with an alert state
      if: ${{ steps.dependabot-metadata.outputs.alert-state != '' }}
      run: gh pr edit "$PR_URL" --add-label "vulnerability"
      env:
        PR_URL: ${{github.event.pull_request.html_url}}
        GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}


Close Stale Issues

name: "Close Stale Issues"

on:
  workflow_dispatch:
  schedule:
  - cron: "0 */3 * * *"

jobs:
  cleanup:
    permissions:
      issues: write
      pull-requests: write
    runs-on: ubuntu-latest
    name: Stale issue job
    steps:
    - uses: actions/stale@v9
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        days-before-stale: 60
        days-before-close: 7
        exempt-issue-labels: 'needs-triage,help wanted,backlog'
        exempt-pr-labels: 'needs-triage'
        stale-issue-label: 'closing-soon'
        operations-per-run: 100
        enable-statistics: true
        stale-issue-message: |
          Marking this issue as stale due to inactivity. This helps our maintainers find and focus on the active issues. If this issue receives no comments in the next 7 days it will automatically be closed.
        stale-pr-label: 'closing-soon'
        stale-pr-message: |
          Marking this pull request as stale due to inactivity. This helps our maintainers find and focus on the active pull requests.
        debug-only: false
        ascending: true
        exempt-all-milestones: true
        exempt-all-assignees: true


Minimal Tests

name: Minimal Tests

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

permissions:
  contents: read

jobs:
  Check:

    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.8", "3.11", "3.12"]
        platform: [ubuntu-latest, macos-latest, windows-latest]

    env:
      AWS_DEFAULT_REGION: us-east-1

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

    steps:
      - uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Upgrade Pip
        run: python -m pip install --upgrade pip
      - name: Install Poetry
        run: python -m pip install poetry
      - name: Install Requirements
        run: |
          poetry config virtualenvs.in-project true
          poetry config virtualenvs.path .venv
          poetry install -vvv
      - name: Test Metadata
        run: poetry run pytest tests/unit/test_metadata.py
      - name: Test Session
        run: poetry run pytest tests/unit/test_session.py
      - name: Test Utils
        run: poetry run pytest tests/unit/test_utils.py
      - name: Test Moto
        run: poetry run pytest -n 4 tests/unit/test_moto.py


Issue Minimum Response Time

name: Issue Minimum Response Time
on:
  workflow_dispatch:
  schedule:
  - cron: "0 */3 * * *"
jobs:
  evaluate:
    runs-on: ubuntu-latest
    permissions:
      issues: write
    steps:
      - name: Issue Minimum Response ⏰
        uses: malachi-constant/issue-minimum-response@latest
        with:
          exempt_user_list: "github-actions[bot]"
          exempt_labels: "help wanted"
          exempt_authors: "malachi-constant,jaidisido,kukushking,LeonLuttenberger,cnfait,dependabot[bot]"
          token: ${{secrets.GITHUB_TOKEN}}
          label: needs-triage


Check PR title

name: Check PR title

on:
  pull_request_target:
    types:
      - opened
      - reopened
      - edited
      - synchronize

jobs:
  lint:
    runs-on: ubuntu-latest
    permissions:
      statuses: write
    steps:
      - uses: aslafy-z/conventional-pr-title-action@v3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


Snyk

name: Snyk

on:
  workflow_dispatch:

  schedule:
    - cron: "0 9 * * 1"  # runs each Monday at 9:00 UTC

permissions:
  contents: read
  security-events: write

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Snyk to check for vulnerabilities
        uses: snyk/actions/python-3.8@master
        continue-on-error: true  # To make sure that SARIF upload gets called
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --sarif-file-output=snyk.sarif
      - name: Upload result to GitHub Code Scanning
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: snyk.sarif


Static Checking

name: Static Checking

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

permissions:
  contents: read

jobs:
  Check:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.9]

    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 Requirements
        run: |
          python -m pip install --upgrade pip
          python -m pip install poetry
          poetry config virtualenvs.create false --local
          poetry install --all-extras -vvv
      - name: ruff format check
        run: ruff format --check .
      - name: ruff check
        run: ruff check --output-format=github .
      - name: mypy check
        run: mypy --install-types --non-interactive awswrangler
      - name: Documentation check
        run: doc8 --max-line-length 120 docs/source
      - name: Check poetry.lock consistency with pyproject.toml
        run: poetry check --lock


Unlabel Assigned Issues

name: Unlabel Assigned Issues
on:
  issues:
    types:
      - assigned
permissions:
  contents: read

jobs:
  unlabel-issue:
    permissions:
      issues: write  # for andymckay/labeler to label issues
      pull-requests: write  # for andymckay/labeler to label PRs
    runs-on: ubuntu-latest
    steps:
      - name: unlabel-issues
        uses: andymckay/labeler@master
        with:
          remove-labels: "needs-triage"


Auto Assign PRs once opened

name: Auto Assign PRs once opened
on:
  pull_request:
    types: [opened]
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: bubkoo/auto-assign@v1
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CONFIG_FILE: .github/auto-assign.yml


coverage:
  status:
    project:
      default:
        # basic
        target: auto
        threshold: 1%
        base: auto
        flags:
          - unit
        paths:
          - "rocketpy"
        # advanced settings
        branches:
          - master
          - develop
        if_ci_failed: error #success, failure, error, ignore
        informational: false
        only_pulls: false
    patch:
      default:
        # basic
        target: auto
        threshold: 1%
        base: auto
        # advanced
        branches:
          - master
          - develop
        if_ci_failed: error #success, failure, error, ignore
        only_pulls: false
        flags:
          - "unit"
        paths:
          - "rocketpy"


LintBlack

name: LintBlack

# Adapted: https://github.com/marketplace/actions/lint-action

on: [pull_request]

jobs:
  run-linters:
    name: Run linters
    runs-on: ubuntu-latest

    steps:
      - name: Check out Git repository
        uses: actions/checkout@v2

      - name: Set up Python
        uses: actions/setup-python@v1
        with:
          python-version: 3.8

      - name: Install Python dependencies
        run: pip install black[jupyter]

      - name: Run linters
        uses: wearerequired/lint-action@v1
        with:
          black: true
          auto_fix: true


Publish New Version to PyPI

# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/

name: Publish New Version to PyPI

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v3
        with:
          python-version: "3.8"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build
      - name: Build package
        run: python -m build
      - name: Publish package
        uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}


Tests

name: Tests

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    paths:
      - "**.py"
      - ".github/**"
      - "pyproject.toml"
      - "requirements*"

defaults:
  run:
    shell: bash

jobs:
  Pytest:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        python-version: [3.8, 3.12]
        include:
          - os: macos-latest
            python-version: 3.12
    env:
      OS: ${{ matrix.os }}
      PYTHON: ${{ matrix.python-version }}
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      - name: Cache Python dependencies
        uses: actions/cache@v2
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-tests.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

      - name: Install rocketpy
        run: pip install .

      - name: Test importing rocketpy
        run: python -c "import sys, rocketpy; print(f'{rocketpy.__name__} running on Python {sys.version}')"

      - name: Install test dependencies
        run: pip install -r requirements-tests.txt

      - name: Run Unit Tests
        run: pytest tests/unit --cov=rocketpy

      - name: Run Integration Tests
        run: pytest $(find tests -maxdepth 1 -name "*.py") --cov=rocketpy --cov-append

      - name: Run Documentation Tests
        run: pytest rocketpy --doctest-modules --cov=rocketpy --cov-append

      - name: Run Acceptance Tests
        run: pytest tests/acceptance --cov=rocketpy --cov-append --cov-report=xml

      - name: Upload coverage to artifacts
        uses: actions/upload-artifact@v2
        with:
          name: coverage
          path: coverage.xml

  CodecovUpload:
    needs: Pytest
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Download all coverage reports
        uses: actions/download-artifact@v2
      - name: Upload to Codecov
        uses: codecov/codecov-action@v2
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: |
            coverage.xml


Upload to Codecov

name: Upload to Codecov

on:
  workflow_call:
    inputs:
      codecov_token:
        required: true
        type: string
      os:
        required: true
        type: string
      python:
        required: true
        type: string

jobs:
  upload:
    runs-on: ubuntu-latest
    steps:
      - name: Upload coverage report to Codecov
        uses: codecov/codecov-action@v3
        with:
          token: ${{ inputs.codecov_token }}
          directory: ./coverage/reports/
          env_vars: OS,PYTHON
          files: ./coverage.xml, ./rocketpy/coverage.xml
          flags: unittests
          name: codecov-umbrella
          verbose: true


build

name: build

on:
  push:
    branches: [ main ]
    paths-ignore: [ '**.svg', '**.md' ]
  pull_request:
    branches: [ main ]

permissions:
  contents: read

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.12'

    - name: Run Python unit tests
      run: |
        python3 -u -m unittest tests/tests.py
      env:
        GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

    - name: Verify that the Docker image for the action builds
      run: docker build . --file Dockerfile

    - name: Integration test
      id: integration
      uses: ./
      with:
        colors: dark
        commit-and-push: false
        featured-repository: Chips-n-Salsa
        animated-language-chart: true
        locale: en
        #fail-on-error: false
        #category-order: general, repositories, languages, contributions
      env:
        GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

    - name: Output the outputs of the integration test of the action
      run: |
        echo "exit-code = ${{ steps.integration.outputs.exit-code }}"

    - name: Upload generated SVG as a workflow artifact for inspection if necessary
      uses: actions/upload-artifact@v4
      with:
        name: generated-image
        path: images/userstats.svg


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 ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ main ]
  schedule:
    - cron: '34 20 * * 1'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'python' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
        # Learn more:
        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed

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

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # 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@v3

    # ℹ 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@v3


international samples

name: international samples

on:
  workflow_dispatch:

jobs:
  samples:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
      with:
        ref: samples

    - name: Sample German
      uses: cicirello/user-statistician@v1
      with:
        colors: light
        locale: de
        image-file: images/de.svg
      env:
        GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

    - name: Sample Italian
      uses: cicirello/user-statistician@v1
      with:
        colors: dark
        locale: it
        image-file: images/it.svg
      env:
        GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}