Skip to content

Commit 425e731

Browse files
committed
[Fix #936] Adding Python implementation
Signed-off-by: fjtirado <ftirados@redhat.com>
1 parent b573e61 commit 425e731

File tree

13 files changed

+310
-134
lines changed

13 files changed

+310
-134
lines changed

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.concurrent.atomic.AtomicReference;
3535
import java.util.concurrent.locks.Lock;
3636
import java.util.concurrent.locks.ReentrantLock;
37+
import java.util.function.Supplier;
3738

3839
public class WorkflowMutableInstance implements WorkflowInstance {
3940

@@ -44,6 +45,8 @@ public class WorkflowMutableInstance implements WorkflowInstance {
4445
protected final WorkflowContext workflowContext;
4546
protected Instant startedAt;
4647

48+
protected final Map<String, Object> additionalObjects = new ConcurrentHashMap<String, Object>();
49+
4750
protected AtomicReference<CompletableFuture<WorkflowModel>> futureRef = new AtomicReference<>();
4851
protected Instant completedAt;
4952

@@ -279,4 +282,8 @@ public boolean cancel() {
279282
}
280283

281284
public void restoreContext(WorkflowContext workflow, TaskContext context) {}
285+
286+
public <T> T additionalObject(String key, Supplier<T> supplier) {
287+
return (T) additionalObjects.computeIfAbsent(key, k -> supplier.get());
288+
}
282289
}

impl/core/src/main/java/io/serverlessworkflow/impl/config/AbstractConfigManager.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import java.time.Instant;
1919
import java.time.OffsetDateTime;
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
import java.util.Collections;
2023
import java.util.Optional;
2124

2225
public abstract class AbstractConfigManager implements ConfigManager {
@@ -56,5 +59,19 @@ protected <T> T convert(String value, Class<T> propClass) {
5659
return propClass.cast(result);
5760
}
5861

62+
@Override
63+
public <T> Collection<T> multiConfig(String propName, Class<T> propClass) {
64+
String multiValue = get(propName);
65+
if (multiValue != null) {
66+
Collection<T> result = new ArrayList<>();
67+
for (String value : multiValue.split(",")) {
68+
result.add(convert(value, propClass));
69+
}
70+
return result;
71+
} else {
72+
return Collections.emptyList();
73+
}
74+
}
75+
5976
protected abstract <T> T convertComplex(String value, Class<T> propClass);
6077
}

impl/core/src/main/java/io/serverlessworkflow/impl/config/ConfigManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
package io.serverlessworkflow.impl.config;
1717

1818
import io.serverlessworkflow.impl.ServicePriority;
19+
import java.util.Collection;
1920
import java.util.Optional;
2021

2122
public interface ConfigManager extends ServicePriority {
2223

2324
<T> Optional<T> config(String propName, Class<T> propClass);
2425

26+
<T> Collection<T> multiConfig(String propName, Class<T> propClass);
27+
2528
Iterable<String> names();
2629
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.scripts;
17+
18+
import io.serverlessworkflow.api.types.RunTaskConfiguration;
19+
import io.serverlessworkflow.impl.TaskContext;
20+
import io.serverlessworkflow.impl.WorkflowContext;
21+
import io.serverlessworkflow.impl.WorkflowError;
22+
import io.serverlessworkflow.impl.WorkflowException;
23+
import io.serverlessworkflow.impl.WorkflowModel;
24+
import io.serverlessworkflow.impl.WorkflowModelFactory;
25+
import io.serverlessworkflow.impl.executors.ProcessResult;
26+
import java.io.ByteArrayOutputStream;
27+
28+
public abstract class AbstractScriptRunner implements ScriptRunner {
29+
30+
@Override
31+
public WorkflowModel runScript(
32+
ScriptContext scriptContext,
33+
WorkflowContext workflowContext,
34+
TaskContext taskContext,
35+
WorkflowModel input) {
36+
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
37+
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
38+
try {
39+
runScript(scriptContext, stdout, stderr, workflowContext, taskContext);
40+
return scriptContext
41+
.returnType()
42+
.map(
43+
type ->
44+
modelFromOutput(
45+
type,
46+
workflowContext.definition().application().modelFactory(),
47+
stdout,
48+
stderr))
49+
.orElse(input);
50+
} catch (Exception ex) {
51+
throw new WorkflowException(WorkflowError.runtime(taskContext, ex).build());
52+
}
53+
}
54+
55+
protected abstract void runScript(
56+
ScriptContext scriptContext,
57+
ByteArrayOutputStream stdout,
58+
ByteArrayOutputStream stderr,
59+
WorkflowContext workflowContext,
60+
TaskContext taskContext);
61+
62+
protected WorkflowModel modelFromOutput(
63+
RunTaskConfiguration.ProcessReturnType returnType,
64+
WorkflowModelFactory modelFactory,
65+
ByteArrayOutputStream stdout,
66+
ByteArrayOutputStream stderr) {
67+
return switch (returnType) {
68+
case ALL -> modelFactory.fromAny(new ProcessResult(0, toString(stdout), toString(stderr)));
69+
case NONE -> modelFactory.fromNull();
70+
case CODE -> modelFactory.from(0);
71+
case STDOUT -> modelFactory.from(toString(stdout));
72+
case STDERR -> modelFactory.from(toString(stderr));
73+
};
74+
}
75+
76+
private String toString(ByteArrayOutputStream stream) {
77+
return stream.toString().trim();
78+
}
79+
}

impl/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<version.com.cronutils>9.2.1</version.com.cronutils>
1818
<version.docker.java>3.7.0</version.docker.java>
1919
<version.org.graalvm.polyglot>25.0.1</version.org.graalvm.polyglot>
20+
<version.black.ninia>4.2.0</version.black.ninia>
2021
</properties>
2122
<dependencyManagement>
2223
<dependencies>
@@ -142,6 +143,11 @@
142143
<artifactId>serverlessworkflow-impl-script-js</artifactId>
143144
<version>${project.version}</version>
144145
</dependency>
146+
<dependency>
147+
<groupId>io.serverlessworkflow</groupId>
148+
<artifactId>serverlessworkflow-impl-script-python</artifactId>
149+
<version>${project.version}</version>
150+
</dependency>
145151
<dependency>
146152
<groupId>com.cronutils</groupId>
147153
<artifactId>cron-utils</artifactId>
@@ -168,6 +174,11 @@
168174
<artifactId>polyglot</artifactId>
169175
<version>${version.org.graalvm.polyglot}</version>
170176
</dependency>
177+
<dependency>
178+
<groupId>black.ninia</groupId>
179+
<artifactId>jep</artifactId>
180+
<version>${version.black.ninia}</version>
181+
</dependency>
171182
</dependencies>
172183
</dependencyManagement>
173184
<modules>
@@ -187,5 +198,6 @@
187198
<module>container</module>
188199
<module>test</module>
189200
<module>script-js</module>
201+
<module>python</module>
190202
</modules>
191203
</project>

impl/python/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<modelVersion>4.0.0</modelVersion>
3+
<parent>
4+
<groupId>io.serverlessworkflow</groupId>
5+
<artifactId>serverlessworkflow-impl</artifactId>
6+
<version>8.0.0-SNAPSHOT</version>
7+
</parent>
8+
<artifactId>serverlessworkflow-impl-script-python</artifactId>
9+
<name>Serverless Workflow :: Impl :: Script Python</name>
10+
<dependencies>
11+
<dependency>
12+
<groupId>io.serverlessworkflow</groupId>
13+
<artifactId>serverlessworkflow-impl-core</artifactId>
14+
</dependency>
15+
<dependency>
16+
<groupId>black.ninia</groupId>
17+
<artifactId>jep</artifactId>
18+
</dependency>
19+
</dependencies>
20+
</project>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.executors.script.python;
17+
18+
import io.serverlessworkflow.impl.TaskContext;
19+
import io.serverlessworkflow.impl.WorkflowContext;
20+
import io.serverlessworkflow.impl.config.ConfigManager;
21+
import io.serverlessworkflow.impl.scripts.AbstractScriptRunner;
22+
import io.serverlessworkflow.impl.scripts.ScriptContext;
23+
import io.serverlessworkflow.impl.scripts.ScriptLanguageId;
24+
import java.io.ByteArrayOutputStream;
25+
import java.util.Collection;
26+
import jep.Interpreter;
27+
import jep.SharedInterpreter;
28+
29+
public class PythonScriptTaskRunner extends AbstractScriptRunner {
30+
31+
@Override
32+
public ScriptLanguageId identifier() {
33+
return ScriptLanguageId.PYTHON;
34+
}
35+
36+
private static final String PYTHON_SYS_PATH = "sys.path.append('%s')\n";
37+
private static final String SEARCH_PATH_PROPERTY = "io.serverlessworkflow.impl.";
38+
39+
@Override
40+
protected void runScript(
41+
ScriptContext scriptContext,
42+
ByteArrayOutputStream stdout,
43+
ByteArrayOutputStream stderr,
44+
WorkflowContext workflowContext,
45+
TaskContext taskContext) {
46+
Interpreter py =
47+
workflowContext
48+
.instance()
49+
.additionalObject(
50+
"pyInterpreter",
51+
() -> interpreter(workflowContext.definition().application().configManager()));
52+
scriptContext.args().forEach(py::set);
53+
py.exec(scriptContext.code());
54+
}
55+
56+
protected Interpreter interpreter(ConfigManager configManager) {
57+
Interpreter py = new SharedInterpreter();
58+
Collection<String> searchPaths = configManager.multiConfig(SEARCH_PATH_PROPERTY, String.class);
59+
if (!searchPaths.isEmpty()) {
60+
StringBuilder sb = new StringBuilder("import sys\n");
61+
searchPaths.forEach(path -> sb.append(String.format(PYTHON_SYS_PATH, path)));
62+
py.exec(sb.toString());
63+
}
64+
return py;
65+
}
66+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.serverlessworkflow.impl.executors.script.python.PythonScriptTaskRunner

0 commit comments

Comments
 (0)