Skip to content

Commit a3c5ae9

Browse files
committed
Published to Maven Central Repository + fixed small bugs
1 parent 6322b02 commit a3c5ae9

File tree

53 files changed

+604
-283
lines changed

Some content is hidden

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

53 files changed

+604
-283
lines changed

README.md

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
<img src="logo.svg" alt="logo :D">
44

5-
[![Maven Central](https://img.shields.io/maven-central/v/io.github.w4t3rcs/spring-boot-python-executor.svg)](https://central.sonatype.com/artifact/io.github.w4t3rcs/spring-boot-python-executor-starter)
5+
[![Maven Central](https://img.shields.io/maven-central/v/io.github.w4t3rcs/spring-boot-python-executor-starter.svg)](https://central.sonatype.com/artifact/io.github.w4t3rcs/spring-boot-python-executor-starter)
6+
[![Maven Central](https://img.shields.io/maven-central/v/io.github.w4t3rcs/spring-boot-python-executor-cache-starter.svg)](https://central.sonatype.com/artifact/io.github.w4t3rcs/spring-boot-python-executor-cache-starter)
7+
[![Maven Central](https://img.shields.io/maven-central/v/io.github.w4t3rcs/spring-boot-python-executor-testcontainers.svg)](https://central.sonatype.com/artifact/io.github.w4t3rcs/spring-boot-python-executor-testcontainers)
8+
[![Maven Central](https://img.shields.io/maven-central/v/io.github.w4t3rcs/spring-boot-python-executor-dependencies.svg)](https://central.sonatype.com/artifact/io.github.w4t3rcs/spring-boot-python-executor-dependencies)
69
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
710
[![Java: 17+](https://img.shields.io/badge/Java-17%2B-blue.svg)](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
811
[![Spring Boot: 3.5.3+](https://img.shields.io/badge/Spring%20Boot-3.5.3%2B-brightgreen.svg)](https://spring.io/projects/spring-boot)
912
[![Python: 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
10-
![Tests Passed: 90%](https://img.shields.io/badge/Tests%20Passed-80%25-green.svg)
13+
![Tests Passed: 99%](https://img.shields.io/badge/Tests%20Passed-99%25-green.svg)
1114

1215
<hr>
1316

@@ -424,9 +427,14 @@ you must also add names from `spring.python.cache.names` or it will fail with `N
424427

425428
### Aspect Properties
426429

427-
| Property | Description | Default |
428-
|-------------------------------------|--------------------------------------------------------|-----------------|
429-
| `spring.python.aspect.async-scopes` | What annotations should process scripts asynchronously | `before, after` |
430+
| Property | Description | Default |
431+
|-------------------------------------------------|------------------------------------------------------------------------------------------|----------------|
432+
| `spring.python.aspect.async.scopes` | What annotations should process scripts asynchronously (`before`, `after`) | `before,after` |
433+
| `spring.python.aspect.async.core-pool-size` | Core thread pool size for async Python execution | `10` |
434+
| `spring.python.aspect.async.max-pool-size` | Maximum thread pool size for async Python execution | `50` |
435+
| `spring.python.aspect.async.queue-capacity` | Queue capacity for pending async Python tasks | `100` |
436+
| `spring.python.aspect.async.thread-name-prefix` | Prefix for async Python executor thread names | `AsyncPython-` |
437+
| `spring.python.aspect.async.rejection-policy` | Policy for handling rejected tasks (`caller_runs`, `abort`, `discard`, 'discard_oldest') | `caller_runs` |
430438

431439
## 🔄 Execution Modes
432440

@@ -494,16 +502,16 @@ grpcurl -plaintext -d '{\"script": \"r4java = 2 + 2\"}' \
494502

495503
### Environment Variables
496504

497-
| Variable | Description | Default | Server |
498-
|-----------------------------------------|----------------------------------|----------------------------|-----------|
499-
| `PYTHON_SERVER_TOKEN` | Authentication token | - | Both |
500-
| `PYTHON_SERVER_HOST` | Server bind address | 0.0.0.0 | Both |
501-
| `PYTHON_SERVER_PORT` | Server port | 8000 (REST) / 50051 (gRPC) | Both |
502-
| `PYTHON_SERVER_THREAD_POOL_MAX_WORKERS` | Max worker threads | 10 | gRPC only |
503-
| `PYTHON_RESULT_APPEARANCE` | Result variable name | r4java | Both |
504-
| `PYTHON_ADDITIONAL_IMPORTS` | Additional Python packages | - | Both |
505-
| `PYTHON_ADDITIONAL_IMPORTS_DELIMITER` | Delimiter for imports | , | Both |
506-
| `PYTHON_LOGGING_ENABLED` | Whether request logs are enabled | True | Both |
505+
| Variable | Description | Default | Server |
506+
|-----------------------------------------|--------------------------------|----------------------------|-----------|
507+
| `PYTHON_SERVER_TOKEN` | Authentication token | - | Both |
508+
| `PYTHON_SERVER_HOST` | Server bind address | 0.0.0.0 | Both |
509+
| `PYTHON_SERVER_PORT` | Server port | 8000 (REST) / 50051 (gRPC) | Both |
510+
| `PYTHON_SERVER_THREAD_POOL_MAX_WORKERS` | Max worker threads | 10 | gRPC only |
511+
| `PYTHON_RESULT_APPEARANCE` | Result variable name | r4java | Both |
512+
| `PYTHON_ADDITIONAL_IMPORTS` | Additional Python packages | - | Both |
513+
| `PYTHON_ADDITIONAL_IMPORTS_DELIMITER` | Delimiter for imports | , | Both |
514+
| `PYTHON_LOGGING_ENABLED` | Whether to enable request logs | True | Both |
507515

508516
#### PYTHON_ADDITIONAL_IMPORTS
509517

@@ -562,7 +570,8 @@ public class CalculationService {
562570
o4java{result} # This value will be returned to Java
563571
""";
564572
Map<String, Object> arguments = Map.of("a", a, "b", b);
565-
return pythonProcessor.process(script, Integer.class, arguments);
573+
PythonExecutionResponse<Integer> response = pythonProcessor.process(script, Integer.class, arguments);
574+
return response.body();
566575
}
567576
}
568577
```
@@ -600,7 +609,8 @@ public class PricingService {
600609
"customer", customer
601610
);
602611

603-
return pythonProcessor.process(script, Double.class, arguments);
612+
PythonExecutionResponse<Double> response = pythonProcessor.process(script, Double.class, arguments);
613+
return response.body();
604614
}
605615
}
606616
```
@@ -644,8 +654,9 @@ public class SentimentAnalysisService {
644654
""";
645655

646656
Map<String, Object> arguments = Map.of("text", text);
647-
648-
return pythonProcessor.process(script, Double.class, arguments);
657+
658+
PythonExecutionResponse<Double> response = pythonProcessor.process(script, Double.class, arguments);
659+
return response.body();
649660
}
650661
}
651662
```

demo-app/docker-compose.yaml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
services:
2-
# python-server:
3-
# image: "w4t3rcs/spring-boot-python-executor-python-grpc-server"
4-
# ports:
5-
# - "50051:50051"
6-
# environment:
7-
# PYTHON_SERVER_TOKEN: secret
8-
# PYTHON_ADDITIONAL_IMPORTS: scikit-learn,numpy,pandas
9-
python-server:
10-
image: "w4t3rcs/spring-boot-python-executor-python-rest-server"
2+
python-grpc-server:
3+
image: "w4t3rcs/spring-boot-python-executor-python-grpc-server:latest"
114
ports:
12-
- "8000:8000"
5+
- "50051:50051"
136
environment:
147
PYTHON_SERVER_TOKEN: secret
15-
PYTHON_ADDITIONAL_IMPORTS: scikit-learn,numpy,pandas
8+
PYTHON_ADDITIONAL_IMPORTS: scikit-learn,numpy,pandas,scipy
9+
# python-rest-server:
10+
# image: "w4t3rcs/spring-boot-python-executor-python-rest-server:latest"
11+
# ports:
12+
# - "8000:8000"
13+
# environment:
14+
# PYTHON_SERVER_TOKEN: secret
15+
# PYTHON_ADDITIONAL_IMPORTS: scikit-learn,numpy,pandas,scipy
Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.w4t3rcs.demo.controller;
22

3+
import io.w4t3rcs.demo.dto.CalculatorRequest;
34
import io.w4t3rcs.demo.dto.MLScriptRequest;
45
import io.w4t3rcs.demo.dto.MLScriptResponse;
5-
import io.w4t3rcs.demo.service.PythonService;
6+
import io.w4t3rcs.demo.service.MLPythonService;
7+
import io.w4t3rcs.demo.service.PriceCalculatorPythonService;
8+
import io.w4t3rcs.demo.service.SimplePythonService;
69
import lombok.RequiredArgsConstructor;
710
import org.springframework.http.ResponseEntity;
811
import org.springframework.web.bind.annotation.PostMapping;
@@ -14,22 +17,40 @@
1417
@RequestMapping("/python")
1518
@RequiredArgsConstructor
1619
public class PythonController {
17-
private final PythonService pythonService;
20+
private final SimplePythonService simplePythonService;
21+
private final PriceCalculatorPythonService priceCalculatorPythonService;
22+
private final MLPythonService mlPythonService;
1823

1924
@PostMapping("/before")
20-
public ResponseEntity<String> executeBefore(@RequestBody MLScriptRequest request) {
21-
pythonService.doSomethingWithPythonBefore(request);
25+
public ResponseEntity<?> doSomethingBefore() {
26+
simplePythonService.doSomethingWithPythonBefore();
2227
return ResponseEntity.ok().build();
2328
}
2429

2530
@PostMapping("/inside")
26-
public ResponseEntity<MLScriptResponse> executeInside(@RequestBody MLScriptRequest request) {
27-
return ResponseEntity.ok().body(pythonService.doSomethingWithPythonInside(request));
31+
public ResponseEntity<?> doSomethingInside() {
32+
simplePythonService.doSomethingWithPythonInside();
33+
return ResponseEntity.ok().build();
2834
}
2935

3036
@PostMapping("/after")
31-
public ResponseEntity<MLScriptResponse> executeAfter(@RequestBody MLScriptRequest request) {
32-
pythonService.doSomethingWithPythonAfter(request);
37+
public ResponseEntity<?> doSomethingAfter() {
38+
simplePythonService.doSomethingWithPythonAfter();
3339
return ResponseEntity.ok().build();
3440
}
41+
42+
@PostMapping("/calculator")
43+
public ResponseEntity<Double> calculatePrice(@RequestBody CalculatorRequest request) {
44+
return ResponseEntity.ok(priceCalculatorPythonService.calculatePrice(request.product(), request.customer()));
45+
}
46+
47+
@PostMapping("/ml1")
48+
public ResponseEntity<MLScriptResponse> executeML1Script(@RequestBody MLScriptRequest mlScriptRequest) {
49+
return ResponseEntity.ok(mlPythonService.processML1Script(mlScriptRequest));
50+
}
51+
52+
@PostMapping("/ml2")
53+
public ResponseEntity<Double> executeML2Script(@RequestBody String text) {
54+
return ResponseEntity.ok(mlPythonService.processML2Script(text));
55+
}
3556
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package io.w4t3rcs.demo.dto;
2+
3+
public record CalculatorRequest(ProductDto product, CustomerDto customer) {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package io.w4t3rcs.demo.dto;
2+
3+
public record CustomerDto(int loyaltyYears) {
4+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.w4t3rcs.demo.dto;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public final class ProductDto {
7+
private int basePrice;
8+
private int quantity;
9+
10+
public ProductDto(int basePrice, int quantity) {
11+
this.basePrice = basePrice;
12+
this.quantity = quantity;
13+
}
14+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.w4t3rcs.demo.service;
2+
3+
import io.w4t3rcs.demo.dto.MLScriptRequest;
4+
import io.w4t3rcs.demo.dto.MLScriptResponse;
5+
import io.w4t3rcs.python.dto.PythonExecutionResponse;
6+
import io.w4t3rcs.python.processor.PythonProcessor;
7+
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.stereotype.Service;
10+
11+
import java.util.Map;
12+
13+
@Slf4j
14+
@Service
15+
@RequiredArgsConstructor
16+
public class MLPythonService {
17+
private static final String ML1_SCRIPT = "ml1_script.py";
18+
private static final String ML2_SCRIPT = "ml2_script.py";
19+
private final PythonProcessor pythonProcessor;
20+
21+
public MLScriptResponse processML1Script(MLScriptRequest mlScriptRequest) {
22+
Map<String, Object> arguments = Map.of("request", mlScriptRequest);
23+
PythonExecutionResponse<MLScriptResponse> response = pythonProcessor.process(ML1_SCRIPT, MLScriptResponse.class, arguments);
24+
return response.body();
25+
}
26+
27+
public double processML2Script(String text) {
28+
Map<String, Object> arguments = Map.of("text", text);
29+
PythonExecutionResponse<Double> response = pythonProcessor.process(ML2_SCRIPT, Double.class, arguments);
30+
return response.body();
31+
}
32+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.w4t3rcs.demo.service;
2+
3+
import io.w4t3rcs.demo.dto.CustomerDto;
4+
import io.w4t3rcs.demo.dto.ProductDto;
5+
import io.w4t3rcs.python.dto.PythonExecutionResponse;
6+
import io.w4t3rcs.python.processor.PythonProcessor;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Service;
9+
10+
import java.util.Map;
11+
import java.util.Random;
12+
13+
@Service
14+
@RequiredArgsConstructor
15+
public class PriceCalculatorPythonService {
16+
private static final String CALCULATOR_SCRIPT = "price_calculator.py";
17+
private final PythonProcessor pythonProcessor;
18+
19+
public double calculatePrice(ProductDto product, CustomerDto customer) {
20+
int randomMultiplier = new Random().nextInt(10);
21+
product.setBasePrice(product.getBasePrice() * randomMultiplier);
22+
Map<String, Object> arguments = Map.of(
23+
"product", product,
24+
"customer", customer
25+
);
26+
27+
PythonExecutionResponse<Double> response = pythonProcessor.process(CALCULATOR_SCRIPT, Double.class, arguments);
28+
return response.body();
29+
}
30+
}

demo-app/src/main/java/io/w4t3rcs/demo/service/PythonService.java

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.w4t3rcs.demo.service;
2+
3+
import io.w4t3rcs.demo.dto.DictScriptResponse;
4+
import io.w4t3rcs.python.annotation.PythonAfter;
5+
import io.w4t3rcs.python.annotation.PythonAfters;
6+
import io.w4t3rcs.python.annotation.PythonBefore;
7+
import io.w4t3rcs.python.annotation.PythonBefores;
8+
import io.w4t3rcs.python.processor.PythonProcessor;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.springframework.stereotype.Service;
12+
13+
@Slf4j
14+
@Service
15+
@RequiredArgsConstructor
16+
public class SimplePythonService {
17+
private static final String SIMPLE_SCRIPT = "simple_script.py";
18+
private static final String NUMERIC_SCRIPT = "numeric_script.py";
19+
private static final String DICT_SCRIPT = "dict_script.py";
20+
private final PythonProcessor pythonProcessor;
21+
22+
@PythonBefores({
23+
@PythonBefore(SIMPLE_SCRIPT),
24+
@PythonBefore(NUMERIC_SCRIPT),
25+
@PythonBefore(DICT_SCRIPT),
26+
})
27+
public void doSomethingWithPythonBefore() {
28+
log.info("doSomethingWithPythonBefore()");
29+
}
30+
31+
public void doSomethingWithPythonInside() {
32+
log.info("doSomethingWithPythonInside()");
33+
log.info("1 --> {}", pythonProcessor.process(SIMPLE_SCRIPT, String.class));
34+
log.info("2 --> {}", pythonProcessor.process(NUMERIC_SCRIPT, Float.class));
35+
log.info("3 --> {}", pythonProcessor.process(DICT_SCRIPT, DictScriptResponse.class));
36+
}
37+
38+
@PythonAfters({
39+
@PythonAfter(SIMPLE_SCRIPT),
40+
@PythonAfter(NUMERIC_SCRIPT),
41+
@PythonAfter(DICT_SCRIPT),
42+
})
43+
public void doSomethingWithPythonAfter() {
44+
log.info("doSomethingWithPythonAfter()");
45+
}
46+
}

0 commit comments

Comments
 (0)