In essence I have this program:
from sympy.solvers import solve
from sympy import Symbol
x = Symbol('x')
print solve(x**2 - 1, x)
And I call this from Java using this code:
public static BufferedReader runFile(Class<?> c, String py, List<String> args) {
String cmd = c.getResource(py).getPath();
cmd=cmd.split(py)[0];
cmd=cmd.substring(0,cmd.length()-1);
args.add(0, py);
args.add(0, "python");
final ProcessBuilder pb = new ProcessBuilder(args);
new ProcessBuilder();
pb.directory(new File(cmd));
pb.redirectError();
try {
System.out.println(pb.directory().getPath());
for(String s:pb.command()){
System.out.println(s);
}
Process p=pb.start();
return new BufferedReader(new InputStreamReader(p.getErrorStream()));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
When I run the Python program from a terminal everything works as intended, with nothing in the error stream, and it prints [-1,1]. But if I run it from the program, I get this in the error stream:
Traceback (most recent call last):
File "solve.py", line 1, in <module>
from sympy.solvers import solve
ImportError: No module named sympy.solvers
Since specifying the full path of Python fixes your problem, you most likely have multiple installations of Python on your system. Rather than PYTHONPATH being different, I suspect it is actually PATH that is different. As a result, your command line uses the Python interpreter you intend, while Java uses another one.
To determine where this alternate install is, which -a python may be useful, but if not, examine PATH from inside your Java code and see if you can find Python in one of those directories.
Regardless, if you really need to specify the full Python path in Java, you should make this a configuration option. It will probably be different on different machines. Storing it in a file seems most prudent.
Your PYTHONPATH (or less likely your working directory) is different when running from your Java context.
You can
import sys
print sys.path
which may help you to ensure the path is the same for both.
Telling us more about how your environment is set up will help to get more specific answers.
eg. Maybe the Java is running via a web server or something?
Here are couple of ways to fix the path problem:
Make sure the directory containing sympy is in your PYTHONPATH environment variable
If you're really desperate, append the correct directory to sys.path
import sys
sys.append("/some/dir/with/sympy")
from sympy.solvers import solve
...
Related
I am trying to run a piece of Python code via a Java application. The command when put directly into Command Prompt cd'd to the working directory runs exactly as intended. However, my attempts to use the Runtime and ProcessBuilder classes in conjunction with the Process class has yielded no sign of correct function which would be the creation of a CSV file for every call of the code.
I am running this program using Intellij on Windows 10. I have added each directory I am using to my environmental PATH variable as well as attempting full paths in my commands and just file names. The only source of life I can find is that if I include a .waitFor() method a .isAlive() method will return true before the .waitFor() method is called.
I have searched through various similar questions and concluded that using a ProcessBuilder object is the best way to go and that the biggest issue is probably the structure of my command. However, I have made many iterations and have found nothing that changes the caught error to anything useful.
Here is the privacy augmented code that I have been running, I wrote out the command in full in the process builder as that is the last iteration I have attempted.
for (int y = 1; y < iterator; y++) {
try {
String command =
"C:\\Users\\myName\\AppData\\Local\\Programs\\Python\\Python37\\python C:\\Users\\myName\\IdeaProjects\\projectApplication\\script.py ";
String pythonInputPath = " C:\\Users\\myName\\IdeaProjects\\projectApplication\\bin\\output" + y + ".wav ";
ProcessBuilder pb = new ProcessBuilder(command+Arrays.toString(pythonCommandString).replaceAll("\\s","")+pythonInputPath+Integer.toString(y));
Process p = pb.start();
//Process checks
System.out.println(p.isAlive());
p.waitFor();
System.out.println(p.isAlive());
//Destroying process once complete to ensure smooth iterations
p.destroy();
} catch (Exception ex) {
System.out.println("Problems with python script execution: " + ex);
}
}
They python code takes in a WAV file (pythonInputPath) that is a product of earlier part of the application, an Integer[] that usually includes ~20 values (pythonCommandString), and a single iteration integer (y).
The first call to .isAlive() is true and the second is false as expected however the script normally creates a CSV that should be output to a bin file that exists in the working director and that fails to occur when running from Java. From other examples I expected using the Process builder as opposed to the Runtime stream to work, however, there is no difference in my implementation.
Do not concatenate the program with its arguments. Quoting Oracle ProcessBuilder docs
Each process builder manages these process attributes: a command, a
list of strings which signifies the external program file to be
invoked and its arguments, if any
and
ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
Just use the constructor you use, but pass each argument as a separate string, otherwise the OS will try to find an application that is named as a whole command line you gave, and obviously there is no such program
Before you report this as a duplicate, understand that I have now spent a few hours looking over similar questions from a multitude of different websites, many being from here. They do not explain the solution well enough for me to take their answers and apply them to my own problem. If you still feel the itch to report this, go for it. All you'll be doing is preventing me from learning to code better.
I am attempting to call a python script and pass it 5 arguments. I have tried a few different ways to do this and believe the process builder route is my best option. However, I have a few questions as it does not seem to be the right code:
Do I need to be giving process builder a path to an executable, or can I just give it a path to the normal .py file?
Do I need to collect the output from the python file?
If there are any other aspects of the problem I am not seeing, please let me know. My code looks correct compared to others' on the internet doing the same thing. It is listed below:
private void runPython(String pythonPath, HashMap<String, String> map){
pythonPath = "C:/Users/Carlos/PycharmProjects/autoHTML/javaToExcel.py";
try {
ProcessBuilder pb = new ProcessBuilder(pythonPath + "/" + "python", pythonPath, map.get("Driver Advances"), map.get("Driver Loans"),
map.get("Escrow Fund"), map.get("Maintenance Fund"), map.get("Highway Use tax - 2290"));
Process p = pb.start();
catch(Exception e) {
System.out.println("Python error");
}
}
if there is any code you want or any questions you need answered to help me out, please let me know.
To create an operating system processes, you can use ProcessBuilder which takes 2 arguments:
The process to run, here this is the path to your Python executable (a.k.a: "C:/Users/Carlos/PycharmProjects/autoHTML/javaToExcel.py/python.exe" or something similar).
The arguments to pass to your process:
The path to your python script and its own arguments.
You can try with:
ProcessBuilder pb = new ProcessBuilder(
"C:/Users/Carlos/PycharmProjects/autoHTML/javaToExcel.py/python.exe",
pythonPath,
map.get("Driver Advances"),
map.get("Driver Loans"),
map.get("Escrow Fund"),
map.get("Maintenance Fund"),
map.get("Highway Use tax - 2290"));
I wrote a Python program that consists out of five .py script files.
I want to execute the main of those python scripts from within a Java Application.
What are my options to do so? Using the PythonInterpreter doesn't work, as for example the datetime module can't be loaded from Jython (and I don't want the user to determine his Python path for those dependencies to work).
I compiled the whole folder to .class files using Jython's compileall. Can I embed these .class files somehow to execute the main file from within my Java Application, or how should I proceed?
Have a look at the ProcessBuilder class in java: https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html.
The command used in the java constructor should be the same as what you would type in a command line. For example:
Process p = new ProcessBuilder("python", "myScript.py", "firstargument").start();
(the process builder does the same thing as the python subprocess module).
Have a look at running scripts through processbuilder
N.B. as for the Jython part of the question, if you go to the jython website (have a look at the FAQ section of their website www.jython.org). Check the entry "use jython from java".
I'm also interested in running Python code directly within Java, using Jython, and avoiding the need for an installed Python interpreter.
The article, 'Embedding Jython in Java Applications' explains how to reference an external *.py Python script, and pass it argument parameters, no installed Python interpreter necessary:
#pymodule.py - make this file accessible to your Java code
def square(value):
return value*value
This function can then be executed either by creating a string that
executes it, or by retrieving a pointer to the function and calling
its call method with the correct parameters:
//Java code implementing Jython and calling pymodule.py
import org.python.util.PythonInterpreter;
import org.python.core.*;
public class ImportExample {
public static void main(String [] args) throws PyException
{
PythonInterpreter pi = new PythonInterpreter();
pi.exec("from pymodule import square");
pi.set("integer", new PyInteger(42));
pi.exec("result = square(integer)");
pi.exec("print(result)");
PyInteger result = (PyInteger)pi.get("result");
System.out.println("result: "+ result.asInt());
PyFunction pf = (PyFunction)pi.get("square");
System.out.println(pf.__call__(new PyInteger(5)));
}
}
Jython's Maven/Gradle/etc dependency strings can be found at http://mvnrepository.com/artifact/org.python/jython-standalone/2.7.1
Jython JavaDoc
It is possible to load the other modules. You just need to specify the python path where your custom modules can be found. See the following test case and I am using the Python datatime/math modules inside my calling function (my_maths()) and I have multiple python files in the python.path which are imported by the main.py
#Test
public void testJython() {
Properties properties = System.getProperties();
properties.put("python.path", ".\\src\\test\\resources");
PythonInterpreter.initialize(System.getProperties(), properties, new String[0]);
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.execfile(".\\src\\test\\resources\\main.py");
interpreter.set("id", 150); //set variable value
interpreter.exec("val = my_maths(id)"); //the calling function in main.py
Integer returnVal = (Integer) interpreter.eval("val").__tojava__(Integer.class);
System.out.println("return from python: " + returnVal);
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I run a batch file from my Java Application?
Are there Java classes to run Windows batch files? For example, start the batch files and receive the results of the batch runs?
Apache Commons Exec is a good way to go. Solves several problems you'd encounter if using pure ProcessBuilder or Runtime.exec.
From the project description:
Executing external processes from Java is a well-known problem area. It is inheriently platform dependent and requires the developer to know and test for platform specific behaviors, for example using cmd.exe on Windows or limited buffer sizes causing deadlocks. The JRE support for this is very limited, albeit better with the new Java SE 1.5 ProcessBuilder class.
The usual ProcessBuilder stuff works with batch files, as does Runtime#exec. The command you're executing is cmd.exe, the batch file name is an argument to it. I believe the argument sequence is cmd.exe, /c, batch_file_name. (Edit: Yup, and in fact, I found a question here that this question duplicates on SO where that's in the answer.)
Runtime.getRuntime().exec("cmd /c start batFileName.bat"); should work.
But to read the output from java process, remove start from the above comman.
Yes, according to my knowledge, RunTime classe can. And ofcourse, ProcessBuilder also like that. I have run number of batch files using Java. Following is the google search result. It has links which are equally important
GOOGLE RESULT
If you want to use native Java and no 3rd party packages then try this using Runtime and Process. I'm not the best Java coder in the world but this should get what you want. It might need some modification to add a loop for reading everything from the input stream.
import java.util.*;
import java.lang.*;
import java.io.*;
public class test
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process batch = rt.exec("test.bat");
batch.waitFor();
//exitValue() contains the ERRORLEVEL from batch file
System.out.println(batch.exitValue());
//getInputStream will get all output from stdout
//getErrorStream will get all error output from stderr
InputStream inStream = batch.getInputStream();
byte[] text = new byte[inStream.available()];
inStream.read(text);
System.out.println(new String(text));
}
catch (IOException ex)
{
}
catch (InterruptedException ex)
{
}
}
}
I know. Heresy. But I'm in a bind. I have a lot of config files that use absolute path names, which creates an incompatibility between OS X and Windows. If I can get OS X (which I'm betting is the more flexible of the two) to recognize Q:/foo/bar/bim.properties as a valid absolute file name, it'll save me days of work spelunking through stack traces and config files.
In the end, I need this bit of Java test code to print "SUCCESS!" when it runs:
import java.io.*;
class DriveLetterTest {
static public void main(String... args) {
File f = new File("S:");
if (f.isDirectory()) {
System.out.println("SUCCESS!");
} else {
System.out.println("FAIL!");
}
}
}
Anyone know how this can be done?
UPDATE: Thanks for all the feedback, everyone. It's now obvious to me I really should have been clearer in my question.
Both the config files and the code that uses them belong to a third-party package I cannot change. (Well, I can change them, but that means incurring an ongoing maintenance load, which I want to avoid if at all possible.)
I'm in complete agreement with all of you who are appalled by this state of affairs. But the fact remains: I can't change the third-party code, and I really want to avoid forking the config files.
Short answer: No.
Long answer: For Java you should use System.getProperties(XXX).
Then you can load a Properties file or Configuration based on what you find in os.name.
Alternate Solution just strip off the S: when you read the existing configuration files on non-Windows machines and replace them with the appropriate things.
Opinion: Personally I would bite the bullet and deal with the technical debt now, fix all the configuration files at build time when the deployment for OSX is built and be done with it.
public class WhichOS
{
public static void main(final String[] args)
{
System.out.format("System.getProperty(\"os.name\") = %s\n", System.getProperty("os.name"));
System.out.format("System.getProperty(\"os.arch\") = %s\n", System.getProperty("os.arch"));
System.out.format("System.getProperty(\"os.version\") = %s\n", System.getProperty("os.version"));
}
}
the output on my iMac is:
System.getProperty("os.name") = Mac OS X
System.getProperty("os.arch") = x86_64
System.getProperty("os.version") = 10.6.4
Honestly, don't hard-code absolute paths in a program, even for a single-platform app. Do the correct thing.
The following is my wrong solution, saved to remind myself not to repeat giving a misdirected advice ... shame on me.
Just create a symbolic link named Q: just at the root directory / to / itself.
$ cd /
$ ln -s / Q:
$ ln -s / S:
You might need to use sudo. Then, at the start of your program, just chdir to /.
If you don't want Q: and S: to show up in the Finder, perform
$ /Developer/Tools/SetFile -P -a V Q:
$ /Developer/Tools/SetFile -P -a V S:
which set the invisible-to-the-Finder bit of the files.
The only way you can replace java.io.File is to replace that class in rt.jar.
I don't recommend that, but the best way to do this is to grab a bsd-port of the OpenJDK code, make necessary changes, build it and redistribute the binary with your project. Write a shell script to use your own java binary and not the built-in one.
PS. Just change your config files! Practice your regex skills and save yourself a lot of time.
If you are not willing to change your config file per OS, what are they for in first place?
Every installation should have its own set of config files and use it accordingly.
But if you insist.. you just have to detect the OS version and if is not Windows, ignore the letter:
Something along the lines:
boolean isWindows = System.getProperty("os.name").toLowerCase()
.contains("windows");
String folder = "S:";
if (isWindows && folder.matches("\\w:")) {
folder = "/";
} else if (isWindows && folder.matches("\\w:.+")) {
folder = folder.substring(2);// ignoring the first two letters S:
}
You get the idea
Most likely you'd have to provide a different java.io.File implementation that can parse out the file paths correctly, maybe there's one someone already made.
The real solution is to put this kind of stuff (hard-coded file paths) in configuration files and not in the source code.
Just tested something out, and discovered something interesting: In Windows, if the current directory is on the same logical volume (i.e. root is the same drive letter), you can leave off the drive letter when using a path. So you could just trim off all those drive letters and colons and you should be fine as long as you aren't using paths to items on different disks.
Here's what I finally ended up doing:
I downloaded the source code for the java.io package, and tweaked the code for java.io.File to look for path names that start with a letter and a colon. If it finds one, it prepends "/Volumes/" to the path name, coughs a warning into System.err, then continues as normal.
I've added symlinks under /Volumes to the "drives" I need mapped, so I have:
/Volumes/S:
/Volumes/Q:
I put it into its own jar, and put that jar at the front of the classpath for this project only. This way, the hack affects only me, and only this project.
Net result: java.io.File sees a path like "S:/bling.properties", and then checks the OS. If the OS is OS X, it prepends "/Volumes/", and looks for a file in /Volumes/S:/bling.properties, which is fine, because it can just follow the symlink.
Yeah, it's ugly as hell. But it gets the job done for today.