Skip to content

Add support for Python 3.13; drop support for Python 3.8 #1057

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Oct 15, 2024
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
20 changes: 10 additions & 10 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
version: ['3.8', '3.9', '3.10', '3.11', '3.12']
version: ['3.9', '3.10', '3.11', '3.12', '3.13']
include:
- version: '3.8'
tox-env: py38,py38-mypy,py38-lint,safety
- version: '3.9'
tox-env: py39,py39-mypy,py39-lint,safety
- version: '3.10'
Expand All @@ -28,12 +26,14 @@ jobs:
tox-env: py311,py311-mypy,py311-lint,safety
- version: '3.12'
tox-env: py312,py312-mypy,py312-lint,format,safety
- version: '3.13'
tox-env: py313,py313-mypy,py313-lint,safety
- os: windows-latest
version: '3.12'
tox-env: py312,safety
version: '3.13'
tox-env: py313,safety
- os: macos-latest
version: '3.12'
tox-env: py312,safety
version: '3.13'
tox-env: py313,safety
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down Expand Up @@ -148,8 +148,8 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
version: ['3.8']
tox-env: ['py38']
version: ['3.9']
tox-env: ['py39']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down Expand Up @@ -188,7 +188,7 @@ jobs:
tox run-parallel -p 2

report-coverage:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs:
- run-tests
- run-pypy-tests
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
* Added support for Python 3.13 (#1056)

### Fixed
* Fix an issue with `basilisp test` standard streams output that can lead to failures on MS-Windows (#1080)

### Removed
* Removed support for Python 3.8 (#1083)

## [v0.2.4]
### Added
* Added functions to `basilisp.test` for using and combining test fixtures (#980)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🐍 basilisp 🐍

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

[![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)

Expand Down
2 changes: 1 addition & 1 deletion docs/differencesfromclojure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Hosted on Python
----------------

Unlike Clojure, Basilisp is hosted on the Python VM.
Basilisp supports versions of Python 3.8+.
Basilisp supports versions of Python 3.9+.
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).

.. _type_differences:
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Welcome to Basilisp's documentation!
====================================

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

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.

Expand Down
15 changes: 5 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,36 @@ documentation = "https://basilisp.readthedocs.io/"
classifiers = [
# Trove classifiers
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
"Development Status :: 3 - Alpha",
"Development Status :: 3 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Eclipse Public License 1.0 (EPL-1.0)",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Compilers",
]
include = ["README.md", "LICENSE"]

[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9"
attrs = ">=22.2.0"
graphlib-backport = { version = "^1.1.0", python = "<3.9" }
immutables = ">=0.20,<1.0.0"
prompt-toolkit = ">=3.0.0,<4.0.0"
pyrsistent = ">=0.18.0,<1.0.0"
typing-extensions = ">=4.7.0,<5.0.0"

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

[tool.poetry.group.dev.dependencies]
black = ">=24.0.0"
docutils = [
{ version = "<0.21", python = "3.8" },
{ version = "*", python = ">=3.9" }
]
docutils = "*"
isort = "*"
pygments = "*"
pytest = ">=7.0.0,<9.0.0"
Expand Down Expand Up @@ -79,7 +74,7 @@ build-backend = "poetry.core.masonry.api"

[tool.black]
line-length = 88
target-version = ["py38"]
target-version = ["py39"]
include = '\.pyi?$'
exclude = '''
/(
Expand Down
11 changes: 6 additions & 5 deletions src/basilisp/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import sys
import textwrap
import types
from collections.abc import Sequence
from pathlib import Path
from typing import Any, Callable, List, Optional, Sequence, Type, Union
from typing import Any, Callable, Optional, Union

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


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

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

Handler = Union[
Callable[[argparse.ArgumentParser, argparse.Namespace], None],
Callable[[argparse.ArgumentParser, argparse.Namespace, List[str]], None],
Callable[[argparse.ArgumentParser, argparse.Namespace, list[str]], None],
]


Expand Down Expand Up @@ -729,7 +730,7 @@ def _add_run_subcommand(parser: argparse.ArgumentParser) -> None:
def test(
parser: argparse.ArgumentParser,
args: argparse.Namespace,
extra: List[str],
extra: list[str],
) -> None: # pragma: no cover
init_path(args)
basilisp.init(_compiler_opts(args))
Expand Down
10 changes: 5 additions & 5 deletions src/basilisp/contrib/pytest/testrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import os
import sys
import traceback
from collections.abc import Iterable, Iterator
from pathlib import Path
from types import GeneratorType
from typing import Callable, Iterable, Iterator, Optional, Tuple
from typing import Callable, Optional

import pytest

Expand Down Expand Up @@ -42,10 +43,9 @@ def pytest_configure(config):
# during tests and restore them afterward.
out_var = runtime.Var.find(OUT_VAR_SYM)
err_var = runtime.Var.find(ERR_VAR_SYM)
bindings = {
if bindings := {
k: v for k, v in {out_var: sys.stdout, err_var: sys.stderr}.items() if k
}
if bindings.items():
}:
runtime.push_thread_bindings(lmap.map(bindings))
config.basilisp_bindings = bindings

Expand Down Expand Up @@ -210,7 +210,7 @@ def __init__(self, **kwargs) -> None:
@staticmethod
def _collected_fixtures(
ns: runtime.Namespace,
) -> Tuple[Iterable[FixtureFunction], Iterable[FixtureFunction]]:
) -> tuple[Iterable[FixtureFunction], Iterable[FixtureFunction]]:
"""Collect all of the declared fixtures of the namespace."""
if ns.meta is not None:
return (
Expand Down
38 changes: 19 additions & 19 deletions src/basilisp/contrib/sphinx/autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import sys
import types
from typing import Any, Dict, List, Optional, Tuple, cast
from typing import Any, Optional, cast

from sphinx.ext.autodoc import (
ClassDocumenter,
Expand Down Expand Up @@ -51,7 +51,7 @@
_METHODS_KW = kw.keyword("methods")


def _get_doc(reference: IReference) -> Optional[List[List[str]]]:
def _get_doc(reference: IReference) -> Optional[list[list[str]]]:
"""Return the docstring of an IReference type (e.g. Namespace or Var)."""
docstring = reference.meta and reference.meta.val_at(_DOC_KW)
if docstring is None:
Expand Down Expand Up @@ -91,7 +91,7 @@ def parse_name(self) -> bool:
v = runtime.first(reader.read_str(self.name))
if isinstance(v, sym.Symbol) and v.ns is None:
self.modname = v.name
self.objpath: List[str] = []
self.objpath: list[str] = []
self.args = ""
self.retann = ""
self.fullname = v.name
Expand All @@ -100,7 +100,7 @@ def parse_name(self) -> bool:

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

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

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

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

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

def filter_members(
self, members: List[ObjectMember], want_all: bool
) -> List[Tuple[str, Any, bool]]:
self, members: list[ObjectMember], want_all: bool
) -> list[tuple[str, Any, bool]]:
filtered = []
for member in members:
name, val = member.__name__, member.object
Expand All @@ -173,14 +173,14 @@ def filter_members(
return filtered

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

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

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

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

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

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

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

Expand Down Expand Up @@ -372,7 +372,7 @@ def can_document_member(
and member.meta.val_at(_PROTOCOL_KW) is True
)

def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]:
assert self.object is not None
assert want_all
ns = self.object.ns
Expand All @@ -390,8 +390,8 @@ def get_object_members(self, want_all: bool) -> Tuple[bool, List[ObjectMember]]:
)

def filter_members(
self, members: List[ObjectMember], want_all: bool
) -> List[Tuple[str, Any, bool]]:
self, members: list[ObjectMember], want_all: bool
) -> list[tuple[str, Any, bool]]:
filtered = []
for member in members:
name, val = member.__name__, member.object
Expand Down Expand Up @@ -428,7 +428,7 @@ def can_document_member(
and issubclass(member.value, IType)
)

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


Expand Down
Loading