Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 77 additions & 49 deletions .github/workflows/python-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,31 @@ jobs:
- name: Check out code
uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

Comment on lines 41 to 45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use a valid setup-python version (v5). v6 does not exist.

actions/setup-python@v6 will 404 and break all jobs. Latest major is v5 (e.g., v5.5.0). Switch all occurrences to @v5. (github.com)

Apply:

-        uses: actions/setup-python@v6
+        uses: actions/setup-python@v5

Do this for lines 42, 82, 108, 148, 190, 276, and 362.

Also applies to: 81-85, 107-111, 147-151, 189-193, 275-279, 361-365

🤖 Prompt for AI Agents
In .github/workflows/python-app.yaml around lines 41-45 (and also update
occurrences at 81-85, 107-111, 147-151, 189-193, 275-279, 361-365), the workflow
uses actions/setup-python@v6 which does not exist; replace each use reference to
actions/setup-python@v6 with actions/setup-python@v5 (or a specific v5 tag such
as v5.5.0) so the action resolves correctly in all the listed locations.

- name: Install Ruff
run: |
python -m pip install --upgrade pip
pip install "ruff==0.9.*"
ruff --version
uvx ruff --version

- name: Run Ruff Linting
run: |
echo "::group::Ruff Linting"
ruff check . --output-format=github
uvx ruff check . --output-format=github
echo "::endgroup::"

- name: Run Ruff Formatting Check
run: |
echo "::group::Ruff Formatting"
ruff format --check --diff .
uvx ruff format --check --diff .
echo "::endgroup::"

test:
Expand All @@ -71,49 +72,49 @@ jobs:
- name: Check out code
uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[dev] pytest-xdist
uv pip install --system .[dev] "pytest-xdist==3.*"

- name: Run tests
run: pytest -v -p no:warnings --numprocesses=auto

security:
name: Security Scan
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: lint
steps:
- name: Check out code
uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install security tools
run: |
python -m pip install --upgrade pip
pip install bandit[toml]

- name: Run Bandit security scan
run: |
# Gate on HIGH severity & MEDIUM confidence; produce JSON artifact
bandit -r flixopt/ -c pyproject.toml -f json -o bandit-report.json -q -lll -ii
uvx bandit -r flixopt/ -c pyproject.toml -f json -o bandit-report.json -q --severity-level high --confidence-level medium
# Human-readable output without affecting job status
bandit -r flixopt/ -c pyproject.toml -q --exit-zero
uvx bandit -r flixopt/ -c pyproject.toml -q --exit-zero

- name: Upload security reports
uses: actions/upload-artifact@v4
Expand All @@ -137,6 +138,12 @@ jobs:
with:
fetch-depth: 0

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down Expand Up @@ -166,56 +173,67 @@ jobs:
environment:
name: testpypi
url: https://test.pypi.org/p/flixopt
env:
SKIP_TESTPYPI_UPLOAD: "false"

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

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build setuptools wheel twine
uv pip install --system twine

- name: Build the distribution
run: |
python -m build
uv build

- name: Upload to TestPyPI
run: |
twine upload --repository-url https://test.pypi.org/legacy/ dist/* --verbose
twine upload --repository-url https://test.pypi.org/legacy/ dist/* --verbose --skip-existing
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
TWINE_NON_INTERACTIVE: "1"

- name: Test install from TestPyPI
if: env.SKIP_TESTPYPI_UPLOAD != 'true'
run: |
set -Eeuo pipefail
# Create a temporary environment to test installation
python -m venv test_env
uv venv test_env
source test_env/bin/activate

# Get project name from pyproject.toml (PEP 621)
PACKAGE_NAME=$(python - <<'PY'
import sys, tomllib, pathlib
data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
print(data["project"]["name"])
PY
)

# Extract version from git tag
VERSION=${GITHUB_REF#refs/tags/v}

# Wait and retry while TestPyPI indexes the package
INSTALL_SUCCESS=false
for d in 15 30 60 120 180 360 720 1080; do
for d in 10 20 40 80 120; do
sleep "$d"
echo "Attempting to install $PACKAGE_NAME==$VERSION from TestPyPI (retry after ${d}s)..."

# Install specific version and verify it matches
if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "$PACKAGE_NAME==$VERSION" && \
if uv pip --timeout 60 install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "$PACKAGE_NAME==$VERSION" && \
python -c "from importlib.metadata import version; installed = version('$PACKAGE_NAME'); print(f'Installed: {installed}'); assert '$VERSION' == installed"; then
INSTALL_SUCCESS=true
break
Expand Down Expand Up @@ -248,51 +266,59 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build setuptools wheel twine
uv pip install --system twine

- name: Build the distribution
run: |
python -m build
uv build

- name: Upload to PyPI
run: |
twine upload dist/* --verbose
twine upload dist/* --verbose --skip-existing
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
TWINE_NON_INTERACTIVE: "1"

- name: Verify PyPI installation
run: |
set -Eeuo pipefail
# Create a temporary environment to test installation
python -m venv prod_test_env
uv venv prod_test_env
source prod_test_env/bin/activate

# Get project name from pyproject.toml (PEP 621)
PACKAGE_NAME=$(python - <<'PY'
import sys, tomllib, pathlib
data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
print(data["project"]["name"])
PY
)

# Extract version from git tag
VERSION=${GITHUB_REF#refs/tags/v}

# Wait and retry while PyPI indexes the package
INSTALL_SUCCESS=false
for d in 5 10 15 30 60 120 180 360 720 1080; do
for d in 10 20 40 80 120; do
sleep "$d"
echo "Attempting to install $PACKAGE_NAME==$VERSION from PyPI (retry after ${d}s)..."

# Install specific version and verify it matches
if pip install "$PACKAGE_NAME==$VERSION" && \
if uv pip --timeout 60 install "$PACKAGE_NAME==$VERSION" && \
python -c "from importlib.metadata import version; installed = version('$PACKAGE_NAME'); print(f'Installed: {installed}'); assert '$VERSION' == installed"; then
INSTALL_SUCCESS=true
break
Expand Down Expand Up @@ -326,13 +352,16 @@ jobs:
with:
fetch-depth: 0 # Fetch all history for proper versioning

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.17"
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Sync changelog to docs
run: |
Expand All @@ -341,8 +370,7 @@ jobs:

- name: Install documentation dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[docs]"
uv pip install --system ".[docs]"

- name: Configure Git Credentials
run: |
Expand Down