Vim cannot save to temporary files created by python - python

Goal
I am trying to create and edit a temporary file in vim (exactly the same behavior as a commit script in git/hg/svn).
Current code
I found a method to do so in this answer:
call up an EDITOR (vim) from a python script
import sys, tempfile, os
from subprocess import call
EDITOR = os.environ.get('EDITOR','vim')
initial_message = "write message here:"
with tempfile.NamedTemporaryFile(suffix=".tmp") as tmp:
tmp.write(initial_message)
tmp.flush()
call([EDITOR, tmp.name])
tmp.seek(0)
print tmp.read()
The Issue
When I run the above code, the tempfile does not read the changes made in vim. Here is the output after I have added several other lines in vim:
fgimenez#dn0a22805f> ./note.py
Please edit the file:
fgimenez#dn0a22805f>
Now for the interesting (weird) part. If I change my editor to nano or emacs, the script works just fine! So far, this only seems to break when I use vim or textedit.
As another experiment, I tried calling a couple editors in a row to see what happens. The modified code is:
with tempfile.NamedTemporaryFile(suffix=".tmp") as tmp:
tmp.write(initial_message)
tmp.flush()
# CALLING TWO EDITORS HERE, VIM THEN NANO
call(['vim', tmp.name])
raw_input("pausing between editors, just press enter")
call(['nano', tmp.name])
tmp.seek(0)
print tmp.read()
I.e. I edit with vim then nano. What happens is that nano DOES register the changes made by vim, but python doesn't register anything (same result as before):
fgimenez#dn0a22805f> ./note.py
Please edit the file:
fgimenez#dn0a22805f>
BUT, if I edit with nano first, then vim, python still registers the nano edits but not the vim ones!
with tempfile.NamedTemporaryFile(suffix=".tmp") as tmp:
tmp.write(initial_message)
tmp.flush()
# CALLING TWO EDITORS HERE, NANO THEN VIM
call(['nano', tmp.name])
raw_input("pausing between editors, just press enter")
call(['vim', tmp.name])
tmp.seek(0)
print tmp.read()
Ouput from running the program and adding a\nb\nc in nano and d\ne\nf in vim:
fgimenez#dn0a22805f> ./note.py
Please edit the file:
a
b
c
fgimenez#dn0a22805f>
It seems as if using vim or textedit eliminates the ability to append to the file. I'm completely confused here, and I just want to edit my notes in vim...
Edit 1: Clarifications
I am on osx Mavericks
I call vim from the shell (not MacVim) and end the session with ZZ (also tried :w :q)

