Closing a PDF file, generated when text is updated - Tkinter - python

So I have a function which generates a pdf file from a latex file in Tkinter - but only when a button is clicked.
What I am trying to do now is write a function which updates the pdf file every few seconds, so that the user can see how what they have written so far looks like. What I do is run the function which generates the pdf every few seconds and another function, which is supposed to close the file after a few seconds - but I seem to have a problem with closing the pdf files - hence I need to do it manually, otherwise the updated version of the pdf does not appear on the screen.
Here is the code I have used:
def generate_pdf(self):
global mainName
global pdfDirectory
name=self.getName(self.fl)
f = open('%s.tex'%name,'w')
tex = self.txt.get(1.0,"end-1c")
f.write(tex)
f.close()
proc=subprocess.Popen(['pdflatex','-output-directory', pdfDirectory,'%s.tex'%name])
proc.communicate()
self.open_file(name)
self.master.after(20000,self.generate_pdf)
self.close_file(name)
def open_file(self,filename):
if sys.platform == "win32":
os.startfile('%s.pdf'%filename)
#os.unlink('%s.tex'%filename)
os.unlink('%s.log'%filename)
os.unlink('%s.aux'%filename)
else:
opener ="open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, '%s.pdf'%filename])
def close_file(self,filename):
if sys.platform == "win32":
os.close('%s.pdf'%filename)
else:
closer ="close" if sys.platform == "darwin" else "xdg-close"
subprocess.call([closer, '%s.pdf'%filename])
self.master.after(29000,self.close_file)
The error I get when running it in Windows is:
os.close('%s.pdf'%filename)
TypeError: an integer is required
The error I get when running it in Linux is:
File "interface_updated_Linux.py", line 716, in generate_pdf
self.close_file(name)
File "interface_updated_Linux.py", line 734, in close_file
subprocess.call([closer, '%s.pdf'%filename])
File "/usr/lib64/python2.6/subprocess.py", line 478, in call
p = Popen(*popenargs, **kwargs)
File "/usr/lib64/python2.6/subprocess.py", line 642, in __init__
errread, errwrite)
File "/usr/lib64/python2.6/subprocess.py", line 1238, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory

From the documentation:
os.close(fd)
Close file descriptor fd.
Availability: Unix, Windows.
Note This function is intended for low-level I/O and must be applied
to a file descriptor as returned by os.open() or pipe(). To close a
“file object” returned by the built-in function open() or by popen()
or fdopen(), use its close() method.
You're trying to apply it to a string here.
You should figure out how to pass around the file descriptor among the methods in your class. This might be a little bit more complicated in the case where you're using subprocess to open/close files, but in general you're gonna need a (file handle/file descriptor/process ID) to close a file. Simply using the name is not sufficient since for example you could have two handles to the same file open, how would it know which one to close?

Related

Can't run any scripts, Traceback, FileNotFoundError: [Errno 2]

