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
22 changes: 13 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,19 @@ See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.
- `ERROR_REWRITE_MAP`
- `client_errors`
- `transient_errors`
- `GraphDatabase`
- `.bolt_driver`
- `.neo4j_driver`
- `BoltDriver` and `Neo4jDriver`
- `.open`
- `.parse_target`
- `.default_host`
- `.default_port`
- `.default_target`
- `neo4j.spatial`
- `hydrate_point`
- `dehydrate_point`
- `point_type`
- `neo4j.GraphDatabase`
- `.bolt_driver`
- `.neo4j_driver`
- `neo4j.BoltDriver` and `neo4j.Neo4jDriver`
- `.open`
- `.parse_target`
- `.default_host`
- `.default_port`
- `.default_target`
- Raise `ConfigurationError` instead of ignoring the routing context (URI query parameters) when creating a direct
driver ("bolt[+s[sc]]://" scheme).
- Change behavior of closed drivers:
Expand Down
6 changes: 3 additions & 3 deletions src/neo4j/_codec/hydration/v1/spatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
# limitations under the License.


from ...._spatial import (
from ....spatial import (
_srid_table,
Point,
srid_table,
)
from ...packstream import Structure

Expand All @@ -29,7 +29,7 @@ def hydrate_point(srid, *coordinates):
raised if no such subclass can be found.
"""
try:
point_class, dim = srid_table[srid]
point_class, dim = _srid_table[srid]
except KeyError:
point = Point(coordinates)
point.srid = srid
Expand Down
2 changes: 1 addition & 1 deletion src/neo4j/_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@

from ._codec.hydration import BrokenHydrationObject
from ._conf import iter_items
from ._spatial import Point
from .exceptions import BrokenRecordError
from .graph import (
Node,
Path,
Relationship,
)
from .spatial import Point
from .time import (
Date,
DateTime,
Expand Down
137 changes: 0 additions & 137 deletions src/neo4j/_spatial/__init__.py

This file was deleted.

160 changes: 107 additions & 53 deletions src/neo4j/spatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,75 +16,129 @@

"""Spatial data types for interchange with the DBMS."""

from __future__ import annotations
from __future__ import annotations as _

import typing as _t
from threading import Lock as _Lock


__all__ = [
"CartesianPoint",
"Point",
"WGS84Point",
"dehydrate_point",
"hydrate_point",
"point_type",
]

import typing as t
from functools import wraps

from .._codec.hydration.v1 import spatial as _hydration
# SRID to subclass mappings
_srid_table: dict[int, tuple[type[Point], int]] = {}
_srid_table_lock = _Lock()


if t.TYPE_CHECKING:
from typing_extensions import deprecated
else:
from .._warnings import deprecated

from .._spatial import (
CartesianPoint,
Point,
point_type as _point_type,
WGS84Point,
)


# TODO: 6.0 - remove
@deprecated(
"hydrate_point is considered an internal function and will be removed in "
"a future version"
)
def hydrate_point(srid, *coordinates):
class Point(tuple[float, ...]):
"""
Create a new instance of a Point subclass from a raw set of fields.
Base-class for spatial data.

The subclass chosen is determined by the
given SRID; a ValueError will be raised if no such
subclass can be found.
A point within a geometric space. This type is generally used via its
subclasses and should not be instantiated directly unless there is no
subclass defined for the required SRID.

:param iterable:
An iterable of coordinates.
All items will be converted to :class:`float`.
:type iterable: Iterable[float]
"""
return _hydration.hydrate_point(srid, *coordinates)

#: The SRID (spatial reference identifier) of the spatial data.
#: A number that identifies the coordinate system the spatial type is to
#: be interpreted in.
srid: int | None

# TODO: 6.0 - remove
@deprecated(
"hydrate_point is considered an internal function and will be removed in "
"a future version"
)
@wraps(_hydration.dehydrate_point)
def dehydrate_point(value):
"""
Dehydrator for Point data.
if _t.TYPE_CHECKING:

:param value:
:type value: Point
:returns:
"""
return _hydration.dehydrate_point(value)
@property
def x(self) -> float: ...

@property
def y(self) -> float: ...

@property
def z(self) -> float: ...

def __new__(cls, iterable: _t.Iterable[float]) -> Point:
return tuple.__new__(cls, map(float, iterable))

def __repr__(self) -> str:
return f"POINT({' '.join(map(str, self))})"

def __eq__(self, other: object) -> bool:
try:
return type(self) is type(other) and tuple(self) == tuple(
_t.cast(Point, other)
)
except (AttributeError, TypeError):
return False

def __ne__(self, other: object) -> bool:
return not self.__eq__(other)

def __hash__(self):
return hash(type(self)) ^ hash(tuple(self))


def _point_type(
name: str, fields: tuple[str, str, str], srid_map: dict[int, int]
) -> type[Point]:
"""Dynamically create a Point subclass."""

def srid(self):
try:
return srid_map[len(self)]
except KeyError:
return None

attributes = {"srid": property(srid)}

# TODO: 6.0 - remove
@deprecated(
"point_type is considered an internal function and will be removed in "
"a future version"
)
@wraps(_point_type)
def point_type(name, fields, srid_map):
return _point_type(name, fields, srid_map)
for index, subclass_field in enumerate(fields):

def accessor(self, i=index, f=subclass_field):
try:
return self[i]
except IndexError:
raise AttributeError(f) from None

for field_alias in (subclass_field, "xyz"[index]):
attributes[field_alias] = property(accessor)

cls = _t.cast(type[Point], type(name, (Point,), attributes))

with _srid_table_lock:
for dim, srid_ in srid_map.items():
_srid_table[srid_] = (cls, dim)

return cls


# Point subclass definitions
if _t.TYPE_CHECKING:

class CartesianPoint(Point): ...
else:
CartesianPoint = _point_type(
"CartesianPoint", ("x", "y", "z"), {2: 7203, 3: 9157}
)

if _t.TYPE_CHECKING:

class WGS84Point(Point):
@property
def longitude(self) -> float: ...

@property
def latitude(self) -> float: ...

@property
def height(self) -> float: ...
else:
WGS84Point = _point_type(
"WGS84Point", ("longitude", "latitude", "height"), {2: 4326, 3: 4979}
)
Loading