Skip to content

Commit 41620cd

Browse files
Add support for Python 3.12 (#972)
* Add support for Python 3.12 * Use pyproject.toml to make setuptools pick up the driver version * Add python 3.12 tag to package metadata * Comment why wait_for shim not needed in Python 3.12 * Silence deprecation warnings for Python 3.12 for now --------- Signed-off-by: Rouven Bauer <[email protected]> Signed-off-by: Grant Lodge <[email protected]> Co-authored-by: Grant Lodge <[email protected]>
1 parent fd7206a commit 41620cd

File tree

16 files changed

+66
-37
lines changed

16 files changed

+66
-37
lines changed

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Neo4j versions supported:
1212

1313
Python versions supported:
1414

15+
* Python 3.12 (added in driver version 5.14.0)
1516
* Python 3.11 (added in driver version 5.3.0)
1617
* Python 3.10
1718
* Python 3.9

pyproject.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ classifiers = [
3838
"Programming Language :: Python :: 3.9",
3939
"Programming Language :: Python :: 3.10",
4040
"Programming Language :: Python :: 3.11",
41+
"Programming Language :: Python :: 3.12",
4142
]
4243
dynamic = ["version", "readme"]
4344

@@ -50,17 +51,19 @@ pandas = [
5051
"pandas >= 1.1.0, < 3.0.0",
5152
"numpy >= 1.7.0, < 2.0.0",
5253
]
54+
pyarrow = ["pyarrow >= 1.0.0"]
55+
5356

5457
[build-system]
5558
requires = [
56-
"setuptools~=65.6",
57-
"tomlkit~=0.11.6",
59+
"setuptools >= 66.1.0",
60+
# TODO: 6.0 - can be removed once `setup.py` is simplified
61+
"tomlkit ~= 0.11.6",
5862
]
5963
build-backend = "setuptools.build_meta"
6064

61-
# still in beta
62-
#[tool.setuptools.dynamic]
63-
#version = {attr = "neo4j._meta.version"}
65+
[tool.setuptools.dynamic]
66+
version = {attr = "neo4j._meta.version"}
6467

6568

6669
[tool.coverage]

requirements-dev.txt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# the driver itself
2-
-e .[pandas,numpy]
2+
-e .[pandas,numpy,pyarrow]
33

44
# auto-generate sync driver from async code
55
unasync>=0.5.0
@@ -10,16 +10,10 @@ mypy>=0.971
1010
typing-extensions>=4.3.0
1111
types-pytz>=2022.1.2
1212

13-
# for packaging
14-
setuptools~=65.6
15-
# TODO: 6.0 - can be removed once `setup.py` is simplified
16-
tomlkit~=0.11.6
17-
1813
# needed for running tests
1914
coverage[toml]>=5.5
2015
freezegun >= 1.2.2
2116
mock>=4.0.3
22-
pyarrow>=1.0.0
2317
pytest>=6.2.5
2418
pytest-asyncio>=0.16.0
2519
pytest-benchmark>=3.4.1

setup.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
from neo4j._meta import (
3636
deprecated_package as deprecated,
3737
package,
38-
version,
3938
)
4039

4140

@@ -84,8 +83,4 @@ def changed_package_name(new_name):
8483

8584

8685
with changed_package_name(package):
87-
setup(
88-
# until `[tool.setuptools.dynamic]` in pyproject.toml is out of beta
89-
version=version,
90-
long_description=readme,
91-
)
86+
setup(long_description=readme)

src/neo4j/_async_compat/concurrency.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ async def acquire(self, blocking=True, timeout=-1):
136136
try:
137137
await wait_for(fut, timeout)
138138
except asyncio.CancelledError:
139+
if fut.cancelled():
140+
raise
139141
already_finished = not fut.cancel()
140142
if already_finished:
141143
# Too late to cancel the acquisition.

src/neo4j/_async_compat/shims/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@
2525
# The shipped wait_for can swallow cancellation errors (starting with 3.8).
2626
# See: https://github.com/python/cpython/pull/26097
2727
# and https://github.com/python/cpython/pull/28149
28-
# Since 3.8 and 3.9 already received their final maintenance release, there
29-
# will be now fix for this. So this patch needs to stick around at least until
30-
# we remove support for Python 3.9.
28+
# Ultimately, this got fixed in https://github.com/python/cpython/pull/98518
29+
# (released with Python 3.12) by re-doing how wait_for works.
3130

3231

33-
if sys.version_info >= (3, 8):
32+
if (3, 12) > sys.version_info >= (3, 8):
3433
# copied from Python 3.10's asyncio package with applied patch
3534

3635
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,

testkit/Dockerfile

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,29 @@ ENV PYENV_ROOT /.pyenv
4242
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
4343

4444
# Setup python version
45-
ENV PYTHON_VERSIONS 3.7 3.8 3.9 3.10 3.11
45+
ENV PYTHON_VERSIONS 3.12 3.11 3.10 3.9 3.8 3.7
4646

4747
RUN for version in $PYTHON_VERSIONS; do \
4848
pyenv install $version; \
4949
done
5050
RUN pyenv rehash
51-
RUN pyenv global $(pyenv versions --bare --skip-aliases)
51+
RUN pyenv global $(pyenv versions --bare --skip-aliases | sort --version-sort --reverse)
5252

5353
# Install Latest pip and setuptools for each environment
5454
# + tox and tools for starting the tests
5555
# https://pip.pypa.io/en/stable/news/
5656
RUN for version in $PYTHON_VERSIONS; do \
5757
python$version -m pip install -U pip && \
58-
python$version -m pip install -U setuptools && \
5958
python$version -m pip install -U coverage tox; \
6059
done
60+
61+
# Installing pyarrow lib until pre-built wheel for Python 3.12 exists
62+
# https://github.com/apache/arrow/issues/37880
63+
RUN apt update && \
64+
apt install -y -V lsb-release cmake gcc && \
65+
distro_name=$(lsb_release --id --short | tr 'A-Z' 'a-z') && \
66+
code_name=$(lsb_release --codename --short) && \
67+
wget https://apache.jfrog.io/artifactory/arrow/${distro_name}/apache-arrow-apt-source-latest-${code_name}.deb && \
68+
apt install -y -V ./apache-arrow-apt-source-latest-${code_name}.deb && \
69+
apt update && \
70+
apt install -y -V libarrow-dev # For C++ \

testkit/_common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import re
23
import subprocess
34
import sys
45

@@ -7,14 +8,28 @@
78

89

910
def run(args, env=None):
11+
print(args)
1012
return subprocess.run(
1113
args, universal_newlines=True, stdout=sys.stdout, stderr=sys.stderr,
1214
check=True, env=env
1315
)
1416

1517

18+
def get_python_version():
19+
cmd = [TEST_BACKEND_VERSION, "-V"]
20+
res = subprocess.check_output(cmd, universal_newlines=True,
21+
stderr=sys.stderr)
22+
raw_version = re.match(r"(?:.*?)((?:\d+\.)+(?:\d+))", res).group(1)
23+
return tuple(int(e) for e in raw_version.split("."))
24+
25+
1626
def run_python(args, env=None, warning_as_error=True):
1727
cmd = [TEST_BACKEND_VERSION, "-u"]
28+
if get_python_version() >= (3, 12):
29+
# Ignore warnings for Python 3.12 for now
30+
# https://github.com/dateutil/dateutil/issues/1284 needs to be released
31+
# and propagate through our dependency graph
32+
warning_as_error = False
1833
if warning_as_error:
1934
cmd += ["-W", "error"]
2035
cmd += list(args)

testkitbackend/__main__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ async def main():
4949

5050

5151
if __name__ == "__main__":
52-
warnings.simplefilter("error")
52+
if sys.version_info < (3, 12):
53+
# Ignore warnings for Python 3.12 for now
54+
# https://github.com/dateutil/dateutil/issues/1284 needs to be released
55+
# and propagate through our dependency graph
56+
warnings.simplefilter("error")
5357
if len(sys.argv) == 2 and sys.argv[1].lower().strip() == "async":
5458
async_main()
5559
else:

tests/unit/async_/test_driver.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ async def test_execute_query_keyword_parameters(
716716
@pytest.mark.parametrize("parameters", (
717717
{"_": "a"}, {"foo_": None}, {"foo_": 1, "bar_": 2}
718718
))
719+
@mark_async_test
719720
async def test_reserved_query_keyword_parameters(
720721
mocker, parameters: t.Dict[str, t.Any],
721722
) -> None:

tests/unit/async_/work/test_result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ async def test_data(num_records):
607607
record.data.return_value = expected_data[-1]
608608
assert await result.data("hello", "world") == expected_data
609609
for record in records:
610-
assert record.data.called_once_with("hello", "world")
610+
record.data.assert_called_once_with("hello", "world")
611611

612612

613613
@pytest.mark.parametrize("records", (

tests/unit/mixed/async_compat/test_concurrency.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,6 @@ async def waiter_non_blocking():
225225
assert fut.exception() is exc
226226
awaits += 1
227227

228-
229228
assert not lock.locked()
230229
await asyncio.gather(blocker(), waiter_non_blocking())
231230
assert not lock.locked()

tests/unit/mixed/async_compat/test_shims.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ async def _check_wait_for(wait_for_, should_propagate_cancellation):
4242

4343

4444
@pytest.mark.skipif(
45-
sys.version_info < (3, 8),
46-
reason="wait_for is only broken in Python 3.8+"
45+
not (3, 12) > sys.version_info >= (3, 8),
46+
reason="wait_for is only broken in Python 3.8-3.11 (inclusive)"
4747
)
4848
@mark_async_test
4949
async def test_wait_for_shim_is_necessary_starting_from_3x8():
@@ -56,8 +56,8 @@ async def test_wait_for_shim_is_necessary_starting_from_3x8():
5656

5757

5858
@pytest.mark.skipif(
59-
sys.version_info >= (3, 8),
60-
reason="wait_for is only broken in Python 3.8+"
59+
(3, 12) > sys.version_info >= (3, 8),
60+
reason="wait_for is only broken in Python 3.8-3.11 (inclusive)"
6161
)
6262
@mark_async_test
6363
async def test_wait_for_shim_is_not_necessary_prior_to_3x8():

tests/unit/sync/test_driver.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ def test_execute_query_keyword_parameters(
715715
@pytest.mark.parametrize("parameters", (
716716
{"_": "a"}, {"foo_": None}, {"foo_": 1, "bar_": 2}
717717
))
718+
@mark_sync_test
718719
def test_reserved_query_keyword_parameters(
719720
mocker, parameters: t.Dict[str, t.Any],
720721
) -> None:

tests/unit/sync/work/test_result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ def test_data(num_records):
607607
record.data.return_value = expected_data[-1]
608608
assert result.data("hello", "world") == expected_data
609609
for record in records:
610-
assert record.data.called_once_with("hello", "world")
610+
record.data.assert_called_once_with("hello", "world")
611611

612612

613613
@pytest.mark.parametrize("records", (

tox.ini

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
[tox]
2-
envlist = py{37,38,39,310,311}-{unit,integration,performance}
2+
envlist = py{37,38,39,310,311,312}-{unit,integration,performance}
33

44
[testenv]
55
passenv = TEST_NEO4J_*
66
deps = -r requirements-dev.txt
77
setenv = COVERAGE_FILE={envdir}/.coverage
88
usedevelop = true
9+
# Ignore warnings for Python 3.12 for now
10+
# https://github.com/dateutil/dateutil/issues/1284 needs to be released
11+
# and propagate through our dependency graph
12+
warnargs =
13+
py{37,38,39,310,311}: -W error
14+
py312:
915
commands =
1016
coverage erase
11-
unit: coverage run -m pytest -W error -v {posargs} tests/unit
12-
unit: coverage run -m pytest -v --doctest-modules {posargs} src
13-
integration: coverage run -m pytest -W error -v {posargs} tests/integration
17+
unit: coverage run -m pytest {[testenv]warnargs} -v {posargs} tests/unit
18+
integration: coverage run -m pytest {[testenv]warnargs} -v {posargs} tests/integration
1419
performance: python -m pytest --benchmark-autosave -v {posargs} tests/performance
1520
unit,integration: coverage report

0 commit comments

Comments
 (0)