Earlier this week I put together my first ever working python script. I am trying to create another one in a different directory, but when I try to run any script at all (using Notepad++) I get an error that shows Python seems to be trying to access the old directory and not finding it, even though I haven't told it to look in the old directory. Now the original script doesn't work either. This is what the error message looks like, no matter what I try to run:
python "C:\Users\me\Documents\oldDirectory\oldScript.py"
Process started (PID=12884) >>>
Traceback (most recent call last):
File "C:\Users\me\Documents\oldDirectory\oldScript.py", line 13, in <module>
month = pd.read_csv(sheet)
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\parsers.py", line 610, in read_csv
return _read(filepath_or_buffer, kwds)
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\parsers.py", line 462, in _read
parser = TextFileReader(filepath_or_buffer, **kwds)
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\parsers.py", line 819, in __init__
self._engine = self._make_engine(self.engine)
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\parsers.py", line 1050, in _make_engine
return mapping[engine](self.f, **self.options) # type: ignore[call-arg]
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\parsers.py", line 1867, in __init__
self._open_handles(src, kwds)
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\parsers.py", line 1362, in _open_handles
self.handles = get_handle(
File "C:\Users\me\AppData\Local\Programs\Python\Python39\lib\site-packages\pandas\io\common.py", line 642, in get_handle
handle = open(
FileNotFoundError: [Errno 2] No such file or directory: 'C:\Users\me\Documents\oldDirectory\Table.csv'
<<< Process finished (PID=12884). (Exit code 1)
As you might guess from the error message, my original script used pd.read_csv() and accessed Table.csv. No matter what I try to run, I get this error. What's going on?
You can check again the path of you file to read. If it's not wrong, try without the '.csv' at the end of the file path.
The problem was with how I was using Notepad++ and not the code I was running. To run a script in that environment you press F6 and a dialog pops up asking what you want to execute. This does NOT default to whichever .py you have open at the moment, so when it hit ctrl+F6 to skip the dialog, it kept trying to run my old script.

Subprocess.run() cannot find file, even if path.exists() returns true

I found these two pages:
Subprocess.run() cannot find path
Python3 Subprocess.run cannot find relative referenced file
but it didn't help. The first page talks about using \\ but I already do, and the second one talks about double quotes around one of the arguments.
work = Path("R:\\Work")
resume = work.joinpath("cover_letter_resume_base.doc")
current_date = construct_current_date()
company_name = gather_user_information(question="Company name: ",
error_message="Company name cannot be empty")
position = gather_user_information(question="Position: ",
error_message="Position cannot be empty")
# Construct destination folder string using the company name, job title, and current date
destination = work.joinpath(company_name).joinpath(position).joinpath(current_date)
# Create the destintion folder
os.makedirs(destination, exist_ok=True)
# Construct file name
company_name_position = "{0}_{1}{2}".format(company_name.strip().lower().replace(" ", "_"),
position.strip().lower().replace(" ", "_"), resume.suffix)
resume_with_company_name_job_title = resume.stem.replace("base", company_name_position)
destination_file = destination.joinpath(resume_with_company_name_job_title)
# Copy and rename the resume based on the company and title.
shutil.copy2(src=resume, dst=destination_file)
if destination_file.exists():
print(f"{destination_file} created.")
#subprocess.run(["open", str(destination_file)], check=True)
The program gets the company name and position from the user, generates the current date, creates the directories, and then moves/renames the base resume based on the user input.
Output and Results:
Company name: Microsoft
Position: Software Eng
R:\Work\Microsoft\Software Engineer\20190722\cover_letter_resume_microsoft_software_eng.doc
created.
Error Message:
[WinError 2] The system cannot find the file specified
Traceback (most recent call last):
File "c:/Users/Kiska/python/job-application/main.py", line 59, in <module>
main()
File "c:/Users/Kiska/python/job-application/main.py", line 53, in main
raise error
File "c:/Users/Kiska/python/job-application/main.py", line 48, in main
subprocess.run(["start", str(destination_file)], check=True)
File "C:\Program Files (x86)\Python37-32\lib\subprocess.py", line 472, in run
with Popen(*popenargs, **kwargs) as process:
File "C:\Program Files (x86)\Python37-32\lib\subprocess.py", line 775, in __init__
restore_signals, start_new_session)
File "C:\Program Files (x86)\Python37-32\lib\subprocess.py", line 1178, in _execute_child
startupinfo)
FileNotFoundError: [WinError 2] The system cannot find the file specified
The if statement returns True but subprocess.run() cannot see the file, but I'm not really sure why.
On which operating system are you? The backslashes in your path suggest that you're on Windows and you're using open to open the document with its default application. However, looking at this question Open document with default OS application in Python, both in Windows and Mac OS you should use start instead of open for Windows:
subprocess.run(["start", str(destination_file)], check=True, shell=True)
Also you need to add shell=True for start to work. However, you should read https://docs.python.org/3/library/subprocess.html#security-considerations beforehand.
(I suspect, the error [WinError 2] The system cannot find the file specified appears, because Windows cannot find open - it's not about the document you're trying to open.)

mkstemp opening too many files

