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.

Files changed (48) hide show
  1. arcadedb/__init__.py +150 -0
  2. arcadedb/_build_info.py +5 -0
  3. arcadedb/_dist_conflict.py +81 -0
  4. arcadedb/_jpype_stub.py +44 -0
  5. arcadedb/_logdir.py +182 -0
  6. arcadedb/_logging.py +23 -0
  7. arcadedb/_native/__init__.py +53 -0
  8. arcadedb/_native/bulk.py +71 -0
  9. arcadedb/_native/codec.py +306 -0
  10. arcadedb/_native/columnar.py +373 -0
  11. arcadedb/_native/columns.py +167 -0
  12. arcadedb/_native/database.py +1448 -0
  13. arcadedb/_native/extras.py +1075 -0
  14. arcadedb/_native/interrupts.py +41 -0
  15. arcadedb/_native/lib.py +418 -0
  16. arcadedb/_native/libarcadedb.dll +4 -0
  17. arcadedb/_native/records.py +211 -0
  18. arcadedb/_native/schema_objects.py +473 -0
  19. arcadedb/_native/server.py +118 -0
  20. arcadedb/_params.py +48 -0
  21. arcadedb/aot.py +478 -0
  22. arcadedb/async_executor.py +1119 -0
  23. arcadedb/callbacks.py +419 -0
  24. arcadedb/core.py +2429 -0
  25. arcadedb/dataframe.py +521 -0
  26. arcadedb/exceptions.py +147 -0
  27. arcadedb/exporter.py +309 -0
  28. arcadedb/graph.py +324 -0
  29. arcadedb/graph_batch.py +570 -0
  30. arcadedb/importer.py +338 -0
  31. arcadedb/jvm.py +873 -0
  32. arcadedb/results.py +1399 -0
  33. arcadedb/schema.py +758 -0
  34. arcadedb/server.py +347 -0
  35. arcadedb/transactions.py +38 -0
  36. arcadedb/type_conversion.py +812 -0
  37. arcadedb/vector.py +885 -0
  38. arcadedb-26.11.1.dev0.dist-info/METADATA +115 -0
  39. arcadedb-26.11.1.dev0.dist-info/RECORD +48 -0
  40. arcadedb-26.11.1.dev0.dist-info/WHEEL +5 -0
  41. arcadedb-26.11.1.dev0.dist-info/licenses/LICENSE +201 -0
  42. arcadedb-26.11.1.dev0.dist-info/licenses/NOTICE +29 -0
  43. arcadedb-26.11.1.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +3629 -0
  44. arcadedb-26.11.1.dev0.dist-info/licenses/licenses/Apache-2.0.txt +201 -0
  45. arcadedb-26.11.1.dev0.dist-info/licenses/licenses/GraalVM-CE-LICENSE.txt +430 -0
  46. arcadedb-26.11.1.dev0.dist-info/licenses/licenses/GraalVM-THIRD_PARTY_LICENSE.txt +7357 -0
  47. arcadedb-26.11.1.dev0.dist-info/licenses/licenses/GraalVM-native-image-LICENSE.txt +379 -0
  48. 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
+ ]
@@ -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))
@@ -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")
@@ -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