## 1. Project Overview & Quickstart (astral-sh/ruff)

## File: crates/ty_vendored/ty_extensions/README.md

The `build.rs` packages the stubs in this directory as the vendored
`ty_extensions` package under `vendor/typeshed/stdlib`.

---

## File: crates/ty_vendored/README.md

# Vendored types for the stdlib

This crate vendors [typeshed](https://github.com/python/typeshed)'s stubs for the standard library. The vendored stubs can be found in `crates/ty_vendored/vendor/typeshed`. The file `crates/ty_vendored/vendor/typeshed/source_commit.txt` tells you the typeshed commit that our vendored stdlib stubs currently correspond to.

The typeshed stubs are updated every two weeks via an automated PR using the `sync_typeshed.yaml` workflow in the `.github/workflows` directory. This workflow can also be triggered at any time via [workflow dispatch](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow#running-a-workflow).

---

## File: crates/ty_test/README.md

# Writing type-checking / type-inference tests

Any Markdown file can be a test suite.

In order for it to be run as one, `ty_test::run` must be called with its path; see
`crates/ty_python_semantic/tests/mdtest.rs` for an example that treats all Markdown files
under a certain directory as test suites.

A Markdown test suite can contain any number of tests. A test consists of one or more embedded
"files", each defined by a triple-backticks fenced code block. The code block must have a tag string
specifying its language. We currently support `py` (Python files), `pyi` (type stub files), and
`ipynb` (Jupyter notebook files), as well as [typeshed `VERSIONS`] files and `toml` for
configuration.

The simplest possible test suite consists of just a single test, with a single embedded file:

````markdown

```py
reveal_type(1)  # revealed: Literal[1]
```
````

When running this test, the mdtest framework will write a file with these contents to the default
file path (`/src/mdtest_snippet.py`) in its in-memory file system, run a type check on that file,
and then match the resulting diagnostics with the assertions in the test. Assertions are in the form
of Python comments. If all diagnostics and all assertions are matched, the test passes; otherwise,
it fails.



See actual example mdtest suites in
[`crates/ty_python_semantic/resources/mdtest`](https://github.com/astral-sh/ruff/tree/main/crates/ty_python_semantic/resources/mdtest).

## Assertions

Three kinds of assertions are supported: `# revealed:` (shown above), `# error:`, and
[`# snapshot`](#diagnostic-snapshotting).

### Assertion kinds

#### revealed

A `# revealed:` assertion should always be paired with a call to the `reveal_type` utility, which
reveals (via a diagnostic) the inferred type of its argument (which can be any expression). The text
after `# revealed:` must match exactly with the displayed form of the revealed type of that
expression.

The `reveal_type` function can be imported from the `typing` standard library module (or, for older
Python versions, from the `typing_extensions` pseudo-standard-library module[^extensions]):

```py
from typing import reveal_type

reveal_type("foo")  # revealed: Literal["foo"]
```

For convenience, type checkers also pretend that `reveal_type` is a built-in, so that this import is
not required. Using `reveal_type` without importing it issues a diagnostic warning that it was used
without importing it, in addition to the diagnostic revealing the type of the expression.

The `# revealed:` assertion must always match a revealed-type diagnostic, and will also match the
undefined-reveal diagnostic, if present, so it's safe to use `reveal_type` in tests either with or
without importing it. (Style preference is to not import it in tests, unless specifically testing
something about the behavior of importing it.)

#### error

A comment beginning with `# error:` is an assertion that a type checker diagnostic will be emitted,
with text span starting on that line. The matching can be narrowed in three ways:

- `# error: [invalid-assignment]` requires that the matched diagnostic have the rule code
    `invalid-assignment`. (The square brackets are required.)
- `# error: "Some text"` requires that the diagnostic's full message contain the text `Some text`.
    (The double quotes are required in the assertion comment; they are not part of the matched text.)
- `# error: 8 [rule-code]` or `# error: 8 "Some text"` additionally requires that the matched
    diagnostic's text span begins on column 8 (one-indexed) of this line.

Assertions must contain either a rule code or a contains-text, or both, and may optionally also
include a column number. They must come in order: first column, if present; then rule code, if
present; then contains-text, if present. For example, an assertion using all three would look like
`# error: 8 [invalid-assignment] "Some text"`.

Error assertions in tests intended to test type checker semantics should primarily use rule-code
assertions, with occasional contains-text assertions where needed to disambiguate or validate some
details of the diagnostic message.

### Assertion locations

An assertion comment may be a line-trailing comment, in which case it applies to the line it is on:

```py
x: str = 1  # error: [invalid-assignment]
```

Or it may be a comment on its own line, in which case it applies to the next line that does not
contain an assertion comment:

```py
# error: [invalid-assignment]
x: str = 1
```

Multiple assertions applying to the same line may be stacked:

```py
# error: [invalid-assignment]
# revealed: Literal[1]
x: str = reveal_type(1)
```

Intervening empty lines or non-assertion comments are not allowed; an assertion stack must be one
assertion per line, immediately following each other, with the line immediately following the last
assertion as the line of source code on which the matched diagnostics are emitted.

## Literate style

If multiple code blocks (without an explicit path, see below) are present in a single test, they will
be merged into a single file in the order they appear in the Markdown file. This allows for tests that
interleave code and explanations:

````markdown
# My literate test

This first snippet here:

```py
from typing import Literal

def f(x: Literal[1]):
    pass
```

will be merged with this second snippet here, i.e. `f` is defined here:

```py
f(2)  # error: [invalid-argument-type]
```
````

## Diagnostic Snapshotting

Inline snapshots store the rendered diagnostics directly in the Markdown file. The test compares
these diagnostics with the contents of the corresponding `snapshot` block.

Add `# snapshot` or `# snapshot: <code>` to the lines you want to snapshot. The unqualified form
matches one unmatched diagnostic on the line to which the assertion applies; the qualified form
matches one with the given rule or diagnostic code.

For example, an mdtest with a generated snapshot block looks like this:

````markdown
# Snapshot example

```py
x: int = "a"  # snapshot: invalid-assignment
y: int = "b"  # snapshot
```

Some explanatory prose can go here.

```snapshot
error[invalid-assignment]: Object of type `Literal["a"]` is not assignable to `int`
 --> src/mdtest_snippet.py:1:10
  |
1 | x: int = "a"  # snapshot: invalid-assignment
  |    ---   ^^^ Incompatible value of type `Literal["a"]`
  |    |
  |    Declared type


error[invalid-assignment]: Object of type `Literal["b"]` is not assignable to `int`
 --> src/mdtest_snippet.py:2:10
  |
2 | y: int = "b"  # snapshot
  |    ---   ^^^ Incompatible value of type `Literal["b"]`
  |    |
  |    Declared type
```
````

Snapshot assertions follow the same placement rules as other inline assertions.

To insert or rewrite inline snapshots automatically, run mdtest with
`MDTEST_UPDATE_SNAPSHOTS=1` set. This generates a `snapshot` block after each Python code block
containing snapshot assertions, or updates the existing block for that code. The test path is relative to
`crates/ty_python_semantic/resources/mdtest`. For example:

```sh
MDTEST_UPDATE_SNAPSHOTS=1 cargo test -p ty_python_semantic --test mdtest -- diagnostics/missing_argument.md
```

Or with a test-name filter:

```sh
MDTEST_UPDATE_SNAPSHOTS=1 MDTEST_TEST_FILTER="Missing argument diagnostics" cargo test -p ty_python_semantic --test mdtest
```

External `` snapshots are still supported, but
inline snapshots are generally preferred for new tests.

## Expected panics

It is possible to write tests that expect the type checker to panic during checking. Ideally, we'd fix those panics
but being able to add regression tests even before is useful.

To mark a test as expecting a panic, add an HTML comment like this:

```markdown
<!-- expect-panic: assertion `left == right` failed: Can't merge cycle heads -->
```

The text after `expect-panic:` is a substring that must appear in the panic message. The message is optional;
but it is recommended to avoid false positives.

## Multi-file tests

Some tests require multiple files, with imports from one file into another. For this purpose,
tests can specify explicit file paths in a separate line before the code block (`b.py` below):

````markdown
```py
from b import C
reveal_type(C)  # revealed: Literal[C]
```

`b.py`:

```py
class C: pass
```
````

Relative file names are always relative to the "workspace root", which is also an import root (that
is, the equivalent of a runtime entry on `sys.path`).

The default workspace root is `/src/`. Currently it is not possible to customize this in a test, but
this is a feature we will want to add in the future.

So the above test creates two files, `/src/mdtest_snippet.py` and `/src/b.py`, and sets the workspace
root to `/src/`, allowing imports from `b.py` using the module name `b`.

## Multi-test suites

A single test suite (Markdown file) can contain multiple tests, by demarcating them using Markdown
header lines:

````markdown
# Same-file invalid assignment

```py
x: int = "foo"  # error: [invalid-assignment]
```

# Cross-file invalid assignment

```py
from b import y
x: int = y  # error: [invalid-assignment]
```

`b.py`:

```py
y = "foo"
```
````

This test suite contains two tests, one named "Same-file invalid assignment" and the other named
"Cross-file invalid assignment". The first test involves only a single embedded file, and the second
test involves two embedded files.

The tests are run independently, in independent in-memory file systems and with new ty
[Salsa](https://github.com/salsa-rs/salsa) databases. This means that each is a from-scratch run of
the type checker, with no data persisting from any previous test.

It is possible to filter to individual tests within a single markdown file using the
`MDTEST_TEST_FILTER` environment variable. This variable will match any tests which contain the
value as a case-sensitive substring in its name. An example test name is
`unpacking.md - Unpacking - Tuple - Multiple assignment`, which contains the name of the markdown
file and its parent headers joined together with hyphens.

## Structured test suites

Markdown headers can also be used to group related tests within a suite:

````markdown
# Literals

## Numbers

### Integer

```py
reveal_type(1)  # revealed: Literal[1]
```

### Float

```py
reveal_type(1.0)  # revealed: float
```

## Strings

```py
reveal_type("foo")  # revealed: Literal["foo"]
```
````

This test suite contains three tests, named "Literals - Numbers - Integer", "Literals - Numbers -
Float", and "Literals - Strings".

A header-demarcated section must either be a test or a grouping header; it cannot be both. That is,
a header section can either contain embedded files (making it a test), or it can contain more
deeply-nested headers (headers with more `#`), but it cannot contain both.

## Configuration

The test framework supports a TOML-based configuration format, which is a subset of the full ty
configuration format. This configuration can be specified in fenced code blocks with `toml` as the
language tag:

````markdown
```toml
[environment]
python-version = "3.10"
```
````

This configuration will apply to all tests in the same section, and all nested sections within that
section. Nested sections can override configurations from their parent sections.

To enable logging in an mdtest, set `log = true` at the top level of the TOML block.
See [`MarkdownTestConfig`](https://github.com/astral-sh/ruff/blob/main/crates/ty_test/src/config.rs)
for the full list of supported configuration options.

### Testing with external dependencies

Tests can specify external Python dependencies using a `[project]` section in the TOML configuration.
This allows testing code that uses third-party libraries like `pydantic`, `numpy`, etc.

It is recommended to specify exact versions of packages to ensure reproducibility. The specified
Python version and platform are required for tests with external dependencies, as they are used
during package resolution.

````markdown
```toml
[environment]
python-version = "3.13"
python-platform = "linux"

[project]
dependencies = ["pydantic==2.12.2"]
```

```py
import pydantic

# use pydantic in the test
```
````

When a test has dependencies:

1. The test framework creates a `pyproject.toml` in a temporary directory.
1. The lockfile (e.g., `pydantic.lock`) is copied to the temporary directory.
1. Runs `uv sync --locked` to install the dependencies.
1. Copies the installed packages from the virtual environment's `site-packages` directory into the test's
    in-memory filesystem.
1. Configures the type checker to use these packages.

**Note**: This feature requires `uv` to be installed and available in your `PATH`. The dependencies
are installed fresh for each test that specifies them, so tests with many dependencies may be slower
to run.

To test dependency checks with these packages, also
[supply dependency metadata](#supplying-dependency-metadata).

#### Lockfiles

Each `.md` file with external dependencies has a corresponding `.lock` file of the same name.
This ensures reproducible test results across different environments and CI runs.

When adding new tests or if you need to update dependency versions, regenerate the lockfiles
with either of the following commands:

```bash
# Using the Python runner:
uv run crates/ty_python_semantic/mdtest.py -e external/

# Using `cargo`:
MDTEST_EXTERNAL=1 MDTEST_UPGRADE_LOCKFILES=1 cargo test -p ty_python_semantic --test mdtest mdtest__external
```

After regenerating, commit the updated lockfiles to the repository.

### Specifying a custom typeshed

Some tests will need to override the default typeshed with custom files. The `[environment]`
configuration option `typeshed` can be used to do this:

````markdown
```toml
[environment]
typeshed = "/typeshed"
```
````

For more details, take a look at the [custom-typeshed Markdown test].

### Mocking a Python environment

Mdtest supports mocking a Python environment for a specific test at an arbitrary location, again
using the `[environment]` configuration option:

````markdown
```toml
[environment]
python = ".venv"
```
````

Mdtest also makes it easy to write Python packages to the mock environment's `site-packages`
directory using the `` magic path segment. This would otherwise be hard, due
to the fact that the `site-packages` subdirectory in an environment is located at a different
relative path depending on the platform. In the following test, mdtest will write the Python file to
`.venv/Lib/site-packages/foo.py` in its in-memory filesystem used for the test if the test is being
executed on Windows, and `.venv/lib/python3.13/site-packages/foo.py` otherwise:

````markdown
```toml
[environment]
python = ".venv"
python-version = "3.13"
```

`.venv/<path-to-site-packages>/foo.py`:

```py
X = 1
```
````

The same placeholder can be used in `environment.extra-paths`:

````markdown
```toml
[environment]
extra-paths = ["/.venv/"]
```
````

### Supplying dependency metadata

Tests for dependency checks can supply declarations and module ownership through a
`[dependency-metadata]` fixture. This does not invoke uv or install packages. The imported modules
can come from a [mock Python environment](#mocking-a-python-environment) or from
[external dependencies](#testing-with-external-dependencies) installed through `[project]`.

The two sections are independent: `[project]` supplies installed modules, while
`[dependency-metadata]` describes the declarations and ownership used by dependency checks.
Automatically deriving dependency metadata from `[project]` is deferred to future work.
For now, tests with external dependencies must also supply `[dependency-metadata]` to exercise
dependency checks. Explicit metadata also lets tests model nested projects and ambiguous module
ownership using only mocked modules.

````markdown
```toml
[environment]
python = "/.venv"

[dependency-metadata]
projects = [{ path = "/src", distribution = "app", dependencies = ["requests"] }]

[dependency-metadata.distributions]
app = { name = "my-project" }
requests = { name = "requests" }

[dependency-metadata.module-owners]
my_project = ["app"]
requests = ["requests"]
```
````

Distribution keys are opaque identifiers, and each distribution's `name` is used in diagnostics.
A project can also specify `group-dependencies`. An editable distribution can specify an absolute
`editable-path` in the test filesystem. Module-owner keys are validated as absolute Python module
names, and their values list the distributions that provide each module.

The fixture follows the same section inheritance as other mdtest configuration. Standalone PEP 723
scripts do not inherit this project metadata. Instead, a script can supply its own fixture under
`[tool.ty.dependency-metadata]` in its inline metadata block. Each project entry uses the script's
exact absolute path, not its containing directory:

````markdown
`script.py`:

```py
# /// script
# dependencies = ["requests"]
# [tool.ty.dependency-metadata]
# projects = [{ path = "/src/script.py", dependencies = ["requests"] }]
# [tool.ty.dependency-metadata.distributions]
# requests = { name = "requests" }
# [tool.ty.dependency-metadata.module-owners]
# requests = ["requests"]
# ///

import requests
```
````

`tool.ty.dependency-metadata` is available only in tests. The script's `dependencies` field does not
automatically populate the fixture, and a script without a fixture has no dependency metadata.

## Documentation of tests

Arbitrary Markdown syntax (including of course normal prose paragraphs) is permitted (and ignored by
the test framework) between fenced code blocks. This permits natural documentation of
why a test exists, and what it intends to assert:

````markdown
Assigning a string to a variable annotated as `int` is not permitted:

```py
x: int = "foo"  # error: [invalid-assignment]
```
````

## Running the tests

All Markdown-based tests are executed in a normal `cargo test` / `cargo nextest run` invocation. If you want to run the Markdown tests
*only*, you can filter the tests using `mdtest`:

```bash
cargo test -p ty_python_semantic -- mdtest
```

Alternatively, you can use the `mdtest.py` runner which has a watch mode that will re-run corresponding tests when Markdown files change, and recompile automatically when Rust code changes:

```bash
uv run crates/ty_python_semantic/mdtest.py
```

## Planned features

There are some designed features that we intend for the test framework to have, but have not yet
implemented:

### Multi-line diagnostic assertions

We may want to be able to assert that a diagnostic spans multiple lines, and to assert the columns it
begins and/or ends on. The planned syntax for this will use `<<<` and `>>>` to mark the start and end lines for
an assertion:

```py
(3  # error: 2 [unsupported-operands] <<<
  +
 "foo")  # error: 6 >>>
```

The column assertion `6` on the ending line should be optional.

In cases of overlapping such assertions, resolve ambiguity using more angle brackets: `<<<<` begins
an assertion ended by `>>>>`, etc.

### Configuring search paths and kinds

The ty TOML configuration format hasn't been finalized, and we may want to implement
support in the test framework for configuring search paths before it is designed. If so, we can
define some configuration options for now under the `[tests]` namespace. In the future, perhaps
some of these can be replaced by real ty configuration options; some or all may also be
kept long-term as test-specific options.

Some configuration options we will want to provide:

- We should be able to configure the default workspace root to something other than `/src/` using a
    `workspace-root` configuration option.

- We should be able to add a third-party root using the `third-party-root` configuration option.

- We may want to add additional configuration options for setting additional search path kinds.

Paths for `workspace-root` and `third-party-root` must be absolute.

Relative embedded-file paths are relative to the workspace root, even if it is explicitly set to a
non-default value using the `workspace-root` config.

### I/O errors

We could use an `error=` configuration option in the tag string to make an embedded file cause an
I/O error on read.

### Asserting on full diagnostic output

> [!NOTE]
> [Inline diagnostic snapshots](#diagnostic-snapshotting) use `# snapshot` assertions. The proposal
> below would allow full output assertions without an inline assertion.

The inline comment diagnostic assertions are useful for making quick, readable assertions about
diagnostics in a particular location. But sometimes we will want to assert on the full diagnostic
output of checking an embedded Python file. Or sometimes (see “incremental tests” below) we will
want to assert on diagnostics in a file, without impacting the contents of that file by changing a
comment in it. In these cases, a Python code block in a test could be followed by a fenced code
block with language `output`; this would contain the full diagnostic output for the default file or
for an explicitly specified file, as described below:

````markdown
# full output

```py
x = 1
reveal_type(x)
```

This is just an example, not a proposal that ty would ever actually output diagnostics in
precisely this format:

```output
mdtest_snippet.py, line 2, col 1: revealed type is 'Literal[1]'
```
````

We will want to build tooling to automatically capture and update these “full diagnostic output”
blocks, when tests are run in an update-output mode (probably specified by an environment variable.)

By default, an `output` block will specify diagnostic output for the file
`<workspace-root>/mdtest_snippet.py`. An `output` block can be prefixed by a
``&lt;path>`:` label as usual, to explicitly specify the Python file for which it asserts
diagnostic output.

It is an error for an `output` block to exist, if there is no `py` or `python` block in the same
test for the same file path.

### Incremental tests

Some tests should validate incremental checking, by initially creating some files, checking them,
and then modifying/adding/deleting files and checking again.

We should add the capability to create an incremental test by using the `stage=` option on some
fenced code blocks in the test:

````markdown
# Incremental

## modify a file

Initial file contents:

```py
from b import x
reveal_type(x)
```

`b.py`:

```py
x = 1
```

Initial expected output for the unnamed file:

```output
/src/mdtest_snippet.py, line 1, col 1: revealed type is 'Literal[1]'
```

Now in our first incremental stage, modify the contents of `b.py`:

`b.py`:

```py stage=1
# b.py
x = 2
```

And this is our updated expected output for the unnamed file at stage 1:

```output stage=1
/src/mdtest_snippet.py, line 1, col 1: revealed type is 'Literal[2]'
```

(One reason to use full-diagnostic-output blocks in this test is that updating inline-comment
diagnostic assertions for `mdtest_snippet.py` would require specifying new contents for
`mdtest_snippet.py` in stage 1, which we don't want to do in this test.)
````

It will be possible to provide any number of stages in an incremental test. If a stage re-specifies
a filename that was specified in a previous stage (or the initial stage), that file is modified. A
new filename appearing for the first time in a new stage will create a new file. To delete a
previously created file, specify that file with the tag `delete` in its tag string (in this case, it
is an error to provide non-empty contents). Any previously-created files that are not re-specified
in a later stage continue to exist with their previously-specified contents, and are not "touched".

All stages should be run in order, incrementally, and then the final state should also be re-checked
cold, to validate equivalence of cold and incremental check results.

[^extensions]: `typing-extensions` is a third-party module, but typeshed, and thus type checkers
    also, treat it as part of the standard library.

[custom-typeshed markdown test]: ../ty_python_semantic/resources/mdtest/mdtest_custom_typeshed.md
[typeshed `versions`]: https://github.com/python/typeshed/blob/c546278aae47de0b2b664973da4edb613400f6ce/stdlib/VERSIONS#L1-L18%3E

---

## File: crates/ty_python_semantic/resources/mdtest/doc/public_type_undeclared_symbols.md

# Public type of undeclared symbols

## Summary

A strict application of the [gradual guarantee] would suggest that all assignments to an unannotated
attribute should be allowed; this could be implemented by unioning all such attributes' inferred
types with `Unknown`. However, in practice this requires too many annotations to achieve sound
typing, and we can heuristically pick the "right" type for unannotated attributes most of the time.

## Promotion

We promote the inferred type of an unannotated attribute to our best guess of its intended public
type. For example, we promote literal types to their nominal supertype, because it is unlikely the
author intended the `value` attribute to always hold the literal `0`:

```py
class Counter:
    def __init__(self) -> None:
        self.value = 0

reveal_type(Counter().value)  # revealed: int
```

Class literals stored in inferred attributes are widened when accessed through an instance. A
subclass can override an undeclared class attribute, so a method that accesses the attribute through
`self` cannot assume that it still holds the original class object. Direct access on a specific
class object remains exact:

```py
from typing import Final, NewType, TypeVar, final
from typing_extensions import assert_never

class Response: ...
class HtmlResponse(Response): ...

class TestResponse:
    response_class = Response
    response_classes = (Response,)

    def check(self) -> None:
        reveal_type(self.response_class)  # revealed: type[Response]
        reveal_type(self.response_classes)  # revealed: tuple[type[Response]]
        reveal_type(self.response_class == Response)  # revealed: bool

        if self.response_class == Response:
            true_branch: int = "not an int"  # error: [invalid-assignment]
        else:
            false_branch: int = "not an int"  # error: [invalid-assignment]

class TestHtmlResponse(TestResponse):
    response_class = HtmlResponse

reveal_type(TestResponse.response_class)  # revealed: <class 'Response'>
reveal_type(TestResponse.response_classes)  # revealed: tuple[<class 'Response'>]

def check_type(response: type[TestResponse]) -> None:
    reveal_type(response.response_class)  # revealed: type[Response]

T = TypeVar("T", bound=TestResponse)

def check_typevar(response: T) -> None:
    reveal_type(response.response_class)  # revealed: type[Response]

TClass = TypeVar("TClass", bound=type[TestResponse])

def check_typevar_class(response: TClass) -> None:
    reveal_type(response.response_class)  # revealed: type[Response]

@final
class FinalTestResponse:
    response_class = Response

    @classmethod
    def check(cls) -> None:
        if cls.response_class is not Response:
            assert_never(cls.response_class)

TFinal = TypeVar("TFinal", bound=FinalTestResponse)

def check_final_typevar(response: type[TFinal]) -> None:
    reveal_type(response.response_class)  # revealed: <class 'Response'>

NewTestResponse = NewType("NewTestResponse", TestResponse)

def check_newtype(response: NewTestResponse) -> None:
    reveal_type(response.response_class)  # revealed: type[Response]

class ResponseMeta(type):
    response_class = Response

NewResponseMeta = NewType("NewResponseMeta", ResponseMeta)

def check_newtype_class(response: NewResponseMeta) -> None:
    reveal_type(response.response_class)  # revealed: type[Response]

class AnnotatedResponse:
    response_class: type[Response] = Response

    def check(self) -> None:
        reveal_type(self.response_class)  # revealed: type[Response]
        reveal_type(self.response_class == Response)  # revealed: bool

class FixedResponse:
    response_class: Final = Response

    def check(self) -> None:
        reveal_type(self.response_class)  # revealed: <class 'Response'>
        reveal_type(self.response_class == Response)  # revealed: Literal[True]
```

The same widening applies to undeclared instance attributes assigned in methods:

```py
class InstanceResponse: ...

class Wrapper:
    def __init__(self) -> None:
        self.response_class = InstanceResponse

reveal_type(Wrapper().response_class)  # revealed: type[InstanceResponse]
```

Widening distributes over unions of class literals:

```py
class UnionA: ...
class UnionB: ...

def get_flag() -> bool:
    return bool()

class EitherClass:
    value = UnionA if get_flag() else UnionB

reveal_type(EitherClass().value)  # revealed: type[UnionA | UnionB]
```

Module-level variables keep their narrow inferred type. In particular, class literals in an
invariant collection remain precise enough for exhaustive equality checks:

```py
class OffsetA: ...
class OffsetB: ...

classes = {"a": OffsetA, "b": OffsetB}

def choose(name: str) -> None:
    class_value = classes[name]
    if class_value == OffsetA:
        expected = 1
    elif class_value == OffsetB:
        expected = 2
    reveal_type(expected)  # revealed: Literal[1, 2]
```

## Widening of non-literal singleton types

It's similarly unlikely that an unannotated attribute initialized to a singleton type (like `None`)
is intended to always and only hold the value `None`. But unlike literal types, `None` doesn't have
an obvious candidate super-type to widen to. In this case, we do widen by unioning with `Unknown`:

```py
class Wrapper:
    value = None

wrapper = Wrapper()

reveal_type(wrapper.value)  # revealed: None | Unknown

wrapper.value = 1
```

In this example, the public type is `None | Unknown`, so we also catch uses that are incompatible
with `None`:

```py
def accepts_int(i: int) -> None:
    pass

def f(w: Wrapper) -> None:
    # This is fine
    v: int | None = w.value

    # This function call is incorrect, because `w.value` could be `None`. We therefore emit the following
    # error: "Argument to function `accepts_int` is incorrect: Expected `int`, found `None | Unknown`"
    c = accepts_int(w.value)
```

The same widening also applies to undeclared instance attributes that are only assigned inside
`__init__`:

```py
class InstanceWrapper:
    def __init__(self) -> None:
        self.value = None

reveal_type(InstanceWrapper().value)  # revealed: None | Unknown
```

## Declaring a wider type

Users can always opt in to a wider public type by adding annotations. For the `Wrapper` class, this
could be:

```py
class Wrapper:
    value: int | None = None

w = Wrapper()

# The following public type is now
# revealed: int | None
reveal_type(w.value)

# Incompatible assignments are now caught:
# error: "Object of type `Literal["a"]` is not assignable to attribute `value` of type `int | None`"
w.value = "a"
```

## Declaring a narrower type to avoid promotion

It's also possible to declare a narrower type to avoid promotion. For example, if we know that an
attribute will always hold one of two literal values, we may want to avoid promotion of the literal:

```py
from typing import Literal

class Constant:
    value: Literal[0, 1] = 0

# We would have promoted this to `int` without the explicit annotation:
reveal_type(Constant().value)  # revealed: Literal[0, 1]
```

This also works to avoid widening of singleton types, if for some reason you want an attribute that
can only ever hold that one singleton value:

```py
class NoneWrapper:
    value: None = None

reveal_type(NoneWrapper().value)  # revealed: None
```

## What is meant by 'public' type?

We apply different semantics depending on whether a symbol is accessed from the same scope in which
it was originally defined, or whether it is accessed from an external scope. External scopes will
see the symbol's "public type", which has been discussed above. But within the same scope the symbol
was defined in, we can often use a narrower literal type before promotion. For example:

```py
class Wrapper:
    value = 10

    # Type as seen from the same scope:
    reveal_type(value)  # revealed: Literal[10]

# Type as seen from another scope:
reveal_type(Wrapper.value)  # revealed: int
```

[gradual guarantee]: https://typing.python.org/en/latest/spec/concepts.html#the-gradual-guarantee

---

## File: crates/ty_python_semantic/resources/mdtest/doc/README.md

This directory contains user-facing documentation, but also doubles as an extended test suite that
makes sure that our documentation stays up to date.

---

## File: crates/ty_python_semantic/resources/README.md

Markdown files within the `mdtest/` subdirectory are tests of type inference and type checking;
executed by the `tests/mdtest.rs` integration test.

See `crates/ty_test/README.md` for documentation of this test format.

---

## File: crates/ty/docs/cli.md

# CLI Reference

## ty

An extremely fast Python type checker.

<h3 class="cli-reference">Usage</h3>

```
ty <COMMAND>
```

<h3 class="cli-reference">Commands</h3>

<dl class="cli-reference"><dt>[`ty check`](#ty-check)</dt><dd>Check a project for type errors</dd>
<dt>[`ty server`](#ty-server)</dt><dd>Start the language server</dd>
<dt>[`ty version`](#ty-version)</dt><dd>Display ty's version</dd>
<dt>[`ty explain`](#ty-explain)</dt><dd>Explain rules and other parts of ty</dd>
<dt>[`ty help`](#ty-help)</dt><dd>Print this message or the help of the given subcommand(s)</dd>
</dl>

## ty check

Check a project for type errors

<h3 class="cli-reference">Usage</h3>

```
ty check [OPTIONS] [PATH]...
```

<h3 class="cli-reference">Arguments</h3>

<dl class="cli-reference"><dt id="ty-check--paths">[`PATHS`](#ty-check--paths)</dt><dd>List of files or directories to check [default: the project root]
</dd></dl>

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-check--add-ignore">[`--add-ignore`](#ty-check--add-ignore)</dt><dd>Adds `ty: ignore` comments to suppress all rule diagnostics
</dd><dt id="ty-check--color">[`--color`](#ty-check--color) *when*</dt><dd>Control when colored output is used
Possible values:

<li>`auto`:  Display colors if the output goes to an interactive terminal</li>
<li>`always`:  Always display colors</li>
<li>`never`:  Never display colors</li>
</dd><dt id="ty-check--config">[`--config`](#ty-check--config), `-c` *config-option*</dt><dd>A TOML `&lt;KEY&gt; = &lt;VALUE&gt;` pair (such as you might find in a `ty.toml` configuration file)
overriding a specific configuration option.
Overrides of individual settings using this option always take precedence
over all configuration files.
</dd><dt id="ty-check--config-file">[`--config-file`](#ty-check--config-file) *path*</dt><dd>The path to a `ty.toml` file to use for configuration.
While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context.
May also be set with the `TY_CONFIG_FILE` environment variable.</dd><dt id="ty-check--error">[`--error`](#ty-check--error) *rule*</dt><dd>Treat the given rule as having severity 'error'. Can be specified multiple times. Use 'all' to apply to all rules.
</dd><dt id="ty-check--error-on-warning">[`--error-on-warning`](#ty-check--error-on-warning)</dt><dd>Use exit code 1 if there are any warning-level diagnostics.
Cannot be used in combination with `--exit-zero` or `--exit-zero-on-warning`.
</dd><dt id="ty-check--exclude">[`--exclude`](#ty-check--exclude) *exclude*</dt><dd>Glob patterns for files to exclude from type checking.
Uses gitignore-style syntax to exclude files and directories from type checking. Supports patterns like `tests/`, `*.tmp`, `**/__pycache__/**`.
</dd><dt id="ty-check--exclude-scripts">[`--exclude-scripts`](#ty-check--exclude-scripts)</dt><dd>Exclude files containing PEP 723 inline script metadata unless passed explicitly. Use `--include-scripts` to disable
</dd><dt id="ty-check--exit-zero">[`--exit-zero`](#ty-check--exit-zero)</dt><dd>Always use exit code 0, even when there are error-level diagnostics.
Cannot be used in combination with `--error-on-warning`.
</dd><dt id="ty-check--exit-zero-on-warning">[`--exit-zero-on-warning`](#ty-check--exit-zero-on-warning)</dt><dd>Use exit code 0 if there are no error-level diagnostics.
Cannot be used in combination with `--error-on-warning`.
</dd><dt id="ty-check--extra-search-path">[`--extra-search-path`](#ty-check--extra-search-path) *path*</dt><dd>Additional path to use as a module-resolution source (can be passed multiple times).
This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use `--python` to point ty to your Python environment if it is in an unusual location.
</dd><dt id="ty-check--fix">[`--fix`](#ty-check--fix)</dt><dd>Apply fixes to resolve errors
</dd><dt id="ty-check--force-exclude">[`--force-exclude`](#ty-check--force-exclude)</dt><dd>Enforce exclusions, even for paths passed to ty directly on the command-line. Use `--no-force-exclude` to disable
</dd><dt id="ty-check--help">[`--help`](#ty-check--help), `-h`</dt><dd>Print help (see a summary with '-h')
</dd><dt id="ty-check--ignore">[`--ignore`](#ty-check--ignore) *rule*</dt><dd>Disables the rule. Can be specified multiple times. Use 'all' to apply to all rules.
</dd><dt id="ty-check--no-progress">[`--no-progress`](#ty-check--no-progress)</dt><dd>Hide all progress outputs.
For example, spinners or progress bars.
</dd><dt id="ty-check--output-format">[`--output-format`](#ty-check--output-format) *output-format*</dt><dd>The format to use for printing diagnostic messages
May also be set with the `TY_OUTPUT_FORMAT` environment variable.Possible values:

<li>`full`:  Print diagnostics verbosely, with context and helpful hints (default)</li>
<li>`concise`:  Print diagnostics concisely, one per line</li>
<li>`gitlab`:  Print diagnostics in the JSON format expected by GitLab Code Quality reports</li>
<li>`github`:  Print diagnostics in the format used by GitHub Actions workflow error annotations</li>
<li>`junit`:  Print diagnostics as a JUnit-style XML report</li>
</dd><dt id="ty-check--project">[`--project`](#ty-check--project) *project*</dt><dd>Run the command within the given project directory.
All `pyproject.toml` files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (`.venv`) unless the `venv-path` option is set.
Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.
</dd><dt id="ty-check--python">[`--python`](#ty-check--python), `--venv` *path*</dt><dd>Path to your project's Python environment or interpreter.
ty uses your Python environment to resolve third-party imports in your code.
This can be a path to:

<li>A Python interpreter, e.g. `.venv/bin/python3` - A virtual environment directory, e.g. `.venv` - A system Python [`sys.prefix`](https://docs.python.org/3/library/sys.html#sys.prefix) directory, e.g. `/usr`</li>

If you're using a project management tool such as uv or you have an activated Conda or virtual environment, you should not generally need to specify this option.

</dd><dt id="ty-check--python-platform">[`--python-platform`](#ty-check--python-platform), `--platform` *platform*</dt><dd>Target platform to assume when resolving types.
This is used to specialize the type of `sys.platform` and will affect the visibility of platform-specific functions and attributes. If the value is set to `all`, no assumptions are made about the target platform. If unspecified, the current system's platform will be used.
</dd><dt id="ty-check--python-version">[`--python-version`](#ty-check--python-version), `--target-version` *version*</dt><dd>Python version to assume when resolving types.
The Python version affects allowed syntax, type definitions of the standard library, and type definitions of first- and third-party modules that are conditional on the Python version.
If a version is not specified on the command line or in a configuration file, ty will try the following techniques in order of preference to determine a value: 1. Check for the `project.requires-python` setting in a `pyproject.toml` file and use the minimum version from the specified range 2. Check for an activated or configured Python environment and attempt to infer the Python version of that environment 3. Fall back to the latest stable Python version supported by ty (see `ty check --help` output)
Possible values:

<li>`3.7`</li>
<li>`3.8`</li>
<li>`3.9`</li>
<li>`3.10`</li>
<li>`3.11`</li>
<li>`3.12`</li>
<li>`3.13`</li>
<li>`3.14`</li>
<li>`3.15`</li>
</dd><dt id="ty-check--quiet">[`--quiet`](#ty-check--quiet), `-q`</dt><dd>Use quiet output (or `-qq` for silent output)
</dd><dt id="ty-check--respect-ignore-files">[`--respect-ignore-files`](#ty-check--respect-ignore-files)</dt><dd>Respect file exclusions via `.gitignore` and other standard ignore files. Use `--no-respect-ignore-files` to disable
</dd><dt id="ty-check--typeshed">[`--typeshed`](#ty-check--typeshed), `--custom-typeshed-dir` *path*</dt><dd>Custom directory to use for stdlib typeshed stubs
</dd><dt id="ty-check--verbose">[`--verbose`](#ty-check--verbose), `-v`</dt><dd>Use verbose output (or `-vv` and `-vvv` for more verbose output)
</dd><dt id="ty-check--warn">[`--warn`](#ty-check--warn) *rule*</dt><dd>Treat the given rule as having severity 'warn'. Can be specified multiple times. Use 'all' to apply to all rules.
</dd><dt id="ty-check--watch">[`--watch`](#ty-check--watch), `-W`</dt><dd>Watch files for changes and recheck files related to the changed files
</dd></dl>

## ty server

Start the language server

<h3 class="cli-reference">Usage</h3>

```
ty server
```

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-server--help">[`--help`](#ty-server--help), `-h`</dt><dd>Print help
</dd></dl>

## ty version

Display ty's version

<h3 class="cli-reference">Usage</h3>

```
ty version [OPTIONS]
```

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-version--help">[`--help`](#ty-version--help), `-h`</dt><dd>Print help
</dd><dt id="ty-version--output-format">[`--output-format`](#ty-version--output-format) *output-format*</dt><dd>The format in which to display the version information
[default: text]Possible values:

<li>`text`</li>
<li>`json`</li>
</dd></dl>

## ty generate-shell-completion

Generate shell completion

<h3 class="cli-reference">Usage</h3>

```
ty generate-shell-completion <SHELL>
```

<h3 class="cli-reference">Arguments</h3>

<dl class="cli-reference"><dt id="ty-generate-shell-completion--shell">[`SHELL`](#ty-generate-shell-completion--shell)</dt></dl>

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-generate-shell-completion--help">[`--help`](#ty-generate-shell-completion--help), `-h`</dt><dd>Print help
</dd></dl>

## ty explain

Explain rules and other parts of ty

<h3 class="cli-reference">Usage</h3>

```
ty explain <COMMAND>
```

<h3 class="cli-reference">Commands</h3>

<dl class="cli-reference"><dt>[`ty explain rule`](#ty-explain-rule)</dt><dd>Explain a rule (or all rules)</dd>
<dt>[`ty explain help`](#ty-explain-help)</dt><dd>Print this message or the help of the given subcommand(s)</dd>
</dl>

### ty explain rule

Explain a rule (or all rules)

<h3 class="cli-reference">Usage</h3>

```
ty explain rule [OPTIONS] [RULE]
```

<h3 class="cli-reference">Arguments</h3>

<dl class="cli-reference"><dt id="ty-explain-rule--rule">[`RULE`](#ty-explain-rule--rule)</dt><dd>Rule to explain
Defaults to all rules if omitted.
</dd></dl>

<h3 class="cli-reference">Options</h3>

<dl class="cli-reference"><dt id="ty-explain-rule--help">[`--help`](#ty-explain-rule--help), `-h`</dt><dd>Print help (see a summary with '-h')
</dd><dt id="ty-explain-rule--output-format">[`--output-format`](#ty-explain-rule--output-format) *output-format*</dt><dd>Output format
[default: text]Possible values:

<li>`text`</li>
<li>`json`</li>
</dd></dl>

### ty explain help

Print this message or the help of the given subcommand(s)

<h3 class="cli-reference">Usage</h3>

```
ty explain help [COMMAND]
```

## ty help

Print this message or the help of the given subcommand(s)

<h3 class="cli-reference">Usage</h3>

```
ty help [COMMAND]
```

---

## File: crates/ty/docs/configuration.md

# Configuration
## `rules`

Configures the enabled rules and their severity.

The keys are either rule names or `all` to set a default severity for all rules.
See [the rules documentation](https://ty.dev/rules) for a list of all available rules.

Valid severities are:

* `ignore`: Disable the rule.
* `warn`: Enable the rule and create a warning diagnostic.
* `error`: Enable the rule and create an error diagnostic.

By default, ty exits with code 1 if it emits any warning or error diagnostics.
Set `terminal.error-on-warning` to `false` to exit with code 0 if all diagnostics have `warning` severity.

**Default value**: `{...}`

**Type**: `dict[RuleName | "all", "ignore" | "warn" | "error"]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.rules]
    possibly-unresolved-reference = "warn"
    division-by-zero = "ignore"
    ```

=== "ty.toml"

    ```toml
    [rules]
    possibly-unresolved-reference = "warn"
    division-by-zero = "ignore"
    ```

---

## `analysis`

### `allowed-unresolved-imports`

A list of module glob patterns for which `unresolved-import` diagnostics should be suppressed.

Details on supported glob patterns:
- `*` matches zero or more characters except `.`. For example, `foo.*` matches `foo.bar` but
  not `foo.bar.baz`; `foo*` matches `foo` and `foobar` but not `foo.bar` or `barfoo`; and `*foo`
  matches `foo` and `barfoo` but not `foo.bar` or `foobar`.
- `**` matches any number of module components (e.g., `foo.**` matches `foo`, `foo.bar`, etc.)
- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

---

### `replace-imports-with-any`

A list of module glob patterns whose imports should be replaced with `typing.Any`.

Unlike `allowed-unresolved-imports`, this setting replaces the module's type information
with `typing.Any` even if the module can be resolved. Import diagnostics are
unconditionally suppressed for matching modules.

- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

When multiple patterns match, later entries take precedence.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

---

### `respect-type-ignore-comments`

Whether ty should respect `type: ignore` comments.

When set to `false`, `type: ignore` comments are treated like any other normal
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).

Setting this option can be useful when using ty alongside other type checkers or when
you prefer using `ty: ignore` over `type: ignore`.

Defaults to `true`.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

---

### `return-type-inference`

This setting controls when ty infers return types for functions without an explicit
return type annotation.

- `"never"`: Do not infer return types from function bodies. Unannotated functions
  will have an inferred return type of `Unknown`.
- `"always"`: Infer return types for unannotated functions and methods.

Enabling return type inference will never override explicit return type annotations.
Lambda functions are not affected by this setting and their return type will always
be inferred.

Return types for generator functions and asynchronous functions are currently not
inferred, but we plan to change this soon.

Functions in stubs and `if TYPE_CHECKING` blocks are never considered for return type
inference.

**Default value**: `"never"`

**Type**: `"never" | "always"`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    return-type-inference = "always"
    ```

=== "ty.toml"

    ```toml
    [analysis]
    return-type-inference = "always"
    ```

---

### `strict-equality-semantics`

Configure ty's behavior regarding type inference and narrowing of equality
checks. Defaults to `false`.

By default, ty makes various assumptions about equality checks that match the
intuitions of most Python programmers, but may not be fully sound in all situations.
Enabling this option makes ty more conservative about these assumptions, making it
less likely to infer `Literal[True]` or `Literal[False]` as the result of an
equality check. This has various effects on type checking, including fewer type
narrowing opportunities and more conservative assumptions regarding control flow.

One way in which ty will by default make unsound assumptions is by narrowing an
object `x` of type `str` to `Literal["a"]` after an `if x == "a"` check. This is
unsound because a subclass of `str` with value `"a"` will (by default) compare equal
to `"a"`, but will not be of type `Literal["a"]`:

```pycon
>>> # `Literal["a"]` can only be inhabited by instances of exactly `str`, not
>>> # subclasses, but str subclasses compare equal by default:
>>> class StringSubclass(str): ...
...
>>> StringSubclass("a") == "a"
True
>>>
>>> # This also applies to `StrEnum`s:
>>> from enum import StrEnum
>>> class MyEnum(StrEnum):
...     A = "a"
...
>>> MyEnum.A == "a"
True
```

Enabling this option prevents the unsound narrowing of `x` to `Literal["a"]`,
and instead keeps it as `str`:

```python
from typing import Literal

def parse(value: str) -> Literal["a"] | None:
    # with `strict-equality-semantics = true`, no narrowing will occur here,
    # and an error will be emitted on the `return` statement.
    if value == "a":
        return value
    return None
```

Another assumption ty makes by default is that subclasses will never override `__eq__` or
`__ne__`. This allows ty to narrow the following union based on an equality check, despite
the fact that an instance of a subclass of `Foo` could compare equal to `None`, and it's
perfectly valid to pass an instance of a subclass into the `x` parameter of this function:

```python
def narrow(x: Foo | None, other: Foo) -> None:
    if x == other:
        # with this option enabled, `x` will still have type `Foo | None` here,
        # since it is legal to subclass `Foo` and override its `__eq__` method.
        reveal_type(x)
```

Many operations in Python implicitly call `__eq__` under the hood; enabling this option
will also impact those operations. For example, this option will also impact narrowing from
`in` checks, and narrowing in `match` statements that use value patterns:

```python
def narrow_in(x: Foo | None, other: list[Foo]) -> None:
    if x in other:
        # with this option enabled, `x` will still have type `Foo | None` here,
        # since the `in` operator implicitly calls `__eq__` on each element of `other`.
        reveal_type(x)


def narrow_match(x: str) -> None:
    match x:
        case "a":
            # with this option enabled, `x` will still have type `str` here,
            # since this `case` branch will be taken by any object that compares
            # equal to `"a"`, including subclasses of `str`.
            reveal_type(x)
```

**Default value**: `false`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Preserve broad builtin types instead of narrowing them to literals
    strict-equality-semantics = true
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Preserve broad builtin types instead of narrowing them to literals
    strict-equality-semantics = true
    ```

---

### `strict-generic-narrowing`

Whether ty should use strict narrowing for unspecialized generic classes in
`isinstance()` and `issubclass()` checks, `match` class patterns, and `TypeIs` checks.

When enabled, ty narrows to the top materialization of the class. For example,
`isinstance(value, list)` narrows a value of type `object` to `Top[list[Unknown]]`,
representing the (infinite) union of all possible `list` specializations. Iterating
over the list would yield values of type `object`.

When disabled, ty uses gradual generic narrowing, preserving compatible type
arguments from the original type where possible. For example,
`isinstance(value, list)` narrows a value of type `Sequence[int]` to `list[int]`.
If no specialization is available, the same check narrows a value of type `object`
to `list[Unknown]`; items of any type can then be appended to the list. Class
patterns such as `case list():` follow the same behavior.

Defaults to `false`.

**Default value**: `false`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.analysis]
    # Use the top materialization when narrowing to an unspecialized generic class
    strict-generic-narrowing = true
    ```

=== "ty.toml"

    ```toml
    [analysis]
    # Use the top materialization when narrowing to an unspecialized generic class
    strict-generic-narrowing = true
    ```

---

## `environment`

### `extra-paths`

User-provided paths that should take first priority in module resolution.

This is an advanced option that should usually only be used for first-party or third-party
modules that are not installed into your Python environment in a conventional way.
Use the `python` option to specify the location of your Python environment.

This option is similar to mypy's `MYPYPATH` environment variable and pyright's `stubPath`
configuration setting.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    extra-paths = ["./shared/my-search-path"]
    ```

=== "ty.toml"

    ```toml
    [environment]
    extra-paths = ["./shared/my-search-path"]
    ```

---

### `python`

Path to your project's Python environment or interpreter.

ty uses the `site-packages` directory of your project's Python environment
to resolve third-party (and, in some cases, first-party) imports in your code.

This can be a path to:

- A Python interpreter, e.g. `.venv/bin/python3`
- A virtual environment directory, e.g. `.venv`
- A system Python [`sys.prefix`] directory, e.g. `/usr`

If you're using a project management tool such as uv, you should not generally need to
specify this option, as commands such as `uv run` will set the `VIRTUAL_ENV` environment
variable to point to your project's virtual environment. ty can also infer the location of
your environment from an activated Conda environment, and will look for a `.venv` directory
in the project root if none of the above apply. Failing that, ty will look for a `python3`
or `python` binary available in `PATH`.

Scripts with inline metadata use their own Python environment. They can use an explicitly
configured environment, an activated environment, or an environment selected by the editor.
Unlike projects, they do not automatically use a `.venv` directory.

[`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix

**Default value**: `null`

**Type**: `str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    python = "./custom-venv-location/.venv"
    ```

=== "ty.toml"

    ```toml
    [environment]
    python = "./custom-venv-location/.venv"
    ```

---

### `python-platform`

Specifies the target platform that will be used to analyze the source code.
If specified, ty will understand conditions based on comparisons with `sys.platform`, such
as are commonly found in typeshed to reflect the differing contents of the standard library across platforms.
If `all` is specified, ty will assume that the source code can run on any platform.

If no platform is specified, ty will use the current platform:
- `win32` for Windows
- `darwin` for macOS
- `android` for Android
- `ios` for iOS
- `linux` for everything else

**Default value**: `<current-platform>`

**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    # Tailor type stubs and conditionalized type definitions to windows.
    python-platform = "win32"
    ```

=== "ty.toml"

    ```toml
    [environment]
    # Tailor type stubs and conditionalized type definitions to windows.
    python-platform = "win32"
    ```

---

### `python-version`

Specifies the version of Python that will be used to analyze the source code.
The version should be specified as a string in the format `M.m` where `M` is the major version
and `m` is the minor (e.g. `"3.7"` or `"3.12"`).
If a version is provided, ty will generate errors if the source code makes use of language features
that are not supported in that version.

ty officially supports type checking code that targets Python 3.10 and later. Python 3.7
through 3.9 can still be selected, but ty may produce false positives or false negatives for
standard-library APIs because its bundled stubs do not fully describe those versions.

If a version is not specified, ty will try the following techniques in order of preference
to determine a value:
1. Check for the `project.requires-python` setting in a `pyproject.toml` file
   and use the minimum version from the specified range
2. Check for an activated or configured Python environment
   and attempt to infer the Python version of that environment
3. Fall back to the default value (see below)

Scripts with inline metadata use their `requires-python` field instead of
`project.requires-python`. They do not inherit the Python version of the enclosing project.

For some language features, ty can also understand conditionals based on comparisons
with `sys.version_info`. These are commonly found in typeshed, for example,
to reflect the differing contents of the standard library across Python versions.

**Default value**: `"3.15"`

**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | "3.15"`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    python-version = "3.12"
    ```

=== "ty.toml"

    ```toml
    [environment]
    python-version = "3.12"
    ```

---

### `root`

The root paths of the project, used for finding first-party modules.

Accepts a list of directory paths searched in priority order (first has highest priority).

If left unspecified, ty will try to detect common project layouts and initialize `root` accordingly.
The project root (`.`) is always included. Additionally, the following directories are included
if they exist and are not packages (i.e. they do not contain `__init__.py` or `__init__.pyi` files):

* `./src`
* `./` (if a `.//` directory exists)
* `./python`

Scripts with inline metadata have no first-party roots by default because they are
single-file programs. Set `root = ["."]` to allow importing local modules.

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    # Multiple directories (priority order)
    root = ["./src", "./lib", "./vendor"]
    ```

=== "ty.toml"

    ```toml
    [environment]
    # Multiple directories (priority order)
    root = ["./src", "./lib", "./vendor"]
    ```

---

### `typeshed`

Optional path to a "typeshed" directory on disk for us to use for standard-library types.
If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
bundled as a zip file in the binary

**Default value**: `null`

**Type**: `str`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.environment]
    typeshed = "/path/to/custom/typeshed"
    ```

=== "ty.toml"

    ```toml
    [environment]
    typeshed = "/path/to/custom/typeshed"
    ```

---

## `overrides`

Configuration override that applies to specific files based on glob patterns.

An override allows you to apply different rule configurations to specific
files or directories. Multiple overrides can match the same file, with
later overrides take precedence. Override rules take precedence over global
rules for matching files.

For example, to relax enforcement of rules in test files:

```toml
[[tool.ty.overrides]]
include = ["tests/**", "**/test_*.py"]

[tool.ty.overrides.rules]
possibly-unresolved-reference = "warn"
```

Or, to ignore a rule in generated files but retain enforcement in an important file:

```toml
[[tool.ty.overrides]]
include = ["generated/**"]
exclude = ["generated/important.py"]

[tool.ty.overrides.rules]
possibly-unresolved-reference = "ignore"
```


### `exclude`

A list of file and directory patterns to exclude from this override.

Patterns follow a syntax similar to `.gitignore`.
Exclude patterns take precedence over include patterns within the same override.

If not specified, defaults to `[]` (excludes no files).

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

---

### `include`

A list of file and directory patterns to include for this override.

The `include` option follows a similar syntax to `.gitignore` but reversed:
Including a file or directory will make it so that it (and its contents)
are affected by this override.

If not specified, defaults to `["**"]` (matches all files).

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = [
        "src",
        "tests",
    ]
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = [
        "src",
        "tests",
    ]
    ```

---

### `rules`

Rule overrides for files matching the include/exclude patterns.

These rules will be merged with the global rules, with override rules
taking precedence for matching files. You can set rules to different
severity levels or disable them entirely.

**Default value**: `{...}`

**Type**: `dict[RuleName | "all", "ignore" | "warn" | "error"]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.rules]
    possibly-unresolved-reference = "ignore"
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.rules]
    possibly-unresolved-reference = "ignore"
    ```

---

## `overrides.analysis`

#### `allowed-unresolved-imports`

A list of module glob patterns for which `unresolved-import` diagnostics should be suppressed.

Details on supported glob patterns:
- `*` matches zero or more characters except `.`. For example, `foo.*` matches `foo.bar` but
  not `foo.bar.baz`; `foo*` matches `foo` and `foobar` but not `foo.bar` or `barfoo`; and `*foo`
  matches `foo` and `barfoo` but not `foo.bar` or `foobar`.
- `**` matches any number of module components (e.g., `foo.**` matches `foo`, `foo.bar`, etc.)
- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.analysis]
    # Suppress errors for all `test` modules except `test.foo`
    allowed-unresolved-imports = ["test.**", "!test.foo"]
    ```

---

#### `replace-imports-with-any`

A list of module glob patterns whose imports should be replaced with `typing.Any`.

Unlike `allowed-unresolved-imports`, this setting replaces the module's type information
with `typing.Any` even if the module can be resolved. Import diagnostics are
unconditionally suppressed for matching modules.

- Prefix a pattern with `!` to exclude matching modules

When multiple patterns match, later entries take precedence.

Glob patterns can be used in combinations with each other. For example, to suppress errors for
any module where the first component contains the substring `test`, use `*test*.**`.

When multiple patterns match, later entries take precedence.

**Default value**: `[]`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.analysis]
    # Replace all pandas and numpy imports with Any
    replace-imports-with-any = ["pandas.**", "numpy.**"]
    ```

---

#### `respect-type-ignore-comments`

Whether ty should respect `type: ignore` comments.

When set to `false`, `type: ignore` comments are treated like any other normal
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).

Setting this option can be useful when using ty alongside other type checkers or when
you prefer using `ty: ignore` over `type: ignore`.

Defaults to `true`.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.analysis]
    # Disable support for `type: ignore` comments
    respect-type-ignore-comments = false
    ```

---

#### `return-type-inference`

This setting controls when ty infers return types for functions without an explicit
return type annotation.

- `"never"`: Do not infer return types from function bodies. Unannotated functions
  will have an inferred return type of `Unknown`.
- `"always"`: Infer return types for unannotated functions and methods.

Enabling return type inference will never override explicit return type annotations.
Lambda functions are not affected by this setting and their return type will always
be inferred.

Return types for generator functions and asynchronous functions are currently not
inferred, but we plan to change this soon.

Functions in stubs and `if TYPE_CHECKING` blocks are never considered for return type
inference.

**Default value**: `"never"`

**Type**: `"never" | "always"`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.analysis]
    return-type-inference = "always"
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.analysis]
    return-type-inference = "always"
    ```

---

#### `strict-equality-semantics`

Configure ty's behavior regarding type inference and narrowing of equality
checks. Defaults to `false`.

By default, ty makes various assumptions about equality checks that match the
intuitions of most Python programmers, but may not be fully sound in all situations.
Enabling this option makes ty more conservative about these assumptions, making it
less likely to infer `Literal[True]` or `Literal[False]` as the result of an
equality check. This has various effects on type checking, including fewer type
narrowing opportunities and more conservative assumptions regarding control flow.

One way in which ty will by default make unsound assumptions is by narrowing an
object `x` of type `str` to `Literal["a"]` after an `if x == "a"` check. This is
unsound because a subclass of `str` with value `"a"` will (by default) compare equal
to `"a"`, but will not be of type `Literal["a"]`:

```pycon
>>> # `Literal["a"]` can only be inhabited by instances of exactly `str`, not
>>> # subclasses, but str subclasses compare equal by default:
>>> class StringSubclass(str): ...
...
>>> StringSubclass("a") == "a"
True
>>>
>>> # This also applies to `StrEnum`s:
>>> from enum import StrEnum
>>> class MyEnum(StrEnum):
...     A = "a"
...
>>> MyEnum.A == "a"
True
```

Enabling this option prevents the unsound narrowing of `x` to `Literal["a"]`,
and instead keeps it as `str`:

```python
from typing import Literal

def parse(value: str) -> Literal["a"] | None:
    # with `strict-equality-semantics = true`, no narrowing will occur here,
    # and an error will be emitted on the `return` statement.
    if value == "a":
        return value
    return None
```

Another assumption ty makes by default is that subclasses will never override `__eq__` or
`__ne__`. This allows ty to narrow the following union based on an equality check, despite
the fact that an instance of a subclass of `Foo` could compare equal to `None`, and it's
perfectly valid to pass an instance of a subclass into the `x` parameter of this function:

```python
def narrow(x: Foo | None, other: Foo) -> None:
    if x == other:
        # with this option enabled, `x` will still have type `Foo | None` here,
        # since it is legal to subclass `Foo` and override its `__eq__` method.
        reveal_type(x)
```

Many operations in Python implicitly call `__eq__` under the hood; enabling this option
will also impact those operations. For example, this option will also impact narrowing from
`in` checks, and narrowing in `match` statements that use value patterns:

```python
def narrow_in(x: Foo | None, other: list[Foo]) -> None:
    if x in other:
        # with this option enabled, `x` will still have type `Foo | None` here,
        # since the `in` operator implicitly calls `__eq__` on each element of `other`.
        reveal_type(x)


def narrow_match(x: str) -> None:
    match x:
        case "a":
            # with this option enabled, `x` will still have type `str` here,
            # since this `case` branch will be taken by any object that compares
            # equal to `"a"`, including subclasses of `str`.
            reveal_type(x)
```

**Default value**: `false`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.analysis]
    # Preserve broad builtin types instead of narrowing them to literals
    strict-equality-semantics = true
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.analysis]
    # Preserve broad builtin types instead of narrowing them to literals
    strict-equality-semantics = true
    ```

---

#### `strict-generic-narrowing`

Whether ty should use strict narrowing for unspecialized generic classes in
`isinstance()` and `issubclass()` checks, `match` class patterns, and `TypeIs` checks.

When enabled, ty narrows to the top materialization of the class. For example,
`isinstance(value, list)` narrows a value of type `object` to `Top[list[Unknown]]`,
representing the (infinite) union of all possible `list` specializations. Iterating
over the list would yield values of type `object`.

When disabled, ty uses gradual generic narrowing, preserving compatible type
arguments from the original type where possible. For example,
`isinstance(value, list)` narrows a value of type `Sequence[int]` to `list[int]`.
If no specialization is available, the same check narrows a value of type `object`
to `list[Unknown]`; items of any type can then be appended to the list. Class
patterns such as `case list():` follow the same behavior.

Defaults to `false`.

**Default value**: `false`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [[tool.ty.overrides]]
    include = ["src"]

    [tool.ty.overrides.analysis]
    # Use the top materialization when narrowing to an unspecialized generic class
    strict-generic-narrowing = true
    ```

=== "ty.toml"

    ```toml
    [[overrides]]
    include = ["src"]

    [overrides.analysis]
    # Use the top materialization when narrowing to an unspecialized generic class
    strict-generic-narrowing = true
    ```

---

## `src`

### `exclude`

A list of file and directory patterns to exclude from type checking.

Patterns follow a syntax similar to `.gitignore`:

- `./src/` matches only a directory
- `./src` matches both files and directories
- `src` matches files or directories named `src`
- `*` matches any (possibly empty) sequence of characters (except `/`).
- `**` matches zero or more path components.
  This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error.
  A sequence of more than two consecutive `*` characters is also invalid.
- `?` matches any single character except `/`
- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,
  so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.
- `!pattern` negates a pattern (undoes the exclusion of files that would otherwise be excluded)

All paths are anchored relative to the project root (`src` only
matches `/src` and not `/test/src`).
To exclude any directory or file named `src`, use `**/src` instead.

By default, ty excludes commonly ignored directories:

- `**/.bzr/`
- `**/.direnv/`
- `**/.eggs/`
- `**/.git/`
- `**/.git-rewrite/`
- `**/.hg/`
- `**/.mypy_cache/`
- `**/.nox/`
- `**/.pants.d/`
- `**/.pytype/`
- `**/.ruff_cache/`
- `**/.svn/`
- `**/.tox/`
- `**/.venv/`
- `**/__pypackages__/`
- `**/_build/`
- `**/buck-out/`
- `**/dist/`
- `**/node_modules/`
- `**/venv/`

You can override any default exclude by using a negated pattern. For example,
to re-include `dist` use `exclude = ["!dist"]`

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

=== "ty.toml"

    ```toml
    [src]
    exclude = [
        "generated",
        "*.proto",
        "tests/fixtures/**",
        "!tests/fixtures/important.py"  # Include this one file
    ]
    ```

---

### `exclude-scripts`

Whether to exclude files containing PEP 723 inline script metadata unless they are
explicitly passed on the command line.

**Default value**: `false`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    exclude-scripts = true
    ```

=== "ty.toml"

    ```toml
    [src]
    exclude-scripts = true
    ```

---

### `include`

A list of files and directories to check. The `include` option
follows a similar syntax to `.gitignore` but reversed:
Including a file or directory will make it so that it (and its contents)
are type checked.

- `./src/` matches only a directory
- `./src` matches both files and directories
- `src` matches a file or directory named `src`
- `*` matches any (possibly empty) sequence of characters (except `/`).
- `**` matches zero or more path components.
  This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error.
  A sequence of more than two consecutive `*` characters is also invalid.
- `?` matches any single character except `/`
- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,
  so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.

All paths are anchored relative to the project root (`src` only
matches `/src` and not `/test/src`).

`exclude` takes precedence over `include`.

**Default value**: `null`

**Type**: `list[str]`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    include = [
        "src",
        "tests",
    ]
    ```

=== "ty.toml"

    ```toml
    [src]
    include = [
        "src",
        "tests",
    ]
    ```

---

### `respect-ignore-files`

Whether to automatically exclude files that are ignored by `.ignore`,
`.gitignore`, `.git/info/exclude`, and global `gitignore` files.
Enabled by default.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.src]
    respect-ignore-files = false
    ```

=== "ty.toml"

    ```toml
    [src]
    respect-ignore-files = false
    ```

---

## `terminal`

### `error-on-warning`

Use exit code 1, even if all diagnostics only had `warning` severity.

Defaults to `true`.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.terminal]
    # Exit with code 0 if all diagnostics had `warning` severity.
    error-on-warning = false
    ```

=== "ty.toml"

    ```toml
    [terminal]
    # Exit with code 0 if all diagnostics had `warning` severity.
    error-on-warning = false
    ```

---

### `output-format`

The format to use for printing diagnostic messages.

Defaults to `full`.

**Default value**: `full`

**Type**: `full | concise | github | gitlab | junit`

**Example usage**:

=== "pyproject.toml"

    ```toml
    [tool.ty.terminal]
    output-format = "concise"
    ```

=== "ty.toml"

    ```toml
    [terminal]
    output-format = "concise"
    ```

---

## 2. Official Technical Reference & Guides (astral-sh/docs)

## File: README.md

# Astral Documentation

This repository contains documentation for the Astral toolchain: [Ruff](https://github.com/astral-sh/ruff), [uv](https://github.com/astral-sh/uv), and [ty](https://github.com/astral-sh/ty).

## License

uv is licensed under either of

- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in uv by you, as defined in the Apache-2.0 license, shall be
dually licensed as above, without any additional terms or conditions.

---

## File: site/python-build-standalone/search/search_index.json

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Python Standalone Builds","text":"This project produces self-contained, highly portable, high-performance Python distributions. These Python distributions contain a fully usable, full-featured Python installation: most extension modules from the Python standard library are present, and their library dependencies are either distributed with the distribution or are statically linked. The Python distributions are built in a manner to minimize run-time dependencies. This includes limiting the CPU instructions that can be used and limiting the set of shared libraries required at run-time. The goal is for the produced distribution to work on any system for the targeted architecture. The builds incorporate compiler optimizations such as profile-guided optimization (PGO), link-time optimization (LTO), and, where appropriate, BOLT post-link binary optimization. Together, these techniques improve runtime performance while preserving portability. The most common method of using these distributions is with uv. To run a particular distribution using `uv`: `uvx --managed-python python\n` A Python version or another specifier can be included: `uvx --managed-python python@3.13\nuvx --managed-python python@3.14+freethreaded\n`"},{"location":"building/","title":"Building","text":""},{"location":"building/#building","title":"Building","text":"A Python distribution can be built on a Linux, macOS, or Windows host. Regardless of the operating system, uv must be installed. Additional operating system requirements are outlined in the following sections. Regardless of the host, to build a Python distribution: `$ uv run --no-dev build.py\n` On Linux and macOS, `./build.py` can also be used; it uses `uv run --no-dev` via its shebang. To build a different version of Python: `$ uv run --no-dev build.py --python cpython-3.14\n` Various build options can be specified: `# With profile-guided optimizations (generated code should be faster)\n$ uv run --no-dev build.py --options pgo\n# Produce a debug build.\n$ uv run --no-dev build.py --options debug\n# Produce a free-threaded build without extra optimizations\n$ uv run --no-dev build.py --options freethreaded+noopt\n` Different platforms support different build options. `uv run --no-dev build.py --help` will show the available build options and other usage information."},{"location":"building/#linux","title":"Linux","text":"The host system must be x86-64 or aarch64. The execution environment must have access to a Docker daemon (all build operations are performed in Docker containers for isolation from the host system). Docker Buildx must be installed and available as `docker buildx`. `build.py` accepts a `--target-triple` argument to support building for non-native targets (i.e., cross-compiling). This option can be used to build for musl libc: `$ ./build.py --target-triple x86_64-unknown-linux-musl\n` Or on an x86-64 host for different architectures: `$ ./build.py --target-triple armv7-unknown-linux-gnueabi\n$ ./build.py --target-triple armv7-unknown-linux-gnueabihf\n$ ./build.py --target-triple ppc64le-unknown-linux-gnu\n$ ./build.py --target-triple riscv64-unknown-linux-gnu\n$ ./build.py --target-triple s390x-unknown-linux-gnu\n`"},{"location":"building/#jessie-image-package-authentication","title":"Jessie image package authentication","text":"The `build`, `gcc`, and `rust` Dockerfiles include `cpython-unix/base.Dockerfile`. These amd64 images use Debian Jessie for build compatibility. Cross-compilation targets also use the `gcc` image for toolchain builds. Building these images requires Docker Buildx with support for Dockerfile 1.6's `ADD --checksum` instruction. The base Dockerfile pins the SHA-256 digests of the three archived `Packages.gz` indexes because Jessie's archive signing keys have expired. Docker fetches them over HTTPS and verifies their digests before APT uses them. APT then verifies downloaded packages against the hashes in these fixed indexes. `trusted=yes` permits this separate trust root; `apt-get update` is blocked to prevent replacing the pinned indexes with unauthenticated metadata. The pinned base image lacks both `apt-transport-https` and `ca-certificates`. Their bootstrap download uses HTTP, authenticated by the pinned package hashes. Once they are installed, subsequent package downloads use HTTPS with certificate verification. The index digests come from the SHA256 sections of the archived Jessie Release, Jessie updates Release, and Jessie security Release files. The corresponding archive key fingerprints in the digest-pinned base image are `126C0D24BD8A2942CC7DF8AC7638D0442B90D010` for Jessie and Jessie updates, and `D21169141CECD440F2EB8DDA9D6D8F6BC857C906` for Jessie security. Any change to the snapshot or index digests requires independently authenticating the replacement metadata."},{"location":"building/#macos","title":"macOS","text":"The Xcode command-line tools must be installed. `/usr/bin/clang` must exist. macOS SDK headers must be installed. If you see errors such as `stdio.h` not being found, try running `xcode-select --install` to install them. Verify they are installed by running `xcrun --show-sdk-path`. It should print something like `/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk` on modern versions of macOS. The `--target-triple` argument can be used to build for an Intel Mac on an arm64 (Apple Silicon) host: `$ ./build.py --target-triple x86_64-apple-darwin\n` Additionally, an arm64 macOS host can be used to build Linux aarch64 and x86-64 targets using Docker: `$ ./build.py --target-triple aarch64-unknown-linux-gnu\n$ ./build.py --target-triple x86_64-unknown-linux-gnu\n` The `APPLE_SDK_PATH` environment variable is recognized as the path to the Apple SDK to use. If not defined, the build will attempt to find an SDK by running `xcrun --show-sdk-path`. `aarch64-apple-darwin` builds require a macOS 11.0+ SDK. It should be possible to build for `aarch64-apple-darwin` from an Intel 10.15 machine (as long as the 11.0+ SDK is used)."},{"location":"building/#windows","title":"Windows","text":"Visual Studio 2022 (or later) is required. For `x86_64-pc-windows-msvc` targets, use Visual Studio 2026 when building CPython 3.15 or newer. The `i686-pc-windows-msvc` and `aarch64-pc-windows-msvc` targets continue to use Visual Studio 2022. A compatible Windows SDK is required (10.0.26100.0 as of CPython 3.10). A `git.exe` must be on `PATH` (to clone `libffi` from source). Cygwin must be installed with the `autoconf`, `automake`, `libtool`, and `make` packages, which are required to build `libffi`. Building can be done from the `x64 Native Tools Command Prompt`, by calling the vcvars batch file, or by adjusting the `PATH` and environment variables. You will need to specify the path to `sh.exe` from Cygwin: `$ uv run --no-dev build.py --sh c:\\cygwin\\bin\\sh.exe\n` When using a version of Visual Studio other than 2022, the version must be specified with the `--vs` option. For example, to build CPython 3.15 with Visual Studio 2026: `$ uv run --no-dev build.py --sh c:\\cygwin\\bin\\sh.exe --vs 2026 --python cpython-3.15\n` To produce a debug build, pass `--options debug` or `freethreaded+debug`: `$ uv run --no-dev build.py --sh c:\\cygwin\\bin\\sh.exe --options debug\n` The artifacts carry the `_d` suffix of CPython's Debug configuration (`python_d.exe`, `python314_d.dll`, `_asyncio_d.pyd`) and link against the debug C runtime (`ucrtbased.dll` and `vcruntime*d.dll`). That runtime ships with Visual Studio and is not redistributable, so a debug distribution only runs on a machine with Visual Studio installed. To build a 32-bit x86 binary, simply use an `x86 Native Tools Command Prompt` instead of `x64`."},{"location":"distributions/","title":"Distribution Archives","text":""},{"location":"distributions/#distribution-archives","title":"Distribution Archives","text":"This project produces tarball archives containing Python distributions."},{"location":"distributions/#full-archive","title":"Full Archive","text":"The canonical output of this project's build system are `.tar.zst` (zstandard compressed tarballs) files. All files within the tar are prefixed with `python/`. Within the `python/` directory are the following well-known paths: PYTHON.json Machine readable file describing this Python distribution. See the `PYTHON.json File` section for the format of this file. By convention, the `build/` directory contains artifacts from building this distribution (object files, libraries, etc) and the `install/` directory contains a working, self-contained Python installation of this distribution. The `PYTHON.json` file should be read to determine where specific entities are located within the archive."},{"location":"distributions/#pythonjson-file","title":"PYTHON.json File","text":"The `PYTHON.json` file describes the Python distribution in a machine readable manner. This file is meant to be opened by downstream consumers of this distribution so that they may learn things about the distribution without having to resort to heuristics. The file contains a JSON map. This map has the following keys: version Version number of the file format. Currently `8`. target_triple A target triple defining the platform and architecture of the machine that binaries target. We use Rust's set of defined targets for values. Run `rustup target list` to see potential values. Example values are `x86_64-unknown-linux-gnu`, `x86_64-unknown-linux-musl`, `x86_64-pc-windows-msvc`, and `x86_64-apple-darwin`. (Version 5 or above only.) optimizations String indicating what optimization profile has been applied to the build. Known values include `debug`, `noopt`, `pgo`, `lto`, and `pgo+lto`. (Deprecated in version 8 in favor of `build_options`.) build_options String indicating what build options were used. Options are separated by a `+`. Known values include `debug`, `noopt`, `pgo`, `lto`, `static`, and `freethreaded`. (Version 8 or above only.) os Target operating system for the distribution. e.g. `linux`, `macos`, or `windows`. (Deprecated in version 5 in favor of `target_triple`.) arch Target architecture for the distribution. e.g. `x86` (32-bit) or `x86_64` (64-bit). (Deprecated in version 5 in favor of `target_triple`.) python_tag The PEP 425 Python Tag value. e.g. `cp313`. (Version 5 or above only.) python_abi_tag The PEP 425 ABI Tag value. e.g. `cp313m`. This may be null if the distribution's platform doesn't expose the concept of an ABI tag. (Version 5 or above only.) python_platform_tag The PEP 425 Platform Tag value. e.g. `linux_x86_64`. (Version 5 or above only.) python_flavor Type of Python distribution. e.g. `cpython`. (Deprecated in version 5 in favor of PEP 425 tags.) python_implementation_cache_tag Tag used by import machinery to derive filenames for bytecode files. This is the value exposed by `sys.implementation.cache_tag` (Version 5 or above only.) python_implementation_hex_version Hexadecimal expression of implementation version. This is the value exposed by `sys.implementation.hexversion`. (Version 5 or above only.) python_implementation_name Name of Python implementation. This is the value exposed by `sys.implementation.name`. (Version 5 or above only.) python_implementation_version Array of version components of Python implementation. This is the value exposed by `sys.implementation.version`. Unlike `sys.implementation.version`, all elements are strings, not a mix of numbers and strings. (Version 5 or above only.) python_version Version of Python distribution. e.g. `3.13.0`. python_major_minor_version `X.Y` version string consisting of Python major and minor version. (Version 5 or above only.) python_paths Mapping of `sysconfig` path names to paths in the distribution. Keys are values like `stdlib` and `include`. Values are relative paths within the distribution. See https://docs.python.org/3/library/sysconfig.html#installation-paths for the meaning of keys. (Version 5 or above only.) python_paths_abstract Mapping of `sysconfig` path names with placeholder values. See https://docs.python.org/3/library/sysconfig.html#installation-paths for the meaning of keys. This is equivalent to calling `sysconfig.get_paths(expand=False)`. (Version 6 or above only.) python_config_vars Mapping of string configuration names to string values. This is equivalent to `sysconfig.get_config_vars()` with all values normalized to strings. Many configuration values may represent state as it existed in the build environment and aren't appropriate for the run-time environment on a different system. (Version 6 or above only.) python_exe Relative path to main Python interpreter executable. python_include Relative path to include path for Python headers. If this path is on the compiler's include path, `#include &lt;Python.h&gt;` should work. (Deprecated in version 5 in favor of `python_paths`.) python_stdlib Relative path to Python's standard library (where `.py` and resource files are located). (Deprecated in version 5 in favor of `python_paths`.) python_stdlib_platform_config Relative path to a `config-&lt;platform&gt;` directory in the standard library containing files used to embed Python in a binary. This is a standard directory present in POSIX Python installations and is not specific to this project. The key may be absent if no platform config directory exists. (Version 5 or above only.) python_stdlib_test_packages Array of strings of Python packages that define tests. (Version 4 or above only.) python_suffixes A map defining file suffixes for various Python file types. Each entry in the map is an array of strings. The map has the following keys. `bytecode` Suffixes for bytecode modules. Corresponds to `importlib.machinery.BYTECODE_SUFFIXES`. e.g. `[\".pyc\"]`. `debug_bytecode` Suffixes for debug bytecode modules. Corresponds to `importlib.machinery.DEBUG_BYTECODE_SUFFIXES`. e.g. `[\".pyc\"]`. `extension` Suffixes for extension modules. Corresponds to `importlib.machinery.EXTENSION_SUFFIXES`. e.g. `[\".cpython-313-x86_64-linux-gnu.so\", \".abi3.so\", \".so\"]`. `optimized_bytecode` Suffixes for optimized bytecode modules. Corresponds to `importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES`. e.g. `[\".pyc\"]`. `source` Suffixes for source modules. Corresponds to `importlib.machinery.SOURCE_SUFFIXES`. e.g. `[\".py\"]`. (Version 5 or above only.) python_bytecode_magic_number Magic number to use for bytecode files, expressed as a hexadecimal string. (Version 5 or above only.) libpython_link_mode How `libpython` is linked. Values can be one of the following: `static` Statically linked. `shared` Dynamically linked. (A `libpythonXY` shared library will be part of the distribution.) (Version 5 or above only.) link_mode Alias of `libpython_link_mode`. (Version 4 or above only. Deprecated in version 5.) python_symbol_visibility Defines how Python symbols are defined in binaries. `global-default` (UNIX only.) Symbols are defined as global and have default binding, making them visible outside their defining component. `dllexport` (Windows only.) Symbols are exported via `__declspec(dllexport)`, making them visible to external libraries. (Version 5 or above only.) python_extension_module_loading Defines support for loading Python extension modules. The value is an array of strings denoting support for various loading mechanisms. Note that downstream consumers reconstructing a new binary from object files or a static library can alter support depending on how that binary is linked. The special values are as follows. `builtin` Supports loading of builtin extension modules compiled into the binary. (This should always be present.) `shared-library` Supports loading of extension modules defined as shared libraries. e.g. from `.so` or `.pyd` files. (Version 5 or above only.) apple_sdk_canonical_name Optional canonical name of Apple SDK used to build. Should only be present for target triples with `apple` in them. The canonical name can be used to find a copy of this SDK on another machine. (Version 7 or above only.) apple_sdk_platform Optional name of the platform of the Apple SDK used to build. Should only be present for target triples with `apple` in them. e.g. `macosx` (Version 7 or above only.) apple_sdk_version Optional version of the Apple SDK used to build. Should only be present for target triples with `apple` in them. If relinking build artifacts, ideally this exact SDK version is used. Newer versions will likely work. Older versions or Clang toolchains associated with older versions may not. (Version 7 or above only.) apple_sdk_deployment_target Optional version of the Apple SDK deployment target used to build. This effectively establishes a minimum version of the target operating system this binary is purportedly compatible with. (Version 7 or above only.) crt_features Describes C Runtime features/requirements for binaries. The value is an array of strings denoting various properties. The special string values are as follows. `glibc-dynamic` Binaries link dynamically against glibc. `glibc-max-symbol-version:N` Denotes the max symbol version seen in glibc versioned symbols. This effectively advertises the oldest version of glibc that binaries support and indirectly advertises the oldest Linux distributions binaries can run on. `static` Binaries link the CRT statically. `vcruntime:N` Binaries link against the Microsoft Visual C++ Redistributable Runtime, version `N`. `N` is a string like `140`, which denotes the version component in a `vcruntimeXYZ.dll` file. `libSystem` Binaries link against `libSystem.B.dylib`, which is the mega library backing a lot of systems-level functionality in macOS. (Version 5 or above only.) run_tests The path to a Python script to run the test harness for this distribution. (Version 5 or above only.) build_info A map describing build configuration and artifacts for this distribution. See the `build_info Data` section below. licenses Array of strings containing the license shortname identifiers from the SPDX license list (https://spdx.org/licenses/) for the Python distribution. (Version 2 or above only.) license_path Path to a text file containing the license for this Python distribution. (Version 2 or above only.) tcl_library_path Relative path to location of tcl library files. The path should be a directory tree containing tcl files to support the tkinter extension. This will include a subset of the library files provided by the tcl and tk packages. This points to the root directory containing tcl resources. Actual tcl resources are in sub-directories underneath, as identified by `tcl_library_paths`. (Version 3 or above only.) tcl_library_paths Array of relative paths holding tcl library files relative to `tcl_library_path`. Because `tcl_library_path` can be shared with other resources (e.g. on UNIX the path is typically `install/lib`, which holds system libraries as well), distributions may advertise the list of directories under `tcl_library_path` actually containing tcl resources. (Version 5 or above only.)"},{"location":"distributions/#build_info-data","title":"build_info Data","text":"The `build_info` key in the `PYTHON.json` file describes build artifacts in the Python distribution. The primary goal of the data is to give downstream distribution consumers enough details to integrate build artifacts into their own build systems. This includes the ability to produce a Python binary with a custom set of built-in extension modules. This map has the following keys: core A map describing the core Python distribution (essentially `libpython`). objs An array of paths to object files constituting the Python core distribution. Core object files are typically object files that are linked together to create libpython. links An array of linking requirement maps. (See below for data format.) shared_lib Path to a shared library representing `libpython`. May not be defined. (Version 4 or above only.) static_lib Path to a static library representing `libpython`. May not be defined. (Version 4 or above only.) inittab_object Path to object file defining `_PyImport_Inittab`, which defines built-in extension modules. (Version 5 or above only.) inittab_source Path to source code file that defines `_PyImport_Inittab`. On CPython, this will point to a `config.c` file. (Version 5 or above only.) inittab_cflags Array of strings constituting compiler flags to use when compiling `inittab_source`. (Version 5 or above only.) extensions A map of extension names to an array of maps describing candidate extensions. Extensions are non-core/non-essential parts of the Python distribution that are frequently built as standalone entities. Names in this map denote the name of the extension module. Values are arrays of maps. Each map represents a potential candidate providing the extension. There is frequently only a single extension candidate. Multiple candidates can occur if there are e.g. varying libraries an extension can be linked against to supply underlying functionality. Each map has the following keys: in_core Boolean indicating if this extension is defined by the core distribution. If true, object files should be in the `['core']['objs']` array, not the `objs` array in this map. Downstream consumers should key off this value to determine how to assemble this extension's code into a new distribution. This field was introduced to support Windows, where CPython's Visual Studio project files define various extensions as part of the project providing libpython. This is in contrast to make-based builds, where the `Modules/Setup.*` files treat each extension as separate entities. init_fn The name of the extension module initialization function for this extension. The string value may be `NULL`, which may need special handling by consumers. licenses Array of strings containing the license shortname identifiers from the SPDX license list (https://spdx.org/licenses/). If this field is missing, licenses are unknown. Empty array denotes no known licenses. The license applies to additional libraries needed by this extension, not the extension itself, as extensions should be licensed the same as the Python distribution. (Version 2 or above only.) license_path Paths to text files containing the licenses for this extension. (Version 2 or above only.) license_public_domain Bool indicating that the license for the extension is in the public domain. There is no SPDX identifier for public domain. And we want to be explicit about something being in the public domain because of the legal implications. (Version 2 or above only.) links An array of linking requirement maps. (See below for data format.) objs An array of paths to object files constituting this extension module. required Boolean indicating if this extension is required to initialize the Python interpreter. shared_lib The path to a shared library defining this extension module. May not be defined. (Version 4 or above only.) static_lib The path to a static library defining this extension module. May not be defined. variant String describing this extension variant. Downstream consumers can key off this value to choose an appropriate extension variant when there are multiple options. object_file_format Denotes the data format for object files. Can be one of the following values. `elf` Standard object file format for Linux. `llvm-bitcode:N` Files are LLVM bitcode produced with LLVM version `N`. e.g. `llvm-bitcode:10.0.0`. This variant is typically seen for builds using LTO. `coff` Standard object file format for Windows. `mach-o` Standard object file format for macOS. (Version 5 or newer only.) Each entry in a `links` array is a map with the following keys: name Name of the library being linked against. path_static Path to the static version of this library, if available in the distribution. path_dynamic Path to the dynamic version of this library, if available in the distribution. framework Denotes that the link target is a macOS framework. system Denotes that the link target is a system library. System libraries are typically passed into the linker by name only and found using default library search paths."},{"location":"distributions/#install-only-archive","title":"Install Only Archive","text":"At release time, this project produces tar files containing just the Python installation, without the `PYTHON.json` or build files from the full `.tar.zst` archives. These are referred to as install only archives. An install only archive is created by taking a `.tar.zst` and rewriting `python/install/*` to `python/*`. All files not under `python/install/*` are not carried forward to the install only archive. For each Python version, target, and threading variant, the fastest available build is selected for the install only archive. Builds are generally preferred in the following order: `pgo+lto`, `pgo`, `lto`, `noopt`. An install only stripped archive is also available. This archive is equivalent to install only, but without debug symbols, which results in a smaller download and on-disk footprint. For CPython 3.13 and newer, install only and install only stripped archives are created separately for the GIL-enabled and free-threaded variants. Free-threaded archives include `freethreaded` in the filename. For musl targets, install only archives use dynamically linked builds. Fully static musl builds are only available as full archives with `+static` in their build options. For maximum compatibility, install only and install only stripped archives use gzip compression."},{"location":"quirks/","title":"Behavior Quirks","text":""},{"location":"quirks/#behavior-quirks","title":"Behavior Quirks","text":"While these Python distributions are intended to be broadly compatible with the Python ecosystem, there are a few known behavior quirks that affect specific environments, packages, or use cases. "},{"location":"quirks/#if-special-keys-do-not-work-in-the-python-repl","title":"If special keys do not work in the Python REPL","text":"If you attempt to run `python` and the backspace key doesn't erase characters or the arrow keys don't work as expected, this is because the executable can't find the terminfo database. If this happens, the Python REPL will print the following warning message on startup: `Cannot read termcap database;\nusing dumb terminal settings.\n` When you type a special key like the backspace key, this is registered as a key press. There is special software (typically `readline` or `libedit`) that most interactive programs use that intercepts these special key presses and converts them into special behavior, such as moving the cursor back instead of forward. But because computer environments are different, there needs to be some definition of how these special behaviors are performed. This is the terminfo database. When `readline` and `libedit` are compiled, there is typically a hard-coded set of search locations for the terminfo database baked into the built library. And when you build a program (like Python) locally, you link against `readline` or `libedit` and get these default locations for free. These Python distributions compile and use their own version of `libedit` to avoid a dependency on what is (or isn't) installed on your system. This means that they do not use your system-provided libraries for reading the terminfo database. This version of `libedit` is configured to look for in locations that should work for most OSes (specifically, `/usr/share/terminfo` on macOS, and `/etc/terminfo`, `/lib/terminfo`, and `/usr/share/terminfo` on Linux, which should cover all major Linux distributions), but it is possible that your environment has it somewhere else. If your OS stores the terminfo database in an uncommon location, you can set the `TERMINFO_DIRS` environment variable so that `libedit` can find it. For instance, you may need to do something like: $ TERMINFO_DIRS=/uncommon/place/terminfo install/bin/python3.10 If you are running on a relatively standard OS and this does not work out of the box, please file a bug report so we can add the location of the terminfo database to the build. For convenience, a relatively recent copy of the terminfo database is distributed in the `share/terminfo` directory (`../../share/terminfo` relative to the `bin/python3` executable) in Linux distributions. Note that ncurses and derived libraries don't know how to find this directory since they are configured to use absolute paths to the terminfo database and the absolute path of the Python distribution is obviously not known at build time! So actually using this bundled terminfo database will require custom code setting `TERMINFO_DIRS` before ncurses/libedit/readline are loaded. "},{"location":"quirks/#no-tix-on-unix","title":"No tix on UNIX","text":"Tix is an old widget library for Tcl/Tk. Python previously had a wrapper for it in `tkinter.tix`, but it was deprecated in Python 3.6 (the recommendation is to use `tkinter.ttk`) and removed in Python 3.13. The macOS and Linux distributions from this project do not build and ship Tix, even for Python versions 3.12 and below. We had previously attempted to ship Tix support on Linux, but it was broken and nobody reported an issue about it. The macOS distributions from this project never shipped support for Tix. The official Python.org macOS installers and Apple's build of Python do not ship support for Tix, either, so this project behaves similarly to those distributions. "},{"location":"quirks/#no-pipexe-on-windows","title":"No `pip.exe` on Windows","text":"The Windows distributions have `pip` installed however no `Scripts/pip.exe`, `Scripts/pip3.exe`, and `Scripts/pipX.Y.exe` files are provided because the way these executables are built isn't portable. (It might be possible to change how these are built to make them portable.) To use pip, run `python.exe -m pip`. (It is generally a best practice to invoke pip via `python -m pip` on all platforms so you can be explicit about the `python` executable that pip uses.) "},{"location":"quirks/#linking-static-library-on-macos","title":"Linking Static Library on macOS","text":"Python 3.10+ makes use of the `__builtin_available()` compiler feature. This functionality requires a symbol from `libclang_rt`, which may not be linked by default. Failure to link against `libclang_rt` could result in a linker error due to an undefined symbol `___isOSVersionAtLeast`. To work around this linker failure, link against the static library `libclang_rt.&lt;platform&gt;.a` present in the Clang installation. e.g. `libclang_rt.osx.a`. You can find this library by invoking `clang --print-search-dirs` and looking in the `lib/darwin` directory under the printed `libraries` directory. An example path is `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/lib/darwin/libclang_rt.osx.a`. A copy of the `libclang_rt.&lt;platform&gt;.a` from the Clang used to build the distribution is included in the archive. However, it isn't annotated in `PYTHON.json` because we're unsure if using the file with another build/version of Clang is supported. Use at your own risk. See https://jonnyzzz.com/blog/2018/06/05/link-error-2/ and https://jonnyzzz.com/blog/2018/06/13/link-error-3/ for more on this topic. "},{"location":"quirks/#use-of-libedit-on-linux","title":"Use of `libedit` on Linux","text":"Python 3.10+ Linux distributions link against `libedit` (as opposed to `readline`) by default, as `libedit` is supported on 3.10+ outside of macOS. Most Python builds on Linux will link against `readline` because `readline` is the dominant library on Linux. Some functionality may behave subtly differently as a result of our choice to link `libedit` by default. (We choose `libedit` by default to avoid GPL licensing requirements of `readline`.) "},{"location":"quirks/#references-to-build-time-paths","title":"References to Build-Time Paths","text":"The built Python distribution captures some absolute paths and other build-time configuration in a handful of files:  <li>In a `_sysconfigdata_*.py` file in the standard library. e.g.   `lib/python3.10/_sysconfigdata__linux_x86_64-linux-gnu.py`.</li> <li>In a `Makefile` under a `config-*` directory in the standard library.   e.g. `lib/python3.10/config-3.10-x86_64-linux-gnu/Makefile`.</li> <li>In python-build-standalone's metadata file `PYTHON.json` (mostly   reflected values from `_sysconfigdata_*.py`).</li>  Each of these serves a different use case. But the general theme is various aspects of the Python distribution attempt to capture how Python was built. The most common use of these values is to facilitate compiling or linking other software against this Python build. For example, the `_sysconfigdata*` module is loaded by the sysconfig module. `sysconfig` in turn is used by packaging tools like `setuptools` and `pip` to figure out how to invoke a compiler for e.g. compiling C extensions from source. When installed by uv, these absolute paths are fixed up to point to the actual location on your system where the distribution was installed, so this quirk generally does not affect uv users. The third-party tool sysconfigpatcher also does this and might be helpful to use or reference if you are installing these distributions on your own. In particular, you may see references to our install-time paths on the build infrastructure, e.g., `/build` and `/install` on Linux, a particular SDK in `/Applications/Xcode.app` on macOS, and temporary directories on Windows. Also, Python reports the compiler and flags in use, just in case it is needed to make binary-compatible extensions. On Linux, for instance, we use our own builds of Clang and potentially some flags (warnings, optimizations, locations of the build environment) that do not work or apply in other environments. We try to configure Python to remove unneeded flags and absolute paths to files in the build environment. references to build-time paths. Python's `sysconfig` system requires listing a compiler, so we leave it set to `clang` without the absolute path, but you should be able to use another compiler like `gcc` to compile extensions, too. If there is a build time normalization that you think should be performed to make distributions more portable, please file a GitHub issue.   "},{"location":"quirks/#former-quirks","title":"Former quirks","text":"The following quirks were previously listed on this page but have since been resolved.  <li>\"Static Linking of musl libc Prevents Extension Module Library   Loading\": Starting with the 20250311 release, the default musl   distributions are dynamically linked by default, so extension modules   should work properly. Note that these now require a system-wide   installation of the musl C library. (This is present by default on   musl-based OSes like Alpine, and many glibc-based distros have a   `musl` package you can safely co-install with glibc, too.) If you   specifically need a statically-linked binary, variants with the   `+static` build option are available, but these retain the quirk   that compiled extension modules (e.g., `musllinux` wheels) cannot be   loaded.</li> <li>\"Missing `libcrypt.so.1`\": The 20230507 release and earlier required   the system library `libcrypt.so.1`, which stopped being shipped by   default in several Linux distributions around 2022. Starting with the   20230726 release, this dependency is now only needed by the deprecated   `crypt` module, which only exists on Python 3.12 and lower. If you   still need this module, your OS may offer a `libxcrypt` package to   provide this library. Alternatively, there are suggestions in What's   New in Python 3.13 about third-party replacements for the `crypt`   module.</li> <li>\"Static Linking of `libX11` / Incompatibility with PyQt on Linux\":   The 20220318 release and earlier exported dynamic symbols for the   internal, statically-linked build of `libX11` and other libraries.   These would cause conflicts and potential crashes when using   third-party extension modules such as PyQt that load an actual shared   `libX11` library (usually provided by your OS). Starting with the   20220502 release, symbols from internal dependencies are no longer   exported.</li> "},{"location":"running/","title":"Running Distributions","text":""},{"location":"running/#running-distributions","title":"Running Distributions","text":""},{"location":"running/#using-uv","title":"Using uv","text":"The most common method of using the prebuilt distributions from this project is with uv. By default, uv will download, install, and use an appropriate distribution if the system does not provide a compatible Python installation that uv can discover. These downloaded distributions are referred to as managed Python installations, as compared to system Python installations. You can configure uv to always use a managed Python installation, for example by using the `--managed-python` flag. To run a particular distribution using `uv`: `uvx --managed-python python\n` A Python version or another specifier can be included: `uvx --managed-python python@3.13\nuvx --managed-python python@3.14+freethreaded\n` The uv documentation on installing Python and Python versions provides more information and examples."},{"location":"running/#obtaining-distributions","title":"Obtaining Distributions","text":"Prebuilt distributions are published as releases on GitHub at https://github.com/astral-sh/python-build-standalone/releases. Simply go to that page and find the latest release along with its release notes. Machines can find the latest release by querying the GitHub releases API. Alternatively, a JSON file publishing metadata about the latest release can be fetched from https://raw.githubusercontent.com/astral-sh/python-build-standalone/latest-release/latest-release.json. The JSON format is simple and hopefully self-descriptive. Published distributions vary by their:  <li>Python version</li> <li>Target machine architecture</li> <li>Build configuration</li> <li>Archive flavor</li>  The Python version is hopefully pretty obvious. The target machine architecture defines the CPU type and operating system the distribution runs on. We use LLVM target triples. Distributions are produced for the following target triples: `aarch64-apple-darwin` macOS ARM CPUs, i.e., Apple Silicon. `x86_64-apple-darwin` macOS Intel CPUs. `x86_64-pc-windows-msvc` Windows 64-bit Intel/AMD CPUs. `i686-pc-windows-msvc` Windows 32-bit Intel/AMD CPUs. `aarch64-pc-windows-msvc` Windows 64-bit ARM CPUs. Available for CPython 3.11 and newer. `x86_64-unknown-linux-gnu` Linux 64-bit Intel/AMD CPUs linking against GNU libc. `x86_64-unknown-linux-musl` Linux 64-bit Intel/AMD CPUs linking against musl libc. Distributions are provided that dynamically link musl or are fully static (`+static`). Dynamically linked distributions require musl to be installed on the host. Fully static distributions have no shared library dependencies, but cannot load Python `.so` extensions. `aarch64-unknown-linux-*` Similar to above except targeting Linux on ARM64 CPUs. Distributions are provided for GNU libc, musl, and static musl. For example, this target supports AWS Graviton EC2 instances. Many Linux ARM devices are also `aarch64`. `x86_64_v2-*` Targets 64-bit Intel/AMD CPUs approximately newer than Nehalem (released in 2008). Binaries will have SSE3, SSE4, and other CPU instructions added after the ~initial x86-64 CPUs were launched in 2003. Binaries will crash if you attempt to run them on an older CPU not supporting the newer instructions. `x86_64_v3-*` Targets 64-bit Intel/AMD CPUs approximately newer than Haswell (released in 2013) and Excavator (released in 2015). Binaries will have AVX, AVX2, MOVBE and other newer CPU instructions. Binaries will crash if you attempt to run them on an older CPU not supporting the newer instructions. Most x86-64 CPUs manufactured after 2013 (Intel) or 2015 (AMD) support this microarchitecture level. An exception is Intel Atom P processors, which Intel released in 2020 but did not include AVX. `x86_64_v4-*` Targets 64-bit Intel/AMD CPUs with some AVX-512 instructions. Requires Intel CPUs manufactured after ~2017. But many Intel CPUs don't have AVX-512. The `x86_64_v2`, `x86_64_v3`, and `x86_64_v4` binaries usually crash on startup when run on an incompatible CPU. We don't recommend running the `x86_64_v4` builds in production because they likely don't yield a reliable performance benefit. Unless you are executing these binaries on a CPU older than ~2008 or ~2013, we recommend running the `x86_64_v2` or `x86_64_v3` binaries, as these should be slightly faster since they take advantage of more modern CPU instructions which are more efficient. But if you want maximum portability, stick with the baseline `x86_64` builds. `armv7-unknown-linux-gnueabi` Linux 32-bit ARM CPUs without hardware floating-point instructions, linking against GNU libc. This is an uncommon platform. In most cases, the hardware floating-point target should be used. These distributions can be used on Debian's `armel` port. `armv7-unknown-linux-gnueabihf` Linux 32-bit ARM CPUs with hardware floating-point instructions, linking against GNU libc. This is a common 32-bit ARM platform. Raspberry Pi model 2 and later can use these distributions on many 32-bit Linux distributions. `ppc64le-unknown-linux-gnu` Linux 64-bit POWER8+ CPUs linking against GNU libc. `riscv64-unknown-linux-gnu` Linux 64-bit RISC-V CPUs linking against GNU libc. `s390x-unknown-linux-gnu` Linux 64-bit IBM Z (s390x) CPUs linking against GNU libc. We recommend using the `*-unknown-linux-gnu` builds on Linux, since they are able to load compiled Python extensions. The non-static `*-unknown-linux-musl` builds should be used on musl-based Linux distributions like Alpine Linux. If you don't need to load compiled extensions not provided by the standard library, or you are willing to compile and link third-party extensions into a custom binary, the static `*-unknown-linux-musl` builds should work just fine. The build configuration denotes how Python and its dependencies were built. Common configurations include: `pgo+lto` Profile-guided optimization and link-time optimization. These should be the fastest distributions since they have the most build-time optimizations. `pgo` Profile-guided optimization. Starting with CPython 3.12, BOLT is also applied alongside traditional PGO on platforms supporting BOLT. (Currently just Linux x86-64.) `lto` Link-time optimization. `noopt` A regular optimized build without PGO or LTO. `debug` A debug build. No optimizations. `freethreaded` A free-threaded build, available for CPython 3.13 and newer. This option is combined with an optimization option, such as `freethreaded+pgo+lto` or `freethreaded+lto`. `static` A fully static musl build. Has no shared library dependencies and cannot load dynamically linked Python extensions. The archive flavor denotes the content in the archive. See Distribution Archives for more. Casual users will likely want to use the `install_only` archive, as most users do not need the build artifacts present in the `full` archive. The `install_only` archive does not include the optimization options in its filename. For each Python version, target, and threading variant, it uses the fastest available build configuration. An `install_only_stripped` archive is also available. This archive is equivalent to `install_only`, but without debug symbols, which results in a smaller download and on-disk footprint. For CPython 3.13 and newer, free-threaded archives are identified by `freethreaded` in the filename. Fully static musl builds are only available as `full` archives with `+static` in their build options. The `install_only` and `install_only_stripped` musl archives use dynamically linked builds."},{"location":"running/#extracting-distributions","title":"Extracting Distributions","text":"Distributions are defined as zstandard or gzip compressed tarballs. Modern versions of `tar` support zstandard and you can extract like any normal archive: `$ tar -axvf path/to/distribution.tar.zstd\n` (The `-a` argument tells tar to guess the compression format by the file extension.) If your `tar` doesn't support `-a` (e.g. the default macOS `tar`), try: `$ tar xvf path/to/distribution.tar.zstd\n` If you do not have `tar`, you can install and use the `zstd` tool (typically available via a `zstd` or `zstandard` system package): `$ zstd -d path/to/distribution.tar.zstd\n$ tar -xvf path/to/distribution.tar\n` If you want to extract the distribution with Python, use the `zstandard` Python package: `import tarfile\nimport zstandard\n\nwith open(\"path/to/distribution.tar.zstd\", \"rb\") as ifh:\n    dctx = zstandard.ZstdDecompressor()\n    with dctx.stream_reader(ifh) as reader:\n        with tarfile.open(mode=\"r|\", fileobj=reader) as tf:\n            tf.extractall(\"path/to/output/directory\")\n`"},{"location":"running/#runtime-requirements","title":"Runtime Requirements","text":""},{"location":"running/#linux","title":"Linux","text":"The produced Linux binaries have minimal references to shared libraries and thus can be executed on most Linux systems. Distributions linked against glibc may reference the following shared libraries:  <li>linux-vdso.so.1</li> <li>libpthread.so.0</li> <li>libdl.so.2 (required by ctypes extension)</li> <li>libutil.so.1</li> <li>librt.so.1</li> <li>libm.so.6</li> <li>libc.so.6</li> <li>ld-linux-x86-64.so.2</li>  On Python 3.12 and earlier, the deprecated `crypt` module additionally requires `libcrypt.so.1`. The minimum glibc version required for most targets is 2.17. This should make binaries compatible with the following Linux distributions:  <li>Fedora 21+</li> <li>RHEL/CentOS 7+</li> <li>openSUSE 13.2+</li> <li>Debian 8+ (Jessie)</li> <li>Ubuntu 14.04+</li>  For the `riscv64-unknown-linux-gnu` target, the minimum glibc version is 2.28. Distributions linked against musl do not depend on glibc. By default, musl distributions are dynamically linked and require musl to be installed on the host. Fully static distributions use the `+static` build option and have no shared library dependencies, but cannot load dynamically linked Python extension modules."},{"location":"running/#windows","title":"Windows","text":"Windows distributions model the requirements of the official Python distributions:  <li>CPython 3.14 and newer: Windows 10 or newer.</li> <li>CPython 3.13 and earlier: Windows 8.1 or newer.</li>  Windows Server support follows the corresponding CPython release's upstream platform policy."},{"location":"running/#extra-python-software","title":"Extra Python Software","text":"Python installations have some additional software pre-installed:  <li>pip</li> <li>setuptools (for Python 3.11 and older)</li>  The intent of the pre-installed software is to facilitate end-user package installation without having to first bootstrap a packaging tool via an insecure installation technique (such as `curl | sh` patterns)."},{"location":"running/#licensing","title":"Licensing","text":"Python and its various dependencies are governed by varied software use licenses. This impacts the rights and requirements of downstream consumers. Most licenses are fairly permissive. Notable exceptions to this are GDBM and readline, which are both licensed under GPL Version 3. We build CPython against libedit - as opposed to readline - to avoid this GPL dependency. This requires patches on CPython &lt; 3.10. Distribution releases before 2023 may link against readline and are therefore subject to the GPL. We globally disable the `_gdbm` extension module to avoid linking against GDBM and introducing a GPL dependency. Distribution releases before 2023 may link against GDBM and be subject to the GPL. It is important to understand the licensing requirements when integrating the output of this project into derived works. To help with this, the JSON document describing the Python distribution contains licensing metadata and the archive contains copies of license texts."},{"location":"running/#reconsuming-build-artifacts","title":"Reconsuming Build Artifacts","text":"Produced Python distributions contain object files and libraries for the built Python and its dependencies. It is possible for downstream consumers to take these build artifacts and link them into a new binary. Reconsuming the build artifacts this way can be a bit fragile due to incompatibilities between the host that generated them and the target that is consuming them. To ensure optimal compatibility, it is highly recommended to use the same toolchain for all operations. This is often harder than it sounds. For example, if these build artifacts were to be combined into a Rust binary, the version of LLVM that the Rust compiler itself was built against can matter. As a concrete example, the Rust 1.31 compiler will produce LLVM intrinsics that vary from intrinsics that would be produced with LLVM/Clang 7. At linking time, you would get errors like the following: `Intrinsic has incorrect argument type!\nvoid (i8*, i8, i64, i1)* @llvm.memset.p0i8.i64\n` The distributions that contain object files are useful for embedding Python in a larger binary. See the PyOxidizer sister project for such a downstream repackager. Some users of these distributions might be better served by the PyOxy sister project. PyOxy takes these Python distributions and adds Rust code to enhance the functionality of the Python interpreter. The official PyOxy release binaries are single-file executables providing a full-featured Python interpreter."},{"location":"status/","title":"Project Status","text":""},{"location":"status/#project-status","title":"Project Status","text":"There is support for producing CPython distributions for Windows, macOS, and Linux. All distributions are highly self-contained and have limited shared library dependencies."},{"location":"status/#target-notes","title":"Target Notes","text":"Linux x86_64 and aarch64 targets (`x86_64-*` and `aarch64-*`) are built on native hardware using a modern LLVM toolchain. All other architectures are cross-compiled on x86_64 using Debian-provided cross-compilation tools. As a result, these targets are not as highly optimized as aarch64 and x86_64 targets."},{"location":"status/#test-failures","title":"Test Failures","text":"This repository contains a `test-distribution.py` script that can be used to run the Python test harness from a distribution archive. Currently, this script is only used to validate a limited set of functionality for each distribution. The entire Python test harness is not run on a regular basis. Work is ongoing to perform this type of testing on a regular basis. The details below on known failing and skipped tests are out of date. In the future, this information will be updated and presented elsewhere. Here, we track the various known failures when running `test-distribution.py --stdlib -- /path/to/distribution.tar.zst -u all`."},{"location":"status/#test__locale","title":"`test__locale`","text":"Known failing on: Windows This test fails as follows: `======================================================================\nERROR: test_float_parsing (test.test__locale._LocaleTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmpjx7a33kd\\python\\install\\lib\\test\\test__locale.py\", line 184, in test_float_parsing\n    if localeconv()['decimal_point'] != '.':\nUnicodeDecodeError: 'locale' codec can't decode byte 0xa0 in position 0: decoding error\n\n======================================================================\nERROR: test_lc_numeric_localeconv (test.test__locale._LocaleTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmpjx7a33kd\\python\\install\\lib\\test\\test__locale.py\", line 130, in test_lc_numeric_localeconv\n    formatting = localeconv()\nUnicodeDecodeError: 'locale' codec can't decode byte 0xa0 in position 0: decoding error\n\n----------------------------------------------------------------------\n`"},{"location":"status/#test_locale","title":"`test_locale`","text":"Known failing on: Windows This test fails on Windows: `======================================================================\nERROR: test_getsetlocale_issue1813 (test.test_locale.TestMiscellaneous)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test_locale.py\", line 567, in test_getsetlocale_issue1813\n    locale.setlocale(locale.LC_CTYPE, loc)\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\locale.py\", line 608, in setlocale\n    return _setlocale(category, locale)\nlocale.Error: unsupported locale setting\n\n----------------------------------------------------------------------\n`"},{"location":"status/#test_subprocess","title":"`test_subprocess`","text":"Known Failing on: Linux This fails in the following manner: `test_executable_without_cwd (test.test_subprocess.ProcessTestCaseNoPoll) ... Could not find platform independent libraries &lt;prefix&gt;\nCould not find platform dependent libraries &lt;exec_prefix&gt;\nConsider setting $PYTHONHOME to &lt;prefix&gt;[:&lt;exec_prefix&gt;]\nFatal Python error: initfsencoding: Unable to get the locale encoding\nModuleNotFoundError: No module named 'encodings'\n\nCurrent thread 0x00007fd77c231740 (most recent call first):\nFAIL\n\n======================================================================\nFAIL: test_executable_without_cwd (test.test_subprocess.ProcessTestCaseNoPoll)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"/tmp/tmp8hef0kr4/python/install/lib/python3.7/test/test_subprocess.py\", line 436, in test_executable_without_cwd\n    executable=sys.executable)\n  File \"/tmp/tmp8hef0kr4/python/install/lib/python3.7/test/test_subprocess.py\", line 355, in _assert_cwd\n    self.assertEqual(47, p.returncode)\nAssertionError: 47 != -6\n` We're unsure what is going on here. The error from `initfsencoding` is what happens when the first `import` during `Py_Initialize()` fails. So it appears the test somehow can't locate the Python standard library."},{"location":"status/#test_tk","title":"`test_tk`","text":"Known Failing on: Linux This fails in the following manner: `======================================================================\nFAIL: test_from (tkinter.test.test_tkinter.test_widgets.ScaleTest)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"/tmp/tmpoqqjd5gi/python/install/lib/python3.7/tkinter/test/test_tkinter/test_widgets.py\", line 867, in test_from\n    self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=float_round)\n  File \"/tmp/tmpoqqjd5gi/python/install/lib/python3.7/tkinter/test/widget_tests.py\", line 106, in checkFloatParam\n    self.checkParam(widget, name, value, conv=conv, **kwargs)\n  File \"/tmp/tmpoqqjd5gi/python/install/lib/python3.7/tkinter/test/widget_tests.py\", line 63, in checkParam\n    self.assertEqual2(widget[name], expected, eq=eq)\n  File \"/tmp/tmpoqqjd5gi/python/install/lib/python3.7/tkinter/test/widget_tests.py\", line 47, in assertEqual2\n    self.assertEqual(actual, expected, msg)\nAssertionError: 14.9 != 15.0\n` This seems like a minor issue and might be a bug in the test itself."},{"location":"status/#test_winconsoleio","title":"`test_winconsoleio`","text":"This fails as follows: `======================================================================\nERROR: test_ctrl_z (test.test_winconsoleio.WindowsConsoleIOTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test_winconsoleio.py\", line 190, in test_ctrl_z\n    a, b = stdin.read(1), stdin.readall()\nOSError: [WinError 87] The parameter is incorrect\n\n======================================================================\nERROR: test_input (test.test_winconsoleio.WindowsConsoleIOTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test_winconsoleio.py\", line 144, in test_input\n    self.assertStdinRoundTrip('abc123')\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test_winconsoleio.py\", line 137, in assertStdinRoundTrip\n    actual = input()\nOSError: [WinError 87] The parameter is incorrect\n\n======================================================================\nFAIL: test_partial_reads (test.test_winconsoleio.WindowsConsoleIOTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test_winconsoleio.py\", line 166, in test_partial_reads\n    self.assertEqual(actual, expected, 'stdin.read({})'.format(read_count))\nAssertionError: b'\\r\\n' != b'\\xcf\\xbc\\xd1\\x9e\\xd0\\xa2\\xce\\xbb\\xd0\\xa4\\xd0\\x99\\r\\n' : stdin.read(1)\n\n======================================================================\nFAIL: test_partial_surrogate_reads (test.test_winconsoleio.WindowsConsoleIOTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test_winconsoleio.py\", line 183, in test_partial_surrogate_reads\n    self.assertEqual(actual, expected, 'stdin.read({})'.format(read_count))\nAssertionError: b'\\xc3\\x84\\r\\n' != b'\\xf4\\x81\\xbf\\xbf\\xf4\\x81\\x80\\x81\\r\\n' : stdin.read(1)\n\n----------------------------------------------------------------------\n\nRan 10 tests in 0.006s\n\nFAILED (failures=2, errors=2)\ntest test_winconsoleio failed\n0:00:00 Re-running test__locale in verbose mode\ntest_float_parsing (test.test__locale._LocaleTests) ... ERROR\ntest_lc_numeric_basic (test.test__locale._LocaleTests) ... skipped 'nl_langinfo is not available'\ntest_lc_numeric_localeconv (test.test__locale._LocaleTests) ... ERROR\ntest_lc_numeric_nl_langinfo (test.test__locale._LocaleTests) ... skipped 'nl_langinfo is not available'\n\n======================================================================\nERROR: test_float_parsing (test.test__locale._LocaleTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test__locale.py\", line 184, in test_float_parsing\n    if localeconv()['decimal_point'] != '.':\nUnicodeDecodeError: 'locale' codec can't decode byte 0xa0 in position 0: decoding error\n\n======================================================================\nERROR: test_lc_numeric_localeconv (test.test__locale._LocaleTests)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n  File \"C:\\Users\\gps\\AppData\\Local\\Temp\\tmp8m94v2m5\\python\\install\\lib\\test\\test__locale.py\", line 130, in test_lc_numeric_localeconv\n    formatting = localeconv()\nUnicodeDecodeError: 'locale' codec can't decode byte 0xa0 in position 0: decoding error\n\n----------------------------------------------------------------------\n`"},{"location":"status/#test-skips","title":"Test Skips","text":""},{"location":"status/#linux","title":"Linux","text":"The following tests are skipped on Linux: test_asdl_parser test irrelevant for an installed Python test_clinic install/lib/Tools/clinic' path does not exist test_dbm_gnu No module named '_gdbm' test_devpoll test works only on Solaris OS family test_gdb test_gdb only works on source builds at the moment. test_kqueue test works only on BSD test_msilib No module named 'msilib' test_ossaudiodev [Errno 2] No such file or directory: '/dev/dsp' test_startfile object `&lt;module 'os' from '.../install/lib/python3.7/os.py'&gt;` has no attribute 'startfile' test_winconsoleio test only relevant on win32 test_winreg No module named 'winreg' test_winsound No module named 'winsound' test_zipfile64 test requires loads of disk-space bytes and a long time to run"},{"location":"status/#macos","title":"macOS","text":"The following tests are skipped on macOS: test_asdl_parser test irrelevant for an installed Python test_clinic python/install/lib/Tools/clinic' path does not exist test_dbm_gnu No module named '_gdbm' test_devpoll test works only on Solaris OS family test_epoll test works only on Linux 2.6 test_gdb Couldn't find gdb on the path test_msilib No module named 'msilib' test_multiprocessing_fork test may crash on macOS (bpo-33725) test_nis No module named 'nis' test_ossaudiodev No module named 'ossaudiodev' test_spwd No module named 'spwd' test_startfile object `&lt;module 'os' from '.../install/lib/python3.7/os.py'&gt;` has no attribute 'startfile' test_tix tix is not built by this project test_tk cannot run without OS X gui process test_ttk_guionly cannot run without OS X gui process test_winconsoleio test only relevant on win32 test_winreg No module named 'winreg' test_winsound No module named 'winsound' test_zipfile64 test requires loads of disk-space bytes and a long time to run"},{"location":"technotes/","title":"Technical Notes","text":""},{"location":"technotes/#technical-notes","title":"Technical Notes","text":""},{"location":"technotes/#how-it-works","title":"How It Works","text":"The first thing the `build.py` scripts do is bootstrap an environment for building Python. Linux builds use Docker images based on deterministic Debian snapshots selected for each target:  <li>x86-64 targets use Debian Jessie and a prebuilt Clang toolchain.</li> <li>aarch64 targets use Debian Stretch and a prebuilt Clang toolchain.</li> <li>Other cross-compiled targets generally use Debian Stretch and   Debian-provided GCC cross-compilers.</li> <li>riscv64 targets use Debian Buster and a Debian-provided GCC   cross-compiler.</li>  The selected toolchain is used to build Python's dependencies (OpenSSL, ncurses, libedit, SQLite, etc.). Finally, Python itself is built. Python is built in such a way that extensions are statically linked against their dependencies. For example, instead of the `sqlite3` Python extension having a run-time dependency against `libsqlite3.so`, the SQLite symbols are statically inlined into the Python extension object file. Extension modules are statically linked into `python` rather than provided as shared extensions in the `lib-dynload` directory. `_dbm` and `_tkinter` are handled differently. On dynamically linked Linux builds, `_dbm` is provided as a shared extension with Berkeley DB statically linked. This allows the module to be easily removed when the Berkeley DB license is undesired (see the DBM section below). On macOS, `_dbm` instead uses the system NDBM implementation. `_tkinter` is also provided as a shared extension, with Tcl and Tk dynamically linked, on builds that support shared extensions. From the built Python, we produce an archive containing the raw Python distribution (as if you had run `make install`) as well as other files useful for downstream consumers."},{"location":"technotes/#setuplocal-hackery","title":"Setup.local Hackery","text":"Starting with Python 3.12, C extension modules are configured and built using `configure`, `Modules/Setup.stdlib`, and `Makefile`. A generated `Modules/Setup.local` file disables selected modules and overrides whether others are linked statically or built as shared libraries. Prior to 3.12, many extensions were configured and built using `setup.py` scripts. These scripts do not provide much flexibility and rely on default behaviors in `distutils`, as well as other inline code in `setup.py`. This default behavior is often undesirable for our desired outcome of producing a standalone Python distribution. Because of this, when building Python prior to 3.12, a custom `Setup.local` file is generated that builds all C extensions in a specific manner. The undesirable behavior of `setup.py` is bypassed and the Python C extensions are compiled just the way we want."},{"location":"technotes/#dependency-notes","title":"Dependency Notes","text":""},{"location":"technotes/#dbm","title":"DBM","text":"Python has the option of building its `_dbm` extension against NDBM, GDBM, and Berkeley DB. GDBM and its NDBM compatibility libraries are licensed under GNU GPL Version 3. Modern versions of Berkeley DB are licensed under GNU AGPL v3. Versions 6.0.19 and older are licensed under the more permissive Sleepycat License. On Linux, we build the `_dbm` extension against Berkeley DB 6.0.19. On macOS, `_dbm` uses the NDBM implementation provided by the system `libSystem` library instead. We explicitly disable the `_gdbm` extension on all targets to avoid the GPL dependency."},{"location":"technotes/#readline-libedit-ncurses","title":"readline / libedit / ncurses","text":"Python has the option of building its `readline` extension against either `libreadline` or `libedit`. `libreadline` is licensed under GNU GPL Version 3, and `libedit` has a more permissive license. `libedit`/`libreadline` link against a curses library, most likely `ncurses`. And `ncurses` has tie-ins with a terminal database. This is a thorny situation, as terminal databases can be difficult to distribute because end-users often want software to respect their terminal databases. But for that to work, `ncurses` needs to be compiled in a way that respects the user's environment. On macOS, we use the system `libedit` and `libncurses`, which is typically provided in `/usr/lib`. On Linux, we build `libedit` and `ncurses` from source and statically link against their respective libraries. Project releases before 2023 linked against `readline` on Linux."},{"location":"technotes/#gettext-locale-module","title":"gettext / locale Module","text":"The `locale` Python module exposes some functionality from the `gettext` software (specifically `libintl`). (Technically, this functionality is exposed from the `_locale` C extension module and `locale` re-exports symbols.) `gettext` is GPL version 3 or later licensed. And having it statically linked in the Python distribution via the `_locale` module can have licensing implications. Python's configure script probes for the ability to compile/link with `-lintl`. If it works, Python is linked against `libintl`. If it doesn't, `libintl` is omitted. (Search `configure` for `ac_cv_lib_intl_textdomain` and `-lintl` references.) With the container based build environment on Linux, presence of `gettext` and `libintl` is deterministic. However, on macOS where there is no sandboxing of the build environment, Python's configure script can find and use a `gettext`/`libintl` installed outside the system default (e.g. via Homebrew or MacPorts). This can result in the built Python referencing a shared library not reliably present on every macOS machine. So our build system disables the configure check. This means that the `gettext`/`libintl` features in the Python distribution are not available."},{"location":"technotes/#libnsl-nis-module","title":"libnsl / nis Module","text":"The `nis` Python extension module has a dependency on `libnsl`. `libnsl` has historically been in base Linux distribution installations. But it is being phased away, with it being an optional install in modern versions of Fedora and RHEL. Because the `nis` extension is perceived to be likely unused functionality, we've decided to not build it instead of adding complexity to deal with the `libnsl` dependency. See further discussion in https://github.com/astral-sh/python-build-standalone/issues/51. The `nis` module was deprecated in Python 3.11 and removed in 3.13."},{"location":"technotes/#upgrading-cpython","title":"Upgrading CPython","text":"This section documents some of the work that needs to be performed when upgrading CPython major versions."},{"location":"technotes/#review-release-notes","title":"Review Release Notes","text":"CPython's release notes often have a section on build system changes. e.g. https://docs.python.org/3/whatsnew/3.13.html#build-changes. These are a must review."},{"location":"technotes/#modulessetup","title":"`Modules/Setup`","text":"The `Modules/Setup` file defines the default extension build settings. We need to audit it for differences such as added/removed extensions, changes to compile settings, etc just in case we have special code handling an extension defined in this file. See code in `cpython.py` dealing with this file."}]}

---

## File: site/ruff/llms.txt

# Ruff

> An extremely fast Python linter and code formatter, written in Rust.

Ruff is an extremely fast Python linter and code formatter, written in Rust.
It can be used to replace Flake8 (plus dozens of plugins), Black, isort, pydocstyle,
pyupgrade, autoflake, and more, all while executing tens or hundreds of times faster
than any individual tool.

Ruff includes both a linter (`ruff check`) and a formatter (`ruff format`).

When fetching documentation, use explicit `index.md` paths for directories, e.g.,
`https://docs.astral.sh/ruff/editors/index.md`. This returns clean markdown instead
of rendered HTML with JS/CSS.


## Concepts

- [Preview](https://docs.astral.sh/ruff/preview/index.md)
- [Versioning](https://docs.astral.sh/ruff/versioning/index.md)

## Configuration

- [Configuring Ruff](https://docs.astral.sh/ruff/configuration/index.md)

## Editors

- [Editor Integration](https://docs.astral.sh/ruff/editors/index.md)
- [Features](https://docs.astral.sh/ruff/editors/features/index.md)
- [Migrating from ruff-lsp](https://docs.astral.sh/ruff/editors/migration/index.md)
- [Settings](https://docs.astral.sh/ruff/editors/settings/index.md)
- [Setup](https://docs.astral.sh/ruff/editors/setup/index.md)

## Formatter

- [The Ruff Formatter](https://docs.astral.sh/ruff/formatter/index.md)
- [Known Deviations from Black](https://docs.astral.sh/ruff/formatter/black/index.md)

## Getting started

- [Installing Ruff](https://docs.astral.sh/ruff/installation/index.md)
- [Tutorial](https://docs.astral.sh/ruff/tutorial/index.md)

## Integrations

- [Integrations](https://docs.astral.sh/ruff/integrations/index.md)

## Linter

- [The Ruff Linter](https://docs.astral.sh/ruff/linter/index.md)

## Reference

- [FAQ](https://docs.astral.sh/ruff/faq/index.md)

---

## File: site/ruff/configuration/index.md

# [Configuring Ruff](#configuring-ruff)

Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file.

Whether you're using Ruff as a linter, formatter, or both, the underlying configuration strategy and semantics are the same.

For a complete enumeration of the available configuration options, see [*Settings*](../settings/).

For the complete list of enabled rules, see [*Default Rules*](../default-rules/).

If left unspecified, Ruff's default configuration is equivalent to:

```
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".git-rewrite",
    ".hg",
    ".ipynb_checkpoints",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pyenv",
    ".pytest_cache",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    ".vscode",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "site-packages",
    "venv",
]

# Same as Black.
line-length = 88
indent-width = 4

# Assume Python 3.11
target-version = "py311"

[tool.ruff.lint]
# select = [...]  # See the Default Rules page for the full listing.
ignore = []

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[\\p{XID_Continue}]*[\\p{XID_Continue}--_]+?))$"

[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false

# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
```

```
# Exclude a variety of commonly ignored directories.
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".git-rewrite",
    ".hg",
    ".ipynb_checkpoints",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pyenv",
    ".pytest_cache",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    ".vscode",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "site-packages",
    "venv",
]

# Same as Black.
line-length = 88
indent-width = 4

# Assume Python 3.11
target-version = "py311"

[lint]
# select = [...]  # See the Default Rules page for the full listing.
ignore = []

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[\\p{XID_Continue}]*[\\p{XID_Continue}--_]+?))$"

[format]
# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false

# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
```

As an example, the following would configure Ruff to:

```
[tool.ruff.lint]
# 1. Enable all flake8-bugbear (`B`) rules, in addition to the defaults.
extend-select = ["B"]

# 2. Avoid enforcing line-length violations (`E501`)
ignore = ["E501"]

# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = ["B"]

# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in selected subdirectories.
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402"]
"**/{tests,docs,tools}/*" = ["E402"]

[tool.ruff.format]
# 5. Use single quotes in `ruff format`.
quote-style = "single"
```

```
[lint]
# 1. Enable all flake8-bugbear (`B`) rules, in addition to the defaults.
extend-select = ["B"]

# 2. Avoid enforcing line-length violations (`E501`)
ignore = ["E501"]

# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = ["B"]

# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in selected subdirectories.
[lint.per-file-ignores]
"__init__.py" = ["E402"]
"**/{tests,docs,tools}/*" = ["E402"]

[format]
# 5. Use single quotes in `ruff format`.
quote-style = "single"
```

Linter plugin configurations are expressed as subsections, e.g.:

```
[tool.ruff.lint]
# Add "Q" to the list of enabled codes.
extend-select = ["Q"]

[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
```

```
[lint]
# Add "Q" to the list of enabled codes.
extend-select = ["Q"]

[lint.flake8-quotes]
docstring-quotes = "double"
```

Ruff respects `pyproject.toml`, `ruff.toml`, and `.ruff.toml` files. All three implement an equivalent schema (though in the `ruff.toml` and `.ruff.toml` versions, the `[tool.ruff]` header and `tool.ruff` section prefix is omitted).

For a complete enumeration of the available configuration options, see [*Settings*](../settings/).

## [Config file discovery](#config-file-discovery)

Similar to [ESLint](https://eslint.org/docs/latest/use/configure/configuration-files#cascading-configuration-objects), Ruff supports hierarchical configuration, such that the "closest" config file in the directory hierarchy is used for every individual file, with all paths in the config file (e.g., `exclude` globs, `src` paths) being resolved relative to the directory containing that config file.

There are a few exceptions to these rules:

1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignores any `pyproject.toml` files that lack a `[tool.ruff]` section.
1. If a configuration file is passed directly via `--config`, those settings are used for *all* analyzed files, and any relative paths in that configuration file (like `exclude` globs or `src` paths) are resolved relative to the *current* working directory.
1. If no config file is found in the filesystem hierarchy, Ruff will fall back to using a default configuration. If a user-specific configuration file exists at `${config_dir}/ruff/pyproject.toml`, that file will be used instead of the default configuration, with `${config_dir}` being determined via [`etcetera`'s base strategy](https://docs.rs/etcetera/latest/etcetera/#native-strategy), and all relative paths being again resolved relative to the *current working directory*.
1. Any config-file-supported settings that are provided on the command-line (e.g., via `--select`) will override the settings in *every* resolved configuration file.

Unlike [ESLint](https://eslint.org/docs/latest/use/configure/configuration-files#cascading-configuration-objects), Ruff does not merge settings across configuration files; instead, the "closest" configuration file is used, and any parent configuration files are ignored. In lieu of this implicit cascade, Ruff supports an [`extend`](../settings/#extend) field, which allows you to inherit the settings from another config file, like so:

```
[tool.ruff]
# Extend the `pyproject.toml` file in the parent directory...
extend = "../pyproject.toml"

# ...but use a different line length.
line-length = 100
```

```
# Extend the `ruff.toml` file in the parent directory...
extend = "../ruff.toml"

# ...but use a different line length.
line-length = 100
```

All of the above rules apply equivalently to `pyproject.toml`, `ruff.toml`, and `.ruff.toml` files. If Ruff detects multiple configuration files in the same directory, the `.ruff.toml` file will take precedence over the `ruff.toml` file, and the `ruff.toml` file will take precedence over the `pyproject.toml` file.

### [Inferring the Python version](#inferring-the-python-version)

When no discovered configuration specifies a [`target-version`](../settings/#target-version), Ruff will attempt to fall back to the minimum version compatible with the `requires-python` field in a nearby `pyproject.toml`. The rules for this behavior are as follows:

1. If a configuration file is passed directly, Ruff does not attempt to infer a missing `target-version`.
1. If a configuration file is found in the filesystem hierarchy, Ruff will infer a missing `target-version` from the `requires-python` field in a `pyproject.toml` file in the same directory as the found configuration.
1. If we are using a user-level configuration from `${config_dir}/ruff/pyproject.toml`, the `requires-python` field in the first `pyproject.toml` file found in an ancestor of the current working directory takes precedence over the `target-version` in the user-level configuration.
1. If no configuration files are found, Ruff will infer the `target-version` from the `requires-python` field in the first `pyproject.toml` file found in an ancestor of the current working directory.

Note that in these last two cases, the behavior of Ruff may differ depending on the working directory from which it is invoked.

## [Python file discovery](#python-file-discovery)

When passed a path on the command-line, Ruff will automatically discover all Python files in that path, taking into account the [`exclude`](../settings/#exclude) and [`extend-exclude`](../settings/#extend-exclude) settings in each directory's configuration file.

Files can also be selectively excluded from linting or formatting by scoping the `exclude` setting to the tool-specific configuration tables. For example, the following would prevent `ruff` from formatting `.pyi` files, but would continue to include them in linting:

```
[tool.ruff.format]
exclude = ["*.pyi"]
```

```
[format]
exclude = ["*.pyi"]
```

By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](../settings/#respect-gitignore)).

Files that are passed to `ruff` directly are always analyzed, regardless of the above criteria, unless [`force-exclude`](../settings/#force-exclude) is also enabled (via CLI or settings file). For example, without `force-exclude` enabled, `ruff check /path/to/excluded/file.py` will always lint `file.py`.

### [Default inclusions](#default-inclusions)

By default, Ruff will discover files matching `*.py`, `*.pyi`, `*.ipynb`, or `pyproject.toml`. In [preview](../preview/) mode, Ruff will also discover `*.pyw` by default.

To lint or format files with additional file extensions, use the [`extend-include`](../settings/#extend-include) setting. You can also change the default selection using the [`include`](../settings/#include) setting.

```
[tool.ruff]
include = ["pyproject.toml", "src/**/*.py", "scripts/**/*.py"]
```

```
include = ["pyproject.toml", "src/**/*.py", "scripts/**/*.py"]
```

Warning

Paths provided to `include` *must* match files. For example, `include = ["src"]` will fail since it matches a directory.

## [Jupyter Notebook discovery](#jupyter-notebook-discovery)

Ruff has built-in support for linting and formatting [Jupyter Notebooks](https://jupyter.org/), which are linted and formatted by default on version `0.6.0` and higher.

If you'd prefer to either only lint or only format Jupyter Notebook files, you can use the section-specific `exclude` option to do so. For example, the following would only lint Jupyter Notebook files and not format them:

```
[tool.ruff.format]
exclude = ["*.ipynb"]
```

```
[format]
exclude = ["*.ipynb"]
```

And, conversely, the following would only format Jupyter Notebook files and not lint them:

```
[tool.ruff.lint]
exclude = ["*.ipynb"]
```

```
[lint]
exclude = ["*.ipynb"]
```

You can completely disable Jupyter Notebook support by updating the [`extend-exclude`](../settings/#extend-exclude) setting:

```
[tool.ruff]
extend-exclude = ["*.ipynb"]
```

```
extend-exclude = ["*.ipynb"]
```

If you'd like to ignore certain rules specifically for Jupyter Notebook files, you can do so by using the [`per-file-ignores`](../settings/#per-file-ignores) setting:

```
[tool.ruff.lint.per-file-ignores]
"*.ipynb" = ["T20"]
```

```
[lint.per-file-ignores]
"*.ipynb" = ["T20"]
```

Some rules have different behavior when applied to Jupyter Notebook files. For example, when applied to `.py` files the [`module-import-not-at-top-of-file` (`E402`)](../rules/module-import-not-at-top-of-file/) rule detect imports at the top of a file, but for notebooks it detects imports at the top of a **cell**. For a given rule, the rule's documentation will always specify if it has different behavior when applied to Jupyter Notebook files.

## [Command-line interface](#command-line-interface)

Some configuration options can be provided or overridden via dedicated flags on the command line. This includes those related to rule enablement and disablement, file discovery, logging level, and more:

```
$ ruff check path/to/code/ --select F401 --select F403 --quiet
```

All other configuration options can be set via the command line using the `--config` flag, detailed below.

### [The `--config` CLI flag](#the-config-cli-flag)

The `--config` flag has two uses. It is most often used to point to the configuration file that you would like Ruff to use, for example:

```
$ ruff check path/to/directory --config path/to/ruff.toml
```

However, the `--config` flag can also be used to provide arbitrary overrides of configuration settings using TOML `<KEY> = <VALUE>` pairs. This is mostly useful in situations where you wish to override a configuration setting that does not have a dedicated command-line flag.

In the below example, the `--config` flag is the only way of overriding the `dummy-variable-rgx` configuration setting from the command line, since this setting has no dedicated CLI flag. The `per-file-ignores` setting could also have been overridden via the `--per-file-ignores` dedicated flag, but using `--config` to override the setting is also fine:

```
$ ruff check path/to/file --config path/to/ruff.toml --config "lint.dummy-variable-rgx = '__.*'" --config "lint.per-file-ignores = {'some_file.py' = ['F841']}"
```

Configuration options passed to `--config` are parsed in the same way as configuration options in a `ruff.toml` file. As such, options specific to the Ruff linter need to be prefixed with `lint.` (`--config "lint.dummy-variable-rgx = '__.*'"` rather than simply `--config "dummy-variable-rgx = '__.*'"`), and options specific to the Ruff formatter need to be prefixed with `format.`.

If a specific configuration option is simultaneously overridden by a dedicated flag and by the `--config` flag, the dedicated flag takes priority. In this example, the maximum permitted line length will be set to 90, not 100:

```
$ ruff format path/to/file --line-length=90 --config "line-length=100"
```

Specifying `--config "line-length=90"` will override the `line-length` setting from *all* configuration files detected by Ruff, including configuration files discovered in subdirectories. In this respect, specifying `--config "line-length=90"` has the same effect as specifying `--line-length=90`, which will similarly override the `line-length` setting from all configuration files detected by Ruff, regardless of where a specific configuration file is located.

### [Argfile support](#argfile-support)

Ruff supports reading command-line arguments from a file, which is especially useful when passing a large number of file paths that might exceed your shell's command-line length limit. To use an argfile, prefix the file path with an `@` symbol:

```
$ ruff check @path/to/args.txt
```

The arguments in the file must all be written on their own line. For example, `args.txt` might contain:

```
--select
F401
--quiet
path/to/code1/
path/to/code2/
```

If a path including the leading `@` exists, Ruff treats it as a literal path instead. For example, `ruff check @list.py` checks `@list.py` if it exists, even if `list.py` also exists. Otherwise, Ruff reads arguments from `list.py`. This applies to files and directories, including arguments after `--` and arguments inside response files.

### [Full command-line interface](#full-command-line-interface)

See `ruff help` for the full list of Ruff's top-level commands:

```
Ruff: An extremely fast Python linter and code formatter.

Usage: ruff [OPTIONS] <COMMAND>

Commands:
  check    Run Ruff on the given files or directories
  rule     Explain a rule (or all rules)
  config   List or describe the available configuration options
  linter   List all supported upstream linters
  clean    Clear any caches in the current directory and any subdirectories
  format   Run the Ruff formatter on the given files or directories
  server   Run the language server
  analyze  Run analysis over Python source code
  version  Display Ruff's version
  help     Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help (see more with '--help')
  -V, --version  Print version

Log levels:
  -v, --verbose  Enable verbose logging
  -q, --quiet    Print diagnostics, but nothing else
  -s, --silent   Disable all logging (but still exit with status code "1" upon
                 detecting diagnostics)

Global options:
      --config <CONFIG_OPTION>
          Either a path to a TOML configuration file (`pyproject.toml` or
          `ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
          find in a `ruff.toml` configuration file) overriding a specific
          configuration option (e.g., `--config "line-length = 100"` or
          `--config "format.quote-style = 'single'"`). Overrides of individual
          settings using this option always take precedence over all
          configuration files, including configuration files that were also
          specified using `--config`
      --isolated
          Ignore all configuration files
      --color <WHEN>
          Control when colored output is used [possible values: auto, always,
          never]

For help with a specific command, see: `ruff help <command>`.
```

Or `ruff help check` for more on the linting command:

```
Run Ruff on the given files or directories

Usage: ruff check [OPTIONS] [FILES]...

Arguments:
  [FILES]...  List of files or directories to check, or `-` to read from stdin
              [default: .]

Options:
      --fix
          Apply fixes to resolve lint violations. Use `--no-fix` to disable or
          `--unsafe-fixes` to include unsafe fixes
      --unsafe-fixes
          Include fixes that may not retain the original intent of the code.
          Use `--no-unsafe-fixes` to disable
      --show-fixes
          Show an enumeration of all fixed lint violations. Use
          `--no-show-fixes` to disable
      --diff
          Avoid writing any fixed files back; instead, output a diff for each
          changed file to stdout, and exit 0 if there are no diffs. Implies
          `--fix-only`
  -w, --watch
          Run in watch mode by re-running whenever files change
      --fix-only
          Apply fixes to resolve lint violations, but don't report on, or exit
          non-zero for, leftover violations. Implies `--fix`. Use
          `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe
          fixes
      --ignore-noqa
          Ignore any `# noqa` comments
      --output-format <OUTPUT_FORMAT>
          Output serialization format for violations. The default serialization
          format is "full" [env: RUFF_OUTPUT_FORMAT=] [possible values:
          concise, full, json, json-lines, junit, grouped, github, gitlab,
          pylint, rdjson, azure, sarif]
  -o, --output-file <OUTPUT_FILE>
          Specify file to write the linter output to (default: stdout) [env:
          RUFF_OUTPUT_FILE=]
      --target-version <TARGET_VERSION>
          The minimum Python version that should be supported [possible values:
          py37, py38, py39, py310, py311, py312, py313, py314, py315]
      --preview
          Enable preview mode; checks will include unstable rules and fixes.
          Use `--no-preview` to disable
      --extension <EXTENSION>
          List of mappings from file extension to language (one of `python`,
          `ipynb`, `pyi`). For example, to treat `.ipy` files as IPython
          notebooks, use `--extension ipy:ipynb`
      --statistics
          Show counts for every rule with at least one violation
      --add-noqa[=<REASON>]
          Enable automatic additions of `noqa` directives to failing lines.
          Optionally provide a reason to append after the codes
      --add-ignore[=<REASON>]
          Enable automatic additions of `ruff: ignore` comments to failing
          lines. Optionally provide a reason to append after the codes. In
          preview, add suppression comments with rule names instead
      --show-files
          See the files Ruff will be run against with the current settings
      --show-settings
          See the settings Ruff will use to lint a given Python file
  -h, --help
          Print help (see more with '--help')

Rule selection:
      --select <RULE_CODE>
          Comma-separated list of rule codes to enable (or ALL, to enable all
          rules)
      --ignore <RULE_CODE>
          Comma-separated list of rule codes to disable
      --extend-select <RULE_CODE>
          Like --select, but adds additional rule codes on top of those already
          specified
      --per-file-ignores <PER_FILE_IGNORES>
          List of mappings from file pattern to code to exclude
      --extend-per-file-ignores <EXTEND_PER_FILE_IGNORES>
          Like `--per-file-ignores`, but adds additional ignores on top of
          those already specified
      --fixable <RULE_CODE>
          List of rule codes to treat as eligible for fix. Only applicable when
          fix itself is enabled (e.g., via `--fix`)
      --unfixable <RULE_CODE>
          List of rule codes to treat as ineligible for fix. Only applicable
          when fix itself is enabled (e.g., via `--fix`)
      --extend-fixable <RULE_CODE>
          Like --fixable, but adds additional rule codes on top of those
          already specified

File selection:
      --exclude <FILE_PATTERN>
          List of paths, used to omit files and/or directories from analysis
      --extend-exclude <FILE_PATTERN>
          Like --exclude, but adds additional files and directories on top of
          those already excluded
      --respect-gitignore
          Respect file exclusions via `.gitignore` and other standard ignore
          files. Use `--no-respect-gitignore` to disable
      --force-exclude
          Enforce exclusions, even for paths passed to Ruff directly on the
          command-line. Use `--no-force-exclude` to disable

Miscellaneous:
  -n, --no-cache
          Disable cache reads [env: RUFF_NO_CACHE=]
      --cache-dir <CACHE_DIR>
          Path to the cache directory [env: RUFF_CACHE_DIR=]
      --stdin-filename <STDIN_FILENAME>
          The name of the file when passing it through stdin
  -e, --exit-zero
          Exit with status code "0", even upon detecting lint violations
      --exit-non-zero-on-fix
          Exit with a non-zero status code if any files were modified via fix,
          even if no lint violations remain

Log levels:
  -v, --verbose  Enable verbose logging
  -q, --quiet    Print diagnostics, but nothing else
  -s, --silent   Disable all logging (but still exit with status code "1" upon
                 detecting diagnostics)

Global options:
      --config <CONFIG_OPTION>
          Either a path to a TOML configuration file (`pyproject.toml` or
          `ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
          find in a `ruff.toml` configuration file) overriding a specific
          configuration option (e.g., `--config "line-length = 100"` or
          `--config "format.quote-style = 'single'"`). Overrides of individual
          settings using this option always take precedence over all
          configuration files, including configuration files that were also
          specified using `--config`
      --isolated
          Ignore all configuration files
      --color <WHEN>
          Control when colored output is used [possible values: auto, always,
          never]
```

Or `ruff help format` for more on the formatting command:

```
Run the Ruff formatter on the given files or directories

Usage: ruff format [OPTIONS] [FILES]...

Arguments:
  [FILES]...  List of files or directories to format, or `-` to read from stdin
              [default: .]

Options:
      --check
          Avoid writing any formatted files back; instead, exit with a non-zero
          status code if any files would have been modified, and zero otherwise
      --diff
          Avoid writing any formatted files back; instead, exit with a non-zero
          status code and the difference between the current file and how the
          formatted file would look like
      --extension <EXTENSION>
          List of mappings from file extension to language (one of `python`,
          `ipynb`, `pyi`). For example, to treat `.ipy` files as IPython
          notebooks, use `--extension ipy:ipynb`
      --target-version <TARGET_VERSION>
          The minimum Python version that should be supported [possible values:
          py37, py38, py39, py310, py311, py312, py313, py314, py315]
      --preview
          Enable preview mode; enables unstable formatting. Use `--no-preview`
          to disable
      --output-format <OUTPUT_FORMAT>
          Output serialization format for violations, when used with `--check`.
          The default serialization format is "full" [env: RUFF_OUTPUT_FORMAT=]
          [possible values: concise, full, json, json-lines, junit, grouped,
          github, gitlab, pylint, rdjson, azure, sarif]
  -h, --help
          Print help (see more with '--help')

Miscellaneous:
  -n, --no-cache
          Disable cache reads [env: RUFF_NO_CACHE=]
      --cache-dir <CACHE_DIR>
          Path to the cache directory [env: RUFF_CACHE_DIR=]
      --stdin-filename <STDIN_FILENAME>
          The name of the file when passing it through stdin
      --exit-non-zero-on-format
          Exit with a non-zero status code if any files were modified via
          format, even if all files were formatted successfully

File selection:
      --respect-gitignore
          Respect file exclusions via `.gitignore` and other standard ignore
          files. Use `--no-respect-gitignore` to disable
      --exclude <FILE_PATTERN>
          List of paths, used to omit files and/or directories from analysis
      --extend-exclude <FILE_PATTERN>
          Like --exclude, but adds additional files and directories on top of
          those already excluded
      --force-exclude
          Enforce exclusions, even for paths passed to Ruff directly on the
          command-line. Use `--no-force-exclude` to disable

Format configuration:
      --line-length <LINE_LENGTH>  Set the line-length

Editor options:
      --range <RANGE>  When specified, Ruff will try to only format the code in
                       the given range.
                       It might be necessary to extend the start backwards or
                       the end forwards, to fully enclose a logical line.
                       The `<RANGE>` uses the format
                       `<start_line>:<start_column>-<end_line>:<end_column>`.

Log levels:
  -v, --verbose  Enable verbose logging
  -q, --quiet    Print diagnostics, but nothing else
  -s, --silent   Disable all logging (but still exit with status code "1" upon
                 detecting diagnostics)

Global options:
      --config <CONFIG_OPTION>
          Either a path to a TOML configuration file (`pyproject.toml` or
          `ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
          find in a `ruff.toml` configuration file) overriding a specific
          configuration option (e.g., `--config "line-length = 100"` or
          `--config "format.quote-style = 'single'"`). Overrides of individual
          settings using this option always take precedence over all
          configuration files, including configuration files that were also
          specified using `--config`
      --isolated
          Ignore all configuration files
      --color <WHEN>
          Control when colored output is used [possible values: auto, always,
          never]
```

## [Shell autocompletion](#shell-autocompletion)

Ruff supports autocompletion for most shells. A shell-specific completion script can be generated by `ruff generate-shell-completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`, `powershell`, or `zsh`.

Tip

You can run `echo $SHELL` to help you determine your shell.

To enable shell autocompletion for Ruff, run one of the following:

```
echo 'eval "$(ruff generate-shell-completion bash)"' >> ~/.bashrc
```

```
echo 'eval "$(ruff generate-shell-completion zsh)"' >> ~/.zshrc
```

```
echo 'ruff generate-shell-completion fish | source' > ~/.config/fish/completions/ruff.fish
```

```
echo 'eval (ruff generate-shell-completion elvish | slurp)' >> ~/.elvish/rc.elv
```

```
if (!(Test-Path -Path $PROFILE)) {
  New-Item -ItemType File -Path $PROFILE -Force
}
Add-Content -Path $PROFILE -Value '(& ruff generate-shell-completion powershell) | Out-String | Invoke-Expression'
```

Then restart the shell or source the shell config file.

---

## File: site/ruff/editors/index.md

# [Editor Integrations](#editor-integrations)

Ruff can be integrated with various editors and IDEs to provide a seamless development experience. This section provides instructions on [how to set up Ruff with your editor](setup/) and [configure it to your liking](settings/).

## [Language Server Protocol](#language-server-protocol)

The editor integration is mainly powered by the Ruff Language Server which implements the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). The server is written in Rust and is available as part of the `ruff` CLI via `ruff server`. It is a single, common backend built directly into Ruff, and a direct replacement for [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp), our previous language server. You can read more about `ruff server` in the [`v0.4.5` blog post](https://astral.sh/blog/ruff-v0.4.5).

The server supports surfacing Ruff diagnostics, providing Code Actions to fix them, and formatting the code using Ruff's built-in formatter. Currently, the server is intended to be used alongside another Python Language Server in order to support features like navigation and autocompletion.

The Ruff Language Server was available first in Ruff [v0.4.5](https://astral.sh/blog/ruff-v0.4.5) in beta and stabilized in Ruff [v0.5.3](https://github.com/astral-sh/ruff/releases/tag/0.5.3).

---

## File: site/ruff/editors/features/index.md

# [Features](#features)

This section provides a detailed overview of the features provided by the Ruff Language Server.

## [Diagnostic Highlighting](#diagnostic-highlighting)

Provide diagnostics for your Python code in real-time.

## [Dynamic Configuration](#dynamic-configuration)

The server dynamically refreshes the diagnostics when a configuration file is changed in the workspace, whether it's a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file.

The server relies on the file watching capabilities of the editor to detect changes to these files. If an editor does not support file watching, the server will not be able to detect changes to the configuration file and thus will not refresh the diagnostics.

## [Formatting](#formatting)

Provide code formatting for your Python code. The server can format an entire document or a specific range of lines.

The VS Code extension provides the `Ruff: Format Document` command to format an entire document. In VS Code, the range formatting can be triggered by selecting a range of lines, right-clicking, and selecting `Format Selection` from the context menu.

### [Markdown code blocks](#markdown-code-blocks)

The Ruff formatter can also format Python code blocks in Markdown files. The Ruff VS Code extension provides the `Format Document` command for Markdown files, which will then format the code blocks with the same settings as used for regular Python files.

Note that Ruff will not format any other parts of Markdown files. If you want to use Ruff formatting in addition to another Markdown formatting extension, then you will need to set one as the default in VS Code, and manually run the `Format Document With...` (or `Ruff: Format document`) command to run any other formatters separately.

To set Ruff as the default formatter for Markdown files in VS Code, add the following to your `settings.json`:

```
{
  "[markdown]": {
    "editor.defaultFormatter": "charliermarsh.ruff"
  }
}
```

Note that Ruff does not support range formatting for Markdown files, so if you have format-on-save enabled, you may also need to specify the following:

```
{
  "[markdown]": {
    "editor.formatOnSave": true,
    "editor.formatOnSaveMode": "file"
  }
}
```

See the [formatter documentation](https://docs.astral.sh/ruff/formatter/#markdown-code-formatting) for more details.

## [Code Actions](#code-actions)

Code actions are context-sensitive suggestions that can help you fix issues in your code. They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. The Ruff Language Server provides the following code actions:

- Apply a quick fix for a diagnostic that has a fix available (e.g., removing an unused import).
- Ignore a diagnostic with a `# noqa` comment.
- Apply all quick fixes available in the document.
- Organize imports in the document.

You can even run these actions on-save. For example, to fix all issues and organize imports on save in VS Code, add the following to your `settings.json`:

```
{
  "[python]": {
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
      "source.organizeImports.ruff": "explicit"
    }
  }
}
```

### [Fix Safety](#fix-safety)

Ruff's automatic fixes are labeled as "safe" and "unsafe". By default, the "Fix all" action will not apply unsafe fixes. However, unsafe fixes can be applied manually with the "Quick fix" action. Application of unsafe fixes when using "Fix all" can be enabled by setting `unsafe-fixes = true` in your Ruff configuration file.

See the [Ruff fix documentation](https://docs.astral.sh/ruff/linter/#fix-safety) for more details on how fix safety works.

## [Hover](#hover)

The server can provide the rule documentation when focusing over a NoQA code in the comment. Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.

## [Jupyter Notebook](#jupyter-notebook)

Similar to Ruff's CLI, the Ruff Language Server fully supports Jupyter Notebook files with all the capabilities available to Python files.

Note

Ruff has built-in support for Jupyter Notebooks. The native language server will discover and lint or format `.ipynb` files by default as of version 0.6.0. Refer to the [Jupyter Notebook discovery](../../configuration/#jupyter-notebook-discovery) section for more details.

---

## File: site/ruff/editors/migration/index.md

# [Migrating from `ruff-lsp`](#migrating-from-ruff-lsp)

[`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) is the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) implementation for Ruff to power the editor integrations. It is written in Python and is a separate package from Ruff itself. The **native server**, however, is the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) implementation which is **written in Rust** and is available under the `ruff server` command. This guide is intended to help users migrate from [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) to the native server.

Note

The native server was first introduced in Ruff version `0.3.5`. It was marked as [beta in version `0.4.5`](https://astral.sh/blog/ruff-v0.4.5) and officially [stabilized in version `0.5.3`](https://github.com/astral-sh/ruff/releases/tag/0.5.3). It is recommended to use the latest version of Ruff to ensure the best experience.

The migration process involves any or all of the following:

1. Migrate [deprecated settings](#unsupported-settings) to the [new settings](#new-settings)
1. [Remove settings](#removed-settings) that are no longer supported
1. Update the `ruff` version

Read on to learn more about the unsupported or new settings, or jump to the [examples](#examples) that enumerate some of the common settings and how to migrate them.

## [Unsupported Settings](#unsupported-settings)

The following [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) settings are not supported by the native server:

- `lint.run`: This setting is no longer relevant for the native language server, which runs on every keystroke by default.
- `lint.args`, `format.args`: These settings have been replaced by more granular settings in the native server like [`lint.select`](../settings/#select), [`format.preview`](../settings/#format_preview), etc. along with the ability to override any configuration using the [`configuration`](../settings/#configuration) setting.

The following settings are not accepted by the language server but are still used by the [VS Code extension](https://github.com/astral-sh/ruff-vscode). Refer to their respective documentation for more information on how each is used by the extension:

- [`path`](../settings/#path)
- [`interpreter`](../settings/#interpreter)

## [Removed Settings](#removed-settings)

Additionally, the following settings are not supported by the native server and should be removed:

- `ignoreStandardLibrary`
- `showNotifications`

## [New Settings](#new-settings)

The native server introduces several new settings that [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) does not have:

- [`configuration`](../settings/#configuration)
- [`configurationPreference`](../settings/#configurationpreference)
- [`exclude`](../settings/#exclude)
- [`format.preview`](../settings/#format_preview)
- [`lineLength`](../settings/#linelength)
- [`lint.select`](../settings/#select)
- [`lint.extendSelect`](../settings/#extendselect)
- [`lint.ignore`](../settings/#ignore)
- [`lint.preview`](../settings/#lint_preview)

## [Examples](#examples)

All of the examples mentioned below are only valid for the [VS Code extension](https://github.com/astral-sh/ruff-vscode). For other editors, please refer to their respective documentation sections in the [settings](../settings/) page.

### [Configuration file](#configuration-file)

If you've been providing a configuration file as shown below:

```
{
    "ruff.lint.args": "--config ~/.config/custom_ruff_config.toml",
    "ruff.format.args": "--config ~/.config/custom_ruff_config.toml"
}
```

You can migrate to the new server by using the [`configuration`](../settings/#configuration) setting like below which will apply the configuration to both the linter and the formatter:

```
{
    "ruff.configuration": "~/.config/custom_ruff_config.toml"
}
```

### [`lint.args`](#lintargs)

If you're providing the linter flags by using `ruff.lint.args` like so:

```
{
    "ruff.lint.args": "--select=E,F --unfixable=F401 --unsafe-fixes"
}
```

You can migrate to the new server by using the [`lint.select`](../settings/#select) and [`configuration`](../settings/#configuration) setting like so:

```
{
    "ruff.lint.select": ["E", "F"],
    "ruff.configuration": {
        "unsafe-fixes": true,
        "lint": {
            "unfixable": ["F401"]
        }
    }
}
```

The following options can be set directly in the editor settings:

- [`lint.select`](../settings/#select)
- [`lint.extendSelect`](../settings/#extendselect)
- [`lint.ignore`](../settings/#ignore)
- [`lint.preview`](../settings/#lint_preview)

The remaining options can be set using the [`configuration`](../settings/#configuration) setting.

### [`format.args`](#formatargs)

If you're also providing formatter flags by using `ruff.format.args` like so:

```
{
    "ruff.format.args": "--line-length 80 --config='format.quote-style=double'"
}
```

You can migrate to the new server by using the [`lineLength`](../settings/#linelength) and [`configuration`](../settings/#configuration) setting like so:

```
{
    "ruff.lineLength": 80,
    "ruff.configuration": {
        "format": {
            "quote-style": "double"
        }
    }
}
```

The following options can be set directly in the editor settings:

- [`lineLength`](../settings/#linelength)
- [`format.preview`](../settings/#format_preview)

The remaining options can be set using the [`configuration`](../settings/#configuration) setting.

---

## File: site/ruff/editors/settings/index.md

# [Settings](#settings)

The Ruff Language Server provides a set of configuration options to customize its behavior along with the ability to use an existing `pyproject.toml` or `ruff.toml` file to configure the linter and formatter. This is done by providing these settings while initializing the server. VS Code provides a UI to configure these settings, while other editors may require manual configuration. The [setup](../setup/) section provides instructions on where to place these settings as per the editor.

## [Top-level](#top-level)

### [`configuration`](#configuration)

The `configuration` setting allows you to configure editor-specific Ruff behavior. This can be done in one of the following ways:

1. **Configuration file path:** Specify the path to a `ruff.toml` or `pyproject.toml` file that contains the configuration. User home directory and environment variables will be expanded.
1. **Inline JSON configuration:** Directly provide the configuration as a JSON object.

Added in Ruff `0.9.8`

The **Inline JSON configuration** option was introduced in Ruff `0.9.8`.

The default behavior, if `configuration` is unset, is to load the settings from the project's configuration (a `ruff.toml` or `pyproject.toml` in the project's directory), consistent with when running Ruff on the command-line.

The [`configurationPreference`](#configurationpreference) setting controls the precedence if both an editor-provided configuration (`configuration`) and a project level configuration file are present.

#### [Resolution order](#configuration_resolution_order)

In an editor, Ruff supports three sources of configuration, prioritized as follows (from highest to lowest):

1. **Specific settings:** Individual settings like [`lineLength`](#linelength) or [`lint.select`](#select) defined in the editor
1. [**`ruff.configuration`**](#configuration): Settings provided via the [`configuration`](#configuration) field (either a path to a configuration file or an inline configuration object)
1. **Configuration file:** Settings defined in a `ruff.toml` or `pyproject.toml` file in the project's directory (if present)

For example, if the line length is specified in all three sources, Ruff will use the value from the [`lineLength`](#linelength) setting.

**Default value**: `null`

**Type**: `string`

**Example usage**:

*Using configuration file path:*

```
{
    "ruff.configuration": "~/path/to/ruff.toml"
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      configuration = "~/path/to/ruff.toml"
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "configuration": "~/path/to/ruff.toml"
        }
      }
    }
  }
}
```

*Using inline configuration:*

```
{
    "ruff.configuration": {
        "lint": {
            "unfixable": ["F401"],
            "extend-select": ["TID251"],
            "flake8-tidy-imports": {
                "banned-api": {
                    "typing.TypedDict": {
                        "msg": "Use `typing_extensions.TypedDict` instead",
                    }
                }
            }
        },
        "format": {
            "quote-style": "single"
        }
    }
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      configuration = {
        lint = {
          unfixable = {"F401"},
          ["extend-select"] = {"TID251"},
          ["flake8-tidy-imports"] = {
            ["banned-api"] = {
              ["typing.TypedDict"] = {
                msg = "Use `typing_extensions.TypedDict` instead"
              }
            }
          }
        },
        format = {
          ["quote-style"] = "single"
        }
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "configuration": {
            "lint": {
              "unfixable": ["F401"],
              "extend-select": ["TID251"],
              "flake8-tidy-imports": {
                "banned-api": {
                  "typing.TypedDict": {
                    "msg": "Use `typing_extensions.TypedDict` instead"
                  }
                }
              }
            },
            "format": {
              "quote-style": "single"
            }
          }
        }
      }
    }
  }
}
```

### [`configurationPreference`](#configurationpreference)

The strategy to use when resolving settings across VS Code and the filesystem. By default, editor configuration is prioritized over `ruff.toml` and `pyproject.toml` files.

- `"editorFirst"`: Editor settings take priority over configuration files present in the workspace.
- `"filesystemFirst"`: Configuration files present in the workspace takes priority over editor settings.
- `"editorOnly"`: Ignore configuration files entirely i.e., only use editor settings.

**Default value**: `"editorFirst"`

**Type**: `"editorFirst" | "filesystemFirst" | "editorOnly"`

**Example usage**:

```
{
    "ruff.configurationPreference": "filesystemFirst"
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      configurationPreference = "filesystemFirst"
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "configurationPreference": "filesystemFirst"
        }
      }
    }
  }
}
```

### [`exclude`](#exclude)

A list of file patterns to exclude from linting and formatting. See [the documentation](https://docs.astral.sh/ruff/settings/#exclude) for more details.

**Default value**: `null`

**Type**: `string[]`

**Example usage**:

```
{
    "ruff.exclude": ["**/tests/**"]
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      exclude = ["**/tests/**"]
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "exclude": ["**/tests/**"]
        }
      }
    }
  }
}
```

### [`lineLength`](#linelength)

The line length to use for the linter and formatter.

**Default value**: `null`

**Type**: `int`

**Example usage**:

```
{
    "ruff.lineLength": 100
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      lineLength = 100
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "lineLength": 100
        }
      }
    }
  }
}
```

### [`fixAll`](#fixall)

Whether to register the server as capable of handling `source.fixAll` code actions.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.fixAll": false
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      fixAll = false
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "fixAll": false
        }
      }
    }
  }
}
```

### [`organizeImports`](#organizeimports)

Whether to register the server as capable of handling `source.organizeImports` code actions.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.organizeImports": false
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      organizeImports = false
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "organizeImports": false
        }
      }
    }
  }
}
```

### [`showSyntaxErrors`](#showsyntaxerrors)

*New in Ruff [v0.5.0](https://astral.sh/blog/ruff-v0.5.0#changes-to-e999-and-reporting-of-syntax-errors)*

Whether to show syntax error diagnostics.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.showSyntaxErrors": false
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      showSyntaxErrors = false
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "showSyntaxErrors": false
        }
      }
    }
  }
}
```

### [`logLevel`](#loglevel)

The log level to use for the server.

**Default value**: `"info"`

**Type**: `"trace" | "debug" | "info" | "warn" | "error"`

**Example usage**:

```
{
    "ruff.logLevel": "debug"
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      logLevel = "debug"
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "logLevel": "debug"
        }
      }
    }
  }
}
```

### [`logFile`](#logfile)

Path to the log file to use for the server.

If not set, logs will be written to stderr.

**Default value**: `null`

**Type**: `string`

**Example usage**:

```
{
    "ruff.logFile": "~/path/to/ruff.log"
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      logFile = "~/path/to/ruff.log"
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "logFile": "~/path/to/ruff.log"
        }
      }
    }
  }
}
```

## [`codeAction`](#codeaction)

Enable or disable code actions provided by the server.

### [`disableRuleComment.enable`](#disablerulecommentenable)

Whether to display Quick Fix actions to disable rules via `noqa` suppression comments.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.codeAction.disableRuleComment.enable": false
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      codeAction = {
        disableRuleComment = {
          enable = false
        }
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "codeAction": {
            "disableRuleComment": {
              "enable": false
            }
          }
        }
      }
    }
  }
}
```

### [`fixViolation.enable`](#fixviolationenable)

Whether to display Quick Fix actions to autofix violations.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.codeAction.fixViolation.enable": false
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      codeAction = {
        fixViolation = {
          enable = false
        }
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "codeAction": {
            "fixViolation": {
              "enable": false
            }
          }
        }
      }
    }
  }
}
```

## [`lint`](#lint)

Settings specific to the Ruff linter.

### [`enable`](#lint_enable)

Whether to enable linting. Set to `false` to use Ruff exclusively as a formatter.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.lint.enable": false
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      lint = {
        enable = false
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "lint": {
            "enable": false
          }
        }
      }
    }
  }
}
```

### [`preview`](#lint_preview)

Whether to enable Ruff's preview mode when linting.

**Default value**: `null`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.lint.preview": true
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      lint = {
        preview = true
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "lint": {
            "preview": true
          }
        }
      }
    }
  }
}
```

### [`select`](#select)

Rules to enable by default. See [the documentation](https://docs.astral.sh/ruff/settings/#lint_select).

**Default value**: `null`

**Type**: `string[]`

**Example usage**:

```
{
    "ruff.lint.select": ["E", "F"]
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      lint = {
        select = {"E", "F"}
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "lint": {
            "select": ["E", "F"]
          }
        }
      }
    }
  }
}
```

### [`extendSelect`](#extendselect)

Rules to enable in addition to those in [`lint.select`](#select).

**Default value**: `null`

**Type**: `string[]`

**Example usage**:

```
{
    "ruff.lint.extendSelect": ["W"]
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      lint = {
        extendSelect = {"W"}
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "lint": {
            "extendSelect": ["W"]
          }
        }
      }
    }
  }
}
```

### [`ignore`](#ignore)

Rules to disable by default. See [the documentation](https://docs.astral.sh/ruff/settings/#lint_ignore).

**Default value**: `null`

**Type**: `string[]`

**Example usage**:

```
{
    "ruff.lint.ignore": ["E4", "E7"]
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      lint = {
        ignore = {"E4", "E7"}
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "lint": {
            "ignore": ["E4", "E7"]
          }
        }
      }
    }
  }
}
```

## [`format`](#format)

Settings specific to the Ruff formatter.

### [`preview`](#format_preview)

Whether to enable Ruff's preview mode when formatting.

**Default value**: `null`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.format.preview": true
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      format = {
        preview = true
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "format": {
            "preview": true
          }
        }
      }
    }
  }
}
```

### [`backend`](#format_backend)

The backend to use for formatting files. Following options are available:

- `"internal"`: Use the built-in Ruff formatter
- `"uv"`: Use uv for formatting (requires uv >= 0.8.13)

For `internal`, the formatter version will match the selected Ruff version while for `uv`, the formatter version may differ.

Starting the server with `ruff server --untrusted-workspace` disables the uv backend. With this flag, the server always uses the internal formatter, regardless of this setting. This prevents workspace configuration from causing uv to execute untrusted code.

**Default value**: `"internal"`

**Type**: `"internal" | "uv"`

**Example usage**:

```
{
    "ruff.format.backend": "uv"
}
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      format = {
        backend = "uv"
      }
    }
  }
})
```

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          "format": {
            "backend": "uv"
          }
        }
      }
    }
  }
}
```

## [VS Code specific](#vs-code-specific)

Additionally, the Ruff extension provides the following settings specific to VS Code. These settings are not used by the language server and are only relevant to the extension.

### [`enable`](#enable)

Whether to enable the Ruff extension. Modifying this setting requires restarting VS Code to take effect.

**Default value**: `true`

**Type**: `bool`

**Example usage**:

```
{
    "ruff.enable": false
}
```

### [`importStrategy`](#importstrategy)

Strategy for loading the `ruff` executable.

- `fromEnvironment` finds Ruff in the environment, falling back to the bundled version
- `useBundled` uses the version bundled with the extension

**Default value**: `"fromEnvironment"`

**Type**: `"fromEnvironment" | "useBundled"`

**Example usage**:

```
{
    "ruff.importStrategy": "useBundled"
}
```

### [`interpreter`](#interpreter)

A list of paths to Python interpreters. Even though this is a list, only the first interpreter is used.

The interpreter is used to find the `ruff` executable when [`ruff.importStrategy`](#importstrategy) is set to `fromEnvironment`.

**Default value**: `[]`

**Type**: `string[]`

**Example usage**:

```
{
    "ruff.interpreter": ["/home/user/.local/bin/python"]
}
```

### [`nativeServer`](#nativeserver)

Deprecated

This setting is deprecated as of Ruff 0.17. The Ruff extension will always use the native language server.

Whether to use the native language server, [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) or automatically decide between the two based on the Ruff version and extension settings.

**Default value**: `"auto"`

**Type**: `"on" | "off" | "auto" | true | false`

**Example usage**:

```
{
    "ruff.nativeServer": "on"
}
```

### [`path`](#path)

A list of path to `ruff` executables.

The first executable in the list which is exists is used. This setting takes precedence over the [`ruff.importStrategy`](#importstrategy) setting.

**Default value**: `[]`

**Type**: `string[]`

**Example usage**:

```
{
    "ruff.path": ["/home/user/.local/bin/ruff"]
}
```

### [`trace.server`](#traceserver)

The trace level for the language server. Refer to the [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#traceValue) for more information.

**Default value**: `"off"`

**Type**: `"off" | "messages" | "verbose"`

**Example usage**:

```
{
    "ruff.trace.server": "messages"
}
```

---

## File: site/ruff/editors/setup/index.md

# [Setup](#setup)

We have specific setup instructions depending on your editor of choice. If you don't see your editor on this list and would like a setup guide, please open an issue.

If you're transferring your configuration from [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp), regardless of editor, there are several settings which have changed or are no longer available. See the [migration guide](../migration/) for more.

Note

The setup instructions provided below are on a best-effort basis. If you encounter any issues while setting up the Ruff in an editor, please [open an issue](https://github.com/astral-sh/ruff/issues/new) for assistance and help in improving this documentation.

## [VS Code](#vs-code)

Install the Ruff extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff). It is recommended to have the Ruff extension version `2024.32.0` or later to get the best experience with the Ruff Language Server.

For more documentation on the Ruff extension, refer to the [README](https://github.com/astral-sh/ruff-vscode/blob/main/README.md) of the extension repository.

## [Neovim](#neovim)

The Ruff language server can be setup in Neovim using the built-in LSP client either via [`vim.lsp.config`](<https://neovim.io/doc/user/lsp.html#vim.lsp.config()>) (Neovim 0.11+) or the [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) plugin (Neovim 0.10 and earlier).

To setup using [`vim.lsp.config`](<https://neovim.io/doc/user/lsp.html#vim.lsp.config()>), you can either use the [default configuration provided in `nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig/blob/master/lsp/ruff.lua) or configure the server without any external dependencies.

The following configuration needs to be stored in `nvim/lsp/ruff.lua` or `nvim/after/lsp/ruff.lua`:

```
---@type vim.lsp.Config  
return {
  cmd = { 'ruff', 'server' },
  filetypes = { 'python' },
  root_markers = { 'pyproject.toml', 'ruff.toml', '.ruff.toml', '.git' },
  init_options = {
    settings = {
      -- Ruff language server settings go here
    }
  }
}
```

And, then enable the server by including the following in your `init.lua`:

```
vim.lsp.enable('ruff')
```

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      -- Ruff language server settings go here
    }
  }
})

vim.lsp.enable('ruff')
```

Note

[`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) has [deprecated](https://github.com/neovim/nvim-lspconfig/issues/3693) support for Neovim 0.10 and earlier in favor of using [`vim.lsp.config`](<https://neovim.io/doc/user/lsp.html#vim.lsp.config()>) instead.

```
require('lspconfig').ruff.setup({
  init_options = {
    settings = {
      -- Ruff language server settings go here
    }
  }
})
```

Note

If the installed version of `nvim-lspconfig` includes the changes from [neovim/nvim-lspconfig@`70d1c2c`](https://github.com/neovim/nvim-lspconfig/commit/70d1c2c31a88af4b36019dc1551be16bffb8f9db), you will need to use Ruff version `0.5.3` or later.

If you're using Ruff alongside another language server (like Pyright), you may want to defer to that language server for certain capabilities, like [`textDocument/hover`](../features/#hover):

```
vim.api.nvim_create_autocmd("LspAttach", {
  group = vim.api.nvim_create_augroup('lsp_attach_disable_ruff_hover', { clear = true }),
  callback = function(args)
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    if client == nil then
      return
    end
    if client.name == 'ruff' then
      -- Disable hover in favor of Pyright
      client.server_capabilities.hoverProvider = false
    end
  end,
  desc = 'LSP: Disable hover capability from Ruff',
})
```

If you'd like to use Ruff exclusively for linting, formatting, and organizing imports, you can disable those capabilities for Pyright:

```
vim.lsp.config('pyright', {
  settings = {
    pyright = {
      -- Using Ruff's import organizer
      disableOrganizeImports = true,
    },
    python = {
      analysis = {
        -- Ignore all files for analysis to exclusively use Ruff for linting
        ignore = { '*' },
      },
    },
  },
})
```

By default, the log level for Ruff is set to `info`. To change the log level, you can set the [`logLevel`](../settings/#loglevel) setting:

```
vim.lsp.config('ruff', {
  init_options = {
    settings = {
      logLevel = 'debug',
    }
  }
})
```

By default, Ruff will write logs to stderr which will be available in Neovim's LSP client log file (`:lua vim.print(vim.lsp.get_log_path())`). It's also possible to divert these logs to a separate file with the [`logFile`](../settings/#logfile) setting.

To view the trace logs between Neovim and Ruff, set the log level for Neovim's LSP client to `debug`:

```
vim.lsp.log.set_level('debug')
```

```
vim.lsp.set_log_level('debug')
```

With the [`conform.nvim`](https://github.com/stevearc/conform.nvim) plugin for Neovim.

```
require("conform").setup({
    formatters_by_ft = {
        python = {
          -- To fix auto-fixable lint errors.
          "ruff_fix",
          -- To run the Ruff formatter.
          "ruff_format",
          -- To organize the imports.
          "ruff_organize_imports",
        },
    },
})
```

With the [`nvim-lint`](https://github.com/mfussenegger/nvim-lint) plugin for Neovim.

```
require("lint").linters_by_ft = {
  python = { "ruff" },
}
```

With the [ALE](https://github.com/dense-analysis/ale) plugin for Neovim or Vim. *Neovim (using Lua):*

```
-- Linters
vim.g.ale_linters = { python = { "ruff" } }
-- Fixers
vim.g.ale_fixers = { python = { "ruff", "ruff_format" } }
```

*Vim (using Vimscript):*

```
" Linters
let g:ale_linters = { "python": ["ruff"] }
" Fixers
let g:ale_fixers = { "python": ["ruff", "ruff_format"] }
```

For the fixers, `ruff` will run `ruff check --fix` (to fix all auto-fixable problems) whereas `ruff_format` will run `ruff format`.

## [Vim](#vim)

The [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) plugin can be used to configure the Ruff Language Server in Vim. To set it up, install [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) plugin and register the server using the following in your `.vimrc`:

```
if executable('ruff')
    au User lsp_setup call lsp#register_server({
        \ 'name': 'ruff',
        \ 'cmd': {server_info->['ruff', 'server']},
        \ 'allowlist': ['python'],
        \ 'workspace_config': {},
        \ })
endif
```

See the `vim-lsp` [documentation](https://github.com/prabirshrestha/vim-lsp/blob/master/doc/vim-lsp.txt) for more details on how to configure the language server.

If you're using Ruff alongside another LSP (like Pyright), you may want to defer to that LSP for certain capabilities, like [`textDocument/hover`](../features/#hover) by adding the following to the function `s:on_lsp_buffer_enabled()`:

```
function! s:on_lsp_buffer_enabled() abort
    " add your keybindings here (see https://github.com/prabirshrestha/vim-lsp?tab=readme-ov-file#registering-servers)

    let l:capabilities = lsp#get_server_capabilities('ruff')
    if !empty(l:capabilities)
      let l:capabilities.hoverProvider = v:false
    endif
endfunction
```

Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright) extension for [coc.nvim](https://github.com/neoclide/coc.nvim).

Ruff can also be integrated via [efm language server](https://github.com/mattn/efm-langserver) in just a few lines. Following is an example config for efm to use Ruff for linting and formatting Python files:

```
tools:
  python-ruff:
    lint-command: "ruff check --stdin-filename ${INPUT} --output-format concise --quiet -"
    lint-stdin: true
    lint-formats:
      - "%f:%l:%c: %m"
    format-command: "ruff format --stdin-filename ${INPUT} --quiet -"
    format-stdin: true
```

## [Helix](#helix)

Open the [language configuration file](https://docs.helix-editor.com/languages.html#languagestoml-files) for Helix and add the language server as follows:

```
[language-server.ruff]
command = "ruff"
args = ["server"]
```

Then, you'll register the language server as the one to use with Python. If you don't already have a language server registered to use with Python, add this to `languages.toml`:

```
[[language]]
name = "python"
language-servers = ["ruff"]
```

Otherwise, if you already have `language-servers` defined, you can simply add `"ruff"` to the list. For example, if you already have `pylsp` as a language server, you can modify the language entry as follows:

```
[[language]]
name = "python"
language-servers = ["ruff", "pylsp"]
```

Note

Support for multiple language servers for a language is only available in Helix version [`23.10`](https://github.com/helix-editor/helix/blob/master/CHANGELOG.md#2310-2023-10-24) and later.

If you want to, as an example, turn on auto-formatting, add `auto-format = true`:

```
[[language]]
name = "python"
language-servers = ["ruff", "pylsp"]
auto-format = true
```

See the [Helix documentation](https://docs.helix-editor.com/languages.html) for more settings you can use here.

You can pass settings into `ruff server` using `[language-server.ruff.config.settings]`. For example:

```
[language-server.ruff.config.settings]
lineLength = 80

[language-server.ruff.config.settings.lint]
select = ["E4", "E7"]
preview = false

[language-server.ruff.config.settings.format]
preview = true
```

By default, the log level for Ruff is set to `info`. To change the log level, you can set the [`logLevel`](../settings/#loglevel) setting:

```
[language-server.ruff]
command = "ruff"
args = ["server"]

[language-server.ruff.config.settings]
logLevel = "debug"
```

You can also divert Ruff's logs to a separate file with the [`logFile`](../settings/#logfile) setting.

To view the trace logs between Helix and Ruff, pass in the `-v` (verbose) flag when starting Helix:

```
hx -v path/to/file.py
```

## [Kate](#kate)

1. Activate the [LSP Client plugin](https://docs.kde.org/stable5/en/kate/kate/plugins.html#kate-application-plugins).
1. Setup LSP Client [as desired](https://docs.kde.org/stable5/en/kate/kate/kate-application-plugin-lspclient.html).
1. Finally, add this to `Settings` -> `Configure Kate` -> `LSP Client` -> `User Server Settings`:

```
{
  "servers": {
    "python": {
      "command": ["ruff", "server"],
      "url": "https://github.com/astral-sh/ruff",
      "highlightingModeRegex": "^Python$",
      "settings": {}
    }
  }
}
```

See [LSP Client documentation](https://docs.kde.org/stable5/en/kate/kate/kate-application-plugin-lspclient.html) for more details on how to configure the server from there.

Important

Kate's LSP Client plugin does not support multiple servers for the same language. As a workaround, you can use the [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server) along with the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin to use Ruff alongside another language server. Note that this setup won't use the [server settings](../settings/) because the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin uses the `ruff` executable and not the language server.

## [Sublime Text](#sublime-text)

To use Ruff with Sublime Text, install Sublime Text's [LSP](https://github.com/sublimelsp/LSP) and [LSP-ruff](https://github.com/sublimelsp/LSP-ruff) package.

## [PyCharm](#pycharm)

Starting with version 2025.3, PyCharm supports Ruff out of the box:

1. Go to **Python | Tools | Ruff** in the Settings dialog.

1. Select the **Enable** checkbox.

1. In the Execution mode setting, select how PyCharm should search for the executable:

   **Interpreter** mode: PyCharm searches for an executable installed in your interpreter. To install the Ruff package for the selected interpreter, click *Install Ruff*.

   **Path** mode: PyCharm searches for an executable in `$PATH`. If the executable is not found, you can specify the path by clicking the Browse... icon.

1. Select which options should be enabled.

For more information, refer to [PyCharm documentation](https://www.jetbrains.com/help/pycharm/lsp-tools.html#ruff).

### [Via External Tool](#via-external-tool)

Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html) in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there, add a new tool with the following configuration:

Ruff should then appear as a runnable action:

### [Via third-party plugin](#via-third-party-plugin)

Ruff is also available as the [Ruff](https://plugins.jetbrains.com/plugin/20574-ruff) plugin on the IntelliJ Marketplace (maintained by [@koxudaxi](https://github.com/koxudaxi)).

## [Emacs](#emacs)

Ruff can be utilized as a language server via [`Eglot`](https://github.com/joaotavora/eglot), which is in Emacs's core. To enable Ruff with automatic formatting on save, use the following configuration:

```
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               '(python-base-mode . ("ruff" "server"))))
(add-hook 'python-base-mode-hook
          (lambda ()
            (eglot-ensure)
            (add-hook 'after-save-hook 'eglot-format nil t)))
```

Ruff is available as [`flymake-ruff`](https://melpa.org/#/flymake-ruff) on MELPA:

```
(require 'flymake-ruff)
(add-hook 'python-mode-hook #'flymake-ruff-load)
```

Ruff is also available as [`emacs-ruff-format`](https://github.com/scop/emacs-ruff-format):

```
(require 'ruff-format)
(add-hook 'python-mode-hook 'ruff-format-on-save-mode)
```

Alternatively, it can be used via the [Apheleia](https://github.com/radian-software/apheleia) formatter library, by setting this configuration:

```
;; Replace default (black) to use ruff for sorting import and formatting.
(setf (alist-get 'python-mode apheleia-mode-alist)
      '(ruff-isort ruff))
(setf (alist-get 'python-ts-mode apheleia-mode-alist)
      '(ruff-isort ruff))
```

## [TextMate](#textmate)

Ruff is also available via the [`textmate2-ruff-linter`](https://github.com/vigo/textmate2-ruff-linter) bundle for TextMate.

## [Zed](#zed)

Ruff support is now built into Zed (no separate extension required).

By default, Zed uses Ruff for formatting and linting.

To set up editor-wide Ruff options, provide the [server settings](../settings/) under the [`lsp.ruff.initialization_options.settings`](https://zed.dev/docs/configuring-zed#lsp) key of your `settings.json` file:

```
{
  "lsp": {
    "ruff": {
      "initialization_options": {
        "settings": {
          // Ruff server settings go here
          "lineLength": 80,
          "lint": {
            "extendSelect": ["I"],
          }
        }
      }
    }
  }
}
```

[`format_on_save`](https://zed.dev/docs/configuring-zed#format-on-save) is enabled by default. You can disable it for Python by changing `format_on_save` in your `settings.json` file:

```
{
  "languages": {
    "Python": {
      "format_on_save": "off"
    }
  }
}
```

You can configure Ruff to fix lint violations and/or organize imports on-save by enabling the `source.fixAll.ruff` and `source.organizeImports.ruff` code actions respectively:

```
{
  "languages": {
    "Python": {
      "code_actions_on_format": {
        // Organize imports
        "source.organizeImports.ruff": true,
        // Fix all auto-fixable lint violations
        "source.fixAll.ruff": true
      }
    }
  }
}
```

---

## File: site/ruff/faq/index.md

# [FAQ](#faq)

## [Is the Ruff linter compatible with Black?](#is-the-ruff-linter-compatible-with-black)

Yes. The Ruff linter is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as the [`line-length`](../settings/#line-length) setting is consistent between the two.

Ruff is designed to be used alongside a formatter (like Ruff's own formatter, or Black) and, as such, will defer implementing stylistic rules that are obviated by automated formatting.

Note that Ruff's linter and Black treat line-length enforcement a little differently. Black, like Ruff's formatter, makes a best-effort attempt to adhere to the [`line-length`](../settings/#line-length), but avoids automatic line-wrapping in some cases (e.g., within comments). Ruff, on the other hand, will flag [`line-too-long`](../rules/line-too-long/) (`E501`) for any line that exceeds the [`line-length`](../settings/#line-length) setting. As such, if [`line-too-long`](../rules/line-too-long/) (`E501`) is enabled, Ruff can still trigger line-length violations even when Black or `ruff format` is enabled.

## [How does Ruff's formatter compare to Black?](#how-does-ruffs-formatter-compare-to-black)

The Ruff formatter is designed to be a drop-in replacement for [Black](https://github.com/psf/black).

Specifically, the formatter is intended to emit near-identical output when run over Black-formatted code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines are formatted identically. When migrating an existing project from Black to Ruff, you should expect to see a few differences on the margins, but the vast majority of your code should be unchanged.

When run over *non*-Black-formatted code, the formatter makes some different decisions than Black, and so more deviations should be expected, especially around the treatment of end-of-line comments.

See [*Style Guide*](../formatter/#style-guide) for more.

## [How does Ruff's linter compare to Flake8?](#how-does-ruffs-linter-compare-to-flake8)

Ruff can be used as a drop-in replacement for Flake8 when used (1) without or with a small number of plugins, (2) alongside Black, and (3) on Python 3 code.

Under those conditions, Ruff implements every rule in Flake8. In practice, that means Ruff implements all of the `F` rules (which originate from Pyflakes), along with a subset of the `E` and `W` rules (which originate from pycodestyle).

Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools natively, including:

- [autoflake](https://pypi.org/project/autoflake/)
- [eradicate](https://pypi.org/project/eradicate/)
- [flake8-2020](https://pypi.org/project/flake8-2020/)
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
- [flake8-async](https://pypi.org/project/flake8-async)
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/astral-sh/ruff/issues/1646))
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
- [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-copyright](https://pypi.org/project/flake8-copyright/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
- [flake8-executable](https://pypi.org/project/flake8-executable/)
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
- [flake8-import-conventions](https://pypi.org/project/flake8-import-conventions/)
- [flake8-logging](https://pypi.org/project/flake8-logging-format/)
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)
- [flake8-print](https://pypi.org/project/flake8-print/)
- [flake8-pyi](https://pypi.org/project/flake8-pyi/)
- [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
- [flake8-quotes](https://pypi.org/project/flake8-quotes/)
- [flake8-raise](https://pypi.org/project/flake8-raise/)
- [flake8-return](https://pypi.org/project/flake8-return/)
- [flake8-self](https://pypi.org/project/flake8-self/)
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
- [flake8-slots](https://pypi.org/project/flake8-slots/)
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-todos](https://pypi.org/project/flake8-todos/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
- [isort](https://pypi.org/project/isort/)
- [mccabe](https://pypi.org/project/mccabe/)
- [pandas-vet](https://pypi.org/project/pandas-vet/)
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [perflint](https://pypi.org/project/perflint/) ([#4789](https://github.com/astral-sh/ruff/issues/4789))
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
- [pyupgrade](https://pypi.org/project/pyupgrade/)
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)

Note that, in some cases, Ruff uses different rule codes and prefixes than would be found in the originating Flake8 plugins. For example, Ruff uses `TID252` to represent the `I252` rule from flake8-tidy-imports. This helps minimize conflicts across plugins and allows any individual plugin to be toggled on or off with a single (e.g.) `--select TID`, as opposed to `--select I2` (to avoid conflicts with the isort rules, like `I001`).

Beyond the rule set, Ruff's primary limitation vis-à-vis Flake8 is that it does not support custom lint rules. (Instead, popular Flake8 plugins are re-implemented in Rust as part of Ruff itself.) One minor difference is that Ruff doesn't include all the 'opinionated' rules from flake8-bugbear.

## [How does Ruff's linter compare to Pylint?](#how-does-ruffs-linter-compare-to-pylint)

At time of writing, Pylint implements ~409 total rules, while Ruff implements over 900, of which at least 209 overlap with the Pylint rule set (see: [#970](https://github.com/astral-sh/ruff/issues/970)).

Pylint implements many rules that Ruff does not, and vice versa. For example, Pylint does more type inference than Ruff (e.g., Pylint can validate the number of arguments in a function call). As such, Ruff is not a "pure" drop-in replacement for Pylint (and vice versa), as they enforce different sets of rules.

Despite these differences, many users have successfully switched from Pylint to Ruff, especially those using Ruff alongside a [type checker](./#how-does-ruff-compare-to-ty-mypy-pyright-or-pyre), which can cover some of the functionality that Pylint provides.

Like Flake8, Pylint supports plugins (called "checkers"), while Ruff implements all rules natively and does not support custom or third-party rules. Unlike Pylint, Ruff is capable of automatically fixing its own lint violations.

In some cases, Ruff's rules may yield slightly different results than their Pylint counterparts. For example, Ruff's [`too-many-branches`](../rules/too-many-branches/) does not count `try` blocks as their own branches, unlike Pylint's `R0912`. Ruff's `PL` rule group also includes a small number of rules from Pylint *extensions* (like [`magic-value-comparison`](../rules/magic-value-comparison/)), which need to be explicitly activated when using Pylint. By enabling Ruff's `PL` group, you may see violations for rules that weren't previously enabled through your Pylint configuration.

Pylint parity is being tracked in [#970](https://github.com/astral-sh/ruff/issues/970).

## [How does Ruff compare to ty, Mypy, Pyright or Pyre?](#how-does-ruff-compare-to-ty-mypy-pyright-or-pyre)

Ruff is a linter, not a type checker. It can detect some of the same problems that a type checker can, but a type checker will catch certain errors that Ruff would miss. The opposite is also true: Ruff will catch certain errors that a type checker would typically ignore.

For example, unlike a type checker, Ruff will notify you if an import is unused, by looking for references to that import in the source code; on the other hand, a type checker could flag that you passed an integer argument to a function that expects a string, which Ruff would miss. The tools are complementary.

It's recommended that you use Ruff in conjunction with a type checker, like ty, Mypy, Pyright or Pyre, with Ruff providing faster feedback on lint violations and the type checker providing more detailed feedback on type errors.

## [Which tools does Ruff replace?](#which-tools-does-ruff-replace)

Today, Ruff can be used to replace Flake8 when used with any of the following plugins:

- [flake8-2020](https://pypi.org/project/flake8-2020/)
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
- [flake8-async](https://pypi.org/project/flake8-async)
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/astral-sh/ruff/issues/1646))
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
- [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-copyright](https://pypi.org/project/flake8-copyright/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
- [flake8-executable](https://pypi.org/project/flake8-executable/)
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
- [flake8-import-conventions](https://pypi.org/project/flake8-import-conventions/)
- [flake8-logging](https://pypi.org/project/flake8-logging/)
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)
- [flake8-print](https://pypi.org/project/flake8-print/)
- [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
- [flake8-quotes](https://pypi.org/project/flake8-quotes/)
- [flake8-raise](https://pypi.org/project/flake8-raise/)
- [flake8-return](https://pypi.org/project/flake8-return/)
- [flake8-self](https://pypi.org/project/flake8-self/)
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
- [flake8-slots](https://pypi.org/project/flake8-slots/)
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-todos](https://pypi.org/project/flake8-todos/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [flynt](https://pypi.org/project/flynt/) ([#2102](https://github.com/astral-sh/ruff/issues/2102))
- [mccabe](https://pypi.org/project/mccabe/)
- [pandas-vet](https://pypi.org/project/pandas-vet/)
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [perflint](https://pypi.org/project/perflint/) ([#4789](https://github.com/astral-sh/ruff/issues/4789))
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [tryceratops](https://pypi.org/project/tryceratops/)

Ruff can also replace [Black](https://pypi.org/project/black/), [isort](https://pypi.org/project/isort/), [yesqa](https://pypi.org/project/yesqa/), [eradicate](https://pypi.org/project/eradicate/), and most of the rules implemented in [pyupgrade](https://pypi.org/project/pyupgrade/).

If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, feel free to file an [issue](https://github.com/astral-sh/ruff/issues/new).

## [Do I have to use Ruff's linter and formatter together?](#do-i-have-to-use-ruffs-linter-and-formatter-together)

Nope! Ruff's linter and formatter can be used independently of one another -- you can use Ruff as a formatter, but not a linter, or vice versa.

## [What versions of Python does Ruff support?](#what-versions-of-python-does-ruff-support)

Ruff can lint code for any Python version from 3.7 onwards, including Python 3.15.

Ruff does not support Python 2. Ruff *may* run on pre-Python 3.7 code, although such versions are not officially supported (e.g., Ruff does *not* respect type comments).

Ruff is installable under any Python version from 3.7 onwards.

## [Do I need to install Rust to use Ruff?](#do-i-need-to-install-rust-to-use-ruff)

Nope! Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI. We recommend installing Ruff with [uv](https://docs.astral.sh/uv/), though it's also installable with `pip`, `pipx`, and a [variety of other package managers](../installation/):

```
$ # Install Ruff globally.
$ uv tool install ruff@latest

$ # Or add Ruff to your project.
$ uv add --dev ruff

$ # With pip.
$ pip install ruff

$ # With pipx.
$ pipx install ruff
```

Starting with version `0.5.0`, Ruff can also be installed with our standalone installers:

```
$ # On macOS and Linux.
$ curl -LsSf https://astral.sh/ruff/install.sh | sh

$ # On Windows.
$ powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"

$ # For a specific version.
$ curl -LsSf https://astral.sh/ruff/0.5.0/install.sh | sh
$ powershell -c "irm https://astral.sh/ruff/0.5.0/install.ps1 | iex"
```

Ruff ships with wheels for all major platforms, which enables `uv`, `pip`, and other tools to install Ruff without relying on a Rust toolchain at all.

## [Can I write my own linter plugins for Ruff?](#can-i-write-my-own-linter-plugins-for-ruff)

Ruff does not yet support third-party plugins, though a plugin system is within-scope for the project. See [#283](https://github.com/astral-sh/ruff/issues/283) for more.

## \[How does Ruff's import sorting compare to [isort](https://pypi.org/project/isort/)?\](#how-does-ruffs-import-sorting-compare-to-isort)

Ruff's import sorting is intended to be near-equivalent to isort's when using isort's `profile = "black"`.

There are a few known differences in how Ruff and isort treat aliased imports, and in how Ruff and isort treat inline comments in some cases (see: [#1381](https://github.com/astral-sh/ruff/issues/1381), [#2104](https://github.com/astral-sh/ruff/issues/2104)).

For example, Ruff tends to group non-aliased imports from the same module:

```
from numpy import cos, int8, int16, int32, int64, tan, uint8, uint16, uint32, uint64
from numpy import sin as np_sin
```

Whereas isort splits them into separate import statements at each aliased boundary:

```
from numpy import cos, int8, int16, int32, int64
from numpy import sin as np_sin
from numpy import tan, uint8, uint16, uint32, uint64
```

Ruff also correctly classifies some modules as standard-library that aren't recognized by isort, like `_string` and `idlelib`.

Like isort, Ruff's import sorting is compatible with Black.

## [How does Ruff determine which of my imports are first-party, third-party, etc.?](#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)

Ruff accepts a `src` option that in your `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file, specifies the directories that Ruff should consider when determining whether an import is first-party.

For example, if you have a project with the following structure:

```
my_project
├── pyproject.toml
└── src
    └── foo
        ├── __init__.py
        └── bar
            ├── __init__.py
            └── baz.py
```

When Ruff sees an import like `import foo`, it will then iterate over the `src` directories, looking for a corresponding Python module (in reality, a directory named `foo` or a file named `foo.py`). For module paths with multiple components like `import foo.bar`, Ruff will require that the full relative path `foo/bar` exists as a directory, or that `foo/bar.py` or `foo/bar.pyi` exist as files. Finally, for imports of the form `from foo import bar`, Ruff will only use `foo` when determining whether a module is first-party or third-party.

If there is a directory whose name matches a third-party package, but does not contain Python code, it could happen that the above algorithm incorrectly infers an import to be first-party. To prevent this, you can modify the [`known-third-party`](../settings/#lint_isort_known-third-party) setting. For example, if you import the package `wandb` but also have a subdirectory of your `src` with the same name, you can add the following:

```
[tool.ruff.lint.isort]
known-third-party = ["wandb"]
```

```
[lint.isort]
known-third-party = ["wandb"]
```

If the `src` field is omitted, Ruff will default to using the "project root", along with a `"src"` subdirectory, as the first-party sources, to support both flat and nested project layouts. The "project root" is typically the directory containing your `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file, unless a configuration file is provided on the command-line via the `--config` option, in which case, the current working directory is used as the project root.

In this case, Ruff would check the `"src"` directory by default, but we can configure it as an explicit, exclusive first-party source like so:

```
[tool.ruff]
# Ruff supports a top-level `src` option in lieu of isort's `src_paths` setting.
# All paths are relative to the project root, which is the directory containing the pyproject.toml.
src = ["src"]
```

```
# Ruff supports a top-level `src` option in lieu of isort's `src_paths` setting.
# All paths are relative to the project root, which is the directory containing the pyproject.toml.
src = ["src"]
```

If your `pyproject.toml`, `ruff.toml`, or `.ruff.toml` extends another configuration file, Ruff will still use the directory containing your `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file as the project root (as opposed to the directory of the file pointed to via the `extends` option).

For example, if you add a configuration file to the `tests` directory in the above example, you'll want to explicitly set the `src` option in the extended configuration file:

```
[tool.ruff]
extend = "../pyproject.toml"
src = ["../src"]
```

```
extend = "../pyproject.toml"
src = ["../src"]
```

Beyond this `src`-based detection, Ruff will also attempt to determine the current Python package for a given Python file (determined via the existence of a `__init__.py` file in a directory), and mark imports from within the same package as first-party. For example, above, `baz.py` would be identified as part of the Python package beginning at `./my_project/src/foo`, and so any imports in `baz.py` that begin with `foo` (like `import foo.bar`) would be considered first-party based on this same-package heuristic.

For a detailed explanation of `src` resolution, see the [contributing guide](../contributing/).

Ruff can also be configured to treat certain modules as (e.g.) always first-party, regardless of their location on the filesystem. For example, you can set [`known-first-party`](../settings/#lint_isort_known-first-party) like so:

```
[tool.ruff]
src = ["src", "tests"]

[tool.ruff.lint]
select = [
    # Pyflakes
    "F",
    # Pycodestyle
    "E",
    "W",
    # isort
    "I001"
]

[tool.ruff.lint.isort]
known-first-party = ["my_module1", "my_module2"]
```

```
src = ["src", "tests"]

[lint]
select = [
    # Pyflakes
    "F",
    # Pycodestyle
    "E",
    "W",
    # isort
    "I001"
]

[lint.isort]
known-first-party = ["my_module1", "my_module2"]
```

Ruff does not yet support all of isort's configuration options, though it does support many of them. You can find the supported settings in the [API reference](../settings/#lintisort).

## [Does Ruff support Jupyter Notebooks?](#does-ruff-support-jupyter-notebooks)

Ruff has built-in support for linting and formatting [Jupyter Notebooks](https://jupyter.org/). Refer to the [Jupyter Notebook section](../configuration/#jupyter-notebook-discovery) for more details.

Ruff also integrates with [nbQA](https://github.com/nbQA-dev/nbQA), a tool for running linters and code formatters over Jupyter Notebooks.

After installing `ruff` and `nbqa`, you can run Ruff over a notebook like so:

```
$ nbqa ruff Untitled.ipynb
Untitled.ipynb:cell_1:2:5: F841 Local variable `x` is assigned to but never used
Untitled.ipynb:cell_2:1:1: E402 Module level import not at top of file
Untitled.ipynb:cell_2:1:8: F401 `os` imported but unused
Found 3 errors.
1 potentially fixable with `--fix`.
```

## [Does Ruff support NumPy- or Google-style docstrings?](#does-ruff-support-numpy-or-google-style-docstrings)

Yes! To enforce a docstring convention, add a [`convention`](../settings/#lint_pydocstyle_convention) setting following to your configuration file:

```
[tool.ruff.lint.pydocstyle]
convention = "google"  # Accepts: "google", "numpy", or "pep257".
```

```
[lint.pydocstyle]
convention = "google"  # Accepts: "google", "numpy", or "pep257".
```

For example, if you're coming from flake8-docstrings, and your originating configuration uses `--docstring-convention=numpy`, you'd instead set `convention = "numpy"` in your `pyproject.toml`, as above.

Alongside [`convention`](../settings/#lint_pydocstyle_convention), you'll want to explicitly enable the `D` rule code prefix, since the `D` rules are not enabled by default:

```
[tool.ruff.lint]
select = ["D"]

[tool.ruff.lint.pydocstyle]
convention = "google"
```

```
[lint]
select = ["D"]

[lint.pydocstyle]
convention = "google"
```

Enabling a [`convention`](../settings/#lint_pydocstyle_convention) will disable any rules that are not included in the specified convention. As such, the intended workflow is to enable a convention and then selectively enable or disable any additional rules on top of it:

```
[tool.ruff.lint]
select = [
    "D",
    # Augment the convention by requiring an imperative mood for all docstrings.
    "D401",
]

ignore = [
    # Relax the convention by _not_ requiring documentation for every function parameter.
    "D417",
]

[tool.ruff.lint.pydocstyle]
convention = "google"
```

```
[lint]
select = [
    "D",
    # Augment the convention by requiring an imperative mood for all docstrings.
    "D401",
]

ignore = [
    # Relax the convention by _not_ requiring documentation for every function parameter.
    "D417",
]

[lint.pydocstyle]
convention = "google"
```

By default, no [`convention`](../settings/#lint_pydocstyle_convention) is set, and so the enabled rules are determined by the [`select`](../settings/#lint_select) setting alone.

## [What is "preview"?](#what-is-preview)

Preview enables a collection of newer rules and fixes that are considered experimental or unstable. See the [preview documentation](../preview/) for more details; or, to see which rules are currently in preview, visit the [rules reference](../rules/).

## [How can I tell what settings Ruff is using to check my code?](#how-can-i-tell-what-settings-ruff-is-using-to-check-my-code)

Run `ruff check /path/to/code.py --show-settings` to view the resolved settings for a given file.

## [I want to use Ruff, but I don't want to use `pyproject.toml`. What are my options?](#i-want-to-use-ruff-but-i-dont-want-to-use-pyprojecttoml-what-are-my-options)

In lieu of a `pyproject.toml` file, you can use a `ruff.toml` file for configuration. The two files are functionally equivalent and have an identical schema, with the exception that a `ruff.toml` file can omit the `[tool.ruff]` section header. For example:

```
[tool.ruff]
line-length = 88

[tool.ruff.lint.pydocstyle]
convention = "google"
```

```
line-length = 88

[lint.pydocstyle]
convention = "google"
```

Ruff doesn't currently support INI files, like `setup.cfg` or `tox.ini`.

## [How can I change Ruff's default configuration?](#how-can-i-change-ruffs-default-configuration)

When no configuration file is found, Ruff will look for a user-specific `ruff.toml` file as a last resort. This behavior is similar to Flake8's `~/.config/flake8`.

On macOS and Linux, Ruff expects that file to be located at `~/.config/ruff/ruff.toml`, and respects the `XDG_CONFIG_HOME` specification.

On Windows, Ruff expects that file to be located at `~\AppData\Roaming\ruff\ruff.toml`.

Note

Prior to `v0.5.0`, Ruff would read user-specific configuration from `~/Library/Application Support/ruff/ruff.toml` on macOS. While Ruff will still respect such configuration files, the use of `~/Library/Application Support` is considered deprecated.

For more, see the [`etcetera`](https://crates.io/crates/etcetera) crate.

## [Ruff tried to fix something — but it broke my code. What's going on?](#ruff-tried-to-fix-something-but-it-broke-my-code-whats-going-on)

Ruff labels fixes as "safe" and "unsafe". By default, Ruff will fix all violations for which safe fixes are available, while unsafe fixes can be applied via the [`unsafe-fixes`](../settings/#unsafe-fixes) setting, or passing the [`--unsafe-fixes`](../settings/#unsafe-fixes) flag to `ruff check`. For more, see [the fix documentation](../linter/#fixes).

Even still, given the dynamic nature of Python, it's difficult to have *complete* certainty when making changes to code, even for seemingly trivial fixes. If a "safe" fix breaks your code, please [file an Issue](https://github.com/astral-sh/ruff/issues/new).

## [How can I disable/force Ruff's color output?](#how-can-i-disableforce-ruffs-color-output)

Ruff's color output is powered by the [`colored`](https://crates.io/crates/colored) crate, which attempts to automatically detect whether the output stream supports color. However, you can force colors off by setting the `NO_COLOR` environment variable to any value (e.g., `NO_COLOR=1`), or force colors on by setting `FORCE_COLOR` to any non-empty value (e.g., `FORCE_COLOR=1`).

[`colored`](https://crates.io/crates/colored) also supports the `CLICOLOR` and `CLICOLOR_FORCE` environment variables (see the [spec](https://bixense.com/clicolors/)).

## [Ruff behaves unexpectedly when using `source.*` code actions in Notebooks. What's going on?](#source-code-actions-in-notebooks)

Ruff does not support `source.organizeImports` and `source.fixAll` code actions in Jupyter Notebooks (`notebook.codeActionsOnSave` in VS Code). It's recommended to use the `notebook` prefixed code actions for the same such as `notebook.source.organizeImports` and `notebook.source.fixAll` respectively.

Ruff requires to have a full view of the notebook to provide accurate diagnostics and fixes. For example, if you have a cell that imports a module and another cell that uses that module, Ruff needs to see both cells to mark the import as used. If Ruff were to only see one cell at a time, it would incorrectly mark the import as unused.

When using the `source.*` code actions for a Notebook, Ruff will be asked to fix any issues for each cell in parallel, which can lead to unexpected behavior. For example, if a user has configured to run `source.organizeImports` code action on save for a Notebook, Ruff will attempt to fix the imports for the entire notebook corresponding to each cell. This leads to the client making the same changes to the notebook multiple times, which can lead to unexpected behavior ([astral-sh/ruff-vscode#680](https://github.com/astral-sh/ruff-vscode/issues/680), [astral-sh/ruff-vscode#640](https://github.com/astral-sh/ruff-vscode/issues/640), [astral-sh/ruff-vscode#391](https://github.com/astral-sh/ruff-vscode/issues/391)).

---

## File: site/ruff/formatter/index.md

# [The Ruff Formatter](#the-ruff-formatter)

The Ruff formatter is an extremely fast Python code formatter designed as a drop-in replacement for [Black](https://pypi.org/project/black/), available as part of the `ruff` CLI via `ruff format`.

## [`ruff format`](#ruff-format)

`ruff format` is the primary entrypoint to the formatter. It accepts a list of files or directories, and formats all discovered Python files:

```
ruff format                   # Format all files in the current directory.
ruff format path/to/code/     # Format all files in `path/to/code` (and any subdirectories).
ruff format path/to/file.py   # Format a single file.
```

Similar to Black, running `ruff format /path/to/file.py` will format the given file or directory in-place, while `ruff format --check /path/to/file.py` will avoid writing any formatted files back, and instead exit with a non-zero status code upon detecting any unformatted files.

For the full list of supported options, run `ruff format --help`.

## [Philosophy](#philosophy)

The initial goal of the Ruff formatter is *not* to innovate on code style, but rather, to innovate on performance, and provide a unified toolchain across Ruff's linter, formatter, and any and all future tools.

As such, the formatter is designed as a drop-in replacement for [Black](https://github.com/psf/black), but with an excessive focus on performance and direct integration with Ruff. Given Black's popularity within the Python ecosystem, targeting Black compatibility ensures that formatter adoption is minimally disruptive for the vast majority of projects.

Specifically, the formatter is intended to emit near-identical output when run over existing Black-formatted code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines are formatted identically. (See: [*Style Guide*](#style-guide).)

Given this focus on Black compatibility, the formatter thus adheres to [Black's (stable) code style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html), which aims for "consistency, generality, readability and reducing git diffs". To give you a sense for the enforced code style, here's an example:

```
# Input
def _make_ssl_transport(
    rawsock, protocol, sslcontext, waiter=None,
    *, server_side=False, server_hostname=None,
    extra=None, server=None,
    ssl_handshake_timeout=None,
    call_connection_made=True):
    '''Make an SSL transport.'''
    if waiter is None:
      waiter = Future(loop=loop)

    if extra is None:
      extra = {}

    ...

# Ruff
def _make_ssl_transport(
    rawsock,
    protocol,
    sslcontext,
    waiter=None,
    *,
    server_side=False,
    server_hostname=None,
    extra=None,
    server=None,
    ssl_handshake_timeout=None,
    call_connection_made=True,
):
    """Make an SSL transport."""
    if waiter is None:
        waiter = Future(loop=loop)

    if extra is None:
        extra = {}

    ...
```

Like Black, the Ruff formatter does *not* support extensive code style configuration; however, unlike Black, it *does* support configuring the desired quote style, indent style, line endings, and more. (See: [*Configuration*](#configuration).)

While the formatter is designed to be a drop-in replacement for Black, it is not intended to be used interchangeably with Black on an ongoing basis, as the formatter *does* differ from Black in a few conscious ways (see: [*Known deviations*](black/)). In general, deviations are limited to cases in which Ruff's behavior was deemed more consistent, or significantly simpler to support (with negligible end-user impact) given the differences in the underlying implementations between Black and Ruff.

Going forward, the Ruff Formatter will support Black's preview style under Ruff's own [preview](../preview/) mode.

## [Configuration](#configuration)

The Ruff Formatter exposes a small set of configuration options, some of which are also supported by Black (like line width), some of which are unique to Ruff (like quote, indentation style and formatting code examples in docstrings).

For example, to configure the formatter to use single quotes, format code examples in docstrings, a line width of 100, and tab indentation, add the following to your configuration file:

```
[tool.ruff]
line-length = 100

[tool.ruff.format]
quote-style = "single"
indent-style = "tab"
docstring-code-format = true
```

```
line-length = 100

[format]
quote-style = "single"
indent-style = "tab"
docstring-code-format = true
```

For the full list of supported settings, see [*Settings*](../settings/#format). For more on configuring Ruff via `pyproject.toml`, see [*Configuring Ruff*](../configuration/).

Given the focus on Black compatibility (and unlike formatters like [YAPF](https://github.com/google/yapf)), Ruff does not currently expose any other configuration options.

## [Docstring formatting](#docstring-formatting)

The Ruff formatter provides an opt-in feature for automatically formatting Python code examples in docstrings. The Ruff formatter currently recognizes code examples in the following formats:

- The Python [doctest](https://docs.python.org/3/library/doctest.html) format.
- CommonMark [fenced code blocks](https://spec.commonmark.org/0.30/#fenced-code-blocks) with the following info strings: `python`, `py`, `python3`, or `py3`. Fenced code blocks without an info string are assumed to be Python code examples and also formatted.
- reStructuredText [literal blocks](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks). While literal blocks may contain things other than Python, this is meant to reflect a long-standing convention in the Python ecosystem where literal blocks often contain Python code.
- reStructuredText [`code-block` and `sourcecode` directives](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block). As with Markdown, the language names recognized for Python are `python`, `py`, `python3`, or `py3`.

If a code example is recognized and treated as Python, the Ruff formatter will automatically skip it if the code does not parse as valid Python or if the reformatted code would produce an invalid Python program.

Users may also configure the line length limit used for reformatting Python code examples in docstrings. The default is a special value, `dynamic`, which instructs the formatter to respect the line length limit setting for the surrounding Python code. The `dynamic` setting ensures that even when code examples are found inside indented docstrings, the line length limit configured for the surrounding Python code will not be exceeded. Users may also configure a fixed line length limit for code examples in docstrings.

For example, this configuration shows how to enable docstring code formatting with a fixed line length limit:

```
[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = 20
```

```
[format]
docstring-code-format = true
docstring-code-line-length = 20
```

With the above configuration, this code:

```
def f(x):
    '''
    Something about `f`. And an example:

    .. code-block:: python

        foo, bar, quux = this_is_a_long_line(lion, hippo, lemur, bear)
    '''
    pass
```

... will be reformatted (assuming the rest of the options are set to their defaults) as:

```
def f(x):
    """
    Something about `f`. And an example:

    .. code-block:: python

        (
            foo,
            bar,
            quux,
        ) = this_is_a_long_line(
            lion,
            hippo,
            lemur,
            bear,
        )
    """
    pass
```

## [Markdown code formatting](#markdown-code-formatting)

The Ruff formatter can also format Python code blocks in Markdown files. In these files, Ruff will format any CommonMark [fenced code blocks](https://spec.commonmark.org/0.30/#fenced-code-blocks) with the following info strings: `python`, `py`, `python3`, `py3`, `pyi`, or `pycon`. The formatter will automatically skip a code block if the code does not parse as valid Python or if the reformatted code would produce an invalid Python program.

Code blocks marked as `pyi` are formatted like stub files, `pycon` blocks as REPL sessions, and the others use normal Python file formatting. For example:

````
```py
print("hello")
````

```pyi
def foo(): ...
def bar(): ...
```

```

Ruff also supports [Quarto](https://quarto.org/) style executable code blocks
with curly braces surrounding the language name:

```

```{python}
print("hello")
```

```

While [formatting suppression](#format-suppression) comments will be handled as
usual within code blocks, the formatter will also skip formatting any code block
surrounded by appropriate HTML comments, such as:

```

<!-- fmt:off -->

```py
print( 'hello' )
```

<!-- fmt:on -->

```

Any number of code blocks may be contained within a matching pair of `off` and
`on` HTML comments, and any `off` comment *without* a matching `on` comment
will implicitly cover the remaining portion of the document.

The Ruff formatter will also recognize HTML comments from [blacken-docs](https://github.com/adamchainz/blacken-docs/),
`` and ``, which are equivalent
to `` and `` respectively.

To format Markdown files with extensions other than `.md`, configure custom
[`extension`](../settings/#extension) mappings. Ruff will automatically include
these mapped extensions in file discovery:

```

[tool.ruff]

# Treat `.mdx` and `.qmd` files as Markdown

extension = { mdx = "markdown", qmd = "markdown" }

```

```

# Treat `.mdx` and `.qmd` files as Markdown

extension = {mdx="markdown", qmd="markdown"}

```

If you run Ruff via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit), Markdown
support needs to be explicitly included by adding it to `types_or`:

.pre-commit-config.yaml

```

repos:

- repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.17.0 hooks:
  - id: ruff-format types_or: [python, pyi, jupyter, markdown]

```

To *disable* formatting of Markdown files, add them to
[`extend-exclude`](../settings/#extend-exclude) in your project settings:

```

[tool.ruff]

# Disable formatting in Markdown files

extend-exclude = ["\*.md"]

```

```

# Disable formatting in Markdown files

extend-exclude = ["\*.md"]

```

## [Format suppression](#format-suppression)

Like Black, Ruff supports `# fmt: on`, `# fmt: off`, and `# fmt: skip` pragma comments, which can
be used to temporarily disable formatting for a given code block.

`# fmt: on` and `# fmt: off` comments are enforced at the statement level:

```

# fmt: off

not_formatted=3 also_not_formatted=4

# fmt: on

```

As such, adding `# fmt: on` and `# fmt: off` comments within expressions will have no effect. In
the following example, both list entries will be formatted, despite the `# fmt: off`:

```

[ # fmt: off '1', # fmt: on '2', ]

```

Instead, apply the `# fmt: off` comment to the entire statement:

```

# fmt: off

[ '1', '2', ]

# fmt: on

```

Like Black, Ruff will *also* recognize [YAPF](https://github.com/google/yapf)'s `# yapf: disable` and `# yapf: enable` pragma
comments, which are treated equivalently to `# fmt: off` and `# fmt: on`, respectively.

`# fmt: skip` comments suppress formatting for a case header, decorator,
function definition, class definition, or the preceding statements
on the same logical line. The formatter leaves the following unchanged:

```

if True: pass elif False: # fmt: skip pass

@Test @Test2(a,b) # fmt: skip def test(): ...

a = [1,2,3,4,5] # fmt: skip

def test(a,b,c,d,e,f) -> int: # fmt: skip pass

x=1;x=2;x=3 # fmt: skip

```

Adding a `# fmt: skip` comment at the end of an expression will have no effect. In
the following example, the list entry `'1'` will be formatted, despite the `# fmt: skip`:

```

a = call( [ '1', # fmt: skip '2', ], b )

```

Instead, apply the `# fmt: skip` comment to the entire statement:

```

a = call( [ '1', '2', ], b ) # fmt: skip

```

## [Conflicting lint rules](#conflicting-lint-rules)

Ruff's formatter is designed to be used alongside the linter. However, the linter includes
some rules that, when enabled, can cause conflicts with the formatter, leading to unexpected
behavior. When configured appropriately, the goal of Ruff's formatter-linter compatibility is
such that running the formatter should never introduce new lint errors.

When using Ruff as a formatter, we recommend avoiding the following lint rules:

- [`tab-indentation`](../rules/tab-indentation/) (`W191`)
- [`indentation-with-invalid-multiple`](../rules/indentation-with-invalid-multiple/) (`E111`)
- [`indentation-with-invalid-multiple-comment`](../rules/indentation-with-invalid-multiple-comment/) (`E114`)
- [`over-indented`](../rules/over-indented/) (`E117`)
- [`incorrect-blank-line-before-class`](../rules/incorrect-blank-line-before-class/) (`D203`)
- [`docstring-tab-indentation`](../rules/docstring-tab-indentation/) (`D206`)
- [`triple-single-quotes`](../rules/triple-single-quotes/) (`D300`)
- [`bad-quotes-inline-string`](../rules/bad-quotes-inline-string/) (`Q000`)
- [`bad-quotes-multiline-string`](../rules/bad-quotes-multiline-string/) (`Q001`)
- [`bad-quotes-docstring`](../rules/bad-quotes-docstring/) (`Q002`)
- [`avoidable-escaped-quote`](../rules/avoidable-escaped-quote/) (`Q003`)
- [`unnecessary-escaped-quote`](../rules/unnecessary-escaped-quote/) (`Q004`)
- [`missing-trailing-comma`](../rules/missing-trailing-comma/) (`COM812`)
- [`prohibited-trailing-comma`](../rules/prohibited-trailing-comma/) (`COM819`)
- [`multi-line-implicit-string-concatenation`](../rules/multi-line-implicit-string-concatenation/) (`ISC002`) if used without `ISC001` and `flake8-implicit-str-concat.allow-multiline = false`

While the [`line-too-long`](../rules/line-too-long/) (`E501`) rule *can* be used alongside the
formatter, the formatter only makes a best-effort attempt to wrap lines at the configured
[`line-length`](../settings/#line-length). As such, formatted code *may* exceed the line length,
leading to [`line-too-long`](../rules/line-too-long/) (`E501`) errors.

None of the above are included in Ruff's default configuration. However, if you've enabled
any of these rules or their parent categories (like `Q`), we recommend disabling them via the
linter's [`lint.ignore`](../settings/#lint_ignore) setting.

Similarly, we recommend avoiding the following isort settings, which are incompatible with the
formatter's treatment of import statements when set to non-default values:

- [`force-single-line`](../settings/#lint_isort_force-single-line)
- [`force-wrap-aliases`](../settings/#lint_isort_force-wrap-aliases)
- [`lines-after-imports`](../settings/#lint_isort_lines-after-imports)
- [`lines-between-types`](../settings/#lint_isort_lines-between-types)
- [`split-on-trailing-comma`](../settings/#lint_isort_split-on-trailing-comma)

If you've configured any of these settings to take on non-default values, we recommend removing
them from your Ruff configuration.

When an incompatible lint rule or setting is enabled, `ruff format` will emit a warning. If your
`ruff format` is free of warnings, you're good to go!

## [Exit codes](#exit-codes)

`ruff format` exits with the following status codes:

- `0` if Ruff terminates successfully, regardless of whether any files were formatted.
- `1` if Ruff terminates successfully, one or more files were formatted, and `--exit-non-zero-on-format` was specified.
- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an
  internal error.

Meanwhile, `ruff format --check` exits with the following status codes:

- `0` if Ruff terminates successfully, and no files would be formatted if `--check` were not
  specified.
- `1` if Ruff terminates successfully, and one or more files would be formatted if `--check` were
  not specified.
- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an
  internal error.

## [Style Guide](#style-guide)

The formatter is designed to be a drop-in replacement for [Black](https://github.com/psf/black).
This section documents the areas where the Ruff formatter goes beyond Black in terms of code style.

### [Intentional deviations](#intentional-deviations)

While the Ruff formatter aims to be a drop-in replacement for Black, it does differ from Black
in a few known ways. Some of these differences emerge from conscious attempts to improve upon
Black's code style, while others fall out of differences in the underlying implementations.

For a complete enumeration of these intentional deviations, see [*Known deviations*](black/).

Unintentional deviations from Black are tracked in the [issue tracker](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter).
If you've identified a new deviation, please [file an issue](https://github.com/astral-sh/ruff/issues/new).

### [Preview style](#preview-style)

Similar to [Black](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style), Ruff implements formatting changes
under the [`preview`](https://docs.astral.sh/ruff/settings/#format_preview) flag, promoting them to stable through minor releases, in accordance with our [versioning policy](https://github.com/astral-sh/ruff/discussions/6998#discussioncomment-7016766).

### [F-string formatting](#f-string-formatting)

*Stabilized in Ruff 0.9.0*

Unlike Black, Ruff formats the expression parts of f-strings which are the parts inside the curly
braces `{...}`. This is a [known deviation](black/#f-strings) from Black.

Ruff employs several heuristics to determine how an f-string should be formatted which are detailed
below.

#### [Quotes](#quotes)

Ruff will use the [configured quote style](../settings/#format_quote-style) for the f-string expression unless doing so would result in
invalid syntax for the target Python version or requires more backslash escapes than the original
expression. Specifically, Ruff will preserve the original quote style for the following cases:

When the target Python version is < 3.12 and a [self-documenting f-string](https://realpython.com/python-f-strings/#self-documenting-expressions-for-debugging) contains a string
literal with the [configured quote style](../settings/#format_quote-style):

```

# format.quote-style = "double"

f'{10 + len("hello")=}'

# This f-string cannot be formatted as follows when targeting Python < 3.12

f"{10 + len("hello")=}"

```

When the target Python version is < 3.12 and an f-string contains any triple-quoted string, byte
or f-string literal that contains the [configured quote style](../settings/#format_quote-style):

```

# format.quote-style = "double"

f'{"""nested " """}'

# This f-string cannot be formatted as follows when targeting Python < 3.12

f"{'''nested " '''}"

```

For all target Python versions, when a [self-documenting f-string](https://realpython.com/python-f-strings/#self-documenting-expressions-for-debugging) contains an expression between
the curly braces (`{...}`) with a format specifier containing the [configured quote style](../settings/#format_quote-style):

```

# format.quote-style = "double"

f'{1=:"foo}'

# This f-string cannot be formatted as follows for all target Python versions

f"{1=:"foo}"

```

By default, or when targeting Python versions below 3.12, Ruff alternates quote styles for nested
f-strings, starting with the [configured quote style](../settings/#format_quote-style) for the outermost f-string.
For example, consider the following f-string:

```

# format.quote-style = "double"

f"outer f-string {f"nested f-string {f"another nested f-string"} end"} end"

```

With default settings, Ruff formats it as:

```

f"outer f-string {f'nested f-string {f"another nested f-string"} end'} end"

```

When targeting Python 3.12+ and with `nested-string-quote-style = "preferred"`,
Ruff will use the configured quote style for nested strings:

```

f"outer f-string {f"nested f-string {f"another nested f-string"} end"} end"

```

#### [Line breaks](#line-breaks)

Starting with Python 3.12 ([PEP 701](https://peps.python.org/pep-0701/)), the expression parts of an f-string can
span multiple lines. Ruff needs to decide when to introduce a line break in an f-string expression.
This depends on the semantic content of the expression parts of an f-string - for example,
introducing a line break in the middle of a natural-language sentence is undesirable. Since Ruff
doesn't have enough information to make that decision, it adopts a heuristic similar to [Prettier](https://prettier.io/docs/en/next/rationale.html#template-literals):
it will only split the expression parts of an f-string across multiple lines if there was already a line break
within any of the expression parts.

For example, the following code:

```

f"this f-string has a multiline expression { ['red', 'green', 'blue', 'yellow',]} and does not fit within the line length"

```

... is formatted as:

```

# The list expression is split across multiple lines because of the trailing comma

f"this f-string has a multiline expression { [ 'red', 'green', 'blue', 'yellow', ] } and does not fit within the line length"

```

But, the following will not be split across multiple lines even though it exceeds the line length:

```

f"this f-string has a multiline expression {['red', 'green', 'blue', 'yellow']} and does not fit within the line length"

```

If you want Ruff to split an f-string across multiple lines, ensure there's a linebreak somewhere within the
`{...}` parts of an f-string.

### [Fluent layout for method chains](#fluent-layout-for-method-chains)

At times, when developers write long chains of methods on an object, such as

```

x = df.filter(cond).agg(func).merge(other)

```

the intent is to perform a sequence of transformations or operations
on a fixed object of interest - in this example, the object `df`.
Assuming the assigned expression exceeds the `line-length`, this preview
style will format the above as:

```

x = ( df .filter(cond) .agg(func) .merge(other) )

```

This deviates from the stable formatting, and also from Black, both
of which would produce:

```

x = ( df.filter(cond) .agg(func) .merge(other) )

```

Both the stable and preview formatting are variants of something
called a **fluent layout**.

In general, this preview style differs from the stable style
only at the first attribute that precedes
a call or subscript. The preview formatting breaks *before* this attribute,
while the stable formatting breaks *after* the call or subscript.

## [Sorting imports](#sorting-imports)

Currently, the Ruff formatter does not sort imports. In order to both sort imports and format,
call the Ruff linter and then the formatter:

```

ruff check --select I --fix ruff format

````

A unified command for both linting and formatting is [planned](https://github.com/astral-sh/ruff/issues/8232).```
````

---

## File: site/ruff/formatter/black/index.md

This document enumerates the known, intentional differences in code style between Black and Ruff's formatter.

For a list of unintentional deviations, see [issue tracker](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter).

### [Trailing end-of-line comments](#trailing-end-of-line-comments)

Black's priority is to fit an entire statement on a line, even if it contains end-of-line comments. In such cases, Black collapses the statement, and moves the comment to the end of the collapsed statement:

```
# Input
while (
    cond1  # almost always true
    and cond2  # almost never true
):
    print("Do something")

# Black
while cond1 and cond2:  # almost always true  # almost never true
    print("Do something")
```

Ruff, like [Prettier](https://prettier.io/), expands any statement that contains trailing end-of-line comments. For example, Ruff would avoid collapsing the `while` test in the snippet above. This ensures that the comments remain close to their original position and retain their original intent, at the cost of retaining additional vertical space.

This deviation only impacts unformatted code, in that Ruff's output should not deviate for code that has already been formatted by Black.

### [Pragma comments are ignored when computing line width](#pragma-comments-are-ignored-when-computing-line-width)

Pragma comments (`# type`, `# noqa`, `# pyright`, `# pylint`, etc.) are ignored when computing the width of a line. For mixed comments such as `# explanation # noqa: F401`, only the pragma suffix is ignored; the preceding comment text still contributes to the line width. This prevents Ruff from moving pragma comments around, thereby modifying their meaning and behavior:

See Ruff's [pragma comment handling proposal](https://github.com/astral-sh/ruff/discussions/6670) for details.

This is similar to [Pyink](https://github.com/google/pyink) but a deviation from Black. Black avoids splitting any lines that contain a `# type` comment ([#997](https://github.com/psf/black/issues/997)), but otherwise avoids special-casing pragma comments.

As Ruff expands trailing end-of-line comments, Ruff will also avoid moving pragma comments in cases like the following, where moving the `# noqa` to the end of the line causes it to suppress errors on both `first()` and `second()`:

```
# Input
[
    first(),  # noqa
    second()
]

# Black
[first(), second()]  # noqa

# Ruff
[
    first(),  # noqa
    second(),
]
```

### [Line width vs. line length](#line-width-vs-line-length)

Ruff uses the Unicode width of a line to determine if a line fits. Black uses Unicode width for strings, and character width for all other tokens. Ruff *also* uses Unicode width for identifiers and comments.

### [Parenthesizing long nested-expressions](#parenthesizing-long-nested-expressions)

Black 24 and newer parenthesizes long conditional expressions and type annotations in function parameters:

```
# Black
[
    "____________________________",
    "foo",
    "bar",
    (
        "baz"
        if some_really_looooooooong_variable
        else "some other looooooooooooooong value"
    ),
]


def foo(
    i: int,
    x: (
        Loooooooooooooooooooooooong
        | Looooooooooooooooong
        | Looooooooooooooooooooong
        | Looooooong
    ),
    *,
    s: str,
) -> None:
    pass

# Ruff
[
    "____________________________",
    "foo",
    "bar",
    "baz"
    if some_really_looooooooong_variable
    else "some other looooooooooooooong value",
]


def foo(
    i: int,
    x: Loooooooooooooooooooooooong
    | Looooooooooooooooong
    | Looooooooooooooooooooong
    | Looooooong,
    *,
    s: str,
) -> None:
    pass
```

We agree that Ruff's formatting (that matches Black's 23) is hard to read and needs improvement. But we aren't convinced that parenthesizing long nested expressions is the best solution, especially when considering expression formatting holistically. That's why we want to defer the decision until we've explored alternative nested expression formatting styles. See [psf/Black#4123](https://github.com/psf/black/issues/4123) for an in-depth explanation of our concerns and an outline of possible alternatives.

### [Call expressions with a single multiline string argument](#call-expressions-with-a-single-multiline-string-argument)

Unlike Black, Ruff preserves the indentation of a single multiline-string argument in a call expression:

```
# Input
call(
  """"
  A multiline
  string
  """
)

dedent(""""
    A multiline
    string
""")

# Black
call(
  """"
  A multiline
  string
  """
)

dedent(
  """"
  A multiline
  string
"""
)


# Ruff
call(
  """"
  A multiline
  string
  """
)

dedent(""""
    A multiline
    string
""")
```

Black intended to ship a similar style change as part of the 2024 style that always removes the indent. It turned out that this change was too disruptive to justify the cases where it improved formatting. Ruff introduced the new heuristic of preserving the indent. We believe it's a good compromise that improves formatting but minimizes disruption for users.

### [Blank lines at the start of a block](#blank-lines-at-the-start-of-a-block)

Black 24 and newer allows blank lines at the start of a block, where Ruff always removes them:

```
# Black
if x:

  a = 123

# Ruff
if x:
  a = 123
```

Currently, we are concerned that allowing blank lines at the start of a block leads [to unintentional blank lines when refactoring or moving code](https://github.com/astral-sh/ruff/issues/8893#issuecomment-1867259744). However, we will consider adopting Black's formatting at a later point with an improved heuristic. The style change is tracked in [#9745](https://github.com/astral-sh/ruff/issues/9745).

### [F-strings](#f-strings)

Ruff formats expression parts in f-strings whereas Black does not:

```
# Input
f'test{inner   + "nested_string"} including math {5 ** 3 + 10}'

# Black
f'test{inner   + "nested_string"} including math {5 ** 3 + 10}'

# Ruff
f"test{inner + 'nested_string'} including math {5**3 + 10}"
```

For more details on the formatting style, refer to the [f-string formatting](../#f-string-formatting) section.

### [Implicit concatenated strings](#implicit-concatenated-strings)

Ruff merges implicitly concatenated strings if the entire string fits on a single line:

```
# Input
def test(max_history):
    raise argparse.ArgumentTypeError(
        f"The value of `--max-history {max_history}` " f"is not a positive integer."
    )

# Black
def test(max_history):
    raise argparse.ArgumentTypeError(
        f"The value of `--max-history {max_history}` " f"is not a positive integer."
    )

# Ruff
def test(max_history):
    raise argparse.ArgumentTypeError(
        f"The value of `--max-history {max_history}` is not a positive integer."
    )
```

Black's unstable style applies the same formatting.

There are few rare cases where Ruff can't merge the implicitly concatenated strings automatically. In those cases, Ruff preserves if the implicit concatenated strings are formatted over multiple lines:

```
# Input
a = (
    r"aaaaaaa"
    "bbbbbbbbbbbb"
)

# Black
a = r"aaaaaaa" "bbbbbbbbbbbb"

# Ruff
a = (
    r"aaaaaaa"
    "bbbbbbbbbbbb"
)
```

This ensures compatibility with `ISC001` ([#8272](https://github.com/astral-sh/ruff/issues/8272)).

### [`assert` statements](#assert-statements)

Unlike Black, Ruff prefers breaking the message over breaking the assertion, similar to how both Ruff and Black prefer breaking the assignment value over breaking the assignment target:

```
# Input
assert (
    len(policy_types) >= priority + num_duplicates
), f"This tests needs at least {priority+num_duplicates} many types."


# Black
assert (
    len(policy_types) >= priority + num_duplicates
), f"This tests needs at least {priority+num_duplicates} many types."

# Ruff
assert len(policy_types) >= priority + num_duplicates, (
    f"This tests needs at least {priority + num_duplicates} many types."
)
```

### [`global` and `nonlocal` names are broken across multiple lines by continuations](#global-and-nonlocal-names-are-broken-across-multiple-lines-by-continuations)

If a `global` or `nonlocal` statement includes multiple names, and exceeds the configured line width, Ruff will break them across multiple lines using continuations:

```
# Input
global analyze_featuremap_layer, analyze_featuremapcompression_layer, analyze_latencies_post, analyze_motions_layer, analyze_size_model

# Ruff
global \
    analyze_featuremap_layer, \
    analyze_featuremapcompression_layer, \
    analyze_latencies_post, \
    analyze_motions_layer, \
    analyze_size_model
```

### [Trailing own-line comments on imports are not moved to the next line](#trailing-own-line-comments-on-imports-are-not-moved-to-the-next-line)

Black enforces a single empty line between an import and a trailing own-line comment. Ruff leaves such comments in-place:

```
# Input
import os
# comment

import sys

# Black
import os

# comment

import sys

# Ruff
import os
# comment

import sys
```

### [Parentheses around awaited collections are not preserved](#parentheses-around-awaited-collections-are-not-preserved)

Black preserves parentheses around awaited collections:

```
await ([1, 2, 3])
```

Ruff will instead remove them:

```
await [1, 2, 3]
```

This is more consistent to the formatting of other awaited expressions: Ruff and Black both remove parentheses around, e.g., `await (1)`, only retaining them when syntactically required, as in, e.g., `await (x := 1)`.

### [Implicit string concatenations in attribute accesses](#implicit-string-concatenations-in-attribute-accesses)

Given the following unformatted code:

```
print("aaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb))
```

Internally, Black's logic will first expand the outermost `print` call:

```
print(
    "aaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb)
)
```

Since the argument is *still* too long, Black will then split on the operator with the highest split precedence. In this case, Black splits on the implicit string concatenation, to produce the following Black-formatted code:

```
print(
    "aaaaaaaaaaaaaaaa"
    "aaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb)
)
```

Ruff gives implicit concatenations a "lower" priority when breaking lines. As a result, Ruff would instead format the above as:

```
print(
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb)
)
```

In general, Black splits implicit string concatenations over multiple lines more often than Ruff, even if those concatenations *can* fit on a single line. Ruff instead avoids splitting such concatenations unless doing so is necessary to fit within the configured line width.

### [Own-line comments on expressions don't cause the expression to expand](#own-line-comments-on-expressions-dont-cause-the-expression-to-expand)

Given an expression like:

```
(
    # A comment in the middle
    some_example_var and some_example_var not in some_example_var
)
```

Black associates the comment with `some_example_var`, thus splitting it over two lines:

```
(
    # A comment in the middle
    some_example_var
    and some_example_var not in some_example_var
)
```

Ruff will instead associate the comment with the entire boolean expression, thus preserving the initial formatting:

```
(
    # A comment in the middle
    some_example_var and some_example_var not in some_example_var
)
```

### [Tuples are parenthesized when expanded](#tuples-are-parenthesized-when-expanded)

Ruff tends towards parenthesizing tuples (with a few exceptions), while Black tends to remove tuple parentheses more often.

In particular, Ruff will always insert parentheses around tuples that expand over multiple lines:

```
# Input
(a, b), (c, d,)

# Black
(a, b), (
    c,
    d,
)

# Ruff
(
    (a, b),
    (
        c,
        d,
    ),
)
```

There's one exception here. In `for` loops, both Ruff and Black will avoid inserting unnecessary parentheses:

```
# Input
for a, [b, d,] in c:
    pass

# Black
for a, [
    b,
    d,
] in c:
    pass

# Ruff
for a, [
    b,
    d,
] in c:
    pass
```

### [Single-element tuples are always parenthesized](#single-element-tuples-are-always-parenthesized)

Ruff always inserts parentheses around single-element tuples, while Black will omit them in some cases:

```
# Input
(a, b),

# Black
(a, b),

# Ruff
((a, b),)
```

Adding parentheses around single-element tuples adds visual distinction and helps avoid "accidental" tuples created by extraneous trailing commas (see, e.g., [#17181](https://github.com/django/django/pull/17181)).

### [Parentheses around call-chain assignment values are not preserved](#parentheses-around-call-chain-assignment-values-are-not-preserved)

Given:

```
def update_emission_strength():
    (
        get_rgbw_emission_node_tree(self)
        .nodes["Emission"]
        .inputs["Strength"]
        .default_value
    ) = (self.emission_strength * 2)
```

Black will preserve the parentheses in `(self.emission_strength * 2)`, whereas Ruff will remove them.

Both Black and Ruff remove such parentheses in simpler assignments, like:

```
# Input
def update_emission_strength():
    value = (self.emission_strength * 2)

# Black
def update_emission_strength():
    value = self.emission_strength * 2

# Ruff
def update_emission_strength():
    value = self.emission_strength * 2
```

### [Call chain calls break differently in some cases](#call-chain-calls-break-differently-in-some-cases)

Black occasionally breaks call chains differently than Ruff; in particular, Black occasionally expands the arguments for the last call in the chain, as in:

```
# Input
df.drop(
    columns=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
).drop_duplicates().rename(
    columns={
        "a": "a",
    }
).to_csv(path / "aaaaaa.csv", index=False)

# Black
df.drop(
    columns=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
).drop_duplicates().rename(
    columns={
        "a": "a",
    }
).to_csv(
    path / "aaaaaa.csv", index=False
)

# Ruff
df.drop(
    columns=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
).drop_duplicates().rename(
    columns={
        "a": "a",
    }
).to_csv(path / "aaaaaa.csv", index=False)
```

Ruff will only expand the arguments if doing so is necessary to fit within the configured line width.

Note that Black does not apply this last-call argument breaking universally. For example, both Black and Ruff will format the following identically:

```
# Input
df.drop(
    columns=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
).drop_duplicates(a).rename(
    columns={
        "a": "a",
    }
).to_csv(
    path / "aaaaaa.csv", index=False
).other(a)

# Black
df.drop(columns=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]).drop_duplicates(a).rename(
    columns={
        "a": "a",
    }
).to_csv(path / "aaaaaa.csv", index=False).other(a)

# Ruff
df.drop(columns=["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]).drop_duplicates(a).rename(
    columns={
        "a": "a",
    }
).to_csv(path / "aaaaaa.csv", index=False).other(a)
```

Similarly, in some cases, Ruff will collapse composite binary expressions more aggressively than Black, if doing so allows the expression to fit within the configured line width:

```
# Black
assert AAAAAAAAAAAAAAAAAAAAAA.bbbbbb.fooo(
    aaaaaaaaaaaa=aaaaaaaaaaaa
).ccccc() == (len(aaaaaaaaaa) + 1) * fooooooooooo * (
    foooooo + 1
) * foooooo * len(
    list(foo(bar(4, foo), foo))
)

# Ruff
assert AAAAAAAAAAAAAAAAAAAAAA.bbbbbb.fooo(
    aaaaaaaaaaaa=aaaaaaaaaaaa
).ccccc() == (len(aaaaaaaaaa) + 1) * fooooooooooo * (
    foooooo + 1
) * foooooo * len(list(foo(bar(4, foo), foo)))
```

### [Single `with` item targeting Python 3.8 or older](#single-with-item-targeting-python-38-or-older)

Unlike Black, Ruff uses the same layout for `with` statements with a single context manager as it does for `while`, `if` and other compound statements:

```
# Input
def run(data_path, model_uri):
    with pyspark.sql.SparkSession.builder.config(
        key="spark.python.worker.reuse", value=True
    ).config(key="spark.ui.enabled", value=False).master(
        "local-cluster[2, 1, 1024]"
    ).getOrCreate():
        # ignore spark log output
        spark.sparkContext.setLogLevel("OFF")
        print(score_model(spark, data_path, model_uri))

# Black
def run(data_path, model_uri):
    with pyspark.sql.SparkSession.builder.config(
        key="spark.python.worker.reuse", value=True
    ).config(key="spark.ui.enabled", value=False).master(
        "local-cluster[2, 1, 1024]"
    ).getOrCreate():
        # ignore spark log output
        spark.sparkContext.setLogLevel("OFF")
        print(score_model(spark, data_path, model_uri))

# Ruff
def run(data_path, model_uri):
    with (
        pyspark.sql.SparkSession.builder.config(
            key="spark.python.worker.reuse", value=True
        )
        .config(key="spark.ui.enabled", value=False)
        .master("local-cluster[2, 1, 1024]")
        .getOrCreate()
    ):
        # ignore spark log output
        spark.sparkContext.setLogLevel("OFF")
        print(score_model(spark, data_path, model_uri))
```

Ruff's formatting matches the formatting of other compound statements:

```
def test():
    if (
        pyspark.sql.SparkSession.builder.config(
            key="spark.python.worker.reuse", value=True
        )
        .config(key="spark.ui.enabled", value=False)
        .master("local-cluster[2, 1, 1024]")
        .getOrCreate()
    ):
        # ignore spark log output
        spark.sparkContext.setLogLevel("OFF")
        print(score_model(spark, data_path, model_uri))
```

### [The last context manager in a `with` statement may be collapsed onto a single line](#the-last-context-manager-in-a-with-statement-may-be-collapsed-onto-a-single-line)

When using a `with` statement with multiple unparenthesized context managers, Ruff may collapse the last context manager onto a single line, if doing so allows the `with` statement to fit within the configured line width.

Black, meanwhile, tends to break the last context manager slightly differently, as in the following example:

```
# Black
with tempfile.TemporaryDirectory() as d1:
    symlink_path = Path(d1).joinpath("testsymlink")
    with tempfile.TemporaryDirectory(dir=d1) as d2, tempfile.TemporaryDirectory(
        dir=d1
    ) as d4, tempfile.TemporaryDirectory(dir=d2) as d3, tempfile.NamedTemporaryFile(
        dir=d4
    ) as source_file, tempfile.NamedTemporaryFile(
        dir=d3
    ) as lock_file:
        pass

# Ruff
with tempfile.TemporaryDirectory() as d1:
    symlink_path = Path(d1).joinpath("testsymlink")
    with tempfile.TemporaryDirectory(dir=d1) as d2, tempfile.TemporaryDirectory(
        dir=d1
    ) as d4, tempfile.TemporaryDirectory(dir=d2) as d3, tempfile.NamedTemporaryFile(
        dir=d4
    ) as source_file, tempfile.NamedTemporaryFile(dir=d3) as lock_file:
        pass
```

When targeting Python 3.9 or newer, parentheses will be inserted around the context managers to allow for clearer breaks across multiple lines, as in:

```
with tempfile.TemporaryDirectory() as d1:
    symlink_path = Path(d1).joinpath("testsymlink")
    with (
        tempfile.TemporaryDirectory(dir=d1) as d2,
        tempfile.TemporaryDirectory(dir=d1) as d4,
        tempfile.TemporaryDirectory(dir=d2) as d3,
        tempfile.NamedTemporaryFile(dir=d4) as source_file,
        tempfile.NamedTemporaryFile(dir=d3) as lock_file,
    ):
        pass
```

### [Preserving parentheses around single-element lists](#preserving-parentheses-around-single-element-lists)

Ruff preserves at least one set of parentheses around list elements, even if the list only contains a single element. The Black 2025 style or newer, on the other hand, removes the parentheses for single-element lists if they aren't multiline and doing so does not change semantics:

```
# Input
items = [(True)]
items = [(((((True)))))]
items = {(123)}

# Black
items = [True]
items = [True]
items = {123}

# Ruff
items = [(True)]
items = [(True)]
items = {(123)}
```

### [Long lambda expressions](#long-lambda-expressions)

Both Ruff and Black will keep lambda parameters on a single line. However, if the body expression exceeds the configured line length, Ruff will additionally add parentheses around the lambda body and break it over multiple lines:

```
# Ruff
def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: (
            baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs)
        ),
        # More complex expressions also trigger wrapping
        g=lambda self, *args, **kwargs: (
            baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
        ),
    )

# Black
def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        ),
        # More complex expressions also trigger wrapping
        g=lambda self, *args, **kwargs: baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        )
        + 1,
    )
```

### [Require single blank line between function and decorated class in stubs](#require-single-blank-line-between-function-and-decorated-class-in-stubs)

In stub files, Ruff enforces a single blank line between functions and decorated classes, whereas Black does not.

```
# Input and Ruff
def hello(): ...

@lambda _, /: _
class A: ...

# Black
def foo(): ...
@decorator
class Bar: ...
```

### [Avoiding additional space around escaped quote in triple-quoted docstring](#avoiding-additional-space-around-escaped-quote-in-triple-quoted-docstring)

In certain cases it is required to maintain a trailing space in a triple-quoted docstring in order to avoid escaping a quote or ending with too many quotes. However, if the last quote is escaped this is not necessary, and Ruff respects this. Black, however, adds the extra space.

```
# Input and Ruff
class Sample:
    """Hello "World\""""

    def __init__(self, name):
        self.name = name

# Black
class Sample:
    """Hello "World\" """

    def __init__(self, name):
        self.name = name
```

---

## File: site/ruff/installation/index.md

# [Installing Ruff](#installing-ruff)

Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI.

Ruff can be invoked directly with [`uvx`](https://docs.astral.sh/uv/):

```
uvx ruff@0.17.0 check   # Lint all files in the current directory.
uvx ruff@0.17.0 format  # Format all files in the current directory.
```

Or installed with `uv` (recommended), `pip`, or `pipx`:

```
$ # Install Ruff globally.
$ uv tool install ruff@latest

$ # Or add Ruff to your project.
$ uv add --dev ruff

$ # With pip.
$ pip install ruff

$ # With pipx.
$ pipx install ruff
```

Once installed, you can run Ruff from the command line:

```
$ ruff check   # Lint all files in the current directory.
$ ruff format  # Format all files in the current directory.
```

Starting with version `0.5.0`, Ruff can also be installed with our standalone installers:

```
$ # On macOS and Linux.
$ curl -LsSf https://astral.sh/ruff/install.sh | sh

$ # On Windows.
$ powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"

$ # For a specific version.
$ curl -LsSf https://astral.sh/ruff/0.5.0/install.sh | sh
$ powershell -c "irm https://astral.sh/ruff/0.5.0/install.ps1 | iex"
```

For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff`](https://formulae.brew.sh/formula/ruff) on Homebrew:

```
$ brew install ruff
```

For **Conda** users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:

```
$ conda install -c conda-forge ruff
```

For **pkgx** users, Ruff is also available as [`ruff`](https://pkgx.dev/pkgs/github.com/charliermarsh/ruff/) on the `pkgx` registry:

```
$ pkgx install ruff
```

For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.org/packages/extra/x86_64/ruff/) on the official repositories:

```
$ pacman -S ruff
```

For **Alpine** users, Ruff is also available as [`ruff`](https://pkgs.alpinelinux.org/package/edge/community/x86_64/ruff) on the community repositories:

```
$ apk add ruff
```

For **openSUSE Tumbleweed** users, Ruff is also available in the distribution repository:

```
$ sudo zypper install python3-ruff
```

On **Docker**, it is published as `ghcr.io/astral-sh/ruff`, tagged for each release and `latest` for the latest release.

```
$ docker run -v .:/io --rm ghcr.io/astral-sh/ruff check
$ docker run -v .:/io --rm ghcr.io/astral-sh/ruff:0.3.0 check

$ # Or, for Podman on SELinux.
$ docker run -v .:/io:Z --rm ghcr.io/astral-sh/ruff check
```

---

## File: site/ruff/integrations/index.md

# [Integrations](#integrations)

## [GitHub Actions](#github-actions)

GitHub Actions has everything you need to run Ruff out-of-the-box:

```
name: CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff
      # Update output format to enable automatic inline annotations.
      - name: Run Ruff
        run: ruff check --output-format=github .
```

Ruff can also be used as a GitHub Action via [`ruff-action`](https://github.com/astral-sh/ruff-action).

By default, `ruff-action` runs as a pass-fail test to ensure that a given repository doesn't contain any lint rule violations as per its [configuration](../configuration/). However, under-the-hood, `ruff-action` installs and runs `ruff` directly, so it can be used to execute any supported `ruff` command (e.g., `ruff check --fix`).

`ruff-action` supports all GitHub-hosted runners, and can be used with any published Ruff version (i.e., any version available on [PyPI](https://pypi.org/project/ruff/)).

To use `ruff-action`, create a file (e.g., `.github/workflows/ruff.yml`) inside your repository with:

```
name: Ruff
on: [ push, pull_request ]
jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v3
```

Alternatively, you can include `ruff-action` as a step in any other workflow file:

```
      - uses: astral-sh/ruff-action@v3
```

`ruff-action` accepts optional configuration parameters via `with:`, including:

- `version`: The Ruff version to install (default: latest).
- `args`: The command-line arguments to pass to Ruff (default: `"check"`).
- `src`: The source paths to pass to Ruff (default: `[".", "src"]`).

For example, to run `ruff check --select B ./src` using Ruff version `0.8.0`:

```
- uses: astral-sh/ruff-action@v3
  with:
    version: 0.8.0
    args: check --select B
    src: "./src"
```

## [GitLab CI/CD](#gitlab-cicd)

You can add the following configuration to `.gitlab-ci.yml` to run a `ruff format` in parallel with a `ruff check` compatible with GitLab's codequality report.

```
.base_ruff:
  stage: build
  interruptible: true
  image:
    name: ghcr.io/astral-sh/ruff:0.17.0-alpine
  before_script:
    - cd $CI_PROJECT_DIR
    - ruff --version

Ruff Check:
  extends: .base_ruff
  script:
    - ruff check --output-format=gitlab --output-file=code-quality-report.json
  artifacts:
    reports:
      codequality: $CI_PROJECT_DIR/code-quality-report.json

Ruff Format:
  extends: .base_ruff
  script:
    - ruff format --diff
```

## [pre-commit](#pre-commit)

Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit):

```
- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.17.0
  hooks:
    # Run the linter.
    - id: ruff-check
    # Run the formatter.
    - id: ruff-format
```

To enable lint fixes, add the `--fix` argument to the lint hook:

```
- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.17.0
  hooks:
    # Run the linter.
    - id: ruff-check
      args: [ --fix ]
    # Run the formatter.
    - id: ruff-format
```

To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed filetypes:

```
- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.17.0
  hooks:
    # Run the linter.
    - id: ruff-check
      types_or: [ python, pyi ]
      args: [ --fix ]
    # Run the formatter.
    - id: ruff-format
      types_or: [ python, pyi ]
```

When running with `--fix`, Ruff's lint hook should be placed *before* Ruff's formatter hook, and *before* Black, isort, and other formatting tools, as Ruff's fix behavior can output code changes that require reformatting.

When running without `--fix`, Ruff's formatter hook can be placed before or after Ruff's lint hook.

(As long as your Ruff configuration avoids any [linter-formatter incompatibilities](../formatter/#conflicting-lint-rules), `ruff format` should never introduce new lint errors, so it's safe to run Ruff's format hook *after* `ruff check --fix`.)

## [`mdformat`](#mdformat)

[mdformat](https://mdformat.readthedocs.io/en/stable/users/plugins.html#code-formatter-plugins) is capable of formatting code blocks within Markdown. The [`mdformat-ruff`](https://github.com/Freed-Wu/mdformat-ruff) plugin enables mdformat to format Python code blocks with Ruff.

## [Docker](#docker)

Ruff provides a distroless Docker image including the `ruff` binary. The following tags are published:

- `ruff:latest`
- `ruff:{major}.{minor}.{patch}`, e.g., `ruff:0.6.6`
- `ruff:{major}.{minor}`, e.g., `ruff:0.6` (the latest patch version)

In addition, ruff publishes the following images:

- Based on `alpine:3.20`:
- `ruff:alpine`
- `ruff:alpine3.20`
- Based on `debian:bookworm-slim`:
- `ruff:debian-slim`
- `ruff:bookworm-slim`
- Based on `buildpack-deps:bookworm`:
- `ruff:debian`
- `ruff:bookworm`

As with the distroless image, each image is published with ruff version tags as `ruff:{major}.{minor}.{patch}-{base}` and `ruff:{major}.{minor}-{base}`, e.g., `ruff:0.6.6-alpine`.