mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 00:39:35 +08:00
* fix: upgrade playwright to 1.58.0 to address Chromium CVEs - Add playwright>=1.58.0 to override-dependencies in pyproject.toml - Update uv.lock: playwright 1.49.0 -> 1.58.0, pyee 12.0.0 -> 13.0.1 - Fixes CVE-2026-2313, CVE-2026-2314, CVE-2026-2315, CVE-2026-2319, CVE-2026-2321, CVE-2026-2441, CVE-2026-2648, CVE-2026-2649 - Ensures Docker builds download updated Chromium with security patches * fix: update npm to latest version to address brace-expansion CVE-2026-33750 - Add npm update after Node.js installation in Dockerfile - Fixes CVE-2026-33750 in system npm's brace-expansion dependency - System npm had brace-expansion 2.0.2, update gets 5.0.5+ - Low risk change: npm is backward compatible, only affects CLI tool * revert: remove npm update from Dockerfile - npm update attempts were causing CI build failures - Bundled npm has issues but updating it is proving problematic - Focus on playwright CVE fix which is the primary concern - brace-expansion CVE-2026-33750 is lower priority (DoS only) * chore: sync uv.lock files sync uv.lock files * fix(mcp): dedupe edges in connect_components (#12701) * fix(mcp): make add_connection idempotent to avoid duplicate edges connect_components used to append a new edge unconditionally. Because the edge id is deterministic from source/target/handles, calling it for a pair the flow already had wired up (UI-then-MCP, batch retry, or just a repeat call) produced a second edge with the same id, double-wiring the flow at runtime. Before appending, scan the existing edges for one with the same id and return that instead. Different outputs/inputs between the same pair still produce distinct ids and remain supported. * test(mcp): cover dedupe against UI-saved edges, broaden match key Older Langflow UIs saved edges with an `xy-edge__` id prefix instead of the current `reactflow__edge-`, so an id-based dedup would miss the UI-then-MCP case for any flow that came from an older version. Switch the existence check to a structural one (source, target, sourceHandle name, targetHandle fieldName) so the same logical connection dedupes regardless of id format. Add a fixture-driven test that loads MemoryChatbotNoLLM.json (an xy-edge-prefixed flow) and replays each connection through add_connection, asserting the edge count does not grow. * fix(mcp): validate_flow fast-fails and reports partial errors (#12697) * fix(mcp): validate_flow fast-fails and reports partial errors validate_flow polled /monitor/builds for up to 30 seconds waiting for every component to finish before reporting errors. When a component fails early (for example a missing required field), downstream components never run, so the loop waited out the full window and returned just "Build timed out: N/M components completed" with no actionable context. - Short-circuit as soon as any completed build reports valid: false; return those errors immediately instead of polling on. - On timeout, include the errors from the builds that did complete plus a component_count so the caller can see progress. - Extract _collect_build_errors so the poll loop and timeout branch share the same error shape. * fix(mcp): stream validate_flow build inline instead of polling The previous implementation triggered an async build and polled /monitor/builds, which depended on FastAPI BackgroundTasks firing the log_vertex_build calls after the trigger request had returned. Under ASGI test transport these tasks never run, so /monitor/builds stayed empty and validate_flow timed out with component_count=0. Switch to event_delivery=direct so the build streams its events back inside the same request: - Drive the build via client.stream_post and aggregate per-vertex results from end_vertex events. - Fast-fail on the first vertex with valid=false, since downstream vertices depend on it and would not produce useful information. - Surface top-level error events as a single flow-level error. - Replace _collect_build_errors with _extract_vertex_error, which reads the structured error payload from the end_vertex outputs. Update the lfx unit tests to use the streaming shape and tighten the backend integration test to assert real success now that the build actually runs end to end under ASGI. * fix(graph): make end_all_traces_in_context Python 3.10 compatible The implementation called asyncio.create_task(coro, context=context), but the context= keyword was added to create_task in Python 3.11. On 3.10 it raised TypeError. The bug was latent because nothing in the test suite previously consumed a streaming build response far enough for Starlette to dispatch the post-response BackgroundTasks where this code lives. The validate_flow streaming change exposes it. On 3.10, route the create_task call through context.run so the new Task copies the captured context as its current context, matching the isolation the 3.11 path provides via the context= kwarg. Add a regression test that asserts end_all_traces sees the value of a contextvar set before the context was captured, even after the caller mutates that var. * fix: failing wxo list llm test (#12700) patch service layer and update failing test * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Try to fix the missing typer import --------- Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local> Co-authored-by: Adam Aghili <Adam.Aghili@ibm.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai> Co-authored-by: Hamza Rashid <74062092+HzaRashid@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Hare <ericrhare@gmail.com>
256 lines
9.1 KiB
YAML
256 lines
9.1 KiB
YAML
name: Python tests
|
|
|
|
on:
|
|
workflow_call:
|
|
secrets:
|
|
OPENAI_API_KEY:
|
|
required: true
|
|
ANTHROPIC_API_KEY:
|
|
required: true
|
|
CODECOV_TOKEN:
|
|
required: false
|
|
inputs:
|
|
python-versions:
|
|
description: "(Optional) Python versions to test"
|
|
required: true
|
|
type: string
|
|
default: "['3.10', '3.11', '3.12', '3.13']"
|
|
ref:
|
|
description: "(Optional) ref to checkout"
|
|
required: false
|
|
type: string
|
|
nightly:
|
|
description: "Whether run is from the nightly build"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
runs-on:
|
|
description: "Runner to use for the tests"
|
|
required: false
|
|
type: string
|
|
default: "ubuntu-latest"
|
|
workflow_dispatch:
|
|
inputs:
|
|
python-versions:
|
|
description: "(Optional) Python versions to test"
|
|
required: true
|
|
type: string
|
|
default: "['3.10', '3.11', '3.12', '3.13']"
|
|
runs-on:
|
|
description: "Runner to use for the tests"
|
|
required: false
|
|
type: choice
|
|
options:
|
|
- ubuntu-latest
|
|
- self-hosted
|
|
- '["self-hosted", "linux", "ARM64", "langflow-ai-arm64-40gb-ephemeral"]'
|
|
default: ubuntu-latest
|
|
env:
|
|
POETRY_VERSION: "1.8.2"
|
|
NODE_VERSION: "22"
|
|
PYTEST_RUN_PATH: "src/backend/tests"
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
|
|
jobs:
|
|
build:
|
|
name: Unit Tests - Python ${{ matrix.python-version }} - Group ${{ matrix.group }}
|
|
runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
|
|
strategy:
|
|
matrix:
|
|
python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
|
|
splitCount: [5]
|
|
group: [1, 2, 3, 4, 5]
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
id: setup-node
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: "Setup Environment"
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ matrix.python-version }}
|
|
prune-cache: false
|
|
|
|
- name: Install the project
|
|
run: uv sync
|
|
|
|
- name: Generate dynamic coverage configuration
|
|
run: |
|
|
echo "Generating dynamic coverage configuration..."
|
|
python3 scripts/generate_coverage_config.py
|
|
echo "Generated .coveragerc with the following exclusions:"
|
|
echo "Bundled components and legacy files excluded from coverage"
|
|
|
|
- name: Run unit tests
|
|
uses: nick-fields/retry@v3
|
|
with:
|
|
timeout_minutes: 15
|
|
max_attempts: 2
|
|
command: make unit_tests args="-x -vv --splits ${{ matrix.splitCount }} --group ${{ matrix.group }} --reruns 5 --cov --cov-config=src/backend/.coveragerc --cov-report=xml --cov-report=html"
|
|
|
|
- name: Upload coverage to Codecov
|
|
uses: codecov/codecov-action@v5
|
|
if: matrix.python-version == '3.10'
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
files: ./coverage.xml
|
|
flags: backend
|
|
name: backend-coverage-group-${{ matrix.group }}
|
|
fail_ci_if_error: false
|
|
directory: ./
|
|
|
|
- name: Upload coverage artifacts
|
|
uses: actions/upload-artifact@v6
|
|
if: matrix.python-version == '3.10'
|
|
with:
|
|
name: backend-coverage-report-group-${{ matrix.group }}
|
|
path: |
|
|
coverage.xml
|
|
htmlcov/
|
|
retention-days: 30
|
|
|
|
integration-tests:
|
|
name: Integration Tests - Python ${{ matrix.python-version }}
|
|
runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
|
|
strategy:
|
|
matrix:
|
|
python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
- name: "Setup Environment"
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ matrix.python-version }}
|
|
prune-cache: false
|
|
- name: Install the project
|
|
run: uv sync
|
|
- name: Run integration tests
|
|
run: make integration_tests_no_api_keys
|
|
env:
|
|
PYLEAK_LOG_LEVEL: debug # enable pyleak logging
|
|
DO_NOT_TRACK: true # disable telemetry reporting
|
|
|
|
lfx-tests:
|
|
name: LFX Tests - Python ${{ matrix.python-version }}
|
|
runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
|
|
strategy:
|
|
matrix:
|
|
python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
- name: "Setup Environment"
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ matrix.python-version }}
|
|
prune-cache: false
|
|
- name: Run lfx tests
|
|
run: make lfx_tests
|
|
env:
|
|
DO_NOT_TRACK: true # disable telemetry reporting
|
|
|
|
- name: Upload coverage to Codecov
|
|
uses: codecov/codecov-action@v5
|
|
if: matrix.python-version == '3.10'
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
files: ./src/lfx/coverage.xml
|
|
flags: lfx
|
|
name: lfx-coverage
|
|
fail_ci_if_error: false
|
|
directory: ./src/lfx/
|
|
|
|
- name: Upload coverage artifacts
|
|
uses: actions/upload-artifact@v6
|
|
if: matrix.python-version == '3.10'
|
|
with:
|
|
name: lfx-coverage-report
|
|
path: |
|
|
src/lfx/coverage.xml
|
|
src/lfx/htmlcov/
|
|
retention-days: 30
|
|
|
|
test-cli:
|
|
name: Test CLI - Python ${{ matrix.python-version }}
|
|
runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
|
|
strategy:
|
|
matrix:
|
|
python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]') }}
|
|
steps:
|
|
- name: Check out the code at a specific ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
- name: "Setup Environment"
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ matrix.python-version }}
|
|
prune-cache: false
|
|
|
|
- name: Check Version
|
|
id: check-version
|
|
# We need to print $3 because langflow-base is a dependency of langflow
|
|
# For langlow we'd use print $2
|
|
run: |
|
|
version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//' | head -n 1)
|
|
url="https://pypi.org/pypi/langflow-base/json"
|
|
if [ ${{ inputs.nightly }} == true ]; then
|
|
url="https://pypi.org/pypi/langflow-base-nightly/json"
|
|
fi
|
|
|
|
last_released_version=$(curl -s $url | jq -r '.releases | keys | .[]' | sort -V | tail -n 1)
|
|
if [ "$version" != "$last_released_version" ]; then
|
|
echo "Version $version has not been released yet. Skipping the rest of the job."
|
|
echo skipped=true >> $GITHUB_OUTPUT
|
|
exit 0
|
|
else
|
|
echo version=$version >> $GITHUB_OUTPUT
|
|
echo skipped=false >> $GITHUB_OUTPUT
|
|
fi
|
|
- name: Build wheel
|
|
if: steps.check-version.outputs.skipped == 'false'
|
|
run: |
|
|
make build main=true
|
|
- name: Install wheel and Test CLI
|
|
if: steps.check-version.outputs.skipped == 'false'
|
|
run: |
|
|
uv venv new-venv
|
|
source new-venv/bin/activate
|
|
uv pip install dist/*.whl
|
|
- name: Test CLI
|
|
if: steps.check-version.outputs.skipped == 'false'
|
|
run: |
|
|
source new-venv/bin/activate
|
|
python -m langflow run --host localhost --port 7860 --backend-only &
|
|
SERVER_PID=$!
|
|
# Wait for the server to start
|
|
timeout 120 bash -c 'until curl -f http://localhost:7860/api/v1/auto_login; do sleep 5; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
|
|
# Terminate the server
|
|
kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
|
|
sleep 20 # give the server some time to terminate
|
|
# Check if the server is still running
|
|
if kill -0 $SERVER_PID 2>/dev/null; then
|
|
echo "Failed to terminate the server"
|
|
exit 0
|
|
else
|
|
echo "Server terminated successfully"
|
|
fi
|