Build Release

name: Build Release

on:
  # Manual Trigger GH CLI -> gh workflow run release.yml --ref <tag>
  workflow_dispatch:

jobs:
  run-python-tests:
    uses: ./.github/workflows/python-tests.yml
    with:
      ref: ${{ github.ref_name }}
    secrets:
      PARAMETER_PASSWORD: ${{ secrets.PARAMETER_PASSWORD }}
    permissions:
      # Permission needed to upload constraints file
      contents: write

  run-javascript-tests:
    uses: ./.github/workflows/js-tests.yml
    with:
      ref: ${{ github.ref_name }}

  run-cypress-tests:
    uses: ./.github/workflows/cypress.yml
    with:
      ref: ${{ github.ref_name }}

  run-playwright-tests:
    uses: ./.github/workflows/playwright.yml
    with:
      ref: ${{ github.ref_name }}

  build-release:
    runs-on: ubuntu-latest

    needs:
      - run-python-tests
      - run-javascript-tests
      - run-cypress-tests
      - run-playwright-tests

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    permissions:
      # Additional permission needed to generate release
      contents: write
      id-token: write

    env:
      # Name of the tag
      GIT_TAG: ${{ github.ref_name }}
      # The owner and repository name
      GH_REPO: ${{ github.repository }}
      # A branch with constraints files on the basis of which
      # the tag will be created.
      CONSTRAINTS_BRANCH: constraints-release
      # The name of the tag that contains the constraints files
      # for the current release
      CONSTRAINTS_TAG: constraints/${{ github.ref_name }}
      # We don't want to use constraints files because they might not exist yet.
      USE_CONSTRAINTS_FILE: "false"

    environment: release

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.ref_name }}
          # Save the access token to the local git config, so
          # later git commands can work.
          persist-credentials: true
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Look up the related GitHub PR branch name
        # Need GH Token to use gh api
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          pip install requests
          echo "GH_PR_BRANCH=$(python scripts/get_release_branch.py)" >> $GITHUB_ENV
      - name: Ensure release branch corresponding to this tag exists
        env:
          GH_PR_BRANCH: ${{ env.GH_PR_BRANCH }}
        run: |
          BRANCH_VERSION=$(echo "$GH_PR_BRANCH" | sed 's/release\///' )
          if [ "$BRANCH_VERSION" != "$GIT_TAG" ]
          then
            echo "ERROR: release branch corresponding to this tag does not exist."
            exit 1
          fi
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Set release version from tag name
        env:
          GIT_TAG: ${{ env.GIT_TAG }}
        run: echo "STREAMLIT_RELEASE_VERSION=$GIT_TAG" >> $GITHUB_ENV
      - name: Create Package
        timeout-minutes: 120
        run: |
          sudo apt install rsync
          make package
      - name: Store Package
        uses: actions/upload-artifact@v4
        with:
          name: Release
          path: lib/dist
      - name: Upload to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: lib/dist/
      - name: Create GitHub Release
        env:
          GIT_TAG: ${{ env.GIT_TAG }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          python scripts/create_release.py

      - name: Checkout branch "${{ env.CONSTRAINTS_BRANCH }}"
        uses: actions/checkout@v4
        with:
          ref: ${{ env.CONSTRAINTS_BRANCH }}
          # Save the access token to the local git config, so
          # later git commands can work.
          persist-credentials: true
          path: constraints
      - uses: actions/download-artifact@v4
        with:
          path: constraints
          pattern: constraints-*
          merge-multiple: true
      - name: Commit and push constraint files as tag
        run: |
          cd constraints;
          git add .
          git config --local user.email "core+streamlitbot-github@streamlit.io"
          git config --local user.name "Streamlit Bot"
          if ! git diff --cached --color --exit-code --ignore-matching-lines="^#.*"
          then
            git commit --all --message "Updating constraints. Github run id:${GITHUB_RUN_ID}

            This update in constraints is automatically committed by the CI based on
            '${GITHUB_REF}' in the '${GITHUB_REPOSITORY}' repository with commit sha ${GITHUB_SHA}.

            The action that build those constraints can be found at https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/
            "
          else
            echo "No changes"
          fi
          git tag "${CONSTRAINTS_TAG}"
          git push origin "${CONSTRAINTS_TAG}";
          git push origin "${CONSTRAINTS_BRANCH}";

      - name: Successful Release Slack Message
        if: ${{ success() }}
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          RUN_ID: ${{ github.run_id }}
        run: python scripts/slack_notifications.py release success
      - name: Failed Release Slack Message
        if: ${{ failure() }}
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          RUN_ID: ${{ github.run_id }}
        run: python scripts/slack_notifications.py release failure


Nightly Build

name: Nightly Build

on:
  schedule:
    # Run job at 10.30pm PST or 11.30pm PDT
    - cron: "30 6 * * *"

jobs:
  create-nightly-tag:
    runs-on: ubuntu-latest

    if: github.repository == 'streamlit/streamlit'

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    permissions:
      # Additional permission needed to generate tag
      contents: write

    outputs:
      TAG: ${{ steps.create_tag.outputs.tag }}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          submodules: "recursive"
          # Save the access token to the local git config, so
          # later git commands can work.
          persist-credentials: true
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Run make develop
        run: make develop
      - name: Create tag
        id: create_tag
        run: |
          git config --global user.email "core+streamlitbot-github@streamlit.io"
          git config --global user.name "Streamlit Bot"

          TAG="$(./scripts/pypi_nightly_create_tag.py)"
          echo "tag=$TAG" >> $GITHUB_OUTPUT

          ./scripts/update_version.py $TAG
          ./scripts/update_name.py streamlit-nightly

          git add lib/setup.py frontend/package.json lib/streamlit/__init__.py lib/streamlit/version.py

          git commit -m "Update version and project name in files"

          git tag -a $TAG -m "Streamlit nightly $TAG"
          git push origin $TAG
      - if: ${{ failure() }}
        name: Nightly Tag Failure Slack Message
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          RUN_ID: ${{ github.run_id }}
        run: python scripts/slack_notifications.py nightly tag

  run-python-tests:
    needs: create-nightly-tag
    permissions:
      # Pass additional permission needed to upload constraints
      contents: write
    uses: ./.github/workflows/python-tests.yml
    with:
      ref: ${{needs.create-nightly-tag.outputs.tag}}
    secrets:
      PARAMETER_PASSWORD: ${{ secrets.PARAMETER_PASSWORD }}

  run-javascript-tests:
    needs: create-nightly-tag
    uses: ./.github/workflows/js-tests.yml
    with:
      ref: ${{needs.create-nightly-tag.outputs.tag}}

  run-cypress-tests:
    needs: create-nightly-tag
    uses: ./.github/workflows/cypress.yml
    with:
      ref: ${{needs.create-nightly-tag.outputs.tag}}

  run-playwright-tests:
    uses: ./.github/workflows/playwright.yml
    with:
      ref: ${{needs.create-nightly-tag.outputs.tag}}

  test-status-notification:
    runs-on: ubuntu-latest

    if: ${{ always() }}
    # By default, jobs listed in needs must all complete successfully for the dependent job to run. always() conditional
    # added as we'd like this job to run whether or not tests pass & slack us regarding failing tests.
    needs:
      - create-nightly-tag
      - run-python-tests
      - run-javascript-tests
      - run-cypress-tests
      - run-playwright-tests

    env:
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
      RUN_ID: ${{ github.run_id }}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ needs.create-nightly-tag.outputs.tag }}
          persist-credentials: false
          submodules: "recursive"
      - if: ${{ needs.run-python-tests.result == 'failure' }}
        run: python scripts/slack_notifications.py nightly python
      - if: ${{ needs.run-javascript-tests.result == 'failure' }}
        run: python scripts/slack_notifications.py nightly js
      - if: ${{ needs.run-cypress-tests.result == 'failure' }}
        run: python scripts/slack_notifications.py nightly cypress
      - if: ${{ needs.run-playwright-tests.result == 'failure' }}
        run: python scripts/slack_notifications.py nightly playwright

  create-nightly-build:
    runs-on: ubuntu-latest

    # Tag creation & tests must all complete successfully for nightly build job to run.
    needs:
      - create-nightly-tag
      - run-python-tests
      - run-javascript-tests
      - run-cypress-tests
      - run-playwright-tests
    permissions:
      id-token: write

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    outputs:
      enable-setup: ${{ steps.exports.outputs.enable-setup }}
      s3-url: ${{ steps.exports.outputs.s3-url }}

    environment: nightly

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ needs.create-nightly-tag.outputs.tag }}
          # Save the access token to the local git config, so
          # later git commands can work.
          persist-credentials: true
          submodules: "recursive"
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Run make develop
        run: make develop
      - name: Verify git tag vs. version
        env:
          TAG: ${{ needs.create-nightly-tag.outputs.tag }}
        run: |
          cd lib
          python setup.py verify
      - name: Build Package
        timeout-minutes: 120
        run: |
          sudo apt update
          sudo apt install rsync
          make package
      - name: Store Whl File
        uses: actions/upload-artifact@v4
        with:
          name: whl_file
          path: lib/dist/*.whl
      - name: Upload wheel to S3
        id: exports
        env:
          AWS_DEFAULT_REGION: us-west-2
          AWS_ACCESS_KEY_ID: ${{ secrets.CORE_PREVIEWS_S3_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.CORE_PREVIEWS_S3_SECRET_KEY }}
        run: |
          sudo apt update
          sudo apt install -y awscli

          cd lib/dist
          export WHEELFILE="$(ls -t *.whl | head -n 1)"

          aws s3 cp "${WHEELFILE}" s3://core-previews/nightly-preview/ --acl public-read
          S3_URL="https://core-previews.s3-us-west-2.amazonaws.com/nightly-preview/${WHEELFILE}"

          echo -e "Wheel file download link: ${S3_URL}"

          cd ../..
          # env variables don't carry over between gh action jobs
          echo "enable-setup=${{ env.AWS_ACCESS_KEY_ID != '' }}" >> $GITHUB_OUTPUT
          echo "s3-url=${S3_URL}" >> $GITHUB_OUTPUT
      - name: Upload to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: lib/dist/
      - if: ${{ failure() }}
        name: Nightly Build Failure Slack Message
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          RUN_ID: ${{ github.run_id }}
        run: python scripts/slack_notifications.py nightly build

  setup-nightly-preview:
    runs-on: ubuntu-latest

    needs: [create-nightly-tag, create-nightly-build]
    if: needs.create-nightly-build.outputs.enable-setup == 'true'

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Core Previews Repo
        uses: actions/checkout@v4
        with:
          repository: streamlit/core-previews
          # The default GITHUB_TOKEN is scoped only to the triggering streamlit/streamlit repo.
          # Accessing streamlit/core-previews repo requires a separate auth token.
          token: ${{ secrets.CORE_PREVIEWS_REPO_TOKEN }}
          # Save the access token to the local git config, so
          # later git commands can work.
          persist-credentials: true
      - name: Setup preview repo
        env:
          NIGHTLY_TAG: ${{ needs.create-nightly-tag.outputs.tag }}
          S3_URL: ${{ needs.create-nightly-build.outputs.s3-url }}
        run: |
          git config --global user.email "core+streamlitbot-github@streamlit.io"
          git config --global user.name "Streamlit Bot"
          git branch -D nightly-preview &>/dev/null || true
          git checkout -b nightly-preview

          echo "$S3_URL" >> requirements.txt

          git add .
          git commit -m "Nightly Preview: ${NIGHTLY_TAG}"
          git push -f origin nightly-preview


CodeQL

name: "CodeQL"

on:
  push:
    branches: ["develop"]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: ["develop"]
  schedule:
    - cron: "22 19 * * 6"

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

    strategy:
      fail-fast: false
      matrix:
        language: ["javascript", "python"]

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          config-file: ./.github/codeql-config.yml
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
        with:
          category: "/language:${{matrix.language}}"


Snowpark Conda Build Test

name: Snowpark Conda Build Test

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-snowpark-conda-package:
    runs-on: ubuntu-latest

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set build config env vars
        uses: ./.github/actions/build_info
      - name: Set up Python 3.12
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Install conda and conda-build
        env:
          MINICONDA_RELEASE: "Miniconda3-py312_24.1.2-0-Linux-x86_64"
        run: |
          curl -sO "https://repo.anaconda.com/miniconda/${MINICONDA_RELEASE}.sh"
          bash "${MINICONDA_RELEASE}.sh" -b
          conda install conda-build
      - name: Build Snowpark Conda Package - Fast
        timeout-minutes: 120
        run: |
          sudo apt install rsync
          conda config --set conda_build.pkg_format 2
          SNOWPARK_CONDA_BUILD=1 BUILD_AS_FAST_AS_POSSIBLE=1 make conda-package


Pull Request Labels

name: Pull Request Labels

on:
  pull_request:
    types: [opened, labeled, unlabeled, synchronize]

jobs:
  security-assessment:
    runs-on: ubuntu-latest

    permissions:
      pull-requests: read

    steps:
      # v2=7b0461786d3bd0c6a8487e9b57814ba3e2c00227
      - uses: mheap/github-action-required-labels@5847eef68201219cf0a4643ea7be61e77837bbce
        with:
          mode: exactly
          count: 1
          labels: "security-assessment-completed"
  do-not-merge-guardrail:
    runs-on: ubuntu-latest

    permissions:
      pull-requests: read

    steps:
      # v2=7b0461786d3bd0c6a8487e9b57814ba3e2c00227
      - uses: mheap/github-action-required-labels@5847eef68201219cf0a4643ea7be61e77837bbce
        with:
          mode: exactly
          count: 0
          labels: "do-not-merge"
  change-description:
    runs-on: ubuntu-latest

    permissions:
      pull-requests: read

    steps:
      # v2=7b0461786d3bd0c6a8487e9b57814ba3e2c00227
      - uses: mheap/github-action-required-labels@5847eef68201219cf0a4643ea7be61e77837bbce
        with:
          mode: exactly
          count: 1
          labels: "change:feature,change:bugfix,change:refactor,change:chore,change:docs,change:other"
  impact-defined:
    runs-on: ubuntu-latest

    permissions:
      pull-requests: read

    steps:
      # v2=7b0461786d3bd0c6a8487e9b57814ba3e2c00227
      - uses: mheap/github-action-required-labels@5847eef68201219cf0a4643ea7be61e77837bbce
        with:
          mode: exactly
          count: 1
          labels: "impact:users,impact:internal"


Run Semgrep Checks

---
name: Run Semgrep Checks

on:
  pull_request:
    branches: [develop]

permissions:
  contents: read

jobs:
  run-semgrep-reusable-workflow:
    uses: snowflakedb/reusable-workflows/.github/workflows/semgrep-v2.yml@main
    secrets:
      token: ${{ secrets.SEMGREP_APP_TOKEN }}


Cypress Update Snapshots

name: Cypress Update Snapshots

on:
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:
    inputs:
      testname:
        description: "Enter the test name to run (see e2e/specs/[testname].spec.js). For example: st_alert"
        default: "st_alert"
        type: "string"
        required: true

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.testname }}
  cancel-in-progress: true

jobs:
  cypress-update-snapshots:
    runs-on: ubuntu-latest

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.ref_name }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Install Cypress dependencies
        run: |
          sudo apt install -y xvfb libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 curl
      - name: Run make develop
        run: make develop
      - name: Register Streamlit User & Mapbox Token
        run: |
          if [ ! -d $HOME/.streamlit ] ; then
            mkdir $HOME/.streamlit
          fi
          echo '[mapbox]' >  ~/.streamlit/config.toml
          MAPBOX_TOKEN=$(curl -sS https://data.streamlit.io/tokens.json | jq -r '.["mapbox-localhost"]') \
            && echo 'token = "'$MAPBOX_TOKEN'"' >> ~/.streamlit/config.toml

          echo '[general]' >  ~/.streamlit/credentials.toml
          echo 'email = "test@streamlit.io"' >> ~/.streamlit/credentials.toml
      - name: Run Cypress test
        run: |
          scripts/run_e2e_tests.py -u "e2e/specs/${{ inputs.testname }}.spec.js"
      - name: Check that all screenshots have been committed
        run: |
          set -x;

          UNTRACKED_FILE_COUNT=$(git ls-files --others --exclude-standard | grep cypress | wc -l | bc -l || echo '0')
          echo "Untracked files count: ${UNTRACKED_FILE_COUNT}"
          if [[ "${UNTRACKED_FILE_COUNT}" -gt 0 ]]; then
            echo "Untracked files:";
            git ls-files --others --exclude-standard | grep cypress;
            exit 1;
          fi
      - name: Store Snapshots
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: cypress_snapshots
          path: frontend/cypress/snapshots/linux/2x/${{ inputs.testname }}.spec.js
      - name: Store Videos
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: cypress_videos
          path: frontend/cypress/videos


Python Minimum Dependency Versions

name: Python Minimum Dependency Versions

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]
  # Allows workflow to be called from other workflows
  workflow_call:
    inputs:
      ref:
        required: true
        type: string

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-python-min
  cancel-in-progress: true

defaults:
  run:
    shell: bash --login -eo pipefail {0}

env:
  FORCE_COLOR: "1"

jobs:
  py-min-deps-test:
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false

    env:
      PYTHON_VERSION: "${{needs.build_info.outputs.PYTHON_MIN_VERSION}}"

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MIN_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MIN_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
        with:
          use_cached_venv: false
      - name: Install min dependencies (force reinstall)
        run: uv pip install -r lib/min-constraints-gen.txt --force-reinstall
      - name: Generate Protobufs
        run: make protobuf
      - name: Make local modules visible
        run: uv pip install --editable ./lib --no-deps
      - name: Run Python Tests
        run: make pytest
      - name: CLI Smoke Tests
        run: make cli-smoke-tests
      - name: Validate min-constraints-gen
        run: |
          make gen-min-dep-constraints

          git_status=$(git status --porcelain -- lib/min-constraints-gen.txt)
          if [[ -n $git_status ]]; then
            echo "::error::The min constraints file is out of date! Please run \`make gen-min-dep-constraints\` and commit the result."
            echo "::group::git diff lib/min-constraints-gen.txt"
            git diff lib/min-constraints-gen.txt
            echo "::endgroup::"
            exit 1
          else
            echo "min constraints file is up to date."
          fi


Playwright E2E Tests

name: Playwright E2E Tests

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]
  # Allows workflow to be called from other workflows
  workflow_call:
    inputs:
      ref:
        required: true
        type: string

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-playwright
  cancel-in-progress: true

jobs:
  playwright-e2e-tests:
    runs-on: ubuntu-latest-32-cores

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Install playwright
        run: python -m playwright install --with-deps
      - name: Run make frontend-fast
        run: make frontend-fast
      - name: Run make playwright
        run: make playwright
      - name: Upload failed test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright_test_results
          path: e2e_playwright/test-results


Ensure Relative Imports Exist in Prod @streamlit/lib


Community Voting

# This workflow automatically comments on issues labeled with 'type:enhancement' or 'type:bug'
# and adds a thumbs-up reaction to the issue to encourage community voting.

name: Community Voting
on:
  issues:
    types:
      - labeled
jobs:
  add-enhancement-comment:
    if: github.event.label.name == 'type:enhancement'
    runs-on: ubuntu-latest
    permissions:
      issues: write
    steps:
      - name: Add comment to issue
        run: gh issue comment ${{ github.event.issue.html_url }} --body "$ISSUE_BODY"
        env:
          GH_TOKEN: ${{ github.token }}
          ISSUE_BODY: |
            **To help Streamlit prioritize this feature, react with a 👍 (thumbs up emoji) to the initial post.**

            Your vote helps us identify which enhancements matter most to our users.

            ![Visits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fstreamlit%2Fstreamlit%2Fissues%2F${{ github.event.issue.number }}&title=visits&edge_flat=false)
      - name: Upvote issue
        uses: aidan-mundy/react-to-issue@b2a9194cf1ea483d633bc2834ed6c4c41c2a45f0
        with:
          issue-number: ${{ github.event.issue.number }}
          reactions: "+1"
  add-bug-comment:
    if: github.event.label.name == 'type:bug'
    runs-on: ubuntu-latest
    permissions:
      issues: write
    steps:
      - name: Add comment to issue
        run: gh issue comment ${{ github.event.issue.html_url }} --body "$ISSUE_BODY"
        env:
          GH_TOKEN: ${{ github.token }}
          ISSUE_BODY: |
            **If this issue affects you, please react with a 👍 (thumbs up emoji) to the initial post.**

            Your feedback helps us prioritize which bugs to investigate and address first.

            ![Visits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fstreamlit%2Fstreamlit%2Fissues%2F${{ github.event.issue.number }}&title=visits&edge_flat=false)
      - name: Upvote issue
        uses: aidan-mundy/react-to-issue@b2a9194cf1ea483d633bc2834ed6c4c41c2a45f0
        with:
          issue-number: ${{ github.event.issue.number }}
          reactions: "+1"


Component-template Repo E2E Tests

name: Component-template Repo E2E Tests

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  component-template-e2e-tests:
    runs-on: ubuntu-latest-8-cores
    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "19.x"
          cache: "yarn"
          cache-dependency-path: "component-lib/yarn.lock"

      - name: Set Python version vars
        uses: ./.github/actions/build_info

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

      - name: Setup virtual env
        uses: ./.github/actions/make_init

      - name: Run make develop
        run: make develop

      - name: Build Package
        timeout-minutes: 120
        run: BUILD_AS_FAST_AS_POSSIBLE=1 make package

      - name: Return path to Streamlit wheel
        id: streamlit_wheel
        shell: bash
        run: |
          streamlit_wheel=$(find "./lib/dist" -maxdepth 1 -name '*.whl')
          streamlit_wheel=$(readlink -e "${streamlit_wheel}")
          echo "output_file=${streamlit_wheel}" >> $GITHUB_OUTPUT

      - name: Install node dependencies for streamlit-component-lib
        working-directory: component-lib
        shell: bash
        run: yarn install

      - name: Build streamlit-component-lib package
        working-directory: component-lib
        shell: bash
        run: yarn run build && npm pack

      - name: Return path to Component Library
        working-directory: component-lib
        id: component_library
        shell: bash
        run: |
          component_lib_tar_gz=$(find "./" -maxdepth 1 -name 'streamlit-component-lib-*.tgz')
          component_lib_tar_gz=$(readlink -e "${component_lib_tar_gz}")
          echo "output_file=${component_lib_tar_gz}" >> $GITHUB_OUTPUT

      - name: Checkout streamlit/component-template
        uses: actions/checkout@v4
        with:
          persist-credentials: false
          repository: streamlit/component-template
          path: ./component-template

      - name: Build components wheels
        uses: ./component-template/.github/actions/build_component_wheels
        id: component_wheels
        with:
          custom_streamlit_component_lib_file: >-
            ${{ steps.component_library.outputs.output_file }}

      - name: Run E2E tests
        uses: ./component-template/.github/actions/run_e2e
        with:
          python_version: ${{ env.PYTHON_MAX_VERSION }}
          streamlit_wheel_file: ${{ steps.streamlit_wheel.outputs.output_file }}


Ensure Relative Imports in Frontend

name: Ensure Relative Imports in Frontend

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]
  # Allows workflow to be called from other workflows
  workflow_call:
    inputs:
      ref:
        required: true
        type: string

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-ensure-relative-imports
  cancel-in-progress: true

jobs:
  ensure-relative-imports:
    runs-on: ubuntu-latest

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Run make develop
        run: make develop
      - name: Run make protobuf
        run: make protobuf
      - name: Run make frontend-lib-prod
        run: make frontend-lib-prod
      - name: Ensure ensure relative imports exist in the @streamlit/lib/dist folder
        run: make ensure-relative-imports


Close Stale PRs

name: Close Stale PRs
on:
  workflow_dispatch:
  schedule:
    # Run every day at 1:30 AM UTC
    - cron: "30 1 * * *"

jobs:
  stale:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/stale@v9
        with:
          # Only activated for pull-requests:
          days-before-issue-stale: -1
          days-before-issue-close: -1
          days-before-pr-stale: 14
          days-before-pr-close: 7
          stale-pr-label: "stale"
          stale-pr-message: >
            This pull request has been automatically marked as stale because
            it has not had recent activity. It will be closed if no further
            activity occurs. Thank you for your contributions.
          exempt-pr-labels: "do-not-merge,QA-Needed"
          remove-pr-stale-when-updated: true


name: Playwright E2E Tests - Custom Components

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]
  # Allows workflow to be called from other workflows
  workflow_call:
    inputs:
      ref:
        required: true
        type: string

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-playwright-custom-component
  cancel-in-progress: true

jobs:
  playwright-e2e-tests-custom-components:
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Install playwright
        run: python -m playwright install --with-deps
      - name: Run make frontend-fast
        run: make frontend-fast
      - name: Run make playwright-custom-components
        run: make playwright-custom-components
      - name: Upload failed test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright_test_results
          path: e2e_playwright/test-results


Python Bare Execution Tests

name: Python Bare Execution Tests

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]
  # Allows workflow to be called from other workflows
  workflow_call:
    inputs:
      ref:
        required: true
        type: string

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-bare-executions
  cancel-in-progress: true

jobs:
  py-bare-executions:
    runs-on: ubuntu-latest

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Run Bare Execution Tests
        run: make bare-execution-tests


Playwright E2E Tests - Changed Files

name: Playwright E2E Tests - Changed Files

on:
  pull_request:
    types: [opened, synchronize, reopened]

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-playwright-changed-files
  cancel-in-progress: true

jobs:
  playwright-e2e-tests-changed-files:
    runs-on: ubuntu-latest-8-cores

    defaults:
      run:
        shell: bash --login -eo pipefail {0}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v44
        with:
          path: e2e_playwright
          # Exclude all files in custom_components folder as they require external dependencies
          files: |
            **/*_test.py
            !custom_components/**
      - name: Check changed files
        id: check_changed_files
        env:
          CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
          CHANGED_FILES_COUNT: ${{ steps.changed-files.outputs.all_changed_files_count }}
        run: |
          echo "Changed files count: ${CHANGED_FILES_COUNT}"
          echo "$CHANGED_FILES"
          if [[ "${CHANGED_FILES_COUNT}" -gt 5 || "${CHANGED_FILES_COUNT}" -lt 1 ]]; then
            # We limit the workflow to a max of 5 changed files, since otherwise it would
            # take too long and would not provide any benefit compared to the main playwright
            # workflow.
            echo "This workflow only supports between 1-5 changed files. Otherwise its skipping running the tests.";
            echo "run_tests=false" >> $GITHUB_OUTPUT
          else
            echo "run_tests=true" >> $GITHUB_OUTPUT
          fi
      - name: Use output
        run: |
          echo "The output value is: ${{ steps.check_changed_files.outputs.run_tests }}"
      - name: Set Python version vars
        uses: ./.github/actions/build_info
      - name: Set up Python ${{ env.PYTHON_MAX_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: "${{ env.PYTHON_MAX_VERSION }}"
      - if: steps.check_changed_files.outputs.run_tests == 'true'
        name: Setup virtual env
        uses: ./.github/actions/make_init
      - if: steps.check_changed_files.outputs.run_tests == 'true'
        name: Install playwright
        run: python -m playwright install --with-deps
      - if: steps.check_changed_files.outputs.run_tests == 'true'
        name: Run make frontend-fast
        run: make frontend-fast
      - if: steps.check_changed_files.outputs.run_tests == 'true'
        name: Run changed playwright tests
        run: |
          cd e2e_playwright;
          rm -rf ./test-results;
          pytest ${{ steps.changed-files.outputs.all_changed_files }} --browser webkit --browser chromium --browser firefox --video retain-on-failure --screenshot only-on-failure --full-page-screenshot --tracing retain-on-failure --output ./test-results/ -n auto --durations=5 -r aR -v
      - name: Upload failed test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright_test_results
          path: e2e_playwright/test-results


Python Unit Tests

name: Python Unit Tests

on:
  push:
    branches:
      - "develop"
  pull_request:
    types: [opened, synchronize, reopened]
  # Allows workflow to be called from other workflows
  workflow_call:
    inputs:
      ref:
        required: true
        type: string
      force-canary:
        description: |
          Forces the current build to be canary.
          Canary builds test all Python versions and do not use constraints.
        default: false
        type: boolean
      constraints-branch:
        description: "The name of the branch from which the constraints files will be downloaded or compared with."
        default: "constraints-develop"
        type: string
    secrets:
      PARAMETER_PASSWORD:
        description: "Token passed from caller workflows for snowflake integration tests"
        required: true

# Avoid duplicate workflows on same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-python
  cancel-in-progress: true

defaults:
  run:
    shell: bash --login -eo pipefail {0}

env:
  FORCE_COLOR: "1"

jobs:
  build_info:
    runs-on: ubuntu-latest

    name: "Build info"

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
          fetch-depth: 2
      - name: Set Python version vars
        id: build_info
        uses: ./.github/actions/build_info
        with:
          force-canary: ${{ inputs.force-canary || false }}

    outputs:
      PYTHON_VERSIONS: ${{ steps.build_info.outputs.PYTHON_VERSIONS }}
      PYTHON_MIN_VERSION: ${{ steps.build_info.outputs.PYTHON_MIN_VERSION }}
      PYTHON_MAX_VERSION: ${{ steps.build_info.outputs.PYTHON_MAX_VERSION }}
      USE_CONSTRAINTS_FILE: ${{ steps.build_info.outputs.USE_CONSTRAINTS_FILE }}

  py-unit-tests:
    needs:
      - build_info

    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        python_version: "${{ fromJson(needs.build_info.outputs.PYTHON_VERSIONS) }}"

    env:
      PYTHON_VERSION: >-
        ${{
          (
            matrix.python_version == 'min' && needs.build_info.outputs.PYTHON_MIN_VERSION ||
            (matrix.python_version == 'max' && needs.build_info.outputs.PYTHON_MAX_VERSION || matrix.python_version)
          )
        }}
      USE_CONSTRAINTS_FILE: "${{ fromJson(needs.build_info.outputs.USE_CONSTRAINTS_FILE )}}"
      CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch || 'constraints-develop' }}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"

      - name: Set up Python ${{ env.PYTHON_VERSION }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Run make develop
        run: make develop
      - name: Run Linters
        run: make pylint
        env:
          RUFF_OUTPUT_FORMAT: github
      - name: Run Type Checkers
        run: scripts/mypy --report
      - name: Run Python Tests
        run: make pytest
      - name: CLI Smoke Tests
        run: make cli-smoke-tests
      - name: Set CONSTRAINTS_FILE env variable
        if: ${{ always() }}
        run: |
          mkdir -p /tmp/constraints
          echo "CONSTRAINTS_FILE=/tmp/constraints/constraints-${PYTHON_VERSION}.txt" >> $GITHUB_ENV
      - name: Generate constraint file for Python ${{ env.PYTHON_VERSION }}
        if: ${{ always() }}
        run: |
          pip freeze | grep -v "\-e git" | tee "${CONSTRAINTS_FILE}"
      - name: Diff constraint file
        if: ${{ always() }}
        run: |
          CONSTRAINT_URL="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${CONSTRAINTS_BRANCH}/constraints-${PYTHON_VERSION}.txt"
          diff -y <(echo "Old"; curl -s "${CONSTRAINT_URL}") <(echo "New"; cat "${CONSTRAINTS_FILE}") || true
      - name: Upload constraints file
        uses: actions/upload-artifact@v4
        with:
          name: constraints-${{ matrix.python_version }}
          path: ${{ env.CONSTRAINTS_FILE }}
          if-no-files-found: error

  py-updated-constraints:
    needs:
      - py-unit-tests
    permissions:
      # Additional permission needed to upload constraints
      contents: write

    runs-on: ubuntu-latest
    if: |
      github.repository == 'streamlit/streamlit' && (
      (github.event_name == 'push' && github.ref_name == 'develop') ||
      (github.event_name == 'schedule')
      )

    name: Upload constraints

    env:
      TARGET_BRANCH: constraints-${{ github.ref_name }}

    steps:
      - name: Checkout branch "${{ env.TARGET_BRANCH }}"
        uses: actions/checkout@v4
        with:
          ref: ${{ env.TARGET_BRANCH }}
          # Save the access token to the local git config, so
          # later git commands can work.
          persist-credentials: true

      - uses: actions/download-artifact@v4
        with:
          path: .
          pattern: constraints-*
          merge-multiple: true

      - name: Commit and push constraint files
        run: |
          git add .
          git config --local user.email "core+streamlitbot-github@streamlit.io"
          git config --local user.name "Automated GitHub Actions commit"
          if ! git diff --cached --color --exit-code --ignore-matching-lines="^#.*"
          then
            git commit --all --message "Updating constraints. Github run id:${GITHUB_RUN_ID}

            This update in constraints is automatically committed by the CI based on
            '${GITHUB_REF}' in the '${GITHUB_REPOSITORY}' repository with commit sha ${GITHUB_SHA}.

            The action that build those constraints can be found at https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/
            "
            git push "origin" "HEAD:${TARGET_BRANCH}";
          else
            echo "No changes"
          fi
        env:
          TARGET_BRANCH: constraints-${{ github.ref_name }}

  py-snowflake-integration-tests:
    needs:
      - build_info

    runs-on: ubuntu-latest

    # Runs triggered by PRs from forks or by dependabot won't run this job, since that PR wouldn't have secrets access
    # See: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
    # Runs triggered by Release/RC are workflow_dispatch events ; Nightly is a schedule event
    if: |
      github.repository == 'streamlit/streamlit' && (
      (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') ||
      (github.event_name == 'push') ||
      (github.event_name == 'workflow_dispatch') ||
      (github.event_name == 'schedule')
      )

    name: >
      Python 3.8: Python tests for Snowflake

    env:
      USE_CONSTRAINTS_FILE: "${{ fromJson(needs.build_info.outputs.USE_CONSTRAINTS_FILE )}}"
      CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch || 'constraints-develop' }}

    steps:
      - name: Checkout Streamlit code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: false
          submodules: "recursive"
      - name: Set up Python 3.8
        uses: actions/setup-python@v5
        with:
          python-version: "3.8"
      - name: Decrypt credentials
        run: ./.github/scripts/decrypt_credentials.sh
        env:
          PARAMETER_PASSWORD: ${{ secrets.PARAMETER_PASSWORD }}
      - name: Setup virtual env
        uses: ./.github/actions/make_init
      - name: Run make develop
        run: make develop
      - name: Run Type Checkers
        run: scripts/mypy --report
      - name: Run Python Tests for Snowflake
        run: make pytest-snowflake


Create Release

on:
  push:
    # Sequence of patterns matched against refs/tags
    tags:
      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10

name: Create Release

permissions:
  contents: read

jobs:
  build:
    permissions:
      contents: write  # for actions/create-release to create a release
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          body: |
            See [CHANGELOG.md](https://github.com/joke2k/faker/blob/${{ github.ref }}/CHANGELOG.md).
          draft: false
          prerelease: false


Python Tests

name: Python Tests

on:
  push:
    branches:
      - master
  pull_request_target:
    types: [opened, synchronize, reopened]

permissions:
  contents: read

jobs:
  flake8:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-flake8
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install flake8
    - name: Lint with flake8
      uses: liskin/gh-problem-matcher-wrap@v3
      with:
        linters: flake8
        run: flake8 --extend-ignore=E203 faker tests

  checkmanifest:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-checkmanifest
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install tox
    - name: Check manifest
      run: tox
      env:
        TOXENV: checkmanifest

  isort:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-isort
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install isort
    - name: Import order checking with isort
      uses: liskin/gh-problem-matcher-wrap@v3
      with:
        linters: isort
        run: isort --check --diff .

  black:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-black
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install tox
    - name: Formatting with black
      run: tox
      env:
        TOXENV: black

  doc8:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-pep8
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install tox
    - name: Doc formatting
      run: tox
      env:
        TOXENV: doc8

  typing:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python: ["3.8", "3.9", "3.10", "3.11", "3.12"]

    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-typing
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python }}
    - name: Install dependencies
      run: |
        python -m pip install mypy
    - name: Static type checking with mypy
      uses: liskin/gh-problem-matcher-wrap@v3
      with:
        linters: mypy
        run: mypy --install-types --non-interactive --config mypy.ini faker

  test_ubuntu:
    runs-on: ubuntu-latest
    needs: [flake8, isort, black, doc8, checkmanifest, typing]
    strategy:
      matrix:
        python: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.10"]

    steps:
      - uses: actions/checkout@v4
      - name: Setup pip cache
        uses: actions/cache@v4
        id: pipcache
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
          restore-keys: |
            ${{ runner.os }}-pip-test_ubuntu
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
      - name: Install Tox and any other packages
        run: python -m pip install tox coveralls
      - name: pytest
        uses: liskin/gh-problem-matcher-wrap@v3
        with:
          linters: pytest
          run: tox -e py
        env:
          COVERALLS_PARALLEL: true
          COVERALLS_FLAG_NAME: run-ubuntu-${{ matrix.python }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COVERALLS_SERVICE_NAME: github
      - name: Publish coverage
        run: coveralls --service=github
        env:
          COVERALLS_PARALLEL: true
          COVERALLS_FLAG_NAME: run-ubuntu-${{ matrix.python }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COVERALLS_SERVICE_NAME: github

  test_windows:
    runs-on: windows-latest
    needs: [flake8, isort, black, doc8, checkmanifest, typing]
    strategy:
      matrix:
        python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
        arch: ["x86", "x64"]

    steps:
      - uses: actions/checkout@v4
      - name: Setup pip cache
        uses: actions/cache@v4
        id: pipcache
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
          restore-keys: |
            ${{ runner.os }}-pip-test_windows
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
      - name: Install Tox and any other packages
        run: python -m pip install tox coveralls
      - name: Run Tox
        # Run tox using the version of Python in `PATH`
        run: tox -e py
      - name: Publish coverage
        run: coveralls --service=github
        env:
          COVERALLS_PARALLEL: true
          COVERALLS_FLAG_NAME: run-ubuntu-${{ matrix.python }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COVERALLS_SERVICE_NAME: github

  test_alpine:
    runs-on: ubuntu-latest
    needs: [flake8, isort, black, doc8, checkmanifest, typing]

    steps:
    - uses: actions/checkout@v4
    - name: Setup pip cache
      uses: actions/cache@v4
      id: pipcache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
        restore-keys: |
          ${{ runner.os }}-pip-test_alpine
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install tox==3.27.1 setuptools
    - name: Run tests
      run: tox
      env:
        TOXENV: alpine
        TEST_ALPINE: 1

  finish:
    needs: [test_ubuntu, test_windows, test_alpine]
    runs-on: ubuntu-latest
    steps:
    - name: Coveralls Finished
      uses: AndreMiras/coveralls-python-action@develop
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        parallel-finished: true


Close inactive issues

name: Close inactive issues
on:
  schedule:
    - cron: "30 1 * * *"

jobs:
  close-issues:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@v9
        with:
          days-before-issue-stale: 90
          days-before-issue-close: 14
          exempt-issue-labels: 'awaiting-approval,work-in-progress'
          stale-issue-label: "stale"
          stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
          close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
          days-before-pr-stale: 90
          days-before-pr-close: 14
          repo-token: ${{ secrets.GITHUB_TOKEN }}