Skip to content

Commit d6c78bd

Browse files
committed
Enable support for free-threading
This PR: 1. marks the `libcst.native` module as free-threading-compatible 2. replaces the use of ProcessPoolExecutor with ThreadPoolExecutor if free-threaded CPython is detected at runtime This depends on #1294 and #1289.
1 parent d002c14 commit d6c78bd

File tree

5 files changed

+59
-92
lines changed

5 files changed

+59
-92
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
os: [macos-latest, ubuntu-latest, windows-latest]
16-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
16+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"]
1717
steps:
18+
- uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.10"
21+
- name: Install hatch
22+
run: pip install -U hatch
1823
- uses: actions/checkout@v4
1924
with:
2025
fetch-depth: 0
@@ -24,69 +29,22 @@ jobs:
2429
cache: pip
2530
cache-dependency-path: "pyproject.toml"
2631
python-version: ${{ matrix.python-version }}
27-
- name: Install hatch
28-
run: |
29-
pip install -U hatch
3032
- uses: actions-rs/toolchain@v1
3133
with:
3234
toolchain: stable
3335
- name: Build LibCST
34-
run: hatch -vv env create
36+
run: hatch -vv env create test
3537
- name: Native Parser Tests
36-
run: hatch run test
38+
run: hatch run test:test
3739
- name: Pure Parser Tests
3840
env:
3941
COVERAGE_FILE: .coverage.pure
4042
LIBCST_PARSER_TYPE: pure
41-
run: hatch run test
43+
run: hatch run test:test
4244
- name: Coverage
4345
run: |
44-
hatch run coverage combine .coverage.pure
45-
hatch run coverage report
46-
47-
# TODO:
48-
# merge into regular CI once hatch has support for creating environments on
49-
# the free-threaded build: https://github.com/pypa/hatch/issues/1931
50-
free-threaded-tests:
51-
name: "test (${{ matrix.os }}, 3.13t)"
52-
runs-on: ${{ matrix.os }}
53-
strategy:
54-
fail-fast: false
55-
matrix:
56-
os: [macos-latest, ubuntu-latest, windows-latest]
57-
steps:
58-
- uses: actions/checkout@v4
59-
with:
60-
fetch-depth: 0
61-
persist-credentials: false
62-
- uses: actions/setup-python@v5
63-
with:
64-
cache: pip
65-
cache-dependency-path: "pyproject.toml"
66-
python-version: '3.13t'
67-
- name: Build LibCST
68-
run: |
69-
# Install build-system.requires dependencies
70-
pip install setuptools setuptools-scm setuptools-rust wheel
71-
# Jupyter is annoying to install on free-threaded Python
72-
pip install -e .[dev-without-jupyter]
73-
- name: Native Parser Tests
74-
# TODO: remove when native modules declare free-threaded support
75-
env:
76-
PYTHON_GIL: '0'
77-
run: |
78-
python -m coverage run -m libcst.tests
79-
- name: Pure Parser Tests
80-
env:
81-
COVERAGE_FILE: .coverage.pure
82-
LIBCST_PARSER_TYPE: pure
83-
run: |
84-
python -m coverage run -m libcst.tests
85-
- name: Coverage
86-
run: |
87-
python -m coverage combine .coverage.pure
88-
python -m coverage report
89-
46+
hatch run test:coverage combine .coverage.pure
47+
hatch run test:coverage report
9048
9149
# Run linters
9250
lint:
@@ -139,7 +97,7 @@ jobs:
13997
- name: Install hatch
14098
run: pip install -U hatch
14199
- uses: ts-graphviz/setup-graphviz@v2
142-
- run: hatch run docs
100+
- run: hatch run docs:docs
143101
- name: Archive Docs
144102
uses: actions/upload-artifact@v4
145103
with:

libcst/codemod/_cli.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@
88
"""
99

1010
import difflib
11+
import functools
1112
import os.path
1213
import re
1314
import subprocess
1415
import sys
1516
import time
1617
import traceback
17-
from concurrent.futures import as_completed, Executor, ProcessPoolExecutor
18+
from concurrent.futures import as_completed, Executor
1819
from copy import deepcopy
1920
from dataclasses import dataclass, replace
2021
from multiprocessing import cpu_count
2122
from pathlib import Path
22-
from typing import Any, AnyStr, cast, Dict, List, Optional, Sequence, Union
23+
from typing import Any, AnyStr, Callable, cast, Dict, List, Optional, Sequence, Union
2324

2425
from libcst import parse_module, PartialParserConfig
2526
from libcst.codemod._codemod import Codemod
@@ -608,14 +609,20 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
608609
python_version=python_version,
609610
)
610611

611-
pool_impl: type[Executor]
612+
pool_impl: Callable[[], Executor]
612613
if total == 1 or jobs == 1:
613614
# Simple case, we should not pay for process overhead.
614615
# Let's just use a dummy synchronous executor.
615616
jobs = 1
616617
pool_impl = DummyExecutor
618+
elif getattr(sys, "_is_gil_enabled", lambda: False)(): # pyre-ignore[16]
619+
from concurrent.futures import ThreadPoolExecutor
620+
621+
pool_impl = functools.partial(ThreadPoolExecutor, max_workers=jobs)
617622
else:
618-
pool_impl = ProcessPoolExecutor
623+
from concurrent.futures import ProcessPoolExecutor
624+
625+
pool_impl = functools.partial(ProcessPoolExecutor, max_workers=jobs)
619626
# Warm the parser, pre-fork.
620627
parse_module(
621628
"",
@@ -631,7 +638,7 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
631638
warnings: int = 0
632639
skips: int = 0
633640

634-
with pool_impl(max_workers=jobs) as executor: # type: ignore
641+
with pool_impl() as executor: # type: ignore
635642
args = [
636643
{
637644
"transformer": transform,

libcst/codemod/_dummy_pool.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ class DummyExecutor(Executor):
2222
Synchronous dummy `concurrent.futures.Executor` analogue.
2323
"""
2424

