Skip to content

Add Python 3.11 support #861

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 3 commits into from
Nov 24, 2022
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Neo4j Driver Change Log (breaking/major changes only)

## Version 5.3
- Python 3.11 support added
- Query strings are now typed `LiteralString` instead of `str` to help mitigate
accidental Cypher injections. There are rare use-cases where a computed
string is necessary. Please use `# type: ignore`, or `typing.cast` to
suppress the type checking in those cases.


## Version 5.2

- No breaking or major changes.
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Neo4j versions supported:

Python versions supported:

* Python 3.11 (added in driver version 5.3.0)
* Python 3.10
* Python 3.9
* Python 3.8
Expand Down
22 changes: 10 additions & 12 deletions neo4j/_async/work/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@
from random import random
from time import perf_counter


if t.TYPE_CHECKING:
import typing_extensions as te

from ..io import AsyncBolt

_R = t.TypeVar("_R")
_P = te.ParamSpec("_P")



from ..._async_compat import async_sleep
from ..._async_compat.util import AsyncUtil
from ..._conf import SessionConfig
Expand All @@ -61,6 +50,15 @@
from .workspace import AsyncWorkspace


if t.TYPE_CHECKING:
import typing_extensions as te

from ..io import AsyncBolt

_R = t.TypeVar("_R")
_P = te.ParamSpec("_P")


log = getLogger("neo4j")


Expand Down Expand Up @@ -237,7 +235,7 @@ def cancel(self) -> None:

async def run(
self,
query: t.Union[str, Query],
query: t.Union[te.LiteralString, Query],
parameters: t.Optional[t.Dict[str, t.Any]] = None,
**kwargs: t.Any
) -> AsyncResult:
Expand Down
6 changes: 5 additions & 1 deletion neo4j/_async/work/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
from .result import AsyncResult


if t.TYPE_CHECKING:
import typing_extensions as te


__all__ = (
"AsyncManagedTransaction",
"AsyncTransaction",
Expand Down Expand Up @@ -95,7 +99,7 @@ async def _consume_results(self):

async def run(
self,
query: str,
query: te.LiteralString,
parameters: t.Optional[t.Dict[str, t.Any]] = None,
**kwparameters: t.Any
) -> AsyncResult:
Expand Down
32 changes: 19 additions & 13 deletions neo4j/_async_compat/network/_bolt_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ async def _connect_secure(cls, resolved_address, timeout, keep_alive, ssl):
await cls.close_socket(s)
raise ServiceUnavailable(
"Timed out trying to establish connection to {!r}".format(
resolved_address))
resolved_address)) from None
except asyncio.CancelledError:
log.debug("[#0000] S: <CANCELLED> %s", resolved_address)
log.debug("[#0000] C: <CLOSE> %s", resolved_address)
Expand All @@ -259,15 +259,18 @@ async def _connect_secure(cls, resolved_address, timeout, keep_alive, ssl):
message="Failed to establish encrypted connection.",
address=(resolved_address.host_name, local_port)
) from error
except OSError as error:
except Exception as error:
log.debug("[#0000] S: <ERROR> %s %s", type(error).__name__,
" ".join(map(repr, error.args)))
log.debug("[#0000] C: <CLOSE> %s", resolved_address)
if s:
await cls.close_socket(s)
raise ServiceUnavailable(
"Failed to establish connection to {!r} (reason {})".format(
resolved_address, error))
if isinstance(error, OSError):
raise ServiceUnavailable(
"Failed to establish connection to {!r} (reason {})"
.format(resolved_address, error)
) from error
raise