I'm using subprocess.run in a loop (more than 10 000 times) to call some java command.
Like this:
import subprocess
import tempfile
for i in range(10000):
ret = subprocess.run(["ls"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(_, name) = tempfile.mkstemp()
with open(name, 'w+') as fp:
fp.write(ret.stdout.decode())
However, after some time, I got the following exception:
Traceback (most recent call last):
File "mwe.py", line 5, in <module>
ret = subprocess.run(["ls"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
File "/usr/lib/python3.5/subprocess.py", line 693, in run
with Popen(*popenargs, **kwargs) as process:
File "/usr/lib/python3.5/subprocess.py", line 947, in __init__
restore_signals, start_new_session)
File "/usr/lib/python3.5/subprocess.py", line 1454, in _execute_child
errpipe_read, errpipe_write = os.pipe()
OSError: [Errno 24] Too many open files
Am I missing something to close some file descriptor?
Thanks
mkstemp returns an already open file descriptor fd followed by the filename. You are ignoring the file descriptor (your choice of the name _ suggests you have explicitly chosen to ignore it) and as a result you are neglecting to close it. Instead, you open the file a second time using the filename, creating a file object that contains a second file descriptor for the same file. Regardless of whether you close that second one, the first one remains open.
Here's a fix to the mkstemp approach:
temporaryFiles = []
for i in range(1000):
...
fd, name = tempfile.mkstemp()
os.write(fd, ... )
os.close(fd)
temporaryFiles.append(name) # remember the filename for future processing/deletion
Building on Wyrmwood's suggestion in the comments, an even better approach would be:
temporaryFiles = []
for i in range(1000):
...
with tempfile.NamedTemporaryFile(delete=False) as tmp:
# tmp is a context manager that will automatically close the file when you exit this clause
tmp.file.write( ... )
temporaryFiles.append(tmp.name) # remember the filename for future processing/deletion
Note that both mkstemp and the NamedTemporaryFile constructor have arguments that allow you to be more specific about the file's location (dir) and naming (prefix, suffix). If you want to keep the files, you should specify dir so that you keep them out of the default temporary directory, since the default location may get cleaned up by the OS.

How does one create custom output stream for subprocess.call

I am trying to get realtime output of a subprocess.call by defining my own output stream but it doesn't seem to work.
Reason: I want to run a subprocess and get output of that call to both stdout(in realtime so i can look at the script and see current progress) as well as logging it to a file
subprocess.py:
import time
while True:
print("Things")
time.sleep(1)
mainprocess.py
import subprocess
import io
class CustomIO(io.IOBase):
def write(self, str):
print("CustomIO: %s"%str)
# logging to be implemented here
customio = CustomIO()
subprocess.call(["python3", "print_process.py"], stdout=customio)
But when i run this code i get this error message:
Traceback (most recent call last):
File "call_test.py", line 9, in <module>
subprocess.call(["python3", "print_process.py"], stdout=customio)
File "/usr/lib/python3.4/subprocess.py", line 537, in call
with Popen(*popenargs, **kwargs) as p:
File "/usr/lib/python3.4/subprocess.py", line 823, in __init__
errread, errwrite) = self._get_handles(stdin, stdout, stderr)
File "/usr/lib/python3.4/subprocess.py", line 1302, in _get_handles
c2pwrite = stdout.fileno()
io.UnsupportedOperation: fileno
So, anyone have any clue if this is possible?
Am i inheriting the wrong baseclass?
Am i not overloading the proper methods?
Or am i completely off the rails and should be going about this in a completely different way?
If you want to process the output of a subprocess, you need to pass stdout=subprocess.PIPE. However, call() and run() will both wait until the process is finished before making it available, so you cannot handle it in real time using these functions.
You need to use subprocess.Popen:
import subprocess as sp
def handle_output(output_line):
...
my_process = sp.Popen(["python3", "print_process.py"],
stdout=sp.PIPE,
universal_newlines=True) # changes stdout from bytes to text
for line in my_process.stdout:
handle_output(line)
my_process.wait()
Update: Make sure to flush the output buffer in your child process:
while True:
print("Things", flush=True)
time.sleep(1)
You need to specify and open stream with a file descriptor. fileno isn't implemented for io.IOBase because this is just an in-memory stream:
Frequently Used Arguments
stdin, stdout and stderr specify the executed program’s standard
input, standard output and standard error file handles, respectively.
Valid values are PIPE, DEVNULL, an existing file descriptor (a
positive integer), an existing file object, and None. PIPE indicates
that a new pipe to the child should be created. DEVNULL indicates that
the special file os.devnull will be used. With the default settings of
None, no redirection will occur;
So you might use sockets, pipes, and open files as stdout, the file descriptor is passed to the child process as it's stdout. I didn't use sockets with subprocess.Popen though, but I expect them to work, I believe what matters here is the file descriptor to the child, not what type of object the file descriptor points to.

Python os.walk and opening apps/files

I'm trying to make a script that crawls through the Applications directory and open up a given file. So here is my code
import os, subprocess
os.chdir('/Applications')
root = '.'
for path, dirs, files in os.walk(root):
#print path
for f in files:
if f == 'Atom':
subprocess.call([f])
break
So I have three questions.
At first I used Atom as the example to execute the script. It opens up fine, but even after opening the app the loop doesn't break and keeps crawling.
Second, the Atom app doesn't open up as it would normally do. It opens up in the directory of the applications folder, which looks something like this.
While it should merely look like this,
And the very important problem is that it didn't work for any other applications which I couldn't understand. Here is the error output when I tried to open AppStore.
./App Store.app
./App Store.app/Contents
./App Store.app/Contents/_CodeSignature
./App Store.app/Contents/MacOS
Traceback (most recent call last):
File "control_files.py", line 32, in <module>
subprocess.call([f])
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 522, in call
return Popen(*popenargs, **kwargs).wait()
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 709, in __init__
errread, errwrite)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1326, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory
What could possibly be the problem?
The previous answers are spot-on about break only exiting the innermost loop.
Another way to escape the loop, and probably neater, would be to hide away this functionality in a function and return from it. Something along the lines of:
def open_program(root, filename):
for path, dirs, files in os.walk(root):
if filename in files:
full_path = os.path.join(path, filename)
subprocess.call([full_path])
return
IMO using filename in files makes the code cleaner, and does pretty much the same work.

Categories

Resources