Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,45 @@ on:
release:
types: [published, edited]

permissions: {}

jobs:
build-and-publish-test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
python-version: "3.10"
- name: Load cached Poetry installation
uses: actions/cache@v5
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# This path assumes the workflow is run on an ubuntu runner
path: ~/.local
key: key-poetry-0
- uses: snok/install-poetry@v1
python-version: "3.11"
- uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2
- name: Publish package
shell: bash
run: |
# TODO: Switch to trusted publishing
run: | # zizmor: ignore[use-trusted-publishing]
poetry config repositories.custom 'https://test.pypi.org/legacy/'
poetry config pypi-token.custom ${{ secrets.TEST_PYPI_TOKEN }}
poetry publish --build --no-interaction --repository custom
build-and-publish:
needs: build-and-publish-test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
python-version: "3.10"
- name: Load cached Poetry installation
uses: actions/cache@v5
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# This path assumes the workflow is run on an ubuntu runner
path: ~/.local
key: key-poetry-0
- uses: snok/install-poetry@v1
python-version: "3.11"
- uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2
- name: Publish package
shell: bash
run: |
# TODO: Switch to trusted publishing
run: | # zizmor: ignore[use-trusted-publishing]
echo "Using default repository (PyPi)"
poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}
poetry publish --build --no-interaction
40 changes: 26 additions & 14 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,66 @@ on:
branches:
- main

permissions: {}

jobs:
linting:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
- uses: actions/cache@v4
python-version: "3.11"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: cache-venv
with:
path: .venv
key: venv-6 # increment to reset
key: venv-7 # increment to reset
- run: |
python -m venv .venv --upgrade-deps
source .venv/bin/activate
pip install pre-commit
if: steps.cache-venv.outputs.cache-hit != 'true'
- uses: actions/cache@v4
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: pre-commit-cache
with:
path: ~/.cache/pre-commit
key: ${{ hashFiles('**/pre-commit-config.yaml') }}-5
key: ${{ hashFiles('**/pre-commit-config.yaml') }}-6
- run: |
source .venv/bin/activate
pre-commit run --all-files

test:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
id: setup-python
with:
python-version: "${{ matrix.python-version }}"
- uses: actions/cache@v5
# FIXME: Remove once 3.15 is released
allow-prereleases: true
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: poetry-cache
with:
path: |
~/.local
.venv
key: ${{ hashFiles('**/poetry.lock') }}-${{ steps.setup-python.outputs.python-version }}-9
key: ${{ hashFiles('**/poetry.lock') }}-${{ steps.setup-python.outputs.python-version }}-10
- name: Install Poetry
uses: snok/install-poetry@v1
uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2
with:
virtualenvs-create: false
version: latest
Expand All @@ -73,7 +85,7 @@ jobs:
coverage run -m pytest tests
coverage xml
coverage report
- uses: codecov/codecov-action@v5
- uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
files: ./coverage.xml
fail_ci_if_error: true
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# https://docs.zizmor.sh/integrations/#github-actions
name: Zizmor (GitHub Actions Security)

on:
push:
paths:
- '.github/workflows/**'
pull_request:
paths:
- '.github/workflows/**'

permissions: {}

concurrency:
group: zizmor-${{ github.ref }}
cancel-in-progress: true

jobs:
zizmor:
runs-on: ubuntu-latest
permissions:
contents: read # Required for private repos
actions: read # Required for private repos
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run zizmor
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
advanced-security: false
16 changes: 10 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 25.12.0
rev: 26.5.1
hooks:
- id: black
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand All @@ -19,7 +19,7 @@ repos:
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/pycqa/flake8
rev: 7.3.0
rev: 7.4.0
hooks:
- id: flake8
additional_dependencies: [
Expand All @@ -31,19 +31,19 @@ repos:
'flake8-pytest-style',
'flake8-docstrings',
'flake8-printf-formatting',
'flake8-type-checking==2.9.1',
'flake8-type-checking==3.2.0',
]
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
args: [ "--py39-plus", '--keep-runtime-typing' ]
args: [ "--py311-plus", '--keep-runtime-typing' ]
- repo: https://github.com/pycqa/isort
rev: 7.0.0
rev: 9.0.1
hooks:
- id: isort
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.1
rev: v2.3.1
hooks:
- id: mypy
additional_dependencies:
Expand All @@ -55,3 +55,7 @@ repos:
always_run: true
pass_filenames: false
args: ['-p', 'flake8_type_checking']
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.30.1
hooks:
- id: zizmor
94 changes: 91 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Lets you know which imports to move in or out of
[type-checking](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING) blocks.

