Skip to content

Commit 8d34366

Browse files
authored
Add support for Python 3.13; drop support for Python 3.8 (#1057)
Fixes #1056 I ran [`pyupgrade --py39-plus`](https://github.com/asottile/pyupgrade) on the entire source tree to convert from `typing.{List,Set,...}` to the generic forms of the builtins (among other available upgrades). Upgrading to 3.13 also surfaced #1088 which I filed to address separately since this PR is already quite large.
1 parent f91d59f commit 8d34366

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+397
-523
lines changed

.github/workflows/run-tests.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ jobs:
1616
strategy:
1717
matrix:
1818
os: [ubuntu-latest]
19-
version: ['3.8', '3.9', '3.10', '3.11', '3.12']
19+
version: ['3.9', '3.10', '3.11', '3.12', '3.13']
2020
include:
21-
- version: '3.8'
22-
tox-env: py38,py38-mypy,py38-lint,safety
2321
- version: '3.9'
2422
tox-env: py39,py39-mypy,py39-lint,safety
2523
- version: '3.10'
@@ -28,12 +26,14 @@ jobs:
2826
tox-env: py311,py311-mypy,py311-lint,safety
2927
- version: '3.12'
3028
tox-env: py312,py312-mypy,py312-lint,format,safety
29+
- version: '3.13'
30+
tox-env: py313,py313-mypy,py313-lint,safety
3131
- os: windows-latest
32-
version: '3.12'
33-
tox-env: py312,safety
32+
version: '3.13'
33+
tox-env: py313,safety
3434
- os: macos-latest
35-
version: '3.12'
36-
tox-env: py312,safety
35+
version: '3.13'
36+
tox-env: py313,safety
3737
steps:
3838
- uses: actions/checkout@v4
3939
- uses: actions/setup-python@v5
@@ -148,8 +148,8 @@ jobs:
148148
strategy:
149149
matrix:
150150
os: [ubuntu-latest]
151-
version: ['3.8']
152-
tox-env: ['py38']
151+
version: ['3.9']
152+
tox-env: ['py39']
153153
steps:
154154
- uses: actions/checkout@v4
155155
- uses: actions/setup-python@v5
@@ -188,7 +188,7 @@ jobs:
188188
tox run-parallel -p 2
189189
190190
report-coverage:
191-
runs-on: ubuntu-latest
191+
runs-on: ubuntu-22.04
192192
needs:
193193
- run-tests
194194
- run-pypy-tests

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
* Added support for Python 3.13 (#1056)
10+
811
### Fixed
912
* Fix an issue with `basilisp test` standard streams output that can lead to failures on MS-Windows (#1080)
1013

14+
### Removed
15+
* Removed support for Python 3.8 (#1083)
16+
1117
## [v0.2.4]
1218
### Added
1319
* Added functions to `basilisp.test` for using and combining test fixtures (#980)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🐍 basilisp 🐍
22

3-
A Clojure-compatible(-ish) Lisp dialect targeting Python 3.8+.
3+
A Clojure-compatible(-ish) Lisp dialect targeting Python 3.9+.
44

55
[![PyPI](https://img.shields.io/pypi/v/basilisp.svg?style=flat-square)](https://pypi.org/project/basilisp/) [![python](https://img.shields.io/pypi/pyversions/basilisp.svg?style=flat-square)](https://pypi.org/project/basilisp/) [![pyimpl](https://img.shields.io/pypi/implementation/basilisp.svg?style=flat-square)](https://pypi.org/project/basilisp/) [![readthedocs](https://img.shields.io/readthedocs/basilisp.svg?style=flat-square)](https://basilisp.readthedocs.io/) [![Run tests](https://github.com/basilisp-lang/basilisp/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://github.com/basilisp-lang/basilisp/actions/workflows/run-tests.yml) [![Coveralls github](https://img.shields.io/coveralls/github/basilisp-lang/basilisp.svg?style=flat-square)](https://coveralls.io/github/basilisp-lang/basilisp) [![license](https://img.shields.io/github/license/basilisp-lang/basilisp.svg?style=flat-square)](https://github.com/basilisp-lang/basilisp/blob/master/LICENSE)
66

docs/differencesfromclojure.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Hosted on Python
1616
----------------
1717

1818
Unlike Clojure, Basilisp is hosted on the Python VM.
19-
Basilisp supports versions of Python 3.8+.
19+
Basilisp supports versions of Python 3.9+.
2020
Basilisp projects and libraries may both import Python code and be imported by Python code (once the Basilisp runtime has been :ref:`initialized <bootstrapping>` and the import hooks have been installed).
2121

2222
.. _type_differences:

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Welcome to Basilisp's documentation!
77
====================================
88

9-
Basilisp is a :ref:`Clojure-compatible(-ish) <differences_from_clojure>` Lisp dialect targeting Python 3.8+.
9+
Basilisp is a :ref:`Clojure-compatible(-ish) <differences_from_clojure>` Lisp dialect targeting Python 3.9+.
1010

1111
Basilisp compiles down to raw Python 3 code and executes on the Python 3 virtual machine, allowing natural interoperability between existing Python libraries and new Lisp code.
1212

pyproject.toml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,36 @@ documentation = "https://basilisp.readthedocs.io/"
1313
classifiers = [
1414
# Trove classifiers
1515
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
16-
"Development Status :: 3 - Alpha",
16+
"Development Status :: 3 - Beta",
1717
"Intended Audience :: Developers",
1818
"License :: OSI Approved :: Eclipse Public License 1.0 (EPL-1.0)",
1919
"Programming Language :: Python",
2020
"Programming Language :: Python :: 3",
21-
"Programming Language :: Python :: 3.8",
2221
"Programming Language :: Python :: 3.9",
2322
"Programming Language :: Python :: 3.10",
2423
"Programming Language :: Python :: 3.11",
2524
"Programming Language :: Python :: 3.12",
25+
"Programming Language :: Python :: 3.13",
2626
"Programming Language :: Python :: Implementation :: CPython",
2727
"Programming Language :: Python :: Implementation :: PyPy",
2828
"Topic :: Software Development :: Compilers",
2929
]
3030
include = ["README.md", "LICENSE"]
3131

3232
[tool.poetry.dependencies]
33-
python = "^3.8"
33+
python = "^3.9"
3434
attrs = ">=22.2.0"
35-
graphlib-backport = { version = "^1.1.0", python = "<3.9" }
3635
immutables = ">=0.20,<1.0.0"
3736
prompt-toolkit = ">=3.0.0,<4.0.0"
3837
pyrsistent = ">=0.18.0,<1.0.0"
3938
typing-extensions = ">=4.7.0,<5.0.0"
4039

41-
astor = { version = "^0.8.1", python = "<3.9", optional = true }
4240
pytest = { version = ">=7.0.0,<9.0.0", optional = true }
4341
pygments = { version = ">=2.9.0,<3.0.0", optional = true }
4442

4543
[tool.poetry.group.dev.dependencies]
4644
black = ">=24.0.0"
47-
docutils = [
48-
{ version = "<0.21", python = "3.8" },
49-
{ version = "*", python = ">=3.9" }
50-
]
45+
docutils = "*"
5146
isort = "*"
5247
pygments = "*"
5348
pytest = ">=7.0.0,<9.0.0"
@@ -79,7 +74,7 @@ build-backend = "poetry.core.masonry.api"
7974

8075
[tool.black]
8176
line-length = 88
82-
target-version = ["py38"]
77+
target-version = ["py39"]
8378
include = '\.pyi?$'
8479
exclude = '''
8580
/(

src/basilisp/cli.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import sys
77
import textwrap
88
import types
9+
from collections.abc import Sequence
910
from pathlib import Path
10-
from typing import Any, Callable, List, Optional, Sequence, Type, Union
11+
from typing import Any, Callable, Optional, Union
1112

1213
from basilisp import main as basilisp
1314
from basilisp.lang import compiler as compiler
@@ -109,8 +110,8 @@ def _to_bool(v: Optional[str]) -> Optional[bool]:
109110

110111

111112
def _set_envvar_action(
112-
var: str, parent: Type[argparse.Action] = argparse.Action
113-
) -> Type[argparse.Action]:
113+
var: str, parent: type[argparse.Action] = argparse.Action
114+
) -> type[argparse.Action]:
114115
"""Return an argparse.Action instance (deriving from `parent`) that sets the value
115116
as the default value of the environment variable `var`."""
116117

@@ -381,7 +382,7 @@ def _add_runtime_arg_group(parser: argparse.ArgumentParser) -> None:
381382

382383
Handler = Union[
383384
Callable[[argparse.ArgumentParser, argparse.Namespace], None],
384-
Callable[[argparse.ArgumentParser, argparse.Namespace, List[str]], None],
385+
Callable[[argparse.ArgumentParser, argparse.Namespace, list[str]], None],
385386
]
386387

387388

@@ -729,7 +730,7 @@ def _add_run_subcommand(parser: argparse.ArgumentParser) -> None:
729730
def test(
730731
parser: argparse.ArgumentParser,
731732
args: argparse.Namespace,
732-
extra: List[str],
733+
extra: list[str],
733734
) -> None: # pragma: no cover
734735
init_path(args)
735736
basilisp.init(_compiler_opts(args))

src/basilisp/contrib/pytest/testrunner.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import os
44
import sys
55
import traceback
6+
from collections.abc import Iterable, Iterator
67
from pathlib import Path
78
from types import GeneratorType
8-
from typing import Callable, Iterable, Iterator, Optional, Tuple
9+
from typing import Callable, Optional
910

1011
import pytest
1112

@@ -42,10 +43,9 @@ def pytest_configure(config):
4243
# during tests and restore them afterward.
4344
out_var = runtime.Var.find(OUT_VAR_SYM)
4445
err_var = runtime.Var.find(ERR_VAR_SYM)
45-
bindings = {
46+
if bindings := {
4647
k: v for k, v in {out_var: sys.stdout, err_var: sys.stderr}.items() if k
47-
}
48-
if bindings.items():
48+
}:
4949
runtime.push_thread_bindings(lmap.map(bindings))
5050
config.basilisp_bindings = bindings
5151

@@ -210,7 +210,7 @@ def __init__(self, **kwargs) -> None:
210210
@staticmethod
211211
def _collected_fixtures(
212212
ns: runtime.Namespace,
213-
) -> Tuple[Iterable[FixtureFunction], Iterable[FixtureFunction]]:
213+
) -> tuple[Iterable[FixtureFunction], Iterable[FixtureFunction]]:
214214
"""Collect all of the declared fixtures of the namespace."""
215215
if ns.meta is not None:
216216
return (

src/basilisp/contrib/sphinx/autodoc.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44
import sys
55
import types
6-
from typing import Any, Dict, List, Optional, Tuple, cast
6+
from typing import Any, Optional, cast
77

88
from sphinx.ext.autodoc import (
99
ClassDocumenter,
@@ -51,7 +51,7 @@
5151
_METHODS_KW = kw.keyword("methods")
5252

5353

54-
def _get_doc(reference: IReference) -> Optional[List[List[str]]]:
54+
def _get_doc(reference: IReference) -> Optional[list[list[str]]]:
5555
"""Return the docstring of an IReference type (e.g. Namespace or Var)."""
5656
docstring = reference.meta and reference.meta.val_at(_DOC_KW)
5757
if docstring is None:
@@ -91,7 +91,7 @@ def parse_name(self) -> bool:
9191
v = runtime.first(reader.read_str(self.name))
9292
if isinstance(v, sym.Symbol) and v.ns is None:
9393
self.modname = v.name
94-
self.objpath: List[str] = []
94+
self.objpath: list[str] = []
9595
self.args = ""
9696
self.retann = ""
9797
self.fullname = v.name
@@ -100,7 +100,7 @@ def parse_name(self) -> bool:
100100

101101
def resolve_name(
102102
self, modname: str, parents: Any, path: str, base: Any
103-
) -> Tuple[str, List[str]]:
103+
) -> tuple[str, list[str]]:
104104
"""Unused method since parse_name is overridden."""
105105
return NotImplemented
106106

@@ -122,11 +122,11 @@ def import_object(self, raiseerror: bool = False) -> bool:
122122
self.module = ns.module
123123
return True
124124

125-
def get_doc(self) -> Optional[List[List[str]]]:
125+
def get_doc(self) -> Optional[list[list[str]]]:
126126
assert self.object is not None
127127
return _get_doc(self.object)
128128

129-
def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
129+
def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]:
130130
assert self.object is not None
131131
interns = self.object.interns
132132

@@ -145,8 +145,8 @@ def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
145145
return False, selected
146146

147147
def filter_members(
148-
self, members: List[ObjectMember], want_all: bool
149-
) -> List[Tuple[str, Any, bool]]:
148+
self, members: list[ObjectMember], want_all: bool
149+
) -> list[tuple[str, Any, bool]]:
150150
filtered = []
151151
for member in members:
152152
name, val = member.__name__, member.object
@@ -173,14 +173,14 @@ def filter_members(
173173
return filtered
174174

175175
def sort_members(
176-
self, documenters: List[Tuple["Documenter", bool]], order: str
177-
) -> List[Tuple["Documenter", bool]]:
176+
self, documenters: list[tuple["Documenter", bool]], order: str
177+
) -> list[tuple["Documenter", bool]]:
178178
assert self.object is not None
179179
if order == "bysource":
180180
# By the time this method is called, the object isn't hydrated in the
181181
# Documenter wrapper, so we cannot rely on the existence of that to get
182182
# line numbers. Instead, we have to build an index manually.
183-
line_numbers: Dict[str, int] = {
183+
line_numbers: dict[str, int] = {
184184
s.name: (
185185
cast(int, v.meta.val_at(_LINE_KW, sys.maxsize))
186186
if v.meta is not None
@@ -189,7 +189,7 @@ def sort_members(
189189
for s, v in self.object.interns.items()
190190
}
191191

192-
def _line_num(e: Tuple["Documenter", bool]) -> int:
192+
def _line_num(e: tuple["Documenter", bool]) -> int:
193193
documenter = e[0]
194194
_, name = documenter.name.split("::", maxsplit=1)
195195
return line_numbers.get(name, sys.maxsize)
@@ -238,7 +238,7 @@ def parse_name(self) -> bool:
238238

239239
def resolve_name(
240240
self, modname: str, parents: Any, path: str, base: Any
241-
) -> Tuple[str, List[str]]:
241+
) -> tuple[str, list[str]]:
242242
"""Unused method since parse_name is overridden."""
243243
return NotImplemented
244244

@@ -270,7 +270,7 @@ def get_sourcename(self) -> str:
270270
return f"{file}:docstring of {self.object}"
271271
return f"docstring of {self.object}"
272272

273-
def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
273+
def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]:
274274
assert self.object is not None
275275
return False, []
276276

@@ -292,7 +292,7 @@ def add_directive_header(self, sig: str) -> None:
292292
if self.object.meta.val_at(_DEPRECATED_KW):
293293
self.add_line(" :deprecated:", sourcename)
294294

295-
def get_doc(self) -> Optional[List[List[str]]]:
295+
def get_doc(self) -> Optional[list[list[str]]]:
296296
assert self.object is not None
297297
return _get_doc(self.object)
298298

@@ -372,7 +372,7 @@ def can_document_member(
372372
and member.meta.val_at(_PROTOCOL_KW) is True
373373
)
374374

375-
def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
375+
def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]:
376376
assert self.object is not None
377377
assert want_all
378378
ns = self.object.ns
@@ -390,8 +390,8 @@ def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
390390
)
391391

392392
def filter_members(
393-
self, members: List[ObjectMember], want_all: bool
394-
) -> List[Tuple[str, Any, bool]]:
393+
self, members: list[ObjectMember], want_all: bool
394+
) -> list[tuple[str, Any, bool]]:
395395
filtered = []
396396
for member in members:
397397
name, val = member.__name__, member.object
@@ -428,7 +428,7 @@ def can_document_member(
428428
and issubclass(member.value, IType)
429429
)
430430

431-
def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
431+
def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]:
432432
return ClassDocumenter.get_object_members(self, want_all)
433433

434434

0 commit comments

Comments
 (0)