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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
strategy:
matrix:
python-version: [
"3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"
"3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"
]
os: [ubuntu-latest, macos-latest, windows-latest]

Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
platform:
- runner: ubuntu-latest
target: x86_64
interpreters: &full-interpreters '["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"]'
interpreters: &full-interpreters '["3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"]'
- runner: ubuntu-latest
target: x86
interpreters: *full-interpreters
Expand All @@ -39,7 +39,7 @@ jobs:
# 3.14/3.14t.
- runner: ubuntu-latest
target: armv7
interpreters: &cross-interpreters '["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]'
interpreters: &cross-interpreters '["3.11", "3.12", "3.13", "3.14", "3.14t"]'
- runner: ubuntu-latest
target: s390x
interpreters: *cross-interpreters
Expand Down Expand Up @@ -112,7 +112,7 @@ jobs:
platform:
- runner: windows-latest
target: x64
interpreters: &interpreters '["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]'
interpreters: &interpreters '["3.11", "3.12", "3.13", "3.14", "3.15"]'
free-threaded-interpreters: &free-threaded-interpreters '["3.14t", "3.15t"]'
- runner: windows-latest
target: x86
Expand Down Expand Up @@ -171,7 +171,6 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: |
3.10
3.11
3.12
3.13
Expand All @@ -182,7 +181,7 @@ jobs:
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --interpreter 3.10 3.11 3.12 3.13 3.14 3.15
args: --release --out dist --interpreter 3.11 3.12 3.13 3.14 3.15
sccache: 'true'

# setup-python installs the free-threaded interpreter's binary under the
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ latest
------

* Support multiple packages in ``ImportGraph.nominate_cycle_breakers``.
* Drop support for Python 3.10.

3.18 (2026-10-26)
-----------------
Expand Down
72 changes: 72 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,78 @@ It's a good idea to run ``just full-check`` before getting a review.
This will run linters, docs build and tests under every supported Python version.
You can automatically fix many lint errors using ``just autofix`` (or ``just autofix-python`` and ``just autofix-rust`` separately).

Adding support for a new Python version
========================================

1. In ``pyproject.toml``, add ``3.x`` to the ``Programming Language :: Python :: 3.x``
classifiers.
2. In ``.github/workflows/main.yml``, add ``"3.x"`` and ``"3.xt"`` to the
``test`` job's ``python-version`` matrix.
3. In ``.github/workflows/release.yml``, add ``"3.x"`` and ``"3.xt"`` to the shared
``linux`` and ``musllinux`` interpreter lists. For ``windows``, add ``"3.x"`` to
the regular interpreter lists (including the ARM64 literal list) and ``"3.xt"``
to the free-threaded interpreter lists. For ``macos``, add ``3.x`` to the regular
``actions/setup-python`` list and matching ``--interpreter`` arguments, and ``3.xt``
to the corresponding free-threaded step's list and arguments. Keep the regular and
free-threaded installations separate on Windows/macOS to avoid binary shadowing.
4. Push a branch with this change and trigger the release workflow manually, without pushing
a tag, to see which targets can actually build the new version::

gh workflow run release.yml --ref <your-branch>

Then watch it with ``gh run watch`` or in the Actions tab.
5. For any ``linux``/``musllinux`` job that fails because the build image doesn't have the
new interpreter yet, split that target out of the shared interpreter list it was using
(e.g. ``&full-interpreters``/``&cross-interpreters``) into its own list without the new
version, following the pattern already used for targets that lag behind (see the comments
above the ``linux`` and ``musllinux`` matrices for the current state). To check directly
instead of waiting for a failed build, look up the image's available tags:

- ``linux`` job, native targets (x86_64, x86, aarch64): `quay.io/pypa manylinux2014 tags
<https://quay.io/repository/pypa/manylinux2014_x86_64?tab=tags>`_
- ``linux`` job, cross targets (armv7, s390x, ppc64le): `rust-cross manylinux2014-cross
tags <https://github.com/rust-cross/manylinux-cross/pkgs/container/manylinux2014-cross>`_
- ``musllinux`` job (all targets): `rust-cross rust-musl-cross tags
<https://github.com/rust-cross/rust-musl-cross/pkgs/container/rust-musl-cross>`_
- ``windows``/``macos`` jobs use ``actions/setup-python``, which installs from
`actions/python-versions <https://github.com/actions/python-versions/blob/main/versions-manifest.json>`_
- these are normally available from Python's own release day, including prereleases
(``allow-prereleases: true`` is already set).
6. For each target excluded in step 5, `open a GitHub issue
<https://github.com/python-grimp/grimp/issues/new>`_ to track adding it once the build
image catches up (e.g. "Add Python 3.x wheels for musllinux", one issue per image/family
is fine if several targets share it). Add a comment directly above the relevant
interpreter list in ``release.yml`` referencing the issue number, e.g.::

