11# PythonEmbed
22
3- > [ !WARNING]
4- > ** Pre-release (SNAPSHOT)** — This project has not yet been released. APIs may change without notice. Not suitable for production use.
53
64[ ![ License] ( https://img.shields.io/badge/license-MIT-blue.svg )] ( LICENSE )
75
@@ -42,17 +40,20 @@ That's it. The Gradle plugin handles Python installation, venv creation, and pac
4240- ** Crash isolation** — Python segfaults never kill the JVM; each Python process runs independently
4341- ** Binary protocol** — MessagePack with length-prefixed frames
4442- ** Auto-scaling pool** — ` PythonEmbedPool ` with async ` CompletableFuture ` API, scales between ` minPool ` and ` maxPool `
45- - ** Object handles** — keep Python objects in-process, reference by numeric ID across calls
43+ - ** Object handles** — keep Python objects in-process, reference by numeric ID across calls via ` ref() `
4644- ** Java proxies** — wrap Python objects as Java interfaces via dynamic proxies with automatic camelCase→snake_case conversion
4745- ** Callbacks** — Python-to-Java callbacks via ` _bridge.call() ` and fire-and-forget pushes via ` _bridge.push() `
4846- ** Streaming** — generator/yield results via Java ` Iterator `
49- - ** Type-safe arguments** — ` PythonEmbed.arg() ` converts Java types to Python literals with injection protection
47+ - ** Type-safe arguments** — ` PythonEmbed.arg() ` converts Java types (null, Boolean, Number, String, List, Set, Map, byte [ ] , datetime) to Python literals with injection protection
5048- ** Batch execution** — ` batchEval ` /` batchExec ` send multiple requests in a single round-trip
49+ - ** File execution** — ` execFile(Path) ` executes Python files directly
5150- ** Auto-restart** — ` minPool ` guarantee on crash; unhealthy instances are replaced automatically
52- - ** Health check** — periodic ping/pong with RSS memory, ref count, and GC status reporting
51+ - ** Health check** — periodic ping/pong with RSS memory, ref count, and GC status reporting via ` health() ` and ` ping() `
5352- ** Python auto-download** — [ python-build-standalone] ( https://github.com/astral-sh/python-build-standalone ) when system Python is absent
5453- ** Incremental venv** — rebuild only on dependency changes
5554- ** Python log forwarding** — Python ` logging ` routed to SLF4J via ` python.* ` logger namespace
55+ - ** Close hooks** — ` onBeforeClose ` / ` onAfterClose ` callbacks for resource cleanup
56+ - ** Spring Boot integration** — zero-code auto-configuration with SINGLE/POOL modes and Actuator ` HealthIndicator `
5657
5758## Installation
5859
@@ -111,6 +112,33 @@ try (PythonEmbed py = PythonEmbed.create()) {
111112}
112113```
113114
115+ ### Execute Python Files
116+
117+ ``` java
118+ try (PythonEmbed py = PythonEmbed . create()) {
119+ // Execute a Python script file directly
120+ py. execFile(Path . of(" scripts/data_pipeline.py" ));
121+
122+ // Access variables defined by the script
123+ PythonValue result = py. eval(" processed_data" );
124+ }
125+ ```
126+
127+ ### Variables & Object Handles
128+
129+ ``` java
130+ try (PythonEmbed py = PythonEmbed . create()) {
131+ // Pass Java variables to Python
132+ py. eval(Map . of(" a" , 10 , " b" , 20 ), " a + b" ); // 30
133+
134+ // Get a persistent handle to a Python object
135+ PythonHandle handle = py. ref(" np" );
136+
137+ // Use the handle later (survives across eval/exec calls)
138+ PythonValue arr = py. eval(" np.arange(5).tolist()" );
139+ }
140+ ```
141+
114142### Safe Parameter Injection
115143
116144Use ` PythonEmbed.arg() ` to safely inject Java values into Python code — no risk of injection:
@@ -124,7 +152,10 @@ PythonEmbed.arg("hello"); // 'hello'
124152PythonEmbed . arg(42 ); // 42
125153PythonEmbed . arg(true ); // True
126154PythonEmbed . arg(List . of(1 , 2 )); // [1, 2]
155+ PythonEmbed . arg(Set . of(1 , 2 )); // {1, 2}
127156PythonEmbed . arg(Map . of(" k" , 1 )); // {'k': 1}
157+ PythonEmbed . arg(new byte []{1 ,2 });// b'\x01\x02'
158+ PythonEmbed . arg(Instant . now()); // datetime.datetime.fromtimestamp(...)
128159```
129160
130161### Configuration
@@ -136,6 +167,7 @@ PythonEmbed.Options options = PythonEmbed.Options.builder()
136167 .venvPath(Path . of(" /opt/venv" )) // explicit venv path
137168 .env(Map . of(" CUDA_VISIBLE_DEVICES" , " 0" ))
138169 .warmupScript(" import numpy as np" )
170+ .onBeforeClose(py - > py. exec(" cleanup()" ))
139171 .build();
140172
141173try (PythonEmbed py = PythonEmbed . create(options)) {
@@ -167,6 +199,12 @@ try (PythonEmbedPool pool = PythonEmbedPool.builder()
167199 System . out. println(results. get(1 ). asInt());
168200 });
169201
202+ // Batch exec (fire-and-forget)
203+ pool. batchExec(List . of(
204+ " print('task 1')" ,
205+ " print('task 2')"
206+ )). get();
207+
170208 // Monitor pool
171209 System . out. println(" Size: " + pool. size());
172210 System . out. println(" Active: " + pool. activeCount());
@@ -246,9 +284,60 @@ try {
246284 py. eval(" 1 / 0" );
247285} catch (PythonExecutionException e) {
248286 System . out. println(" Python error: " + e. getMessage());
249- System . out. println(" Cause code: " + e. causeCode());
250- System . out. println(" Error type: " + e. errorType());
251- System . out. println(" Traceback:\n " + e. traceback());
287+ System . out. println(" Cause code: " + e. getCauseCode());
288+ System . out. println(" Error type: " + e. getPythonErrorType());
289+ System . out. println(" Traceback:\n " + e. getPythonTraceback());
290+ }
291+ ```
292+
293+ ## Spring Boot
294+
295+ Add ` python-embed-spring-boot-starter ` for zero-code Spring Boot 3.x integration:
296+
297+ ``` groovy
298+ dependencies {
299+ implementation 'io.github.howtis:python-embed-spring-boot-starter:1.0.0'
300+ }
301+ ```
302+
303+ Configure via ` application.yml ` :
304+
305+ ``` yaml
306+ python-embed :
307+ mode : SINGLE # or POOL
308+ venv-path : /opt/venv # optional override
309+ pool :
310+ min : 2
311+ max : 8
312+ idle-timeout : 60s
313+ health-check-interval : 30s
314+ close-timeout : 30s
315+ options :
316+ timeout-ms : 30000
317+ environment-vars :
318+ CUDA_VISIBLE_DEVICES : " 0"
319+
320+ management :
321+ endpoint :
322+ health :
323+ show-details : always
324+ ` ` `
325+
326+ **SINGLE mode** injects a ` PythonEmbed` bean. **POOL mode** injects a `PythonEmbedPool` bean. Both modes register an Actuator `HealthIndicator`.
327+
328+ ` ` ` java
329+ @RestController
330+ public class PythonController {
331+ private final PythonEmbed py;
332+
333+ public PythonController(PythonEmbed py) {
334+ this.py = py;
335+ }
336+
337+ @GetMapping("/eval")
338+ public Map<String, Object> eval(@RequestParam String expr) {
339+ return Map.of("result", py.eval(expr).toJson());
340+ }
252341}
253342` ` `
254343
@@ -263,6 +352,8 @@ try {
263352
264353- **[python-embed-gradle-plugin](python-embed-gradle-plugin/)** — Gradle plugin for venv creation, package installation, and Python auto-download
265354- **[python-embed-runtime](python-embed-runtime/)** — Java runtime library for process communication, pool management, and type conversion
355+ - **[python-embed-spring-boot-starter](python-embed-spring-boot-starter/)** — Spring Boot 3.x auto-configuration (SINGLE/POOL modes, HealthIndicator)
356+ - **[python-embed-examples](python-embed-examples/)** — 13 real-world examples (eval, numpy, pool-async, callback-bridge, proxy-object, streaming, spring-boot, and more)
266357
267358# # Building from Source
268359
0 commit comments