I'm no Python expert, but it looks like you're keeping the handle to the temp file open while Vim is editing the file, and then attempt to read in the edited contents from the handle. By default, Vim creates a copy of the original file, writes the new contents to another file, and then renames it to the original (see :help 'backupcopy' for the details; other editors like nano apparently don't do it this way). This means that the Python handle still points to the original file (even though it may have already been deleted from the file system, depending on the Vim settings), and you get the original content.
You either need to reconfigure Vim (see :help 'writebackup'), or (better) change the Python implementation to re-open the same temp file name after Vim has exited, in order to get a handle to the new written file contents.

I had the same problem on OS X after my code worked fine on Linux. As Ingo suggests, you can get the latest contents by re-opening the file. To do this, you probably want to create a temporary file with delete=False and then explicitly delete the file when you're done:
import sys, tempfile, os
from subprocess import call
EDITOR = os.environ.get('EDITOR','vim')
initial_message = "write message here:"
with tempfile.NamedTemporaryFile(suffix=".tmp", delete=False) as tmp:
tmp.write(initial_message)
tmp.flush()
call([EDITOR, tmp.name])
tmp.close()
with open(tmp.name) as f:
print f.read()
os.unlink(tmp.name)

Related

How to prevent reload from disk when using open() in Python?

This is my first post on StackOverflow. I'm also a beginner in Python. So I was just tinkering with the open() function, I was making a simple program to replace text in other .txt files. Here is my code:
f = open("file.txt", "r+")
f.truncate(0)
f.write("This text has been replaced.")
f.close()
print("Text replaced")
So, after running this program, the text in "file.txt" is getting changed. However, when I do ctrl + z, it's showing Undo Reload from Disk?, and when you click OK, the text gets back to normal.
How to prevent this? I am using Python 3.9, Pycharm code editor.
Thank you
I think your question might be more about the PyCharm UI editor.
If I understand correctly:
you have "file.txt" open in the PyCharm editor
you run the code above
At this point, the filesystem has the "file.txt" with the updated contents. The PyCharm editor (buffer) still has the old contents.
When you ctrl+z, PyCharm notices the filesystem has been updated and prompts to see how you want to proceed. When you click "OK", PyCharm writes its buffer to the file, which is the original contents.

Editing temporary file with Vim in Python subprocess not working as expected on Mac OS

My initial goal was to get user input via a command-line text-editor from within a Python script. More specifically, my plan was to create a temporary file and populate it with some pre-written text, open the file with a text-editor and allow the user to modify the file content, read the data from the file after the user exits the editor, then finally delete the file after it's all over.
I seem to have found a way to do this that is working for me, but along the way I tried a couple of approaches that did not work and I'd like to understand exactly why.
Consider the following Python script (a slightly modified version of the script taken from this post):
#!/usr/bin/env python2
# -*- encoding: ascii -*-
"""callvim.py
Demonstrates calling a text-editor (e.g. Vim) from within a Python script,
including passing input to the editor and reading output from the editor.
"""
import tempfile
import os
from subprocess import call
# Get the text editor from the shell, otherwise default to Vim
EDITOR = os.environ.get('EDITOR','vim')
# Set initial input with which to populate the buffer
initial_message = "Hello world!"
# Open a temporary file to communicate through
with tempfile.NamedTemporaryFile(suffix=".tmp") as tf:
# Write the initial content to the file I/O buffer
tf.write(initial_message)
# Flush the I/O buffer to make sure the data is written to the file
tf.flush()
# Open the file with the text editor
call([EDITOR, tf.name])
# Rewind the file offset to the beginning of the file
tf.seek(0)
# Read the file data into a variable
edited_message = tf.read()
# Output the data
print(edited_message)
I've tried running this script in two different environments so far: on a macOS computer (running macOS 10.12) and on a Debian computer (running Debian 8.8). Both computers have the same (minor) version of Vim installed (Vim 7.4).
When I run this script with EDITOR=vim on my Debian 8 (Jessie) machine it works as expected. I'm prompted with Vim and a buffer containing the string "Hello world!". After editing the buffer to contain the string "Goodbye world!", saving the file, and exiting Vim, I see the string "Goodbye world!" printed to the console.
When I run the same script on my macOS 10.12 (Sierra) machine it does not seem to work. The same procedure results in "Hello world!" being displayed on-screen - as if the file is being read before it is edited.
However if run the script on my Mac with EDITOR=nano then once again everything seems to work as expected.
I tried a few variations on this script using different methods from the tempfile module (e.g. using tempfile.TemporaryFile() and tempfile.mkstemp()) with the same results.
Now consider the following alternative script:
#!/usr/bin/env python2
# -*- encoding: ascii -*-
"""callvim.py
Demonstrates calling a text-editor (e.g. Vim) from within a Python script,
including passing input to the editor and reading output from the editor.
"""
import subprocess
import os
# Create a temporary file and write some default text
file_path = "tempfile"
file_handle = open(file_path, "w")
file_handle.write("Hello world!")
file_handle.close()
# Open the file with Vim
subprocess.call(["vim", file_path])
# Rewind to the beginning of the file
file_handle = open(file_path, 'r')
# Read the data from the file
data = file_handle.read()
# Close the temporary file
file_handle.close()
# Delete the temporary file
os.remove(file_path)
# Print the data
print(data)
This script, which avoids using the tempfile module, appears to be working consistently across both platforms.
So it seems that this script may be failing for some reason having to do with how Vim and the tempfile Python module interact on macOS. What's going on here?
This is happening because your second script closes the file handle before invoking vim, then opens a new one afterwards, whereas the first script doesn't. It has nothing to do with the tempfile module per se. This code works as expected:
import tempfile, os
from subprocess import call
initial_message = "Hello world!"
tf = tempfile.NamedTemporaryFile(suffix=".tmp", delete=False)
tf.write(initial_message)
tf.close()
call(['vim', tf.name])
tf = open(tf.name, 'r')
edited_message = tf.read()
tf.close()
os.unlink(tf.name)
print(edited_message)
Note the delete=False in the call to NamedTemporaryFile, which ensures that the file isn't deleted when we close it the first time (we have to delete it manually with os.unlink later).
I think what's going on here is that vim isn't editing your file in-place, it's writing to a swap file. When you save and quit, vim replaces the original file with the edited one, leaving your file handle pointing to the old, unedited one. There are ways to prevent vim from using swap files (see, e.g. here), but I wouldn't recommend them. Just remember to update your file handles after vim has done its business.

Django TemporaryUploadedFile does not exist but nevertheless it is read successfully

I have the following situation here. My OS shows that django TemporaryUploadedFile which I got via the POST request does not exist anymore but somehow this uploaded file can be read.
Here is the code
text_file = request.FILES['text_file']
print(text_file.temporary_file_path())
os.system('ls -l ' + text_file.temporary_file_path())
fs = FileSystemStorage()
file_new =fs.save(text_file.name, text_file)
print(text_file.temporary_file_path())
os.system('ls -l ' + text_file.temporary_file_path())
fs.delete(file_new)
for chunk in text_file.chunks():
text += chunk.decode(encoding)
print('Got text OK.')
This gives the following output:
/tmp/tmp0tngal9t.upload foo.txt
-rw------- 1 mine machine 3072889 oct 18 19:29 /tmp/tmp0tngal9t.upload
/tmp/tmp0tngal9t.upload foo.txt
ls: cannot access '/tmp/tmp0tngal9t.upload': No such file or directory
Got text OK.
So TemporaryUploadedFile is disappeared after it was saved to file_new which later is also deleted. Anyway text_file is successfully read by chunks and I get all the text from uploaded foo.txt file. How it is possible? From where text_file.chunks() gets the data if text_file does not exist anymore?
I use:
python 3.5.2
django 1.10.2
ubuntu 16.04.1
I found out that this problem still remains for bare python, so it is not particularly related to django as in this example I just read text_file which were open in request.FILES['text_file'].
I re-asked the similar question here focusing on python only. It turned out that the problem is not so related with python either, but with Linux/Unix system file management. I quote here the answer of Jean-François Fabre:
Nothing to do with Python. In C, Fortran, or Visual Cobol you'd have
the same behaviour as long as the code gets its handle from open
system call.
On Linux/Unix systems, once a process has a handle on a file, it can
read it, even if the file is deleted. For more details check that
question (I wasn't sure if it was OK to do that, it seems to be)
On Windows you just wouldn't be able to delete the file as long as
it's locked by a process.

Automatically inserting a header in vim

Is there a way to auto add a header when i open a new file in vim?
My objective is to automatically add the shebang "#! /usr/bin/python" when i open a new file using the command "vim test.py". If the file is already present, no header should be inserted.
Add this line in your configuration file:
autocmd BufNewFile *.py 0put =\"#!/usr/bin/python\<nl>\"|$
This might be over-kill, but you could look at one of the snippet scripts for Vim, e.g. snipMate -- http://www.vim.org/scripts/script.php?script_id=2540
But, for what you want, you might just map a key to a command that reads in a file. For example:
nmap <leader>r :r boiler_mashbang<cr>
And, then put your boilerplate in the file: boiler_mashbang.

Print a PDF and delete the file when printing has finished

I have a Python application taht will be executed repeatedly. It saves a PDF as a file and then prints it. When printing ends it deletes the file.
My current solution (for the print and delete part) is this:
win32api.ShellExecute(0, "print", file_path, None, ".", 0)
time.sleep(10)
os.remove(self.options.dest_name)
time.sleep(10) is a trick to give the printing process the time to run before file deletion. Without it Acrobat Reader opens (it opens anyway) and alerts that it can't find the file. This because file removal has already occured.
The question is:
how can I do it without this unreliable trick? The best thing would be to have an handler for the printing process and get by it an info about the printing state: I wait for it to report it's completed and I delete the file.
it would be even better if Acrobat Reader wouldn't open, but this is not a great problem.
EDIT: I tried switching to Foxit Reader as the default PDF reader and now it doesn't open when I don't want. ;)
OTHER POSSIBLE SOLUTION:
Cylically check if the file is available (not used by another process) and when it's available again delete it. How could I do it in Python?
At last I've found a good solution, thanks to this answer (and also #Lennart mentioned it on a comment):
install Ghostscript
install GSview (which includes gsprint.exe)
write this code:
file_path = "C:\\temp\\test.pdf"
p = subprocess.Popen(["C:\\Ghostgum\\gsview\\gsprint.exe", "-printer", printer_name, "-colour", file_path],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate() # waits for the gs process to end
os.remove(file_path) # now the file can be removed
No Acrobat windows opening, no file removed before printing... The annoyance: installing GS.
See also: gsprint reference
Rather than hard-coding a filename and printing that, you should use the tempfile module to create a temporary file with a unique name.
import tempfile
file_name = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
If you want, you can run a regular tidy-up script using Window's scheduling tools to delete the files created.
Adobe acrobat has (or at least used to have) a parameter "/t", which made it open, print and exit. By using it, you can call acrobat reader and wait for it to exit, and then delete the file.
Untested code:
>>> import subprocess
# You will have to figure out where your Acrobate reader is located, can be found in the registry:
>>> acrobatexe = "C:\Program Files\Adobe\Acrobat 4.0\Reader\AcroRd32.exe"
>>> subprocess.call([acrobatexe, "/t", tempfilename, "My Windows Printer Name"])
>>> os.unlink(tempfilename)
Something like that.
If you don't want acrobat to open, there are open source software that will print pdfs from the command line. You could include one with your software.
Why not use os.system, which will wait until the process is finished?

Categories