Skip to content

Latest commit

 

History

History
108 lines (64 loc) · 3.87 KB

File metadata and controls

108 lines (64 loc) · 3.87 KB

CPython4J Constitution

1. Java is the host

CPython4J is Java-first. The JVM owns the process, lifecycle, configuration, contracts, and callbacks.

Python is the guest runtime. Python code may implement Java-defined contracts and may call explicitly exposed Java host contracts.

2. Use real CPython

The project runs real CPython through the Python C ABI. It is not a Python implementation on the JVM. It is not GraalPy. It is not Jython. It is not WASM.

3. No JNI, no native helper

The Java side calls native code through Java's Foreign Function & Memory API. No JNI. No compiled C helper or shim.

Host modules and callbacks are built purely through FFM: PyMethodDef/PyModuleDef structs laid out via MemorySegment, function pointers created via Linker.nativeLinker().upcallStub(). The only native library is libpython itself.

The acceptable native boundary is:

Java FFM <-> libpython

The unacceptable native boundary is:

Java <-> JNI wrapper <-> CPython
Java <-> compiled C helper <-> CPython

4. No reflection

The runtime must not use java.lang.reflect.Proxy, Method.invoke(), or any other reflective mechanism. All type information and method dispatch must be resolved at compile time through annotation processors that generate concrete classes. This matches the QuickJs4J approach.

5. Contract-first, not stringly-call-first

The primary API is not py.call("name", ...).

The primary API is:

MyApi api = engine.load(MyApi.class, PythonSource.file(path));

Python modules implement Java interfaces. Java host interfaces can be exposed as Python modules. The annotation processor generates concrete implementation classes at compile time.

6. Structural conversion only

Data crosses the boundary by value:

  • primitives directly;
  • non-primitive structured values through JSON-shaped conversion.

There is no identity-preserving object graph conversion.

7. No Java object magic in Python

Python must not see arbitrary Java classes, methods, or fields.

Allowed:

from host import find_user
user = find_user("u1")

Not allowed:

from java.time import LocalDate
obj.someJavaMethod()

8. No public PyRef in v1

Python state should stay in Python. Java interacts with Python through Java-defined contracts.

No public PyRef should be present in v1. If Python needs state, the Python module owns that state.

9. uv-first, venv-compatible, battery-included

Python environment management is a product feature, not a footnote.

The preferred flow is uv project support. Existing .venv and explicit configuration are supported as escape hatches. A battery-included mode bundles pyproject.toml inside JAR resources so that uv can bootstrap Python and all dependencies on first run.

10. Not a sandbox

Embedded CPython is trusted code. Native Python extensions can crash the JVM process. This project is not a security sandbox.

11. Small core, optional layers

The core should remain small:

  • engine lifecycle;
  • contract loading;
  • host exposure;
  • primitive and JSON conversion;
  • callback bridge;
  • uv/venv environment discovery.

Convenience features such as annotations, custom converters, record naming policies, Gradle plugins, or typed diagnostics can live in optional modules.

12. Battery-included distribution is a goal

It should be possible to ship a Java application that embeds Python workloads as a self-contained artifact. The library should support bundling pyproject.toml and Python sources inside JAR resources. On first run, uv bootstraps CPython and all dependencies into a local cache. After the first run, the cached environment is reused.

The motivating use case is Docling: a Python document AI library with many transitive dependencies including native extensions. A Java team should be able to mvn package and distribute a JAR that runs Docling without requiring end users to install Python.