Skip to content

Commit 7b5fb68

Browse files
authored
feat: Add Python 3.14 support (#552)
* style: apply ruff 0.15 import sorting and lint auto-fixes Ruff 0.13+ changed isort behavior with force-single-line, requiring import reordering. Also applies C420 (dict.fromkeys) and minor formatting. * feat: add Python 3.14 support and make it the default - Add Python 3.14 to CI test matrix and classifiers - Update mypy python_version and ruff target-version to 3.14 - Update release workflow and ReadTheDocs to Python 3.14 - Remove stale Python 3.7 pydot step from CI - Add UP037 and UP042 to ruff ignore (require future annotations / 3.11+) - Remove stale UP038 ignore (rule removed from ruff) - Add match parameter to pytest.warns (PT030) - Upgrade ruff 0.8.1 -> 0.15.0, mypy 1.4.1 -> 1.14.1, watchfiles 0.24.0 -> 1.1.1 * fix(ci): upgrade pillow 11.0.0 -> 11.3.0 for Python 3.14 wheel support Pillow 11.0.0 has no pre-built wheel for Python 3.14 and fails to compile from source in CI due to missing libjpeg headers. * fix(ci): upgrade pytest-cov/coverage for Python 3.14 annotation coverage coverage.py < 7.6.10 incorrectly reports class annotations as uncovered on Python 3.14 due to PEP 649 deferred evaluation (coveragepy#1908). Pin pytest-cov >= 6.0.0 on Python >= 3.9 to pull coverage >= 7.6.10.
1 parent 790d38f commit 7b5fb68

33 files changed

Lines changed: 622 additions & 250 deletions

.github/workflows/python-package.yml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
18+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1919

2020
steps:
2121
- uses: actions/checkout@v4
@@ -33,15 +33,11 @@ jobs:
3333
cache-suffix: "python${{ matrix.python-version }}"
3434
- name: Install the project
3535
run: uv sync --all-extras --dev
36-
- name: Install old pydot for 3.7 only
37-
if: matrix.python-version == 3.7
38-
run: |
39-
uv pip install pydot==2.0.0
4036
#----------------------------------------------
4137
# run ruff
4238
#----------------------------------------------
4339
- name: Linter with ruff
44-
if: matrix.python-version == 3.13
40+
if: matrix.python-version == 3.14
4541
run: |
4642
uv run ruff check .
4743
uv run ruff format --check .
@@ -57,7 +53,7 @@ jobs:
5753
#----------------------------------------------
5854
- name: Upload coverage to Codecov
5955
uses: codecov/codecov-action@v4
60-
if: matrix.python-version == 3.13
56+
if: matrix.python-version == 3.14
6157
with:
6258
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
6359
directory: .

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Setup Python
1919
uses: actions/setup-python@v5
2020
with:
21-
python-version: '3.13'
21+
python-version: '3.14'
2222

2323
- name: Setup Graphviz
2424
uses: ts-graphviz/setup-graphviz@v2

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version: 2
88
build:
99
os: "ubuntu-22.04"
1010
tools:
11-
python: "3.12"
11+
python: "3.14"
1212
apt_packages:
1313
- graphviz
1414
jobs:

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ uv run mypy statemachine/ tests/
7272

7373
## Code style
7474

75-
- **Formatter/Linter:** ruff (line length 99, target Python 3.13)
75+
- **Formatter/Linter:** ruff (line length 99, target Python 3.14)
7676
- **Rules:** pycodestyle, pyflakes, isort, pyupgrade, flake8-comprehensions, flake8-bugbear, flake8-pytest-style
7777
- **Imports:** single-line, sorted by isort
7878
- **Docstrings:** Google convention

conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
@pytest.fixture(autouse=True, scope="session")
88
def add_doctest_context(doctest_namespace): # noqa: PT004
9+
from statemachine.utils import run_async_from_sync
10+
911
from statemachine import State
1012
from statemachine import StateMachine
11-
from statemachine.utils import run_async_from_sync
1213

1314
class ContribAsyncio:
1415
"""

pyproject.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ classifiers = [
1717
"Programming Language :: Python :: 3.11",
1818
"Programming Language :: Python :: 3.12",
1919
"Programming Language :: Python :: 3.13",
20+
"Programming Language :: Python :: 3.14",
2021
"Programming Language :: Python :: 3.7",
2122
"Programming Language :: Python :: 3.8",
2223
"Programming Language :: Python :: 3.9",
@@ -37,7 +38,8 @@ dev = [
3738
"pre-commit",
3839
"mypy",
3940
"pytest",
40-
"pytest-cov",
41+
"pytest-cov >=6.0.0; python_version >='3.9'",
42+
"pytest-cov; python_version <'3.9'",
4143
"pytest-sugar >=1.0.0",
4244
"pytest-mock >=3.10.0",
4345
"pytest-benchmark >=4.0.0",
@@ -113,7 +115,7 @@ directory = "tmp/htmlcov"
113115
show_contexts = true
114116

115117
[tool.mypy]
116-
python_version = "3.13"
118+
python_version = "3.14"
117119
warn_return_any = true
118120
warn_unused_configs = true
119121
disable_error_code = "annotation-unchecked"
@@ -127,7 +129,7 @@ ignore_missing_imports = true
127129
src = ["statemachine"]
128130

129131
line-length = 99
130-
target-version = "py313"
132+
target-version = "py314"
131133

132134
# Exclude a variety of commonly ignored directories.
133135
exclude = [
@@ -166,7 +168,8 @@ select = [
166168
ignore = [
167169
"UP006", # `use-pep585-annotation` Requires Python3.9+
168170
"UP035", # `use-pep585-annotation` Requires Python3.9+
169-
"UP038", # `use-pep585-annotation` Requires Python3.9+
171+
"UP037", # `remove-quotes-from-type-annotation` Not safe without `from __future__ import annotations`
172+
"UP042", # `use-str-enum` Requires Python3.11+
170173
]
171174

172175
# Allow unused variables when underscore-prefixed.

statemachine/signature.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ def bind_expected(self, *args: Any, **kwargs: Any) -> BoundArguments: # noqa: C
9595
elif param.name in kwargs:
9696
if param.kind == Parameter.POSITIONAL_ONLY:
9797
msg = (
98-
"{arg!r} parameter is positional only, "
99-
"but was passed as a keyword"
98+
"{arg!r} parameter is positional only, but was passed as a keyword"
10099
)
101100
msg = msg.format(arg=param.name)
102101
raise TypeError(msg) from None

statemachine/statemachine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def _add_listener(self, listeners: "Listeners", allowed_references: SpecReferenc
182182
return self
183183

184184
def _register_callbacks(self, listeners: List[object]):
185-
self._listeners.update({listener: None for listener in listeners})
185+
self._listeners.update(dict.fromkeys(listeners))
186186
self._add_listener(
187187
Listeners.from_listeners(
188188
(
@@ -223,7 +223,7 @@ def add_listener(self, *listeners):
223223
224224
:ref:`listeners`.
225225
"""
226-
self._listeners.update({o: None for o in listeners})
226+
self._listeners.update(dict.fromkeys(listeners))
227227
return self._add_listener(
228228
Listeners.from_listeners(Listener.from_obj(o) for o in listeners),
229229
allowed_references=SPECS_SAFE,

tests/django_project/workflow/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from django.contrib.auth import get_user_model
22
from django.db import models
3-
43
from statemachine.mixins import MachineMixin
54

65
User = get_user_model()

tests/django_project/workflow/statemachines.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from statemachine import StateMachine
21
from statemachine.states import States
32

3+
from statemachine import StateMachine
4+
45
from .models import WorkflowSteps
56

67

0 commit comments

Comments
 (0)