lora-python 0.2.0__cp38-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lora_python/__init__.py +133 -0
- lora_python/_async.py +113 -0
- lora_python/_native.pyd +0 -0
- lora_python/_native.pyi +39 -0
- lora_python/py.typed +0 -0
- lora_python/types.py +342 -0
- lora_python-0.2.0.dist-info/METADATA +138 -0
- lora_python-0.2.0.dist-info/RECORD +11 -0
- lora_python-0.2.0.dist-info/WHEEL +4 -0
- lora_python-0.2.0.dist-info/licenses/LICENSE +87 -0
- lora_python-0.2.0.dist-info/sboms/lora-python.cyclonedx.json +2755 -0
lora_python/__init__.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""lora-python — typed Python bindings for the Lora graph engine.
|
|
2
|
+
|
|
3
|
+
Two public classes:
|
|
4
|
+
|
|
5
|
+
- ``Database`` : synchronous, PyO3-backed. Holds an in-memory graph and
|
|
6
|
+
runs queries with the GIL released.
|
|
7
|
+
- ``AsyncDatabase``: pure-Python asyncio wrapper. Delegates to a
|
|
8
|
+
background thread via ``asyncio.to_thread`` so the
|
|
9
|
+
event loop stays responsive during heavier queries.
|
|
10
|
+
|
|
11
|
+
Both classes share the same public result / value / param shapes (see
|
|
12
|
+
``lora_python.types``) so switching between sync and async usage is a
|
|
13
|
+
method-name change, nothing else.
|
|
14
|
+
|
|
15
|
+
Example
|
|
16
|
+
-------
|
|
17
|
+
>>> from lora_python import Database
|
|
18
|
+
>>> db = Database.create()
|
|
19
|
+
>>> db.execute("CREATE (:Person {name: $n})", {"n": "Alice"})
|
|
20
|
+
>>> r = db.execute("MATCH (n:Person) RETURN n.name AS name")
|
|
21
|
+
>>> r["rows"]
|
|
22
|
+
[{'name': 'Alice'}]
|
|
23
|
+
|
|
24
|
+
Async:
|
|
25
|
+
|
|
26
|
+
>>> import asyncio
|
|
27
|
+
>>> from lora_python import AsyncDatabase
|
|
28
|
+
>>> async def main():
|
|
29
|
+
... db = await AsyncDatabase.create()
|
|
30
|
+
... await db.execute("CREATE (:Person {name: 'Alice'})")
|
|
31
|
+
... return await db.execute("MATCH (n:Person) RETURN n.name AS name")
|
|
32
|
+
>>> asyncio.run(main())["rows"]
|
|
33
|
+
[{'name': 'Alice'}]
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from ._native import (
|
|
39
|
+
Database,
|
|
40
|
+
LoraError,
|
|
41
|
+
LoraQueryError,
|
|
42
|
+
InvalidParamsError,
|
|
43
|
+
__version__,
|
|
44
|
+
)
|
|
45
|
+
from ._async import AsyncDatabase
|
|
46
|
+
from . import types
|
|
47
|
+
from .types import (
|
|
48
|
+
LoraParam,
|
|
49
|
+
LoraParams,
|
|
50
|
+
LoraValue,
|
|
51
|
+
QueryResult,
|
|
52
|
+
LoraNode,
|
|
53
|
+
LoraRelationship,
|
|
54
|
+
LoraPath,
|
|
55
|
+
LoraDate,
|
|
56
|
+
LoraTime,
|
|
57
|
+
LoraLocalTime,
|
|
58
|
+
LoraDateTime,
|
|
59
|
+
LoraLocalDateTime,
|
|
60
|
+
LoraDuration,
|
|
61
|
+
LoraPoint,
|
|
62
|
+
LoraCartesianPoint,
|
|
63
|
+
LoraCartesianPoint3D,
|
|
64
|
+
LoraWgs84Point,
|
|
65
|
+
LoraWgs84Point3D,
|
|
66
|
+
LoraVector,
|
|
67
|
+
LoraVectorCoordinateType,
|
|
68
|
+
date,
|
|
69
|
+
time,
|
|
70
|
+
localtime,
|
|
71
|
+
datetime,
|
|
72
|
+
localdatetime,
|
|
73
|
+
duration,
|
|
74
|
+
cartesian,
|
|
75
|
+
cartesian_3d,
|
|
76
|
+
wgs84,
|
|
77
|
+
wgs84_3d,
|
|
78
|
+
vector,
|
|
79
|
+
is_node,
|
|
80
|
+
is_relationship,
|
|
81
|
+
is_path,
|
|
82
|
+
is_point,
|
|
83
|
+
is_temporal,
|
|
84
|
+
is_vector,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"Database",
|
|
89
|
+
"AsyncDatabase",
|
|
90
|
+
"LoraError",
|
|
91
|
+
"LoraQueryError",
|
|
92
|
+
"InvalidParamsError",
|
|
93
|
+
"__version__",
|
|
94
|
+
"types",
|
|
95
|
+
# re-exports
|
|
96
|
+
"LoraParam",
|
|
97
|
+
"LoraParams",
|
|
98
|
+
"LoraValue",
|
|
99
|
+
"QueryResult",
|
|
100
|
+
"LoraNode",
|
|
101
|
+
"LoraRelationship",
|
|
102
|
+
"LoraPath",
|
|
103
|
+
"LoraDate",
|
|
104
|
+
"LoraTime",
|
|
105
|
+
"LoraLocalTime",
|
|
106
|
+
"LoraDateTime",
|
|
107
|
+
"LoraLocalDateTime",
|
|
108
|
+
"LoraDuration",
|
|
109
|
+
"LoraPoint",
|
|
110
|
+
"LoraCartesianPoint",
|
|
111
|
+
"LoraCartesianPoint3D",
|
|
112
|
+
"LoraWgs84Point",
|
|
113
|
+
"LoraWgs84Point3D",
|
|
114
|
+
"LoraVector",
|
|
115
|
+
"LoraVectorCoordinateType",
|
|
116
|
+
"date",
|
|
117
|
+
"time",
|
|
118
|
+
"localtime",
|
|
119
|
+
"datetime",
|
|
120
|
+
"localdatetime",
|
|
121
|
+
"duration",
|
|
122
|
+
"cartesian",
|
|
123
|
+
"cartesian_3d",
|
|
124
|
+
"wgs84",
|
|
125
|
+
"wgs84_3d",
|
|
126
|
+
"vector",
|
|
127
|
+
"is_node",
|
|
128
|
+
"is_relationship",
|
|
129
|
+
"is_path",
|
|
130
|
+
"is_point",
|
|
131
|
+
"is_temporal",
|
|
132
|
+
"is_vector",
|
|
133
|
+
]
|
lora_python/_async.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Async-compatible Database wrapper.
|
|
2
|
+
|
|
3
|
+
The PyO3 ``Database`` is synchronous — the engine itself is synchronous
|
|
4
|
+
Rust — but it releases the GIL while running a query, which means the
|
|
5
|
+
heavy work can safely be hoisted off the asyncio event-loop thread.
|
|
6
|
+
|
|
7
|
+
``AsyncDatabase`` does exactly that: each ``await db.execute(...)`` call
|
|
8
|
+
dispatches the sync ``Database.execute`` onto a worker thread via
|
|
9
|
+
``asyncio.to_thread`` on Python 3.9+, or the equivalent
|
|
10
|
+
``loop.run_in_executor`` polyfill on 3.8. The event loop stays free to
|
|
11
|
+
service other coroutines while the engine runs.
|
|
12
|
+
|
|
13
|
+
This is the pragmatic, well-understood pattern for async-wrapping a
|
|
14
|
+
CPU-bound Rust function in Python. It requires no unsafe lifetime
|
|
15
|
+
juggling and stays trivially debuggable.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import contextvars
|
|
22
|
+
import functools
|
|
23
|
+
import sys
|
|
24
|
+
from typing import Any, Callable, Mapping, Optional, TypeVar
|
|
25
|
+
|
|
26
|
+
from ._native import Database as _Database
|
|
27
|
+
from .types import LoraParams, QueryResult
|
|
28
|
+
|
|
29
|
+
_T = TypeVar("_T")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# `asyncio.to_thread` landed in Python 3.9 (bpo-32309). Provide a direct
|
|
33
|
+
# equivalent on 3.8 so the non-blocking behaviour is identical: dispatch
|
|
34
|
+
# the call onto the running loop's default executor with the current
|
|
35
|
+
# context copied over, just like the CPython implementation.
|
|
36
|
+
if sys.version_info >= (3, 9):
|
|
37
|
+
_to_thread = asyncio.to_thread # type: ignore[attr-defined]
|
|
38
|
+
else: # pragma: no cover — exercised in the 3.8 CI leg
|
|
39
|
+
|
|
40
|
+
async def _to_thread(
|
|
41
|
+
func: Callable[..., _T], /, *args: Any, **kwargs: Any
|
|
42
|
+
) -> _T:
|
|
43
|
+
loop = asyncio.get_running_loop()
|
|
44
|
+
ctx = contextvars.copy_context()
|
|
45
|
+
return await loop.run_in_executor(
|
|
46
|
+
None, functools.partial(ctx.run, func, *args, **kwargs)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AsyncDatabase:
|
|
51
|
+
"""asyncio-compatible handle to an in-memory Lora database.
|
|
52
|
+
|
|
53
|
+
All methods delegate to the sync ``Database`` on a worker thread so
|
|
54
|
+
the event loop is never blocked by engine work. Methods are coroutines
|
|
55
|
+
so normal async usage looks like::
|
|
56
|
+
|
|
57
|
+
db = await AsyncDatabase.create()
|
|
58
|
+
result = await db.execute("MATCH (n) RETURN n")
|
|
59
|
+
|
|
60
|
+
Concurrency: a single ``AsyncDatabase`` wraps a single ``Database``;
|
|
61
|
+
concurrent ``execute`` coroutines serialise on the underlying engine's
|
|
62
|
+
mutex but do not block the event loop while waiting.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
__slots__ = ("_inner",)
|
|
66
|
+
|
|
67
|
+
def __init__(self, inner: _Database) -> None:
|
|
68
|
+
self._inner = inner
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
async def create(cls) -> "AsyncDatabase":
|
|
72
|
+
"""Construct a fresh in-memory database. Async for API symmetry."""
|
|
73
|
+
# Construction is cheap (Arc::new + Mutex::new) so we stay on-thread.
|
|
74
|
+
return cls(_Database())
|
|
75
|
+
|
|
76
|
+
async def execute(
|
|
77
|
+
self,
|
|
78
|
+
query: str,
|
|
79
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
80
|
+
) -> QueryResult:
|
|
81
|
+
"""Run a Lora query on a background thread.
|
|
82
|
+
|
|
83
|
+
Returns ``{"columns": [...], "rows": [...]}``. Raises
|
|
84
|
+
``LoraQueryError`` on engine failure or ``InvalidParamsError``
|
|
85
|
+
on a malformed parameter.
|
|
86
|
+
"""
|
|
87
|
+
# The helper runs the callable on the loop's default
|
|
88
|
+
# ThreadPoolExecutor. Since Database.execute releases the GIL,
|
|
89
|
+
# other coroutines on the same event loop are free to progress.
|
|
90
|
+
return await _to_thread(
|
|
91
|
+
self._inner.execute,
|
|
92
|
+
query,
|
|
93
|
+
dict(params) if params is not None else None,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def clear(self) -> None:
|
|
97
|
+
"""Drop every node and relationship."""
|
|
98
|
+
await _to_thread(self._inner.clear)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def node_count(self) -> int:
|
|
102
|
+
return self._inner.node_count
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def relationship_count(self) -> int:
|
|
106
|
+
return self._inner.relationship_count
|
|
107
|
+
|
|
108
|
+
def __repr__(self) -> str: # pragma: no cover — cosmetic
|
|
109
|
+
return (
|
|
110
|
+
f"<lora_python.AsyncDatabase "
|
|
111
|
+
f"nodes={self._inner.node_count} "
|
|
112
|
+
f"relationships={self._inner.relationship_count}>"
|
|
113
|
+
)
|
lora_python/_native.pyd
ADDED
|
Binary file
|
lora_python/_native.pyi
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Type stubs for the PyO3 extension module.
|
|
2
|
+
|
|
3
|
+
Mirrors what the native ``_native`` module exposes. The high-level
|
|
4
|
+
``lora_python`` package re-exports these with richer types from
|
|
5
|
+
``lora_python.types``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Mapping, Optional
|
|
9
|
+
|
|
10
|
+
from .types import QueryResult
|
|
11
|
+
|
|
12
|
+
__version__: str
|
|
13
|
+
|
|
14
|
+
class LoraError(Exception):
|
|
15
|
+
"""Base class for Lora engine errors."""
|
|
16
|
+
|
|
17
|
+
class LoraQueryError(LoraError):
|
|
18
|
+
"""Parse / analyze / execute failure."""
|
|
19
|
+
|
|
20
|
+
class InvalidParamsError(LoraError):
|
|
21
|
+
"""A parameter value could not be mapped to a Lora value."""
|
|
22
|
+
|
|
23
|
+
class Database:
|
|
24
|
+
"""In-memory Lora graph database (sync, PyO3)."""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None: ...
|
|
27
|
+
@staticmethod
|
|
28
|
+
def create() -> "Database": ...
|
|
29
|
+
def execute(
|
|
30
|
+
self,
|
|
31
|
+
query: str,
|
|
32
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
33
|
+
) -> QueryResult: ...
|
|
34
|
+
def clear(self) -> None: ...
|
|
35
|
+
@property
|
|
36
|
+
def node_count(self) -> int: ...
|
|
37
|
+
@property
|
|
38
|
+
def relationship_count(self) -> int: ...
|
|
39
|
+
def __repr__(self) -> str: ...
|
lora_python/py.typed
ADDED
|
File without changes
|
lora_python/types.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Typed Python value model for lora-python.
|
|
2
|
+
|
|
3
|
+
Kept conceptually aligned with the shared TS contract used by
|
|
4
|
+
``lora-node`` / ``lora-wasm`` (see ``crates/shared-ts/types.ts``).
|
|
5
|
+
Python uses pragmatic representations:
|
|
6
|
+
|
|
7
|
+
- Scalars pass through as Python natives (``None``, ``bool``, ``int``,
|
|
8
|
+
``float``, ``str``).
|
|
9
|
+
- Lists and maps come back as ``list`` / ``dict``.
|
|
10
|
+
- Graph, temporal, and spatial values come back as ``TypedDict``s with
|
|
11
|
+
a ``kind`` discriminator. They're plain dicts at runtime — no class
|
|
12
|
+
wrappers to unpack — and narrow cleanly under ``typing.TYPE_CHECKING``.
|
|
13
|
+
|
|
14
|
+
If a caller wants structured objects, the ``is_node`` / ``is_temporal``
|
|
15
|
+
helpers make narrowing explicit.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, List, Literal, Mapping, TypedDict, Union
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Forward-declare the recursive value union.
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
# ``LoraValue`` is recursive; PEP 604 union syntax works on 3.10+, but we
|
|
27
|
+
# fall back to ``Union`` for 3.8/3.9 support.
|
|
28
|
+
LoraValue = Union[
|
|
29
|
+
None,
|
|
30
|
+
bool,
|
|
31
|
+
int,
|
|
32
|
+
float,
|
|
33
|
+
str,
|
|
34
|
+
List["LoraValue"],
|
|
35
|
+
Mapping[str, "LoraValue"],
|
|
36
|
+
"LoraNode",
|
|
37
|
+
"LoraRelationship",
|
|
38
|
+
"LoraPath",
|
|
39
|
+
"LoraDate",
|
|
40
|
+
"LoraTime",
|
|
41
|
+
"LoraLocalTime",
|
|
42
|
+
"LoraDateTime",
|
|
43
|
+
"LoraLocalDateTime",
|
|
44
|
+
"LoraDuration",
|
|
45
|
+
"LoraPoint",
|
|
46
|
+
"LoraVector",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
LoraParam = Union[
|
|
50
|
+
None,
|
|
51
|
+
bool,
|
|
52
|
+
int,
|
|
53
|
+
float,
|
|
54
|
+
str,
|
|
55
|
+
List["LoraParam"],
|
|
56
|
+
Mapping[str, "LoraParam"],
|
|
57
|
+
"LoraDate",
|
|
58
|
+
"LoraTime",
|
|
59
|
+
"LoraLocalTime",
|
|
60
|
+
"LoraDateTime",
|
|
61
|
+
"LoraLocalDateTime",
|
|
62
|
+
"LoraDuration",
|
|
63
|
+
"LoraPoint",
|
|
64
|
+
"LoraVector",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
LoraParams = Mapping[str, LoraParam]
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# Structural values — TypedDicts with a `kind` discriminator.
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class LoraNode(TypedDict):
|
|
75
|
+
kind: Literal["node"]
|
|
76
|
+
id: int
|
|
77
|
+
labels: List[str]
|
|
78
|
+
properties: Mapping[str, LoraValue]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class LoraRelationship(TypedDict):
|
|
82
|
+
kind: Literal["relationship"]
|
|
83
|
+
id: int
|
|
84
|
+
startId: int
|
|
85
|
+
endId: int
|
|
86
|
+
type: str
|
|
87
|
+
properties: Mapping[str, LoraValue]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class LoraPath(TypedDict):
|
|
91
|
+
kind: Literal["path"]
|
|
92
|
+
nodes: List[int]
|
|
93
|
+
rels: List[int]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Temporal — ISO-8601 tagged.
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LoraDate(TypedDict):
|
|
102
|
+
kind: Literal["date"]
|
|
103
|
+
iso: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class LoraTime(TypedDict):
|
|
107
|
+
kind: Literal["time"]
|
|
108
|
+
iso: str
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class LoraLocalTime(TypedDict):
|
|
112
|
+
kind: Literal["localtime"]
|
|
113
|
+
iso: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class LoraDateTime(TypedDict):
|
|
117
|
+
kind: Literal["datetime"]
|
|
118
|
+
iso: str
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class LoraLocalDateTime(TypedDict):
|
|
122
|
+
kind: Literal["localdatetime"]
|
|
123
|
+
iso: str
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class LoraDuration(TypedDict):
|
|
127
|
+
kind: Literal["duration"]
|
|
128
|
+
iso: str
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# Spatial
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Point variants.
|
|
138
|
+
#
|
|
139
|
+
# Python's ``TypedDict`` cannot model "required only on some variants" the
|
|
140
|
+
# way a TS discriminated union can, so we declare one dict per CRS and
|
|
141
|
+
# union them into ``LoraPoint``. Narrow via ``is_point`` + ``srid`` / ``crs``.
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class LoraCartesianPoint(TypedDict):
|
|
146
|
+
kind: Literal["point"]
|
|
147
|
+
srid: Literal[7203]
|
|
148
|
+
crs: Literal["cartesian"]
|
|
149
|
+
x: float
|
|
150
|
+
y: float
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class LoraCartesianPoint3D(TypedDict):
|
|
154
|
+
kind: Literal["point"]
|
|
155
|
+
srid: Literal[9157]
|
|
156
|
+
crs: Literal["cartesian-3D"]
|
|
157
|
+
x: float
|
|
158
|
+
y: float
|
|
159
|
+
z: float
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class LoraWgs84Point(TypedDict):
|
|
163
|
+
kind: Literal["point"]
|
|
164
|
+
srid: Literal[4326]
|
|
165
|
+
crs: Literal["WGS-84-2D"]
|
|
166
|
+
x: float
|
|
167
|
+
y: float
|
|
168
|
+
longitude: float
|
|
169
|
+
latitude: float
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class LoraWgs84Point3D(TypedDict):
|
|
173
|
+
kind: Literal["point"]
|
|
174
|
+
srid: Literal[4979]
|
|
175
|
+
crs: Literal["WGS-84-3D"]
|
|
176
|
+
x: float
|
|
177
|
+
y: float
|
|
178
|
+
z: float
|
|
179
|
+
longitude: float
|
|
180
|
+
latitude: float
|
|
181
|
+
height: float
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
LoraPoint = Union[
|
|
185
|
+
LoraCartesianPoint,
|
|
186
|
+
LoraCartesianPoint3D,
|
|
187
|
+
LoraWgs84Point,
|
|
188
|
+
LoraWgs84Point3D,
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# Vector
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
LoraVectorCoordinateType = Literal[
|
|
198
|
+
"FLOAT64", "FLOAT32", "INTEGER", "INTEGER32", "INTEGER16", "INTEGER8"
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class LoraVector(TypedDict):
|
|
203
|
+
kind: Literal["vector"]
|
|
204
|
+
dimension: int
|
|
205
|
+
coordinateType: LoraVectorCoordinateType
|
|
206
|
+
values: List[float]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# Query result
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class QueryResult(TypedDict):
|
|
215
|
+
columns: List[str]
|
|
216
|
+
rows: List[Mapping[str, LoraValue]]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# Constructors for param-side temporal/spatial values.
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def date(iso: str) -> LoraDate:
|
|
225
|
+
return {"kind": "date", "iso": iso}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def time(iso: str) -> LoraTime:
|
|
229
|
+
return {"kind": "time", "iso": iso}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def localtime(iso: str) -> LoraLocalTime:
|
|
233
|
+
return {"kind": "localtime", "iso": iso}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def datetime(iso: str) -> LoraDateTime:
|
|
237
|
+
return {"kind": "datetime", "iso": iso}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def localdatetime(iso: str) -> LoraLocalDateTime:
|
|
241
|
+
return {"kind": "localdatetime", "iso": iso}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def duration(iso: str) -> LoraDuration:
|
|
245
|
+
return {"kind": "duration", "iso": iso}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def vector(
|
|
249
|
+
values: List[float],
|
|
250
|
+
dimension: int,
|
|
251
|
+
coordinate_type: LoraVectorCoordinateType,
|
|
252
|
+
) -> LoraVector:
|
|
253
|
+
"""Build a LoraVector param/value in the canonical tagged shape."""
|
|
254
|
+
return {
|
|
255
|
+
"kind": "vector",
|
|
256
|
+
"dimension": dimension,
|
|
257
|
+
"coordinateType": coordinate_type,
|
|
258
|
+
"values": list(values),
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def cartesian(x: float, y: float) -> LoraCartesianPoint:
|
|
263
|
+
return {"kind": "point", "srid": 7203, "crs": "cartesian", "x": x, "y": y}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def cartesian_3d(x: float, y: float, z: float) -> LoraCartesianPoint3D:
|
|
267
|
+
return {
|
|
268
|
+
"kind": "point",
|
|
269
|
+
"srid": 9157,
|
|
270
|
+
"crs": "cartesian-3D",
|
|
271
|
+
"x": x,
|
|
272
|
+
"y": y,
|
|
273
|
+
"z": z,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def wgs84(longitude: float, latitude: float) -> LoraWgs84Point:
|
|
278
|
+
return {
|
|
279
|
+
"kind": "point",
|
|
280
|
+
"srid": 4326,
|
|
281
|
+
"crs": "WGS-84-2D",
|
|
282
|
+
"x": longitude,
|
|
283
|
+
"y": latitude,
|
|
284
|
+
"longitude": longitude,
|
|
285
|
+
"latitude": latitude,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def wgs84_3d(
|
|
290
|
+
longitude: float, latitude: float, height: float
|
|
291
|
+
) -> LoraWgs84Point3D:
|
|
292
|
+
return {
|
|
293
|
+
"kind": "point",
|
|
294
|
+
"srid": 4979,
|
|
295
|
+
"crs": "WGS-84-3D",
|
|
296
|
+
"x": longitude,
|
|
297
|
+
"y": latitude,
|
|
298
|
+
"z": height,
|
|
299
|
+
"longitude": longitude,
|
|
300
|
+
"latitude": latitude,
|
|
301
|
+
"height": height,
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
# Narrowing helpers.
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _is_tagged(v: Any, expected: str) -> bool:
|
|
311
|
+
return isinstance(v, dict) and v.get("kind") == expected
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def is_node(v: Any) -> bool:
|
|
315
|
+
"""Return True if ``v`` is a Lora node value."""
|
|
316
|
+
return _is_tagged(v, "node")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def is_relationship(v: Any) -> bool:
|
|
320
|
+
return _is_tagged(v, "relationship")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def is_path(v: Any) -> bool:
|
|
324
|
+
return _is_tagged(v, "path")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def is_point(v: Any) -> bool:
|
|
328
|
+
return _is_tagged(v, "point")
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
_TEMPORAL_KINDS = frozenset(
|
|
332
|
+
{"date", "time", "localtime", "datetime", "localdatetime", "duration"}
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def is_temporal(v: Any) -> bool:
|
|
337
|
+
return isinstance(v, dict) and v.get("kind") in _TEMPORAL_KINDS
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def is_vector(v: Any) -> bool:
|
|
341
|
+
"""Return True if ``v`` is a Lora VECTOR value."""
|
|
342
|
+
return _is_tagged(v, "vector")
|