Skip to content

Commit 69f8dae

Browse files
committed
Prepare for v1.0.0 release
- Remove SNAPSHOT, bump to 1.0.1-SNAPSHOT for next dev cycle - Update README with v1.0.0 features and Spring Boot docs - Add CHANGELOG.md for v1.0.0 - Remove withJavadocJar() from build - Add *.asc and gradle.properties to .gitignore
1 parent cb22c01 commit 69f8dae

6 files changed

Lines changed: 129 additions & 11 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ __pycache__/
1414
*.pyo
1515
.DS_Store
1616
Thumbs.db
17+
*.asc
18+
gradle.properties

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Changelog
2+
3+
## [1.0.0] - 2026-06-23
4+
5+
### Added
6+
- Embed CPython in JVM via subprocess + MessagePack binary protocol
7+
- `PythonEmbed` runtime with eval, exec, execFile, and toJson APIs
8+
- `PythonEmbedPool` with auto-scaling and async `CompletableFuture` API
9+
- Spring Boot 3.x auto-configuration (`python-embed-spring-boot-starter`)
10+
- SINGLE and POOL modes with Actuator `HealthIndicator`
11+
- Gradle plugin (`python-embed-gradle-plugin`) for venv creation and package installation
12+
- Python auto-download via python-build-standalone when system Python is absent
13+
- Object handles with numeric ID referencing for long-lived Python objects
14+
- Generator/streaming support via Java `Iterator`
15+
- Python-to-Java callbacks via `_bridge.call()` and `_bridge.push()`
16+
- Batch execution (`batchEval`/`batchExec`) for multiple requests in one round-trip
17+
- Proxy objects (`PythonProxy`) with dynamic Java interface implementation
18+
- Builder API with fluent construction for `PythonEmbed` and `PythonEmbedPool`
19+
- Type-safe argument conversion (`arg()`): null, Boolean, Number, String, List, Map, Set, byte[], datetime
20+
- Python log forwarding to SLF4J via `python.*` logger namespace
21+
- Periodic health check with RSS memory, ref count, and GC status reporting
22+
- Close hook support for resource cleanup
23+
- 13 example applications in `python-embed-examples`
24+
25+
[1.0.0]: https://github.com/howtis/python-embed/releases/tag/v1.0.0

README.md

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
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

116144
Use `PythonEmbed.arg()` to safely inject Java values into Python code — no risk of injection:
@@ -124,7 +152,10 @@ PythonEmbed.arg("hello"); // 'hello'
124152
PythonEmbed.arg(42); // 42
125153
PythonEmbed.arg(true); // True
126154
PythonEmbed.arg(List.of(1, 2)); // [1, 2]
155+
PythonEmbed.arg(Set.of(1, 2)); // {1, 2}
127156
PythonEmbed.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

141173
try (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

build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group = 'io.github.howtis'
7-
version = '1.0.0-SNAPSHOT'
7+
version = '1.0.1-SNAPSHOT'
88

99
repositories {
1010
mavenCentral()
@@ -23,7 +23,6 @@ subprojects {
2323

2424
java {
2525
withSourcesJar()
26-
withJavadocJar()
2726
toolchain {
2827
languageVersion = JavaLanguageVersion.of(17)
2928
}

python-embed-examples/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ subprojects {
33
apply plugin: 'application'
44

55
group = 'io.github.howtis'
6-
version = '1.0.0-SNAPSHOT'
6+
version = '1.0.0'
77

88
repositories {
99
mavenCentral()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'python-embed-gradle-plugin'

0 commit comments

Comments
 (0)