25-
def __init__(self, max_workers: Optional[int] = None) -> None:
26-
pass
27-
2825
def submit(
2926
self,
3027
fn: Callable[Params, Return],

native/libcst/src/py.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::nodes::traits::py::TryIntoPy;
77
use pyo3::prelude::*;
88

9-
#[pymodule]
9+
#[pymodule(gil_used = false)]
1010
#[pyo3(name = "native")]
1111
pub fn libcst_native(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
1212
#[pyfn(m)]

pyproject.toml

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,6 @@ classifiers = [
2020
requires-python = ">=3.9"
2121
dependencies = ["pyyaml>=5.2"]
2222

23-
[project.optional-dependencies]
24-
dev = [
25-
"libcst[dev-without-jupyter]",
26-
"jupyter>=1.0.0",
27-
"nbsphinx>=0.4.2",
28-
]
29-
dev-without-jupyter = [
30-
"black==25.1.0",
31-
"coverage[toml]>=4.5.4",
32-
"build>=0.10.0",
33-
"fixit==2.1.0",
34-
"flake8==7.2.0",
35-
"Sphinx>=5.1.1",
36-
"hypothesis>=4.36.0",
37-
"hypothesmith>=0.0.4",
38-
"maturin>=1.7.0,<1.8",
39-
"prompt-toolkit>=2.0.9",
40-
"pyre-check==0.9.18; platform_system != 'Windows'",
41-
"setuptools_scm>=6.0.1",
42-
"sphinx-rtd-theme>=0.4.3",
43-
"ufmt==2.8.0",
44-
"usort==1.0.8.post1",
45-
"setuptools-rust>=1.5.2",
46-
"slotscheck>=0.7.1",
47-
"jinja2==3.1.6",
48-
]
49-
5023
[project.urls]
5124
Documentation = "https://libcst.readthedocs.io/en/latest/"
5225
Github = "https://github.com/Instagram/LibCST"
@@ -63,10 +36,26 @@ show_missing = true
6336
skip_covered = true
6437

6538
[tool.hatch.envs.default]
66-
features = ["dev"]
39+
installer = "uv"
40+
dependencies = [
41+
"black==25.1.0",
42+
"coverage[toml]>=4.5.4",
43+
"build>=0.10.0",
44+
"fixit==2.1.0",
45+
"flake8==7.2.0",
46+
"hypothesis>=4.36.0",
47+
"hypothesmith>=0.0.4",
48+
"maturin>=1.7.0,<1.8",
49+
"prompt-toolkit>=2.0.9",
50+
"pyre-check==0.9.18; platform_system != 'Windows'",
51+
"setuptools_scm>=6.0.1",
52+
"ufmt==2.8.0",
53+
"usort==1.0.8.post1",
54+
"setuptools-rust>=1.5.2",
55+
"slotscheck>=0.7.1",
56+
]
6757

6858
[tool.hatch.envs.default.scripts]
69-
docs = "sphinx-build -ab html docs/source docs/build"
7059
fixtures = ["python scripts/regenerate-fixtures.py", "git diff --exit-code"]
7160
format = "ufmt format libcst scripts"
7261
lint = [
@@ -78,6 +67,22 @@ lint = [
7867
test = ["python --version", "python -m coverage run -m libcst.tests"]
7968
typecheck = ["pyre --version", "pyre check"]
8069

70+
[tool.hatch.envs.docs]
71+
extra-dependencies = [
72+
"Sphinx>=5.1.1",
73+
"sphinx-rtd-theme>=0.4.3",
74+
"jupyter>=1.0.0",
75+
"nbsphinx>=0.4.2",
76+
"jinja2==3.1.6",
77+
]
78+
[tool.hatch.envs.docs.scripts]
79+
docs = "sphinx-build -ab html docs/source docs/build"
80+
81+
[tool.hatch.envs.test]
82+
dependencies = ["coverage[toml]>=4.5.4"]
83+
[tools.hatch.envs.test.scripts]
84+
test = ["python --version", "python -m coverage run -m libcst.tests"]
85+
8186
[tool.slotscheck]
8287
exclude-modules = '^libcst\.(testing|tests)'
8388

0 commit comments

Comments
 (0)