arcadedb 26.11.1.dev0__py3-none-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.
Potentially problematic release.
This version of arcadedb might be problematic. Click here for more details.
- arcadedb/__init__.py +150 -0
- arcadedb/_build_info.py +5 -0
- arcadedb/_dist_conflict.py +81 -0
- arcadedb/_jpype_stub.py +44 -0
- arcadedb/_logdir.py +182 -0
- arcadedb/_logging.py +23 -0
- arcadedb/_native/__init__.py +53 -0
- arcadedb/_native/bulk.py +71 -0
- arcadedb/_native/codec.py +306 -0
- arcadedb/_native/columnar.py +373 -0
- arcadedb/_native/columns.py +167 -0
- arcadedb/_native/database.py +1448 -0
- arcadedb/_native/extras.py +1075 -0
- arcadedb/_native/interrupts.py +41 -0
- arcadedb/_native/lib.py +418 -0
- arcadedb/_native/libarcadedb.dll +4 -0
- arcadedb/_native/records.py +211 -0
- arcadedb/_native/schema_objects.py +473 -0
- arcadedb/_native/server.py +118 -0
- arcadedb/_params.py +48 -0
- arcadedb/aot.py +478 -0
- arcadedb/async_executor.py +1119 -0
- arcadedb/callbacks.py +419 -0
- arcadedb/core.py +2429 -0
- arcadedb/dataframe.py +521 -0
- arcadedb/exceptions.py +147 -0
- arcadedb/exporter.py +309 -0
- arcadedb/graph.py +324 -0
- arcadedb/graph_batch.py +570 -0
- arcadedb/importer.py +338 -0
- arcadedb/jvm.py +873 -0
- arcadedb/results.py +1399 -0
- arcadedb/schema.py +758 -0
- arcadedb/server.py +347 -0
- arcadedb/transactions.py +38 -0
- arcadedb/type_conversion.py +812 -0
- arcadedb/vector.py +885 -0
- arcadedb-26.11.1.dev0.dist-info/METADATA +115 -0
- arcadedb-26.11.1.dev0.dist-info/RECORD +48 -0
- arcadedb-26.11.1.dev0.dist-info/WHEEL +5 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/LICENSE +201 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/NOTICE +29 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +3629 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/licenses/Apache-2.0.txt +201 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/licenses/GraalVM-CE-LICENSE.txt +430 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/licenses/GraalVM-THIRD_PARTY_LICENSE.txt +7357 -0
- arcadedb-26.11.1.dev0.dist-info/licenses/licenses/GraalVM-native-image-LICENSE.txt +379 -0
- arcadedb-26.11.1.dev0.dist-info/top_level.txt +1 -0
arcadedb/__init__.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ArcadeDB Python Embedded Bindings
|
|
3
|
+
|
|
4
|
+
A native Python bindings for ArcadeDB that embeds the Java database engine
|
|
5
|
+
directly in the Python process using JPype.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Which build this is: 'jvm' (the arcadedb-jvm distribution, the Java engine through JPype) or 'native' (the
|
|
9
|
+
# arcadedb distribution, the engine as a GraalVM native-image library). The import name is arcadedb for both.
|
|
10
|
+
# `_native.distribution_backend()` is fixed when the wheel is built (the native wheel build flips one line in
|
|
11
|
+
# `_native/__init__.py`). `__backend__` is the engine that runs now: ARCADEDB_BACKEND=native can switch it on in a
|
|
12
|
+
# source checkout, where a developer loads a library they built themselves.
|
|
13
|
+
from . import _native as _native_backend
|
|
14
|
+
from ._dist_conflict import DISTRIBUTIONS as _DISTRIBUTIONS
|
|
15
|
+
from ._dist_conflict import check as _check_distributions
|
|
16
|
+
|
|
17
|
+
_DISTRIBUTION_BACKEND = _native_backend.distribution_backend()
|
|
18
|
+
__backend__ = "native" if _native_backend.enabled() else "jvm"
|
|
19
|
+
|
|
20
|
+
# Both distributions install a package named arcadedb into the same directory; pip overwrites one with the other
|
|
21
|
+
# silently. Refuse to import when the other one is installed too, before anything else runs.
|
|
22
|
+
_check_distributions(_DISTRIBUTION_BACKEND)
|
|
23
|
+
|
|
24
|
+
# The installed distribution's version is the single source of truth: the
|
|
25
|
+
# wheel is versioned from the release tag, while the generated _version.py
|
|
26
|
+
# is derived from the engine jar's version (scripts/write_version.py), so the
|
|
27
|
+
# two disagree on tagged releases
|
|
28
|
+
# (a 26.8.1.dev20 wheel carried __version__ == "26.8.1.dev0"). Fall back to
|
|
29
|
+
# the generated file, then to a placeholder, for source checkouts that were
|
|
30
|
+
# never installed.
|
|
31
|
+
try:
|
|
32
|
+
from importlib.metadata import version as _dist_version
|
|
33
|
+
|
|
34
|
+
__version__ = _dist_version(_DISTRIBUTIONS[_DISTRIBUTION_BACKEND])
|
|
35
|
+
except Exception: # not installed: fall back to the build-time file
|
|
36
|
+
try:
|
|
37
|
+
from ._version import __version__
|
|
38
|
+
except ModuleNotFoundError:
|
|
39
|
+
__version__ = "0.0.0"
|
|
40
|
+
|
|
41
|
+
from ._build_info import ENGINE_VERSION, NATIVE_IMAGE, NATIVE_MARCH, SOURCE_COMMIT # noqa: F401
|
|
42
|
+
|
|
43
|
+
# Import async execution
|
|
44
|
+
from .async_executor import AsyncExecutor
|
|
45
|
+
|
|
46
|
+
# Import record event handle
|
|
47
|
+
from .callbacks import EventHandle
|
|
48
|
+
|
|
49
|
+
# Import core database classes
|
|
50
|
+
from .core import (
|
|
51
|
+
Database,
|
|
52
|
+
DatabaseFactory,
|
|
53
|
+
create_database,
|
|
54
|
+
database_exists,
|
|
55
|
+
open_database,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Import exceptions
|
|
59
|
+
from .exceptions import ArcadeDBError
|
|
60
|
+
|
|
61
|
+
# Import exporter classes
|
|
62
|
+
from .exporter import export_database, export_to_csv
|
|
63
|
+
|
|
64
|
+
# Import graph classes
|
|
65
|
+
from .graph import Document, Edge, Vertex
|
|
66
|
+
|
|
67
|
+
# Import graph batch helper
|
|
68
|
+
from .graph_batch import GraphBatch
|
|
69
|
+
|
|
70
|
+
# Import importer helpers
|
|
71
|
+
from .importer import ImportResult
|
|
72
|
+
|
|
73
|
+
# Which engine this install actually carries. __version__ is the PACKAGE
|
|
74
|
+
# version and can disagree with the bundled JARs without any error.
|
|
75
|
+
from .jvm import jar_fingerprint
|
|
76
|
+
|
|
77
|
+
# Import result classes
|
|
78
|
+
from .results import Result, ResultSet
|
|
79
|
+
|
|
80
|
+
# Import schema classes
|
|
81
|
+
from .schema import IndexType, PropertyType, Schema
|
|
82
|
+
|
|
83
|
+
# Import server classes
|
|
84
|
+
from .server import ArcadeDBServer, create_server
|
|
85
|
+
|
|
86
|
+
# Import transaction management
|
|
87
|
+
from .transactions import TransactionContext
|
|
88
|
+
|
|
89
|
+
# Import type conversion utilities
|
|
90
|
+
from .type_conversion import convert_java_to_python, convert_python_to_java
|
|
91
|
+
|
|
92
|
+
# Import vector utilities and index
|
|
93
|
+
from .vector import (
|
|
94
|
+
VectorIndex,
|
|
95
|
+
to_java_byte_array,
|
|
96
|
+
to_java_float_array,
|
|
97
|
+
to_java_int_array,
|
|
98
|
+
to_python_array,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
__all__ = [
|
|
102
|
+
"__version__",
|
|
103
|
+
"ENGINE_VERSION",
|
|
104
|
+
"NATIVE_IMAGE",
|
|
105
|
+
"SOURCE_COMMIT",
|
|
106
|
+
"__backend__",
|
|
107
|
+
# Exceptions
|
|
108
|
+
"ArcadeDBError",
|
|
109
|
+
# Core classes
|
|
110
|
+
"Database",
|
|
111
|
+
"DatabaseFactory",
|
|
112
|
+
"EventHandle",
|
|
113
|
+
# Record wrappers
|
|
114
|
+
"Document",
|
|
115
|
+
"Vertex",
|
|
116
|
+
"Edge",
|
|
117
|
+
# Build provenance
|
|
118
|
+
"jar_fingerprint",
|
|
119
|
+
"create_database",
|
|
120
|
+
"open_database",
|
|
121
|
+
"database_exists",
|
|
122
|
+
# Result classes
|
|
123
|
+
"ResultSet",
|
|
124
|
+
"Result",
|
|
125
|
+
# Schema classes
|
|
126
|
+
"Schema",
|
|
127
|
+
"IndexType",
|
|
128
|
+
"PropertyType",
|
|
129
|
+
# Transaction management
|
|
130
|
+
"TransactionContext",
|
|
131
|
+
# Async execution
|
|
132
|
+
"AsyncExecutor",
|
|
133
|
+
"GraphBatch",
|
|
134
|
+
"ImportResult",
|
|
135
|
+
# Type conversion
|
|
136
|
+
"convert_java_to_python",
|
|
137
|
+
"convert_python_to_java",
|
|
138
|
+
# Vector search
|
|
139
|
+
"VectorIndex",
|
|
140
|
+
"to_java_byte_array",
|
|
141
|
+
"to_java_float_array",
|
|
142
|
+
"to_java_int_array",
|
|
143
|
+
"to_python_array",
|
|
144
|
+
# Server classes
|
|
145
|
+
"ArcadeDBServer",
|
|
146
|
+
"create_server",
|
|
147
|
+
# Data export
|
|
148
|
+
"export_database",
|
|
149
|
+
"export_to_csv",
|
|
150
|
+
]
|
arcadedb/_build_info.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Written by native-wheel/build_wheel.py."""
|
|
2
|
+
ENGINE_VERSION = "26.10.2" # the ArcadeDB engine release compiled into libarcadedb.dll
|
|
3
|
+
NATIVE_IMAGE = "GraalVM CE 25.4.4.1.1, -O2, -march=compatibility"
|
|
4
|
+
NATIVE_MARCH = "compatibility" # lib.py checks the CPU only for an x86-64-v3 image
|
|
5
|
+
SOURCE_COMMIT = "00f29ff00e17670c21d9049755e9095cbe6c8eb2" # ArcadeData/arcadedb-python commit of the bindings and the Java glue
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Refuse to import when both distributions that provide the `arcadedb` import are installed.
|
|
2
|
+
|
|
3
|
+
This repository builds two distributions. `arcadedb` is the native build (the engine as a GraalVM native-image
|
|
4
|
+
library, loaded with ctypes) and `arcadedb-jvm` is the JVM build (the Java engine in-process through JPype).
|
|
5
|
+
Both put a package named `arcadedb` into the same site-packages directory, so installing one after the other
|
|
6
|
+
overwrites files of the first with files of the second, and pip says nothing. What would be imported is a mix
|
|
7
|
+
of two builds. Package metadata still lists both, so this module looks there and raises an ImportError that
|
|
8
|
+
names both packages and says how to fix it, instead of letting the mix run.
|
|
9
|
+
|
|
10
|
+
`check(backend)` is called first thing in `arcadedb/__init__.py`. `backend` is the build this code belongs to:
|
|
11
|
+
"jvm" or "native".
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from importlib import metadata
|
|
17
|
+
from typing import Callable
|
|
18
|
+
|
|
19
|
+
# backend -> the distribution that carries it
|
|
20
|
+
DISTRIBUTIONS = {"native": "arcadedb", "jvm": "arcadedb-jvm"}
|
|
21
|
+
|
|
22
|
+
# The page that explains the two packages (the "Which package?" page of the docs, linked from the README).
|
|
23
|
+
DOCS_URL = "https://github.com/ArcadeData/arcadedb-python/blob/main/docs/modules/ROOT/pages/which-package.adoc"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DistributionConflictError(ImportError):
|
|
27
|
+
"""Both the native `arcadedb` and the JVM `arcadedb-jvm` distribution are installed."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def other_backend(backend: str) -> str:
|
|
31
|
+
if backend not in DISTRIBUTIONS:
|
|
32
|
+
raise ValueError(f"backend must be 'jvm' or 'native', not {backend!r}")
|
|
33
|
+
return "jvm" if backend == "native" else "native"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def installed(
|
|
37
|
+
backends: tuple[str, ...] = ("native", "jvm"),
|
|
38
|
+
lookup: Callable[[str], str] = metadata.version,
|
|
39
|
+
) -> list[str]:
|
|
40
|
+
"""The distribution names of `backends` that are installed, in the order given.
|
|
41
|
+
|
|
42
|
+
`lookup` raises `importlib.metadata.PackageNotFoundError` for a name that is not installed.
|
|
43
|
+
"""
|
|
44
|
+
found = []
|
|
45
|
+
for backend in backends:
|
|
46
|
+
name = DISTRIBUTIONS[backend]
|
|
47
|
+
try:
|
|
48
|
+
lookup(name)
|
|
49
|
+
except metadata.PackageNotFoundError:
|
|
50
|
+
continue
|
|
51
|
+
found.append(name)
|
|
52
|
+
return found
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def message(backend: str) -> str:
|
|
56
|
+
own, other = DISTRIBUTIONS[backend], DISTRIBUTIONS[other_backend(backend)]
|
|
57
|
+
kind = {"native": "native", "jvm": "JVM"}
|
|
58
|
+
return (
|
|
59
|
+
f"The distribution '{own}' ({kind[backend]} build) cannot be imported while '{other}' "
|
|
60
|
+
f"({kind[other_backend(backend)]} build) is installed. "
|
|
61
|
+
f"Both provide the Python package 'arcadedb', so each install overwrote files of the other "
|
|
62
|
+
f"without a message, and what you would import is a mix of the two builds. "
|
|
63
|
+
f"Keep one: run 'pip uninstall {DISTRIBUTIONS['native']} {DISTRIBUTIONS['jvm']}', then install "
|
|
64
|
+
f"'{DISTRIBUTIONS['native']}' (native, the default for most embedded use) or "
|
|
65
|
+
f"'{DISTRIBUTIONS['jvm']}' (JVM, for heavy long-running loads). "
|
|
66
|
+
f"Which one to pick: {DOCS_URL}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def check(backend: str, lookup: Callable[[str], str] = metadata.version) -> None:
|
|
71
|
+
"""Raise DistributionConflictError when the distribution of the other backend is installed too.
|
|
72
|
+
|
|
73
|
+
Never raises for anything else: an import must not fail because the metadata could not be read.
|
|
74
|
+
"""
|
|
75
|
+
other = other_backend(backend)
|
|
76
|
+
try:
|
|
77
|
+
found = installed((other,), lookup)
|
|
78
|
+
except Exception: # unreadable metadata, an odd sys.path entry: say nothing
|
|
79
|
+
return
|
|
80
|
+
if found:
|
|
81
|
+
raise DistributionConflictError(message(backend))
|
arcadedb/_jpype_stub.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Stand-in for ``jpype`` inside the native `arcadedb` wheel (generated package only).
|
|
2
|
+
|
|
3
|
+
The Python sources are shared with ``arcadedb-jvm`` and import ``jpype`` at module level. The native wheel has no JVM and does
|
|
4
|
+
not depend on JPype, so the build rewrites those imports to this module. Names resolve to placeholder classes: ``isinstance`` checks
|
|
5
|
+
against them are simply False, and any attempt to call, subscript or build one raises :class:`NotOnNativeYet`. ``isJVMStarted()`` is
|
|
6
|
+
always False. Code paths that need real JPype (``jvm_kwargs``, ``get_java_database``, Java-object APIs) are the JVM-only APIs listed in the README.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from arcadedb._native import NotOnNativeYet
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JException(Exception):
|
|
13
|
+
"""Placeholder for ``jpype.JException`` (never raised here)."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _Placeholder(type):
|
|
17
|
+
def __call__(cls, *a, **k):
|
|
18
|
+
raise NotOnNativeYet(
|
|
19
|
+
f"{cls.__name__} needs a JVM; this is the native build (the arcadedb wheel); the JVM build is arcadedb-jvm"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def __getitem__(cls, item):
|
|
23
|
+
raise NotOnNativeYet(
|
|
24
|
+
f"{cls.__name__}[...] needs a JVM; this is the native backend"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def __getattr__(cls, name):
|
|
28
|
+
if name.startswith("__"):
|
|
29
|
+
raise AttributeError(name)
|
|
30
|
+
raise NotOnNativeYet(
|
|
31
|
+
f"{cls.__name__}.{name} needs a JVM; this is the native backend"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def isJVMStarted() -> bool:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def __getattr__(name: str):
|
|
40
|
+
if name.startswith("__"):
|
|
41
|
+
raise AttributeError(name)
|
|
42
|
+
ph = _Placeholder(name, (), {})
|
|
43
|
+
globals()[name] = ph
|
|
44
|
+
return ph
|
arcadedb/_logdir.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Where the engine writes its log, for both builds (the JVM build and the native build).
|
|
2
|
+
|
|
3
|
+
The engine's own ``arcadedb-log.properties`` writes ``./log/arcadedb.log`` into the working directory. Both builds move the file
|
|
4
|
+
log to a per-user directory instead: ``ARCADEDB_LOG_DIR`` if set, else ``arcadedb/log`` under the user's state directory. They
|
|
5
|
+
write a ``logging.properties`` there whose file pattern is ``${arcadedb.server.logsDirectory}/arcadedb.log`` and point two
|
|
6
|
+
system properties at it: ``arcadedb.server.logsDirectory`` (the directory) and ``java.util.logging.config.file`` (that file).
|
|
7
|
+
The JVM build sets them as JVM arguments (``jvm.py``), the native build as isolate arguments (``_native/lib.py``). The engine
|
|
8
|
+
reads both when it first logs, so they have to be in place before the first engine call. This module has no JVM and no JPype.
|
|
9
|
+
|
|
10
|
+
The engine's console handler is off by default on Windows (``ARCADEDB_LOG_CONSOLE`` turns it on or off on any platform). On Windows
|
|
11
|
+
the runtime caches the OS handle of stdout and stderr at start-up. A host that redirects fd 1 or 2 afterwards (pytest's fd capture,
|
|
12
|
+
a service, an embedding application) closes that handle, a database file opened later can get the same handle value, and the next
|
|
13
|
+
console log line is written into that file. The log file does not have that problem: the engine opens it itself.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
from ._logging import get_logger
|
|
24
|
+
|
|
25
|
+
_LOGGER = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
LOG_DIR_ENV = "ARCADEDB_LOG_DIR"
|
|
28
|
+
LOG_CONSOLE_ENV = "ARCADEDB_LOG_CONSOLE"
|
|
29
|
+
LOGGING_CONFIG_NAME = "logging.properties"
|
|
30
|
+
# The config without the console handler has its own file name, so that two processes with different console settings that share a
|
|
31
|
+
# log directory never replace each other's file between the write and the engine's first log call.
|
|
32
|
+
FILE_ONLY_CONFIG_NAME = "logging-file-only.properties"
|
|
33
|
+
CONSOLE_HANDLER = "java.util.logging.ConsoleHandler"
|
|
34
|
+
|
|
35
|
+
# The engine's arcadedb-log.properties without its file pattern, for the native build: the image holds the engine's copy as a
|
|
36
|
+
# resource, but an isolate argument has to name a real file, and there is no jar to read it from. tests/test_log_dir.py compares
|
|
37
|
+
# this text with the file in the engine jar, so an engine upgrade that changes the file fails there until this is updated.
|
|
38
|
+
NATIVE_ENGINE_LOG_PROPERTIES = """\
|
|
39
|
+
handlers = java.util.logging.ConsoleHandler, java.util.logging.FileHandler
|
|
40
|
+
.level = INFO
|
|
41
|
+
io.undertow.level=WARNING
|
|
42
|
+
org.xnio.level=WARNING
|
|
43
|
+
org.jboss.level=WARNING
|
|
44
|
+
org.apache.ratis.grpc.server.GrpcLogAppender.level = SEVERE
|
|
45
|
+
com.arcadedb.level = INFO
|
|
46
|
+
java.util.logging.ConsoleHandler.level = INFO
|
|
47
|
+
java.util.logging.ConsoleHandler.formatter = com.arcadedb.utility.AnsiLogFormatter
|
|
48
|
+
java.util.logging.FileHandler.level = INFO
|
|
49
|
+
java.util.logging.FileHandler.formatter = com.arcadedb.log.LogFormatter
|
|
50
|
+
java.util.logging.FileHandler.limit=100000000
|
|
51
|
+
java.util.logging.FileHandler.count=10
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def state_base_dir() -> Path:
|
|
56
|
+
"""Per-user base directory for state and logs, by platform (stdlib only).
|
|
57
|
+
|
|
58
|
+
The home directory is read only where the platform needs it, and when it
|
|
59
|
+
cannot be determined (no HOME or USERPROFILE, as in a stripped-down service
|
|
60
|
+
environment) the system temp directory is used, so this never raises.
|
|
61
|
+
"""
|
|
62
|
+
if sys.platform == "win32":
|
|
63
|
+
local = os.environ.get("LOCALAPPDATA")
|
|
64
|
+
if local:
|
|
65
|
+
return Path(local)
|
|
66
|
+
elif sys.platform != "darwin":
|
|
67
|
+
xdg = os.environ.get("XDG_STATE_HOME")
|
|
68
|
+
# The XDG spec ignores a relative value.
|
|
69
|
+
if xdg and os.path.isabs(xdg):
|
|
70
|
+
return Path(xdg)
|
|
71
|
+
try:
|
|
72
|
+
home = Path.home()
|
|
73
|
+
except (RuntimeError, KeyError, OSError):
|
|
74
|
+
return Path(tempfile.gettempdir())
|
|
75
|
+
if sys.platform == "win32":
|
|
76
|
+
return home / "AppData" / "Local"
|
|
77
|
+
if sys.platform == "darwin":
|
|
78
|
+
return home / "Library" / "Logs"
|
|
79
|
+
return home / ".local" / "state"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def default_log_dir() -> Path:
|
|
83
|
+
"""Where the engine log, the crash log and logging.properties go.
|
|
84
|
+
|
|
85
|
+
``ARCADEDB_LOG_DIR`` if set, else ``arcadedb/log`` under the
|
|
86
|
+
per-user state directory. Never the working directory.
|
|
87
|
+
"""
|
|
88
|
+
override = os.environ.get(LOG_DIR_ENV)
|
|
89
|
+
if override:
|
|
90
|
+
return Path(os.path.abspath(os.path.expanduser(override)))
|
|
91
|
+
return state_base_dir() / "arcadedb" / "log"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def console_enabled() -> bool:
|
|
95
|
+
"""Whether the engine logs to the console (stdout/stderr) as well as to its log file.
|
|
96
|
+
|
|
97
|
+
``ARCADEDB_LOG_CONSOLE`` decides on every platform: ``1``, ``true``, ``yes`` or ``on`` turns the console handler on; ``0``,
|
|
98
|
+
``false``, ``no`` or ``off`` turns it off. Unset (or any other value), it is on everywhere except Windows, where it is off
|
|
99
|
+
(see the module docstring for why). A ``java.util.logging.config.file`` the caller passes replaces all of this.
|
|
100
|
+
"""
|
|
101
|
+
value = os.environ.get(LOG_CONSOLE_ENV, "").strip().lower()
|
|
102
|
+
if value in ("1", "true", "yes", "on"):
|
|
103
|
+
return True
|
|
104
|
+
if value in ("0", "false", "no", "off"):
|
|
105
|
+
return False
|
|
106
|
+
return sys.platform != "win32"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def logging_config_name(console: Optional[bool] = None) -> str:
|
|
110
|
+
"""The file name of the generated logging config: ``logging.properties`` with the console handler, ``logging-file-only.properties`` without."""
|
|
111
|
+
if console is None:
|
|
112
|
+
console = console_enabled()
|
|
113
|
+
return LOGGING_CONFIG_NAME if console else FILE_ONLY_CONFIG_NAME
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def render_logging_config(engine_text: str, console: Optional[bool] = None) -> str:
|
|
117
|
+
"""The engine's logging config with the file log in ``logsDirectory``, and without the console handler unless ``console``.
|
|
118
|
+
|
|
119
|
+
The jar's own file hard-codes ``./log/arcadedb.log``, which lands in the
|
|
120
|
+
working directory. The engine resolves ``${arcadedb.server.logsDirectory}``
|
|
121
|
+
in the pattern, and the argument that sets it is added by the caller.
|
|
122
|
+
``console`` None means :func:`console_enabled`.
|
|
123
|
+
"""
|
|
124
|
+
if console is None:
|
|
125
|
+
console = console_enabled()
|
|
126
|
+
lines = []
|
|
127
|
+
for line in engine_text.splitlines():
|
|
128
|
+
if re.match(r"\s*java\.util\.logging\.FileHandler\.pattern\s*[=:]", line):
|
|
129
|
+
continue
|
|
130
|
+
if not console:
|
|
131
|
+
handlers = re.match(r"(\s*handlers\s*[=:]\s*)(.*)$", line)
|
|
132
|
+
if handlers:
|
|
133
|
+
kept = [
|
|
134
|
+
h
|
|
135
|
+
for h in re.split(r"[,\s]+", handlers.group(2).strip())
|
|
136
|
+
if h and h != CONSOLE_HANDLER
|
|
137
|
+
]
|
|
138
|
+
line = f"{handlers.group(1)}{', '.join(kept)}"
|
|
139
|
+
elif re.match(r"\s*java\.util\.logging\.ConsoleHandler\.", line):
|
|
140
|
+
continue
|
|
141
|
+
lines.append(line)
|
|
142
|
+
lines.append(
|
|
143
|
+
"java.util.logging.FileHandler.pattern="
|
|
144
|
+
"${arcadedb.server.logsDirectory}/arcadedb.log"
|
|
145
|
+
)
|
|
146
|
+
return "\n".join(lines) + "\n"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def usable_log_dir(log_dir: Path) -> Path:
|
|
150
|
+
"""Create the log directory; return it, or a fresh directory under the system temp directory when it cannot be created or
|
|
151
|
+
written (a read-only home, for example), so that nothing is ever written to the working directory.
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
if not os.access(log_dir, os.W_OK | os.X_OK):
|
|
156
|
+
raise OSError(f"{log_dir} is not writable")
|
|
157
|
+
except OSError as exc:
|
|
158
|
+
fallback = Path(tempfile.mkdtemp(prefix="arcadedb-log-"))
|
|
159
|
+
_LOGGER.warning(
|
|
160
|
+
"Cannot use %s for ArcadeDB logs (%s); using %s. Set %s to choose one.",
|
|
161
|
+
log_dir,
|
|
162
|
+
exc,
|
|
163
|
+
fallback,
|
|
164
|
+
LOG_DIR_ENV,
|
|
165
|
+
)
|
|
166
|
+
return fallback
|
|
167
|
+
return log_dir
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def write_logging_config(log_dir: Path, engine_text: str) -> None:
|
|
171
|
+
"""Write the logging config in ``log_dir`` under :func:`logging_config_name` (replaced atomically, and only when its text changed)."""
|
|
172
|
+
console = console_enabled()
|
|
173
|
+
name = logging_config_name(console)
|
|
174
|
+
config = log_dir / name
|
|
175
|
+
rendered = render_logging_config(engine_text, console)
|
|
176
|
+
try:
|
|
177
|
+
if not config.exists() or config.read_text("iso-8859-1") != rendered:
|
|
178
|
+
tmp = log_dir / f"{name}.{os.getpid()}.tmp"
|
|
179
|
+
tmp.write_text(rendered, encoding="iso-8859-1", newline="\n")
|
|
180
|
+
os.replace(tmp, config)
|
|
181
|
+
except OSError as exc:
|
|
182
|
+
_LOGGER.warning("Cannot write %s: %s", config, exc)
|
arcadedb/_logging.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Internal logging helpers for arcadedb.
|
|
2
|
+
|
|
3
|
+
Centralises the pattern for swallowing exceptions in finalizers and
|
|
4
|
+
best-effort cleanup paths so the suppression is observable at DEBUG level
|
|
5
|
+
instead of being silently dropped.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_logger(name: str) -> logging.Logger:
|
|
12
|
+
"""Return a namespaced logger for an arcadedb submodule."""
|
|
13
|
+
return logging.getLogger(name)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def log_swallowed_exception(logger: logging.Logger, context: str) -> None:
|
|
17
|
+
"""Log the currently-handled exception at DEBUG with full traceback.
|
|
18
|
+
|
|
19
|
+
Use only inside an `except` block where the caller has decided the
|
|
20
|
+
error is non-fatal (e.g. JVM finalizer paths, optional best-effort
|
|
21
|
+
rollback) and continuing is the right behaviour.
|
|
22
|
+
"""
|
|
23
|
+
logger.debug("Swallowed exception %s", context, exc_info=True)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""The native backend: the engine as a GraalVM native-image shared library (``libarcadedb``) that Python loads with ``ctypes``. No JVM, no JPype.
|
|
2
|
+
|
|
3
|
+
It is the only backend of the ``arcadedb`` wheel. In a source checkout or in the ``arcadedb-jvm`` wheel, ``ARCADEDB_BACKEND=native`` selects it
|
|
4
|
+
(with ``ARCADEDB_NATIVE_LIB`` pointing at a library you built); the default there is JPype. The public API is the JVM build's, and the same
|
|
5
|
+
test suite (``tests/``) runs against both.
|
|
6
|
+
|
|
7
|
+
On native: databases and the factory, ``query``/``command`` (SQL and openCypher), transactions and ``run_in_transaction``, results as rows,
|
|
8
|
+
columns and Arrow, pandas, polars and numpy frames, the ``Document``/``Vertex``/``Edge`` wrappers, the schema (types, properties, indexes),
|
|
9
|
+
vector indexes (``find_nearest``, the PQ ``find_nearest_approximate``, ``get_metadata``, ``get_stats``), ``insert_many``, ``GraphBatch``,
|
|
10
|
+
the ``AsyncExecutor`` (``transaction`` included), Python functions in SQL and record callbacks, importers and exporters, and the in-process
|
|
11
|
+
server.
|
|
12
|
+
|
|
13
|
+
Not on native, by design (each raises :class:`NotOnNativeYet`): the Java objects of the JVM build. These are ``get_java_database``,
|
|
14
|
+
``get_java_document``, and the Java object a ``Schema``, type or index method returns where the native wrapper has no such method.
|
|
15
|
+
There is no JVM to start or tune: ``jvm_kwargs`` is ignored with a warning.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NotOnNativeYet(NotImplementedError):
|
|
22
|
+
"""The call hands out a Java object of the JVM build (``get_java_database``, ``get_java_document``, a Schema method's Java object), which the native build does not have."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# The backend used when ARCADEDB_BACKEND is unset. "jpype" here, which is the arcadedb-jvm wheel; the build of the native
|
|
26
|
+
# `arcadedb` wheel (native/wheel/build_wheel.py) rewrites this one line to "native".
|
|
27
|
+
_DEFAULT_BACKEND = "native"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def distribution_backend() -> str:
|
|
31
|
+
"""The build this code was packaged as: "native" (the `arcadedb` distribution) or "jvm" (the `arcadedb-jvm` distribution).
|
|
32
|
+
|
|
33
|
+
Fixed when the wheel is built. ``ARCADEDB_BACKEND`` does not change it: the distribution check in ``arcadedb/__init__.py``
|
|
34
|
+
asks which package this file belongs to, not which engine a developer switched on.
|
|
35
|
+
"""
|
|
36
|
+
return "native" if _DEFAULT_BACKEND == "native" else "jvm"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def enabled() -> bool:
|
|
40
|
+
"""True when the native backend is selected: ``ARCADEDB_BACKEND=native``, or unset in the `arcadedb` (native) wheel (read at call time, so a test can switch it)."""
|
|
41
|
+
return os.environ.get("ARCADEDB_BACKEND", _DEFAULT_BACKEND).lower() == "native"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def needs_jvm(what: str):
|
|
45
|
+
"""A call that hands out a Java object of the JVM build. Native has no JVM, by design: the message names the build that has it."""
|
|
46
|
+
raise NotOnNativeYet(
|
|
47
|
+
f"{what} needs the JVM build (the arcadedb-jvm package); this is the native build (the arcadedb package)"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def not_yet(what: str):
|
|
52
|
+
"""A feature the native build is still missing (none today). A final release allows no test that ends here."""
|
|
53
|
+
raise NotOnNativeYet(f"{what} is not on the native backend yet")
|
arcadedb/_native/bulk.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Columnar bulk insert through one crossing (``arcadedb_n_insert_colp``): whole columns go over by address, with no JSON text. Numpy
|
|
2
|
+
ints, floats and bools, lists of one value type (None is a null), strings (UTF-8 bytes plus offsets, as Arrow lays them out), float32
|
|
3
|
+
vector columns and Arrow arrays are laid out by :mod:`.columnar`; anything else (nested values, mixed-type columns, date-times,
|
|
4
|
+
decimals, integers past 64 bits) returns None and the caller uses the per-row JSON path. The documents are built image-side as
|
|
5
|
+
``DocumentBatcher.insertColumns`` builds them on JPype. ``ARCADEDB_NATIVE_BULK=0`` turns the columnar path off.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from . import columnar, lib
|
|
11
|
+
|
|
12
|
+
_ENABLED = os.environ.get("ARCADEDB_NATIVE_BULK", "1") != "0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _usable():
|
|
16
|
+
return (
|
|
17
|
+
_ENABLED
|
|
18
|
+
and columnar._np is not None
|
|
19
|
+
and lib._lib is not None
|
|
20
|
+
and hasattr(lib._lib, "arcadedb_n_insert_colp")
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _send(db, type_name, packed, rows, commit_every, prefix):
|
|
25
|
+
from .database import _call
|
|
26
|
+
|
|
27
|
+
spec = columnar.spec_of(packed)
|
|
28
|
+
reply = _call(
|
|
29
|
+
prefix,
|
|
30
|
+
"arcadedb_n_insert_colp",
|
|
31
|
+
db._h,
|
|
32
|
+
lib.b(type_name),
|
|
33
|
+
lib.b(columnar.header_of(rows, commit_every, packed)),
|
|
34
|
+
spec,
|
|
35
|
+
len(spec),
|
|
36
|
+
) # `packed` holds the arrays the addresses point into until the call returns
|
|
37
|
+
return int(reply["n"])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def insert_columns_fast(
|
|
41
|
+
db, type_name, columns, commit_every, declared=None, prefix="Failed to bulk-insert"
|
|
42
|
+
):
|
|
43
|
+
"""Insert ``columns`` (name -> column) in one crossing and return the row count, or None when a column cannot be sent this way.
|
|
44
|
+
|
|
45
|
+
``declared``: a dict of declared property types when the caller already knows them (the DataFrame path), else they are read from the
|
|
46
|
+
schema if a column needs them.
|
|
47
|
+
"""
|
|
48
|
+
if not _usable():
|
|
49
|
+
return None
|
|
50
|
+
packed = columnar.pack(columns, columnar.Declared(db, type_name, declared))
|
|
51
|
+
if packed is None:
|
|
52
|
+
return None
|
|
53
|
+
rows, cols = packed
|
|
54
|
+
return _send(db, type_name, cols, rows, commit_every, prefix)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def insert_rows_columnar(db, type_name, chunk):
|
|
58
|
+
"""One ``insert_many`` chunk (a list of dicts with the same keys) through the columnar entry inside the caller's transaction (no
|
|
59
|
+
commit here). Returns True when it was sent, False when the chunk needs the JSON path.
|
|
60
|
+
"""
|
|
61
|
+
if not _usable():
|
|
62
|
+
return False
|
|
63
|
+
columns = columnar.rows_to_columns(chunk)
|
|
64
|
+
if columns is None:
|
|
65
|
+
return False
|
|
66
|
+
packed = columnar.pack(columns, columnar.Declared(db, type_name))
|
|
67
|
+
if packed is None:
|
|
68
|
+
return False
|
|
69
|
+
rows, cols = packed
|
|
70
|
+
_send(db, type_name, cols, rows, 0, f"Failed to bulk-insert into '{type_name}'")
|
|
71
|
+
return True
|