Skip to content
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
3 changes: 3 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Topics

+ :ref:`vector-data-types`

+ :ref:`other-data-types`

+ :ref:`breaking-changes`


Expand All @@ -51,6 +53,7 @@ Topics
types/spatial.rst
types/temporal.rst
types/vector.rst
types/other.rst
breaking_changes.rst


Expand Down
12 changes: 12 additions & 0 deletions docs/source/types/other.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.. _other-data-types:

***********************
Other Driver Data Types
***********************

================
Unsupported Type
================

.. autoclass:: neo4j.types.UnsupportedType
:members:
6 changes: 5 additions & 1 deletion src/neo4j/_codec/hydration/v3/hydration_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@
)
from ..v1.hydration_handler import _GraphHydrator
from ..v2 import temporal as temporal_v2
from . import vector
from . import (
unsupported,
vector,
)


class HydrationHandler(HydrationHandlerABC): # type: ignore[no-redef]
Expand All @@ -64,6 +67,7 @@ def __init__(self):
b"d": temporal_v2.hydrate_datetime, # no time zone
b"E": temporal_v1.hydrate_duration,
b"V": vector.hydrate_vector,
b"?": unsupported.hydrate_unsupported,
}
self.dehydration_hooks.update(
exact_types={
Expand Down
42 changes: 42 additions & 0 deletions src/neo4j/_codec/hydration/v3/unsupported.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from .... import _typing as t
from ....types import UnsupportedType


def hydrate_unsupported(
name: str,
min_bolt_major: int,
min_bolt_minor: int,
extra: dict[str, t.Any],
) -> UnsupportedType:
"""
Hydrator for `UnsupportedType` values.

:param name: name of the type
:param min_bolt_major: minimum major version of the Bolt protocol
supporting this type
:param min_bolt_minor: minimum minor version of the Bolt protocol
supporting this type
:param extra: dict containing optional "message" key
:returns: UnsupportedType instance
"""
return UnsupportedType._new(
name,
(min_bolt_major, min_bolt_minor),
extra.get("message"),
)
2 changes: 1 addition & 1 deletion src/neo4j/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def protocol_version(self) -> tuple[int, int]:
"""
Bolt protocol version with which the remote server communicates.

This is returned as a 2-tuple:class:`tuple` of ``(major, minor)``
This is returned as a 2-:class:`tuple` of ``(major, minor)``
integers.
"""
return self._protocol_version
Expand Down
21 changes: 21 additions & 0 deletions src/neo4j/types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ._unsupported import UnsupportedType


__all__ = [
"UnsupportedType",
]
102 changes: 102 additions & 0 deletions src/neo4j/types/_unsupported.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from __future__ import annotations

from .. import _typing as t # noqa: TC001


class UnsupportedType:
"""
Represents a type unknown to the driver, received from the server.

This type is used for instance when a newer DBMS produces a result
containing a type that the current version of the driver does not yet
understand.

Note that this type may only be received from the server, but cannot be
sent to the server (e.g., as a query parameter).

The attributes exposed by this type are meant for displaying and debugging
purposes.
They may change in future versions of the server, and should not be relied
upon for any logic in your application.
If your application requires handling this type, you must upgrade your
driver to a version that supports it.
"""

_name: str
_minimum_protocol_version: tuple[int, int]
_message: str | None

@classmethod
def _new(
cls,
name: str,
minimum_protocol_version: tuple[int, int],
message: str | None,
) -> t.Self:
obj = cls.__new__(cls)
obj._name = name
obj._minimum_protocol_version = minimum_protocol_version
obj._message = message
return obj

@property
def name(self) -> str:
"""The name of the type."""
return self._name

@property
def minimum_protocol_version(self) -> tuple[int, int]:
"""
The minimum required Bolt protocol version that supports this type.

This is a 2-:class:`tuple` of ``(major, minor)`` integers.

To understand which driver version this corresponds to, refer to the
driver's release notes or documentation.


.. seealso::
<
link to evolving doc listing which version of the driver
supports which Bolt version
>
"""
# TODO fix link above
return self._minimum_protocol_version

@property
def message(self) -> str | None:
"""
Optional, further details about this type.

Any additional information provided by the server about this type.
"""
return self._message

def __str__(self) -> str:
return f"{self.__class__.__name__}<{self._name}>"

def __repr__(self) -> str:
args = [
f" name={self._name!r}",
f" minimum_protocol_version={self._minimum_protocol_version!r}",
]
if self._message is not None:
args.append(f" message={self._message!r}")
return f"<{self.__class__.__name__}{''.join(args)}>"
1 change: 1 addition & 0 deletions testkitbackend/test_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"Feature:API:Summary:GqlStatusObjects": true,
"Feature:API:Type.Spatial": true,
"Feature:API:Type.Temporal": true,
"Feature:API:Type.UnsupportedType": true,
"Feature:API:Type.Vector": true,
"Feature:Auth:Bearer": true,
"Feature:Auth:Custom": true,
Expand Down
15 changes: 15 additions & 0 deletions testkitbackend/totestkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
Duration,
Time,
)
from neo4j.types import UnsupportedType
from neo4j.vector import Vector

from ._warning_check import warning_check
Expand Down Expand Up @@ -310,6 +311,20 @@ def to(name, val):
"data": " ".join(f"{byte:02x}" for byte in v.raw()),
},
}
if isinstance(v, UnsupportedType):
data = {
"name": v.name,
"minimumProtocol": (
f"{v.minimum_protocol_version[0]}"
f".{v.minimum_protocol_version[1]}"
),
}
if v.message is not None:
data["message"] = v.message
return {
"name": "CypherUnsupportedType",
"data": data,
}

raise ValueError("Unhandled type:" + str(type(v)))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import pytest

from neo4j._codec.hydration.v3 import HydrationHandler
from neo4j.types import UnsupportedType

from .._base import HydrationHandlerTestBase


class TestUnsupportedTypeDehydration(HydrationHandlerTestBase):
@pytest.fixture
def hydration_handler(self):
return HydrationHandler()

def test_has_no_transformer(self, hydration_scope):
value = UnsupportedType._new("UUID", (255, 255), None)

transformer = hydration_scope.dehydration_hooks.get_transformer(value)

assert transformer is None
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import pytest

from neo4j._codec.hydration.v3 import HydrationHandler
from neo4j._codec.packstream import Structure
from neo4j.types import UnsupportedType

from .._base import HydrationHandlerTestBase


class TestUnsupportedTypeHydration(HydrationHandlerTestBase):
@pytest.fixture
def hydration_handler(self):
return HydrationHandler()

@pytest.mark.parametrize("with_message", (True, False))
def test_vector(self, hydration_scope, with_message):
name = "2Cool4UType"
min_bolt_major = 42
min_bolt_minor = 128
extra = {}
if with_message:
expected_message = "If only your driver were cooler..."
extra["message"] = expected_message
else:
expected_message = None
expected_min_version = (min_bolt_major, min_bolt_minor)

struct = Structure(b"?", name, min_bolt_major, min_bolt_minor, extra)
unsupported = hydration_scope.hydration_hooks[Structure](struct)

assert isinstance(unsupported, UnsupportedType)
assert unsupported.name == name
assert unsupported.minimum_protocol_version == expected_min_version
assert unsupported.message == expected_message
14 changes: 14 additions & 0 deletions tests/unit/common/types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (c) "Neo4j"
# Neo4j Sweden AB [https://neo4j.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Loading