Skip to content

Commit d0629e4

Browse files
authored
[Python] Type checking in CI (#4325)
1 parent 3a7a004 commit d0629e4

7 files changed

Lines changed: 141 additions & 15 deletions

File tree

.github/workflows/build.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,6 @@ jobs:
148148
with:
149149
dotnet-version: "10.0.x"
150150

151-
- name: Setup dotnet tools
152-
run: dotnet tool restore
153-
154151
- name: Set up Python ${{ matrix.python-version }}
155152
uses: actions/setup-python@v6
156153
with:
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: Python Type Checking
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
paths:
7+
- 'src/Fable.Transforms/Python/**'
8+
- 'src/fable-library-py/**'
9+
- 'tests/Python/**'
10+
- 'pyrightconfig.ci.json'
11+
- 'pyproject.toml'
12+
13+
permissions:
14+
contents: read
15+
pull-requests: write
16+
17+
jobs:
18+
pyright:
19+
runs-on: ubuntu-latest
20+
env:
21+
UV_LINK_MODE: copy
22+
23+
steps:
24+
- uses: actions/checkout@v5
25+
26+
- name: Setup .NET
27+
uses: actions/setup-dotnet@v5
28+
with:
29+
dotnet-version: "10.0.x"
30+
31+
- name: Setup dotnet tools
32+
run: dotnet tool restore
33+
34+
- name: Set up Python 3.12
35+
uses: actions/setup-python@v6
36+
with:
37+
python-version: "3.12"
38+
39+
- name: Install uv
40+
run: |
41+
pipx install uv
42+
pipx install maturin
43+
44+
- name: Build Python (compile only)
45+
run: ./build.sh test python --compile-only
46+
47+
- name: Run Pyright (standard mode)
48+
id: pyright
49+
continue-on-error: true
50+
run: |
51+
if uv run pyright --project pyrightconfig.ci.json temp/tests/Python/ temp/fable-library-py/; then
52+
echo "result=:white_check_mark: All non-excluded files pass" >> $GITHUB_OUTPUT
53+
echo "passed=true" >> $GITHUB_OUTPUT
54+
else
55+
echo "result=:x: Type errors found in non-excluded files" >> $GITHUB_OUTPUT
56+
echo "passed=false" >> $GITHUB_OUTPUT
57+
fi
58+
59+
- name: Get excluded files
60+
id: excluded
61+
run: |
62+
COUNT=$(grep -c '"temp/' pyrightconfig.ci.json || echo "0")
63+
echo "count=$COUNT" >> $GITHUB_OUTPUT
64+
FILES=$(grep '"temp/' pyrightconfig.ci.json | sed 's/.*"temp/temp/' | sed 's/".*//' | sort)
65+
echo "files<<EOF" >> $GITHUB_OUTPUT
66+
echo "$FILES" >> $GITHUB_OUTPUT
67+
echo "EOF" >> $GITHUB_OUTPUT
68+
69+
- name: Find existing comment
70+
uses: peter-evans/find-comment@v3
71+
id: find-comment
72+
with:
73+
issue-number: ${{ github.event.pull_request.number }}
74+
comment-author: 'github-actions[bot]'
75+
body-includes: Python Type Checking
76+
77+
- name: Create or update PR comment
78+
uses: peter-evans/create-or-update-comment@v4
79+
with:
80+
comment-id: ${{ steps.find-comment.outputs.comment-id }}
81+
issue-number: ${{ github.event.pull_request.number }}
82+
edit-mode: replace
83+
body: |
84+
## Python Type Checking (Pyright) Results
85+
86+
${{ steps.pyright.outputs.result }}
87+
88+
| Metric | Value |
89+
| -------------- | ----------------------------------- |
90+
| Excluded files | ${{ steps.excluded.outputs.count }} |
91+
92+
<details>
93+
<summary>Files excluded from type checking</summary>
94+
95+
These files have known type errors and are excluded from CI. Remove from `pyrightconfig.ci.json` as errors are fixed.
96+
97+
```
98+
${{ steps.excluded.outputs.files }}
99+
```
100+
101+
</details>
102+
103+
- name: Fail if type errors
104+
if: false # Temporarily disabled
105+
# if: steps.pyright.outputs.passed == 'false'
106+
run: exit 1

pyrightconfig.ci.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "./pyrightconfig.json",
3+
"exclude": [
4+
"**/.venv/**",
5+
"**/node_modules/**",
6+
"temp/fable-library-py/fable_library/list.py",
7+
"temp/tests/Python/test_applicative.py",
8+
"temp/tests/Python/test_map.py",
9+
"temp/tests/Python/test_misc.py",
10+
"temp/tests/Python/test_type.py",
11+
"temp/tests/Python/fable_modules/thoth_json_python/encode.py"
12+
]
13+
}

src/Fable.Build/FableLibrary/Python.fs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ open Build.Utils
66
open SimpleExec
77
open BlackFox.CommandLine
88