async def _handshake(self, resolved_address):
"""
Expand Down Expand Up @@ -302,10 +305,10 @@ async def _handshake(self, resolved_address):
self.settimeout(original_timeout + 1)
try:
data = await self.recv(4)
except OSError:
except OSError as exc:
raise ServiceUnavailable(
"Failed to read any data from server {!r} "
"after connected".format(resolved_address))
"after connected".format(resolved_address)) from exc
finally:
self.settimeout(original_timeout)
data_size = len(data)
Expand Down Expand Up @@ -513,14 +516,17 @@ def _connect(cls, resolved_address, timeout, keep_alive):
raise ServiceUnavailable(
"Timed out trying to establish connection to {!r}".format(
resolved_address))
except OSError as error:
except Exception as error:
log.debug("[#0000] S: <ERROR> %s %s", type(error).__name__,
" ".join(map(repr, error.args)))
log.debug("[#0000] C: <CLOSE> %s", resolved_address)
cls.close_socket(s)
raise ServiceUnavailable(
"Failed to establish connection to {!r} (reason {})".format(
resolved_address, error))
if isinstance(error, OSError):
raise ServiceUnavailable(
"Failed to establish connection to {!r} (reason {})"
.format(resolved_address, error)
) from error
raise

@classmethod
def _secure(cls, s, host, ssl_context):
Expand Down Expand Up @@ -582,10 +588,10 @@ def _handshake(cls, s, resolved_address):
selector.select(1)
try:
data = s.recv(4)
except OSError:
except OSError as exc:
raise ServiceUnavailable(
"Failed to read any data from server {!r} "
"after connected".format(resolved_address))
"after connected".format(resolved_address)) from exc
data_size = len(data)
if data_size == 0:
# If no data is returned after a successful select
Expand Down
22 changes: 10 additions & 12 deletions neo4j/_sync/work/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@
from random import random
from time import perf_counter


if t.TYPE_CHECKING:
import typing_extensions as te

from ..io import Bolt

_R = t.TypeVar("_R")
_P = te.ParamSpec("_P")



from ..._async_compat import sleep
from ..._async_compat.util import Util
from ..._conf import SessionConfig
Expand All @@ -61,6 +50,15 @@
from .workspace import Workspace


if t.TYPE_CHECKING:
import typing_extensions as te

from ..io import Bolt

_R = t.TypeVar("_R")
_P = te.ParamSpec("_P")


log = getLogger("neo4j")


Expand Down Expand Up @@ -237,7 +235,7 @@ def cancel(self) -> None:

def run(
self,
query: t.Union[str, Query],
query: t.Union[te.LiteralString, Query],
parameters: t.Optional[t.Dict[str, t.Any]] = None,
**kwargs: t.Any
) -> Result:
Expand Down
6 changes: 5 additions & 1 deletion neo4j/_sync/work/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
from .result import Result


if t.TYPE_CHECKING:
import typing_extensions as te


__all__ = (
"ManagedTransaction",
"Transaction",
Expand Down Expand Up @@ -95,7 +99,7 @@ def _consume_results(self):

def run(
self,
query: str,
query: te.LiteralString,
parameters: t.Optional[t.Dict[str, t.Any]] = None,
**kwparameters: t.Any
) -> Result:
Expand Down
6 changes: 4 additions & 2 deletions neo4j/work/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@


if t.TYPE_CHECKING:
import typing_extensions as te

_T = t.TypeVar("_T")


Expand All @@ -38,7 +40,7 @@ class Query:
"""
def __init__(
self,
text: str,
text: te.LiteralString,
metadata: t.Optional[t.Dict[str, t.Any]] = None,
timeout: t.Optional[float] = None
) -> None:
Expand All @@ -47,7 +49,7 @@ def __init__(
self.metadata = metadata
self.timeout = timeout

def __str__(self) -> str:
def __str__(self) -> te.LiteralString:
return str(self.text)


Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
entry_points = {
"console_scripts": [
Expand Down
4 changes: 2 additions & 2 deletions testkit/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ENV PYENV_ROOT /.pyenv
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH

# Setup python version
ENV PYTHON_VERSIONS 3.7 3.8 3.9 3.10
ENV PYTHON_VERSIONS 3.7 3.8 3.9 3.10 3.11

RUN for version in $PYTHON_VERSIONS; do \
pyenv install $version:latest; \
Expand All @@ -53,7 +53,7 @@ RUN pyenv global $(pyenv versions --bare --skip-aliases)
# Install Latest pip and setuptools for each environment
# + tox and tools for starting the tests
# https://pip.pypa.io/en/stable/news/
RUN for version in 3.7 3.8 3.9 3.10; do \
RUN for version in $PYTHON_VERSIONS; do \
python$version -m pip install -U pip && \
python$version -m pip install -U setuptools && \
python$version -m pip install -U coverage tox tox-factor; \
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py{37,38,39,310}-{unit,integration,performance}
envlist = py{37,38,39,310,311}-{unit,integration,performance}

[testenv]
passenv =
Expand Down