The plugin assumes that the imports you only use for type hinting
*are not* required at runtime. When imports aren't strictly required at runtime, it means we can guard them.
*are not* required at runtime. When imports aren't strictly required at runtime, it means we can guard them (or with Python
3.15+ we can make them lazy).

Guarding imports provides 3 major benefits:

Expand Down Expand Up @@ -39,6 +40,14 @@ if TYPE_CHECKING:
x: "pandas.DataFrame"
```

Or with Python 3.15+ it can also become this:

```python
lazy import pandas

x: pandas.DataFrame
```

More examples can be found in the [examples](#examples) section.

<br>
Expand Down Expand Up @@ -118,6 +127,42 @@ enable-extensions = TC, TC2 # or TC1

If you are unsure which `TC` range to pick, see the [rationale](#rationale) for more info.

## Lazy imports

While the plugin recognizes and supports lazy imports, it currently
offers no rule like TC004 that tell you to turn a lazy import back
into a regular import. This is mostly because the only time a lazy
import for sure shouldn't be lazy is when it is directly accessed
in the global scope.

[`flake8-lazy`](https://flake8-lazy.readthedocs.io/en/latest/)
already exists and helps flag exactly those cases and others
so `flake8-type-checking` will not be able to provide anything that
plugin does not already give you, beyond TC001, TC002 and TC003
telling you which imports could benefit from being lazy in addition
to the ones `flake8-lazy` tells you about.

Runtime introspection of `__annotations__` is a corner case where
a lazy import might resolve during module import time, but since it
is not obvious at which point during the module's execution that
introspection happens, a lazy import could still help break import
cycles, so there is no obvious rule for when a lazy import is
redundant.

For the same reason `flake8-type-checking` will never recommend
replacing an existing type checking block with a lazy import,
since there is no way to guarantee that the two are equivalent,
since lazy imports will always get triggered when `__annotations__`
are introspected, regardless of whether a format like
`Format.FORWARDREF` is used. So lazy imports can still result in
additional overhead compared to type checking blocks in some
scenarios. It is not a trade-off the plugin can make for you.

By default `flake8-type-checking` will never recommend turning
imports into lazy imports, see the [configuration](#configuration)
for how to enable Python 3.15+ mode, where some of the error
messages will reference lazy imports as an alternative solution.

## Installation

```shell
Expand All @@ -132,17 +177,39 @@ These options are configurable, and can be set in your flake8 config.

If your code is targeting Python 3.14+ you no longer need to wrap
annotations in quotes or add a future import. So in this case it's
recommended to add `type-checking-p314plus = true` to your flake8
recommended to add `type-checking-py314plus = true` to your flake8
configuration and select the `TC1` rules.

- **setting name**: `type-checking-p314plus`
- **setting name**: `type-checking-py314plus`
- **type**: `bool`

```ini
[flake8]
type-checking-py314plus = true # default false
```

## Python 3.15+

If your code is targeting Python 3.15+ you may want to use lazy
imports instead of moving them into a type checking block. So
TC001, TC002 and TC003 contain additional text to help guide
users to this alternate solution. This setting also implies
`type-checking-py314-plus`.

Lazy imports are detected and special-cased, even without enabling
this setting and are treated as a valid alternative to a type
checking block, regardless of whether the target version will
actually treat them as such, so this setting currently only
changes the error messages users will see.

- **setting name**: `type-checking-p315plus`
- **type**: `bool`

```ini
[flake8]
type-checking-py315plus = true # default false
```

### Typing modules

If you re-export `typing` or `typing_extensions` members from a compatibility
Expand Down Expand Up @@ -194,6 +261,27 @@ imports that *can* be moved.
type-checking-strict = true # default false
```

### Report typing-only uses of `__lazy_modules__`

The plugin, by default, will never report TC00[1-3] errors
for imports covered by `__lazy_modules__`, since starting with
Python 3.15 these will work the same as `lazy import ..` and
`lazy from .. import ..` statements.

If you want to preserve the import time reduction for older Python
versions, you may not want this and instead want to move the import
into a type checking block, you can tell the plugin to ignore
`__lazy_modules__` declarations, which will allow these imports
to report TC00[1-3] errors.

- **setting name**: `type-checking-ignore-dunder-lazy-modules`
- **type**: `bool`

```ini
[flake8]
type-checking-ignore-dunder-lazy-modules = true # default false
```

### Force `from __future__ import annotations` import

The plugin, by default, will only report a TC100 error, if annotations
Expand Down
Loading
Loading