Skip to content
Merged
Show file tree
Hide file tree
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
56 changes: 7 additions & 49 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"]
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install hatch
run: pip install -U hatch
- uses: actions/checkout@v4
with:
fetch-depth: 0
Expand All @@ -24,9 +29,6 @@ jobs:
cache: pip
cache-dependency-path: "pyproject.toml"
python-version: ${{ matrix.python-version }}
- name: Install hatch
run: |
pip install -U hatch
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
Expand All @@ -44,50 +46,6 @@ jobs:
hatch run coverage combine .coverage.pure
hatch run coverage report

# TODO:
# merge into regular CI once hatch has support for creating environments on
# the free-threaded build: https://github.com/pypa/hatch/issues/1931
free-threaded-tests:
name: "test (${{ matrix.os }}, 3.13t)"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@v5
with:
cache: pip
cache-dependency-path: "pyproject.toml"
python-version: '3.13t'
- name: Build LibCST
run: |
# Install build-system.requires dependencies
pip install setuptools setuptools-scm setuptools-rust wheel
# Jupyter is annoying to install on free-threaded Python
pip install -e .[dev-without-jupyter]
- name: Native Parser Tests
# TODO: remove when native modules declare free-threaded support
env:
PYTHON_GIL: '0'
run: |
python -m coverage run -m libcst.tests
- name: Pure Parser Tests
env:
COVERAGE_FILE: .coverage.pure
LIBCST_PARSER_TYPE: pure
run: |
python -m coverage run -m libcst.tests
- name: Coverage
run: |
python -m coverage combine .coverage.pure
python -m coverage report


# Run linters
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -139,7 +97,7 @@ jobs:
- name: Install hatch
run: pip install -U hatch
- uses: ts-graphviz/setup-graphviz@v2
- run: hatch run docs
- run: hatch run docs:docs
- name: Archive Docs
uses: actions/upload-artifact@v4
with:
Expand Down
17 changes: 12 additions & 5 deletions libcst/codemod/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@
"""

import difflib
import functools
import os.path
import re
import subprocess
import sys
import time
import traceback
from concurrent.futures import as_completed, Executor, ProcessPoolExecutor
from concurrent.futures import as_completed, Executor
from copy import deepcopy
from dataclasses import dataclass
from multiprocessing import cpu_count
from pathlib import Path
from typing import AnyStr, cast, Dict, List, Optional, Sequence, Type, Union
from typing import AnyStr, Callable, cast, Dict, List, Optional, Sequence, Type, Union
from warnings import warn

from libcst import parse_module, PartialParserConfig
Expand Down Expand Up @@ -624,14 +625,20 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
python_version=python_version,
)

pool_impl: type[Executor]
pool_impl: Callable[[], Executor]
if total == 1 or jobs == 1:
# Simple case, we should not pay for process overhead.
# Let's just use a dummy synchronous executor.
jobs = 1
pool_impl = DummyExecutor
elif getattr(sys, "_is_gil_enabled", lambda: False)(): # pyre-ignore[16]
from concurrent.futures import ThreadPoolExecutor

pool_impl = functools.partial(ThreadPoolExecutor, max_workers=jobs)
else:
pool_impl = ProcessPoolExecutor
from concurrent.futures import ProcessPoolExecutor

pool_impl = functools.partial(ProcessPoolExecutor, max_workers=jobs)
# Warm the parser, pre-fork.
parse_module(
"",
Expand All @@ -650,7 +657,7 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901
deepcopy(transform.context.scratch) if isinstance(transform, Codemod) else {}
)

with pool_impl(max_workers=jobs) as executor: # type: ignore
with pool_impl() as executor: # type: ignore
try:
futures = [
executor.submit(
Expand Down
3 changes: 0 additions & 3 deletions libcst/codemod/_dummy_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ class DummyExecutor(Executor):
Synchronous dummy `concurrent.futures.Executor` analogue.
"""

def __init__(self, max_workers: Optional[int] = None) -> None:
pass

def submit(
self,
fn: Callable[Params, Return],
Expand Down
2 changes: 1 addition & 1 deletion native/libcst/src/py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use crate::nodes::traits::py::TryIntoPy;
use pyo3::prelude::*;

#[pymodule]
#[pymodule(gil_used = false)]
#[pyo3(name = "native")]
pub fn libcst_native(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
#[pyfn(m)]
Expand Down
61 changes: 32 additions & 29 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,9 @@ classifiers = [
"Typing :: Typed",
]
requires-python = ">=3.9"
dependencies = ["pyyaml>=5.2"]

[project.optional-dependencies]
dev = [
"libcst[dev-without-jupyter]",
"jupyter>=1.0.0",
"nbsphinx>=0.4.2",
]
dev-without-jupyter = [
"black==25.1.0",
"coverage[toml]>=4.5.4",
"build>=0.10.0",
"fixit==2.1.0",
"flake8==7.2.0",
"Sphinx>=5.1.1",
"hypothesis>=4.36.0",
"hypothesmith>=0.0.4",
"maturin>=1.7.0,<1.8",
"prompt-toolkit>=2.0.9",
"pyre-check==0.9.18; platform_system != 'Windows'",
"setuptools_scm>=6.0.1",
"sphinx-rtd-theme>=0.4.3",
"ufmt==2.8.0",
"usort==1.0.8.post1",
"setuptools-rust>=1.5.2",
"slotscheck>=0.7.1",
"jinja2==3.1.6",
dependencies = [
"pyyaml>=5.2; python_version < '3.13'",
"pyyaml-ft; python_version >= '3.13'",
]

[project.urls]
Expand All @@ -63,10 +39,26 @@ show_missing = true
skip_covered = true

[tool.hatch.envs.default]
features = ["dev"]
installer = "uv"
dependencies = [
"black==25.1.0",
"coverage[toml]>=4.5.4",
"build>=0.10.0",
"fixit==2.1.0",
"flake8==7.2.0",
"hypothesis>=4.36.0",
"hypothesmith>=0.0.4",
"maturin>=1.7.0,<1.8",
"prompt-toolkit>=2.0.9",
"pyre-check==0.9.18; platform_system != 'Windows'",
"setuptools_scm>=6.0.1",
"ufmt==2.8.0",
"usort==1.0.8.post1",
"setuptools-rust>=1.5.2",
"slotscheck>=0.7.1",
]

[tool.hatch.envs.default.scripts]
docs = "sphinx-build -ab html docs/source docs/build"
fixtures = ["python scripts/regenerate-fixtures.py", "git diff --exit-code"]
format = "ufmt format libcst scripts"
lint = [
Expand All @@ -78,6 +70,17 @@ lint = [
test = ["python --version", "python -m coverage run -m libcst.tests"]
typecheck = ["pyre --version", "pyre check"]

[tool.hatch.envs.docs]
extra-dependencies = [
"Sphinx>=5.1.1",
"sphinx-rtd-theme>=0.4.3",
"jupyter>=1.0.0",
"nbsphinx>=0.4.2",
"jinja2==3.1.6",
]
[tool.hatch.envs.docs.scripts]
docs = "sphinx-build -ab html docs/source docs/build"

[tool.slotscheck]
exclude-modules = '^libcst\.(testing|tests)'

Expand Down
Loading