Skip to content

Commit e37b56b

Browse files
authored
Drop Python 3.9 (#987)
# Changes ## Drop Python 3.9 EOL of 2025-10-31. See also: - https://devguide.python.org/versions/#:~:text=Release%20manager-,3.9,-PEP%20596 - https://peps.python.org/pep-0596/
2 parents c13914a + 4a40ac0 commit e37b56b

File tree

21 files changed

+143
-697
lines changed

21 files changed

+143
-697
lines changed

.github/workflows/tests.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ jobs:
1212
matrix:
1313
python-version: ['3.14']
1414
tmux-version: ['2.6', '2.7', '2.8', '3.0a', '3.1b', '3.2a', '3.3a', '3.4', '3.5', 'master']
15-
# balance ci coverage across supported python/tmux versions with CI speed
16-
include:
17-
- python-version: '3.9'
18-
tmux-version: '2.6'
19-
- python-version: '3.9'
20-
tmux-version: 'master'
2115
steps:
2216
- uses: actions/checkout@v4
2317

CHANGES

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force
3333

3434
- _Future release notes will be placed here_
3535

36+
### Breaking changes
37+
38+
- Drop Python 3.9, EOL October 5th, 2025 (#987)
39+
40+
tmuxp 1.55.0 was the last release for Python 3.9.
41+
42+
The minimum Python for tmuxp as of 1.56.0 is Python 3.10
43+
3644
### Development
3745

3846
- Add Python 3.14 to test matrix (#986)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ See donation options at <https://tony.sh/support.html>.
294294
# Project details
295295

296296
- tmux support: 1.8+
297-
- python support: >= 3.9, pypy, pypy3
297+
- python support: >= 3.10, pypy, pypy3
298298
- Source: <https://github.com/tmux-python/tmuxp>
299299
- Docs: <https://tmuxp.git-pull.com>
300300
- API: <https://tmuxp.git-pull.com/api.html>

docs/_ext/aafig.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import locale
1818
import logging
19+
import pathlib
1920
import posixpath
2021
import typing as t
2122
from hashlib import sha1 as sha
@@ -130,9 +131,11 @@ def render_aafig_images(app: Sphinx, doctree: nodes.Node) -> None:
130131
options["format"] = format_map[format_]
131132
else:
132133
logger.warning(
133-
f'unsupported builder format "{format_}", please '
134-
"add a custom entry in aafig_format config "
135-
"option for this builder",
134+
(
135+
'unsupported builder format "%s", please add a custom entry in '
136+
"aafig_format config option for this builder"
137+
),
138+
format_,
136139
)
137140
img.replace_self(nodes.literal_block(text, text))
138141
continue
@@ -196,11 +199,9 @@ def render_aafigure(
196199
f = None
197200
try:
198201
try:
199-
with open(
200-
metadata_fname,
201-
encoding=locale.getpreferredencoding(False),
202-
) as f:
203-
extra = f.read()
202+
extra = pathlib.Path(metadata_fname).read_text(
203+
encoding=locale.getpreferredencoding(False)
204+
)
204205
except Exception as e:
205206
raise AafigError from e
206207
finally:
@@ -221,12 +222,9 @@ def render_aafigure(
221222
extra = None
222223
if options["format"].lower() == "svg":
223224
extra = visitor.get_size_attrs()
224-
with open(
225-
metadata_fname,
226-
"w",
227-
encoding=locale.getpreferredencoding(False),
228-
) as f:
229-
f.write(extra)
225+
pathlib.Path(metadata_fname).write_text(
226+
extra, encoding=locale.getpreferredencoding(False)
227+
)
230228

231229
return relfn, outfn, None, extra
232230

pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "tmuxp"
33
version = "1.55.0"
44
description = "Session manager for tmux, which allows users to save and load tmux sessions through simple configuration files."
5-
requires-python = ">=3.9,<4.0"
5+
requires-python = ">=3.10,<4.0"
66
authors = [
77
{name = "Tony Narlock", email = "tony@git-pull.com"}
88
]
@@ -15,7 +15,6 @@ classifiers = [
1515
"Environment :: Web Environment",
1616
"Intended Audience :: Developers",
1717
"Programming Language :: Python",
18-
"Programming Language :: Python :: 3.9",
1918
"Programming Language :: Python :: 3.10",
2019
"Programming Language :: Python :: 3.11",
2120
"Programming Language :: Python :: 3.12",
@@ -156,7 +155,7 @@ exclude_lines = [
156155

157156
[tool.mypy]
158157
strict = true
159-
python_version = "3.9"
158+
python_version = "3.10"
160159
files = [
161160
"src/",
162161
"tests/",
@@ -175,7 +174,7 @@ module = [
175174
ignore_missing_imports = true
176175

177176
[tool.ruff]
178-
target-version = "py39"
177+
target-version = "py310"
179178

180179
[tool.ruff.lint]
181180
select = [

src/tmuxp/_internal/config_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import yaml
1010

1111
if t.TYPE_CHECKING:
12-
from typing_extensions import TypeAlias
12+
from typing import TypeAlias
1313

1414
FormatLiteral = t.Literal["json", "yaml"]
1515

@@ -106,7 +106,7 @@ def _from_file(cls, path: pathlib.Path) -> dict[str, t.Any]:
106106
{'session_name': 'my session'}
107107
"""
108108
assert isinstance(path, pathlib.Path)
109-
content = path.open().read()
109+
content = path.open(encoding="utf-8").read()
110110

111111
if path.suffix in {".yaml", ".yml"}:
112112
fmt: FormatLiteral = "yaml"

src/tmuxp/cli/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434

3535
if t.TYPE_CHECKING:
3636
import pathlib
37-
38-
from typing_extensions import TypeAlias
37+
from typing import TypeAlias
3938

4039
CLIVerbosity: TypeAlias = t.Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
4140
CLISubparserName: TypeAlias = t.Literal[

src/tmuxp/cli/convert.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def command_convert(
9696
answer_yes = True
9797

9898
if answer_yes:
99-
with open(newfile, "w", encoding=locale.getpreferredencoding(False)) as buf:
100-
buf.write(new_workspace)
99+
pathlib.Path(newfile).write_text(
100+
new_workspace, encoding=locale.getpreferredencoding(False)
101+
)
101102
print(f"New workspace file saved to <{newfile}>.") # NOQA: T201 RUF100

src/tmuxp/cli/freeze.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .utils import prompt, prompt_choices, prompt_yes_no
2121

2222
if t.TYPE_CHECKING:
23-
from typing_extensions import TypeAlias, TypeGuard
23+
from typing import TypeAlias, TypeGuard
2424

2525
CLIOutputFormatLiteral: TypeAlias = t.Literal["yaml", "json"]
2626

@@ -210,8 +210,9 @@ def extract_workspace_format(
210210
destdir = os.path.dirname(dest)
211211
if not os.path.isdir(destdir):
212212
os.makedirs(destdir)
213-
with open(dest, "w", encoding=locale.getpreferredencoding(False)) as buf:
214-
buf.write(workspace)
213+
pathlib.Path(dest).write_text(
214+
workspace, encoding=locale.getpreferredencoding(False)
215+
)
215216

216217
if not args.quiet:
217218
print(f"Saved to {dest}.") # NOQA: T201 RUF100

src/tmuxp/cli/import_config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ def import_config(
178178
if prompt_yes_no(f"Save to {dest_path}?"):
179179
dest = dest_path
180180

181-
with open(dest, "w", encoding=locale.getpreferredencoding(False)) as buf:
182-
buf.write(new_config)
181+
pathlib.Path(dest).write_text(
182+
new_config, encoding=locale.getpreferredencoding(False)
183+
)
183184

184185
tmuxp_echo(f"Saved to {dest}.")
185186
else:

0 commit comments

Comments
 (0)