9-
type BuildFableLibraryPython() =
9+
type BuildFableLibraryPython(?skipCore: bool) =
1010
inherit
1111
BuildFableLibrary(
1212
language = "python",
@@ -16,6 +16,8 @@ type BuildFableLibraryPython() =
1616
outDir = Path.Combine("temp", "fable-library-py", "fable_library")
1717
)
1818

19+
let skipCore = defaultArg skipCore false
20+
1921
override this.CopyStage() =
2022
// Copy all Python/F# files to the build directory
2123
Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir
@@ -33,7 +35,11 @@ type BuildFableLibraryPython() =
3335
override this.PostFableBuildStage() =
3436
// Install the python dependencies at the root of the project
3537
Command.Run("uv", "sync", this.BuildDir) // Maturin needs a local virtual environment
36-
Command.Run("uv", "run maturin develop --release", this.BuildDir)
38+
39+
if skipCore then
40+
printfn "Skipping fable-library-core (Rust) build"
41+
else
42+
Command.Run("uv", "run maturin develop --release", this.BuildDir)
3743

3844
// Fix issues with Fable .fsproj not supporting links, so we need to copy the
3945
// files ourself to the output directory
@@ -44,7 +50,7 @@ type BuildFableLibraryPython() =
4450

4551
Shell.deleteDir (this.BuildDir </> "fable_library/fable-library-ts")
4652

47-
// Run Ruff linter checking import sorting and fix any issues
48-
Command.Run("uv", $"run ruff check --select I --fix {this.BuildDir}")
53+
// Run Ruff linter checking import sorting, removing unused imports, and fix any issues
54+
Command.Run("uv", $"run ruff check --select I,F401 --fix {this.BuildDir}")
4955
// Run Ruff formatter on all generated files
5056
Command.Run("uv", $"run ruff format {this.BuildDir}")

src/Fable.Build/Main.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ Available commands:
6363
--threaded Compile and run the tests with the threaded runtime
6464
6565
Options for Python:
66-
--typing Run type checking with Pyright and show the summary
66+
--type-check Run type checking on the generated code with Pyright
67+
--format Format the code generated code with Ruff formatter
6768
6869
standalone Compile standalone + worker version of Fable running
6970
on top of of Node.js

src/Fable.Build/Quicktest/Python.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ open Build.Utils
1010
let private fableLibraryBuildDir = Path.Resolve("temp", "fable-library-py")
1111

1212
let handle (args: string list) =
13+
let skipFableLibraryCore = args |> List.contains "--skip-fable-library-core"
14+
1315
// Install local fable-library as editable package for testing
1416
// This ensures quicktest uses the locally built version, not PyPI
1517
if not (args |> List.contains "--skip-fable-library") then
16-
BuildFableLibraryPython().Run(false)
18+
BuildFableLibraryPython(skipCore = skipFableLibraryCore).Run(false)
1719
// Install fable-library in editable mode
1820
Command.Run("uv", $"pip install -e {fableLibraryBuildDir}")
1921

src/Fable.Build/Test/Python.fs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ let private fableLibraryBuildDir = Path.Resolve("temp", "fable-library-py")
1212

1313
let handle (args: string list) =
1414
let skipFableLibrary = args |> List.contains "--skip-fable-library"
15+
let skipFableLibraryCore = args |> List.contains "--skip-fable-library-core"
1516
let isWatch = args |> List.contains "--watch"
1617
let noDotnet = args |> List.contains "--no-dotnet"
17-
let runTyping = args |> List.contains "--typing"
18+
let runTyping = args |> List.contains "--type-check"
1819
let runFormat = args |> List.contains "--format"
1920

20-
BuildFableLibraryPython().Run(skipFableLibrary)
21+
BuildFableLibraryPython(skipCore = skipFableLibraryCore).Run(skipFableLibrary)
2122

2223
Directory.clean buildDir
2324

@@ -40,7 +41,7 @@ let handle (args: string list) =
4041
if isWatch then
4142
let ruffCmd =
4243
if runFormat then
43-
$"uv run ruff check --select I --fix {buildDir} && uv run ruff format {buildDir} && "
44+
$"uv run ruff check --select I,F401 --fix {buildDir} && uv run ruff format {buildDir} && "
4445
else
4546
""
4647

@@ -71,8 +72,8 @@ let handle (args: string list) =
7172
Command.Fable(fableArgs, workingDirectory = buildDir)
7273

7374
if runFormat then
74-
// Run Ruff linter checking import sorting and fix any issues
75-
Command.Run("uv", $"run ruff check --select I --fix {buildDir}")
75+
// Run Ruff linter checking import sorting and fix any issues, and remove unused imports
76+
Command.Run("uv", $"run ruff check --select I,F401 --fix {buildDir}")
7677
// Run Ruff formatter on all generated files
7778
Command.Run("uv", $"run ruff format {buildDir}")
7879

@@ -104,4 +105,4 @@ let handle (args: string list) =
104105

105106
printfn "Pyright summary: %s" summaryLine
106107
else
107-
printfn "Skipping type checking (use --typing to enable)"
108+
printfn "Skipping type checking (use --type-check to enable)"

0 commit comments

Comments
 (0)