From 468a3bcc51d32b41e60432da8b9e77cf1440e671 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 5 Jan 2026 20:40:18 +0100 Subject: [PATCH 1/5] [Python] Type checking in CI --- .github/workflows/python-type-checking.yml | 105 +++++++++++++++++++++ pyrightconfig.ci.json | 13 +++ src/Fable.Build/FableLibrary/Python.fs | 14 ++- src/Fable.Build/Main.fs | 3 +- src/Fable.Build/Quicktest/Python.fs | 4 +- src/Fable.Build/Test/Python.fs | 13 +-- 6 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/python-type-checking.yml create mode 100644 pyrightconfig.ci.json diff --git a/.github/workflows/python-type-checking.yml b/.github/workflows/python-type-checking.yml new file mode 100644 index 000000000..77acf58a8 --- /dev/null +++ b/.github/workflows/python-type-checking.yml @@ -0,0 +1,105 @@ +name: Python Type Checking + +on: + pull_request: + branches: [main] + paths: + - 'src/Fable.Transforms/Python/**' + - 'src/fable-library-py/**' + - 'tests/Python/**' + - 'pyrightconfig.ci.json' + - 'pyproject.toml' + +permissions: + contents: read + pull-requests: write + +jobs: + pyright: + runs-on: ubuntu-latest + env: + UV_LINK_MODE: copy + + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: "10.0.x" + + - name: Setup dotnet tools + run: dotnet tool restore + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + run: | + pipx install uv + pipx install maturin + + - name: Build Fable Library - Python + run: ./build.sh fable-library --python + + - name: Build Python tests (compile only) + run: | + uv sync + uv pip install -e temp/fable-library-py + dotnet fable tests/Python --outDir temp/tests/Python --lang python --exclude Fable.Core --noCache + + - name: Run Pyright (strict mode) + id: pyright + continue-on-error: true + run: | + if uv run pyright --project pyrightconfig.ci.json temp/tests/Python/ temp/fable-library-py/; then + echo "result=:white_check_mark: All non-excluded files pass" >> $GITHUB_OUTPUT + echo "passed=true" >> $GITHUB_OUTPUT + else + echo "result=:x: Type errors found in non-excluded files" >> $GITHUB_OUTPUT + echo "passed=false" >> $GITHUB_OUTPUT + fi + + - name: Count excluded files + id: excluded + run: | + COUNT=$(grep -c '"temp/' pyrightconfig.ci.json || echo "0") + echo "count=$COUNT" >> $GITHUB_OUTPUT + + - name: Find existing comment + uses: peter-evans/find-comment@v3 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Python Type Checking + + - name: Create or update PR comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + ## Python Type Checking + + ${{ steps.pyright.outputs.result }} + + | Metric | Value | + | -------------- | ----------------------------------- | + | Excluded files | ${{ steps.excluded.outputs.count }} | + +
+ Files excluded from strict checking + + These files have known type errors and are excluded from CI. + Remove from `pyrightconfig.ci.json` as errors are fixed. + +
+ + - name: Fail if type errors + if: false # Temporarily disabled + # if: steps.pyright.outputs.passed == 'false' + run: exit 1 diff --git a/pyrightconfig.ci.json b/pyrightconfig.ci.json new file mode 100644 index 000000000..6d9720c74 --- /dev/null +++ b/pyrightconfig.ci.json @@ -0,0 +1,13 @@ +{ + "extends": "./pyrightconfig.json", + "exclude": [ + "**/.venv/**", + "**/node_modules/**", + "temp/fable-library-py/fable_library/list.py", + "temp/tests/Python/test_applicative.py", + "temp/tests/Python/test_map.py", + "temp/tests/Python/test_misc.py", + "temp/tests/Python/test_type.py", + "temp/tests/Python/fable_modules/thoth_json_python/encode.py" + ] +} diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index ca483b2c3..0d079cc6b 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -6,7 +6,7 @@ open Build.Utils open SimpleExec open BlackFox.CommandLine -type BuildFableLibraryPython() = +type BuildFableLibraryPython(?skipCore: bool) = inherit BuildFableLibrary( language = "python", @@ -16,6 +16,8 @@ type BuildFableLibraryPython() = outDir = Path.Combine("temp", "fable-library-py", "fable_library") ) + let skipCore = defaultArg skipCore false + override this.CopyStage() = // Copy all Python/F# files to the build directory Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir @@ -33,7 +35,11 @@ type BuildFableLibraryPython() = override this.PostFableBuildStage() = // Install the python dependencies at the root of the project Command.Run("uv", "sync", this.BuildDir) // Maturin needs a local virtual environment - Command.Run("uv", "run maturin develop --release", this.BuildDir) + + if skipCore then + printfn "Skipping fable-library-core (Rust) build" + else + Command.Run("uv", "run maturin develop --release", this.BuildDir) // Fix issues with Fable .fsproj not supporting links, so we need to copy the // files ourself to the output directory @@ -44,7 +50,7 @@ type BuildFableLibraryPython() = Shell.deleteDir (this.BuildDir "fable_library/fable-library-ts") - // Run Ruff linter checking import sorting and fix any issues - Command.Run("uv", $"run ruff check --select I --fix {this.BuildDir}") + // Run Ruff linter checking import sorting, removing unused imports, and fix any issues + Command.Run("uv", $"run ruff check --select I,F401 --fix {this.BuildDir}") // Run Ruff formatter on all generated files Command.Run("uv", $"run ruff format {this.BuildDir}") diff --git a/src/Fable.Build/Main.fs b/src/Fable.Build/Main.fs index e79d8c107..a8d79873c 100644 --- a/src/Fable.Build/Main.fs +++ b/src/Fable.Build/Main.fs @@ -63,7 +63,8 @@ Available commands: --threaded Compile and run the tests with the threaded runtime Options for Python: - --typing Run type checking with Pyright and show the summary + --type-check Run type checking on the generated code with Pyright + --format Format the code generated code with Ruff formatter standalone Compile standalone + worker version of Fable running on top of of Node.js diff --git a/src/Fable.Build/Quicktest/Python.fs b/src/Fable.Build/Quicktest/Python.fs index dad00b8a5..8234fc182 100644 --- a/src/Fable.Build/Quicktest/Python.fs +++ b/src/Fable.Build/Quicktest/Python.fs @@ -10,10 +10,12 @@ open Build.Utils let private fableLibraryBuildDir = Path.Resolve("temp", "fable-library-py") let handle (args: string list) = + let skipFableLibraryCore = args |> List.contains "--skip-fable-library-core" + // Install local fable-library as editable package for testing // This ensures quicktest uses the locally built version, not PyPI if not (args |> List.contains "--skip-fable-library") then - BuildFableLibraryPython().Run(false) + BuildFableLibraryPython(skipCore = skipFableLibraryCore).Run(false) // Install fable-library in editable mode Command.Run("uv", $"pip install -e {fableLibraryBuildDir}") diff --git a/src/Fable.Build/Test/Python.fs b/src/Fable.Build/Test/Python.fs index 32b2bef5b..6a82842c1 100644 --- a/src/Fable.Build/Test/Python.fs +++ b/src/Fable.Build/Test/Python.fs @@ -12,12 +12,13 @@ let private fableLibraryBuildDir = Path.Resolve("temp", "fable-library-py") let handle (args: string list) = let skipFableLibrary = args |> List.contains "--skip-fable-library" + let skipFableLibraryCore = args |> List.contains "--skip-fable-library-core" let isWatch = args |> List.contains "--watch" let noDotnet = args |> List.contains "--no-dotnet" - let runTyping = args |> List.contains "--typing" + let runTyping = args |> List.contains "--type-check" let runFormat = args |> List.contains "--format" - BuildFableLibraryPython().Run(skipFableLibrary) + BuildFableLibraryPython(skipCore = skipFableLibraryCore).Run(skipFableLibrary) Directory.clean buildDir @@ -40,7 +41,7 @@ let handle (args: string list) = if isWatch then let ruffCmd = if runFormat then - $"uv run ruff check --select I --fix {buildDir} && uv run ruff format {buildDir} && " + $"uv run ruff check --select I,F401 --fix {buildDir} && uv run ruff format {buildDir} && " else "" @@ -71,8 +72,8 @@ let handle (args: string list) = Command.Fable(fableArgs, workingDirectory = buildDir) if runFormat then - // Run Ruff linter checking import sorting and fix any issues - Command.Run("uv", $"run ruff check --select I --fix {buildDir}") + // Run Ruff linter checking import sorting and fix any issues, and remove unused imports + Command.Run("uv", $"run ruff check --select I,F401 --fix {buildDir}") // Run Ruff formatter on all generated files Command.Run("uv", $"run ruff format {buildDir}") @@ -104,4 +105,4 @@ let handle (args: string list) = printfn "Pyright summary: %s" summaryLine else - printfn "Skipping type checking (use --typing to enable)" + printfn "Skipping type checking (use --type-check to enable)" From 4dbf460435a455998f6afae4c593b90582ee1703 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 5 Jan 2026 20:54:03 +0100 Subject: [PATCH 2/5] Make sure we restore before testing --- .github/workflows/build.yml | 3 --- .github/workflows/python-type-checking.yml | 10 ++-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12a48d959..de2da6f2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -148,9 +148,6 @@ jobs: with: dotnet-version: "10.0.x" - - name: Setup dotnet tools - run: dotnet tool restore - - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: diff --git a/.github/workflows/python-type-checking.yml b/.github/workflows/python-type-checking.yml index 77acf58a8..0d62bdf59 100644 --- a/.github/workflows/python-type-checking.yml +++ b/.github/workflows/python-type-checking.yml @@ -41,14 +41,8 @@ jobs: pipx install uv pipx install maturin - - name: Build Fable Library - Python - run: ./build.sh fable-library --python - - - name: Build Python tests (compile only) - run: | - uv sync - uv pip install -e temp/fable-library-py - dotnet fable tests/Python --outDir temp/tests/Python --lang python --exclude Fable.Core --noCache + - name: Build and test Python + run: ./build.sh test python - name: Run Pyright (strict mode) id: pyright From abeebb7b02fc24ac07a48583f9606ec92e544351 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 5 Jan 2026 20:58:50 +0100 Subject: [PATCH 3/5] Fix comment since we are using standard mode --- .github/workflows/python-type-checking.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-type-checking.yml b/.github/workflows/python-type-checking.yml index 0d62bdf59..a976e458d 100644 --- a/.github/workflows/python-type-checking.yml +++ b/.github/workflows/python-type-checking.yml @@ -41,10 +41,10 @@ jobs: pipx install uv pipx install maturin - - name: Build and test Python - run: ./build.sh test python + - name: Build Python (compile only) + run: ./build.sh test python --compile-only - - name: Run Pyright (strict mode) + - name: Run Pyright (standard mode) id: pyright continue-on-error: true run: | From 38d8b0a7f6dc24efdbc71ea2d6eaa388acc00d46 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 5 Jan 2026 21:02:49 +0100 Subject: [PATCH 4/5] Show the excluded files --- .github/workflows/python-type-checking.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-type-checking.yml b/.github/workflows/python-type-checking.yml index a976e458d..e42f11760 100644 --- a/.github/workflows/python-type-checking.yml +++ b/.github/workflows/python-type-checking.yml @@ -56,11 +56,15 @@ jobs: echo "passed=false" >> $GITHUB_OUTPUT fi - - name: Count excluded files + - name: Get excluded files id: excluded run: | COUNT=$(grep -c '"temp/' pyrightconfig.ci.json || echo "0") echo "count=$COUNT" >> $GITHUB_OUTPUT + FILES=$(grep '"temp/' pyrightconfig.ci.json | sed 's/.*"temp/temp/' | sed 's/".*//' | sort) + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - name: Find existing comment uses: peter-evans/find-comment@v3 @@ -91,6 +95,10 @@ jobs: These files have known type errors and are excluded from CI. Remove from `pyrightconfig.ci.json` as errors are fixed. + ``` + ${{ steps.excluded.outputs.files }} + ``` + - name: Fail if type errors From 9bcd2210c1bd5f407a6ecb946ab2e01846f1acf0 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 5 Jan 2026 21:10:55 +0100 Subject: [PATCH 5/5] Fix output text --- .github/workflows/python-type-checking.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-type-checking.yml b/.github/workflows/python-type-checking.yml index e42f11760..fdb3cb049 100644 --- a/.github/workflows/python-type-checking.yml +++ b/.github/workflows/python-type-checking.yml @@ -81,7 +81,7 @@ jobs: issue-number: ${{ github.event.pull_request.number }} edit-mode: replace body: | - ## Python Type Checking + ## Python Type Checking (Pyright) Results ${{ steps.pyright.outputs.result }} @@ -90,10 +90,9 @@ jobs: | Excluded files | ${{ steps.excluded.outputs.count }} |
- Files excluded from strict checking + Files excluded from type checking - These files have known type errors and are excluded from CI. - Remove from `pyrightconfig.ci.json` as errors are fixed. + These files have known type errors and are excluded from CI. Remove from `pyrightconfig.ci.json` as errors are fixed. ``` ${{ steps.excluded.outputs.files }}