# ghcr.io/rust-cross/rust-musl-cross doesn't yet bundle Python 3.x.
# See https://github.com/python-grimp/grimp/issues/<n>.

This is the only thing that makes the gap actionable later - without an issue number
there's nothing to revisit the exclusion when the image is updated.
7. In ``justfile``, add a ``just test-python 3.x`` and ``3.xt`` lines to the ``test-all``
recipe.
8. In ``CHANGELOG.rst``, add an entry under ``latest``: ``* Officially support Python 3.x.``
If step 5 excluded any targets, add a second entry noting which ones, why, and linking the
issue(s) from step 6 (see existing ``CHANGELOG.rst`` entries about missing 3.15 wheels for
the expected wording).
9. Run ``just full-check`` to confirm everything passes.

Dropping support for a Python version
======================================

1. In ``pyproject.toml``, update ``requires-python`` and remove the ``Programming Language ::
Python :: 3.x`` classifier.
2. In ``.github/workflows/main.yml``, remove ``"3.x"`` (and ``"3.xt"`` if present) from the
``test`` job's ``python-version`` matrix.
3. In ``.github/workflows/release.yml``, remove ``"3.x"`` (and ``"3.xt"``) from every
interpreter list it appears in.
4. In ``justfile``, remove the ``just test-python 3.x`` (and ``3.xt``) line(s) from the
``test-all`` recipe.
5. In ``CHANGELOG.rst``, add an entry under ``latest``: ``* Drop support for Python 3.x.``
6. Run ``just modernize-python 3x`` with the new lowest version (e.g. ``just modernize-python 311``).
7. Remove any code paths or tests that exist solely to support the dropped version.
8. Run ``just full-check`` to confirm everything passes.

Benchmarking
============

Expand Down
8 changes: 7 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ test-python $UV_PYTHON=env("UV_PYTHON", ""):
# Runs tests under all supported Python versions, plus Rust.
[group('testing')]
test-all:
just test-python 3.10
just test-python 3.11
just test-python 3.12
just test-python 3.13
Expand Down Expand Up @@ -118,6 +117,13 @@ lint:
@echo ''
@echo '👍 {{GREEN}} Linting all good.{{NORMAL}}'

# Modernize Python code. (E.g. just modernize-python 311)
[group('linting')]
modernize-python MIN_VERSION:
@find docs src tests -name "*.py" -not -path "tests/assets/*" -exec uv run pyupgrade --py{{MIN_VERSION}}-plus --exit-zero-even-if-changed {} +
@just autofix-python
@just format-python

# Build docs.
[group('docs')]
build-docs:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description = "Builds a queryable graph of the imports within one or more Python
authors = [
{name = "David Seddon", email = "david@seddonym.me"},
]
requires-python = ">=3.10"
requires-python = ">=3.11"
dependencies = []
classifiers = [
"Development Status :: 5 - Production/Stable",
Expand All @@ -24,7 +24,6 @@ classifiers = [
"Operating System :: POSIX",
"Operating System :: Microsoft :: Windows",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand Down Expand Up @@ -60,6 +59,7 @@ dev = [
"requests==2.32.3",
"sqlalchemy==2.0.35",
"google-cloud-audit-log==0.3.0",
"pyupgrade>=3.21.2",
]
docs = [
"sphinx>=7.4.7",
Expand Down
3 changes: 2 additions & 1 deletion src/grimp/application/ports/timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import abc
from types import TracebackType
from typing import Self


class Timer(abc.ABC):
Expand All @@ -22,7 +23,7 @@ def __init__(self) -> None:
# We use a stack so context managers can be nested.
self._start_stack: list[float] = []

def __enter__(self) -> Timer:
def __enter__(self) -> Self:
self._start_stack.append(self.get_current_time())
return self

Expand Down
2 changes: 1 addition & 1 deletion tests/adaptors/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def walk(self, directory_name):

def _walk_contents(
self, directory_contents: dict[str, Any], containing_directory: str
) -> Generator[tuple[str, list[str], list[str]], None, None]:
) -> Generator[tuple[str, list[str], list[str]]]:
directories = []
files = []
for key, value in directory_contents.items():
Expand Down
3 changes: 1 addition & 2 deletions tests/unit/application/graph/test_manipulation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import re
from typing import TypedDict
from typing import NotRequired, TypedDict

import pytest
from typing_extensions import NotRequired # Can move to typing once drop support for 3.10.

from grimp.application.graph import ImportGraph
from grimp.exceptions import ModuleNotPresent
Expand Down
Loading
Loading