Skip to content

Commit 2b305ca

Browse files
authored
Sync Python bindings and tests with upstream (#3270)
1 parent c5999c0 commit 2b305ca

61 files changed

Lines changed: 17954 additions & 5032 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-python-bindings.yml

Lines changed: 171 additions & 104 deletions
Large diffs are not rendered by default.

.github/workflows/test-python-examples.yml

Lines changed: 170 additions & 106 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ ArcadeDB understands multiple languages:
8484
ArcadeDB can be used as:
8585

8686
- Embedded from any language on top of the Java Virtual Machine
87+
- Embedded from Python via bindings: https://github.com/humemai/arcadedb-embedded-python
8788
- Remotely by using [HTTP/JSON](https://docs.arcadedb.com#HTTP-API)
8889
- Remotely by using a [Postgres driver](https://docs.arcadedb.com#Postgres-Driver) (ArcadeDB implements Postgres Wire protocol)
8990
- Remotely by using a [Redis driver](https://docs.arcadedb.com#Redis-API) (only a subset of the operations are implemented)

bindings/python/Dockerfile.build

Lines changed: 105 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
# Excludes JARs listed in jar_exclusions.txt to optimize size
44
#
55
# REQUIRED BUILD ARGS (passed from build.sh):
6+
# PYTHON_VERSION - Python version for wheel (e.g., 3.11, 3.12, 3.13, 3.14)
67
# PACKAGE_NAME - always: arcadedb-embedded
78
# PACKAGE_DESCRIPTION - package description
8-
# ARCADEDB_TAG - version tag from pom.xml (e.g., 25.10.1-SNAPSHOT)
9-
# Default below is fallback; build.sh extracts and passes actual version
109
# TARGET_PLATFORM - target platform for JRE (e.g., linux-x64, linux-arm64, darwin-x64)
1110

11+
ARG PYTHON_VERSION=3.12
1212
ARG PACKAGE_NAME=arcadedb-embedded
1313
ARG PACKAGE_DESCRIPTION="ArcadeDB embedded multi-model database with bundled JRE - no Java installation required"
14-
ARG ARCADEDB_TAG=25.10.1-SNAPSHOT
14+
ARG ARCADEDB_TAG
1515
ARG TARGET_PLATFORM=linux-x64
16+
# When set to 1, prefer jars provided in bindings/python/local-jars/lib from the build context.
17+
# If no local jars are present, the build fails fast to avoid silently falling back.
18+
ARG USE_LOCAL_JARS=0
1619

1720
# Stage 1: Use prebuilt ArcadeDB image to obtain compiled JARs
1821
# JARs are filtered based on jar_exclusions.txt in later stages
@@ -21,15 +24,30 @@ FROM arcadedata/arcadedb:${ARCADEDB_TAG} AS java-builder
2124
# nothing to do here; jars will be copied from /home/arcadedb/lib in the python-builder stage
2225

2326
# Stage 2: Build minimal JRE with jlink
24-
FROM eclipse-temurin:21-jdk-jammy AS jre-builder
27+
FROM eclipse-temurin:25-jdk-jammy AS jre-builder
2528

2629
ARG TARGET_PLATFORM
30+
ARG USE_LOCAL_JARS
2731

2832
WORKDIR /build
2933

30-
# Copy JARs from ArcadeDB image
31-
RUN mkdir -p /build/jars
32-
COPY --from=java-builder /home/arcadedb/lib /build/jars/
34+
# Stash upstream jars from the ArcadeDB image
35+
RUN mkdir -p /build/upstream-jars /build/jars /build/local-jars
36+
COPY --from=java-builder /home/arcadedb/lib /build/upstream-jars/
37+
38+
# Optionally bring in locally built jars from the repo (bindings/python/local-jars/lib)
39+
COPY bindings/python/local-jars/lib/ /build/local-jars/
40+
41+
# Select jar source: local when requested and available; otherwise fall back to upstream image
42+
RUN if [ "$USE_LOCAL_JARS" = "1" ]; then \
43+
if [ -d /build/local-jars ] && [ "$(ls -1 /build/local-jars | wc -l)" -gt 0 ]; then \
44+
echo "📦 Using local jars from build context" && cp /build/local-jars/* /build/jars/; \
45+
else \
46+
echo "❌ USE_LOCAL_JARS=1 but no jars found in bindings/python/local-jars/lib" && exit 1; \
47+
fi; \
48+
else \
49+
echo "📦 Using ArcadeDB image jars" && cp /build/upstream-jars/* /build/jars/; \
50+
fi
3351

3452
# Copy JAR exclusion list
3553
COPY bindings/python/jar_exclusions.txt /build/jar_exclusions.txt
@@ -47,26 +65,43 @@ RUN echo "🗑️ Removing excluded JARs..." && \
4765
done < /build/jar_exclusions.txt && \
4866
echo "📋 JAR count after exclusion: $(ls -1 /build/jars/*.jar | wc -l)"
4967

50-
# Build minimal JRE with jlink (21 modules)
51-
# Based on analysis of ArcadeDB dependencies
52-
# Note: jdk.zipfs is required for JPype JAR filesystem support
53-
RUN echo "🔨 Building minimal JRE for platform: ${TARGET_PLATFORM}" && \
54-
REQUIRED_MODULES="java.base,java.compiler,java.desktop,java.logging,java.management,java.naming,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.incubator.vector,jdk.internal.vm.ci,jdk.jfr,jdk.management,jdk.sctp,jdk.unsupported,jdk.zipfs" && \
55-
echo "📦 Required modules (21):" && \
68+
# Build minimal JRE with jlink
69+
# Automatically detect modules with jdeps, plus ensure jdk.zipfs (for JPype) and jdk.unsupported are included
70+
# We exclude jboss/wildfly JARs because they have broken module descriptors that fail analysis.
71+
# We also do NOT provide a classpath, forcing jdeps to ignore all missing dependencies (intra-jar or external).
72+
RUN echo "🔍 Analyzing JARs with jdeps..." && \
73+
DETECTED_MODULES=$(find /build/jars -name "*.jar" | grep -v "jboss" | grep -v "wildfly" | grep -v "smallrye" | xargs jdeps --print-module-deps --ignore-missing-deps --multi-release 25 | grep -v "Warning" | tr ',' '\n' | grep -v "Warning" | grep -v ":" | grep -v "/" | sort -u | paste -sd "," -) && \
74+
REQUIRED_MODULES="${DETECTED_MODULES},jdk.zipfs,jdk.unsupported" && \
75+
echo "🔨 Building minimal JRE for platform: ${TARGET_PLATFORM}" && \
76+
echo "📦 Detected modules: ${DETECTED_MODULES}" && \
77+
echo "📦 Final modules list: ${REQUIRED_MODULES}" && \
78+
echo "📦 Required modules:" && \
5679
echo "$REQUIRED_MODULES" | tr ',' '\n' | sed 's/^/ - /' && \
57-
echo "" && \
58-
echo "🔨 Running jlink..." && \
59-
jlink \
60-
--module-path "${JAVA_HOME}/jmods" \
61-
--add-modules "${REQUIRED_MODULES}" \
62-
--ignore-signing-information \
63-
--strip-debug \
64-
--no-man-pages \
65-
--no-header-files \
66-
--compress zip-9 \
67-
--output /build/jre && \
68-
echo "" && \
69-
echo "✅ JRE build complete!" && \
80+
JMODS_DIR="${JAVA_HOME}/jmods" && \
81+
if [ ! -d "$JMODS_DIR" ]; then JMODS_DIR="${JAVA_HOME}/lib/jmods"; fi && \
82+
if [ -d "$JMODS_DIR" ]; then \
83+
echo "" ; \
84+
echo "🔨 Running jlink..." ; \
85+
jlink \
86+
--module-path "$JMODS_DIR" \
87+
--add-modules "${REQUIRED_MODULES}" \
88+
--ignore-signing-information \
89+
--strip-debug \
90+
--no-man-pages \
91+
--no-header-files \
92+
--compress zip-9 \
93+
--output /build/jre ; \
94+
echo "" ; \
95+
echo "✅ JRE build complete!" ; \
96+
else \
97+
echo "⚠️ jmods directory not found under ${JAVA_HOME}. Falling back to copying full JDK runtime." ; \
98+
mkdir -p /build/jre ; \
99+
cp -a ${JAVA_HOME}/bin /build/jre/bin ; \
100+
cp -a ${JAVA_HOME}/lib /build/jre/lib ; \
101+
cp -a ${JAVA_HOME}/conf /build/jre/conf || true ; \
102+
cp -a ${JAVA_HOME}/release /build/jre/release || true ; \
103+
echo "✅ Fallback JRE created from full JDK runtime." ; \
104+
fi && \
70105
echo "" && \
71106
JRE_SIZE=$(du -sh /build/jre | cut -f1) && \
72107
echo "📊 JRE size: $JRE_SIZE" && \
@@ -82,16 +117,19 @@ RUN echo "🔨 Building minimal JRE for platform: ${TARGET_PLATFORM}" && \
82117

83118
# Stage 3: Build Python wheel
84119

85-
FROM python:3.11-slim AS python-builder
120+
FROM python:${PYTHON_VERSION}-slim AS python-builder
86121

87-
# Install system dependencies (JDK needed for JPype at build/test time)
122+
# Install minimal build tooling (no external JDK needed; we use bundled JRE)
88123
RUN apt-get update && apt-get install -y \
89-
openjdk-21-jdk \
124+
build-essential \
125+
curl \
126+
file \
127+
patchelf \
90128
&& rm -rf /var/lib/apt/lists/*
91129

92-
# Set JAVA_HOME
93-
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
94-
ENV PATH=$PATH:$JAVA_HOME/bin
130+
# Install uv for faster, deterministic installs
131+
ENV PATH="/root/.cargo/bin:/root/.local/bin:${PATH}"
132+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && uv --version
95133

96134
WORKDIR /build
97135

@@ -117,7 +155,7 @@ COPY bindings/python/README.md ./
117155
COPY ../../pom.xml /arcadedb/pom.xml
118156

119157
# Install Python build dependencies
120-
RUN pip install --no-cache-dir build wheel setuptools jpype1
158+
RUN uv pip install --system build wheel setuptools jpype1 auditwheel
121159

122160
# Re-declare build args for this stage (required after FROM)
123161
ARG PACKAGE_NAME
@@ -150,7 +188,30 @@ RUN if [ -n "${BUILD_VERSION}" ]; then \
150188
sed -i 's|^name = .*|name = "'"${PACKAGE_NAME}"'"|' pyproject.toml && \
151189
sed -i 's|^version = .*|version = "'"${ARCADEDB_VERSION}"'"|' pyproject.toml && \
152190
sed -i 's|^description = .*|description = "'"${PACKAGE_DESCRIPTION}"'"|' pyproject.toml && \
153-
python3 -m build --wheel && \
191+
if echo "${TARGET_PLATFORM}" | grep -q '^linux-'; then \
192+
if [ "${TARGET_PLATFORM}" = "linux-x64" ]; then \
193+
WHEEL_PLAT="manylinux_2_35_x86_64"; \
194+
elif [ "${TARGET_PLATFORM}" = "linux-arm64" ]; then \
195+
WHEEL_PLAT="manylinux_2_35_aarch64"; \
196+
else \
197+
WHEEL_PLAT=""; \
198+
fi; \
199+
if [ -n "${WHEEL_PLAT}" ]; then \
200+
echo "🏷️ Building wheel with platform tag: ${WHEEL_PLAT}"; \
201+
python3 -m build --wheel --config-setting=--build-option=--plat-name=${WHEEL_PLAT}; \
202+
else \
203+
python3 -m build --wheel; \
204+
fi; \
205+
else \
206+
python3 -m build --wheel; \
207+
fi && \
208+
if echo "${TARGET_PLATFORM}" | grep -q '^linux-'; then \
209+
if [ -n "${WHEEL_PLAT}" ] && ! ls /build/dist/*${WHEEL_PLAT}*.whl 1> /dev/null 2>&1; then \
210+
echo "❌ Expected manylinux wheel (${WHEEL_PLAT}) not found"; \
211+
ls -lh /build/dist; \
212+
exit 1; \
213+
fi; \
214+
fi && \
154215
echo "✅ Wheel built successfully!" && \
155216
ls -lh dist/
156217

@@ -160,23 +221,26 @@ FROM python-builder AS export
160221
# The dist directory is preserved from python-builder stage
161222

162223
# Stage 5: Test the built wheel
163-
FROM python:3.11-slim AS tester
224+
FROM python:${PYTHON_VERSION}-slim AS tester
164225

165-
# Install Java runtime for JPype
226+
# No external JRE needed in tester; the wheel includes a bundled JRE
227+
# Keep build-essential in case pytest or wheels need compilation on some platforms
166228
RUN apt-get update && apt-get install -y \
167-
openjdk-21-jre-headless \
229+
build-essential \
230+
curl \
168231
&& rm -rf /var/lib/apt/lists/*
169232

170-
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
171-
ENV PATH=$PATH:$JAVA_HOME/bin
233+
# Install uv for faster, deterministic installs
234+
ENV PATH="/root/.cargo/bin:/root/.local/bin:${PATH}"
235+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && uv --version
172236

173237
WORKDIR /test
174238

175239
# Copy the wheel from builder
176240
COPY --from=python-builder /build/dist/*.whl /tmp/
177241

178242
# Install the wheel and test dependencies
179-
RUN pip install --no-cache-dir /tmp/*.whl pytest pytest-cov
243+
RUN uv pip install --system /tmp/*.whl pytest pytest-cov
180244

181245
# Copy tests
182246
COPY --from=python-builder /build/tests ./tests/
@@ -205,7 +269,7 @@ try:\n\
205269
\n\
206270
result = db.query("sql", "SELECT FROM TestDoc")\n\
207271
for record in result:\n\
208-
print(f"✅ Query result: {record.get_property('\''name'\'')} = {record.get_property('\''value'\'')}")\n\
272+
print(f"✅ Query result: {record.get('\''name'\'')} = {record.get('\''value'\'')}")\n\
209273
\n\
210274
print("🎉 All tests passed!")\n\
211275
finally:\n\

0 commit comments

Comments
 (0)