I have tried the following code by slightly modifying the example in documentation
class Upload():
def POST(self):
web.header('enctype','multipart/form-data')
print strftime("%Y-%m-%d %H:%M:%S", gmtime())
x = web.input(file={})
filedir = '/DiginUploads' # change this to the directory you want to store the file in.
if 'file' in x: # to check if the file-object is created
filepath=x.file.filename.replace('\\','/') # replaces the windows-style slashes with linux ones.
filename=filepath.split('/')[-1] # splits the and chooses the last part (the filename with extension)
fout = open(filedir +'/'+ filename,'w') # creates the file where the uploaded file should be stored
fout.write(x.file.file.read()) # writes the uploaded file to the newly created file.
fout.close() # closes the file, upload complete.
But this works only for csv and txt documents. For Excel/pdf etc file gets created but it can't be opened (corrupted). What should I do to handle this scenario?
I saw this but it is about printing the content which does not address my matter.
You need to use wb (binary) mode when opening the file:
fout = open(filedir +'/'+ filename, 'wb')
Related
I'm trying to create a download function for my streamlit app. But what I currently have allows me to download a zip file via a button on my streamlit app but unfortunately it also saves it to my local folder. I don't want it to save to my local folder. The problem is when I initialize the file_zip object. I want the zip file in a specific name ideally the same name of the file that the user upload with a '.zip' extension (i.e datafile that contains the string file name as a parameter in the function). But everytime I do that it keeps saving the zip file in my local folder. Is there an alternative to this? BTW I'm trying to save list of pandas dataframe into one zip file.
def downloader(list_df, datafile, file_type):
file = datafile.name.split(".")[0]
#create zip file
with zipfile.ZipFile("{}.zip".format(file), 'w', zipfile.ZIP_DEFLATED) as file_zip:
for i in range(len(list_df)):
file_zip.writestr(file+"_group_{}".format(i)+".csv", pd.DataFrame(list_df[i]).to_csv())
file_zip.close()
#pass it to front end for download
zip_name = "{}.zip".format(file)
with open(zip_name, "rb") as f:
bytes=f.read()
b64 = base64.b64encode(bytes).decode()
href = f'Click Here To Download'
st.markdown(href, unsafe_allow_html=True)
It sounds like you want to create the zip file in memory and use it later to build a base64 encoding. You can use an io.BytesIO() object with ZipFile, rewind it, and read the data back for base64 encoding.
import io
def downloader(list_df, datafile, file_type):
file = datafile.name.split(".")[0]
#create zip file
zip_buf = io.BytesIO()
with zipfile.ZipFile(zip_buf, 'w', zipfile.ZIP_DEFLATED) as file_zip:
for i in range(len(list_df)):
file_zip.writestr(file+"_group_{}".format(i)+".csv", pd.DataFrame(list_df[i]).to_csv())
zip_buf.seek(0)
#pass it to front end for download
zip_name = "{}.zip".format(file)
b64 = base64.b64encode(zip_buf.read()).decode()
del zip_buf
href = f'Click Here To download'
st.markdown(href, unsafe_allow_html=True)
I'm developing a Flask application where I want the user to download a set of files from the server. To achieve this objective, I use the zipfile module in order to zip all the files and then send this compressed file to the user.
Here is my code:
#app.route("/get_my_files/<file_name>", methods = ["GET"])
def get_my_files(file_name):
file_exts = [".csv", ".xlsx", ".json"]
file_path = "./user_files/" + file_name
# Create compress file.
memory_file = io.BytesIO()
with zipfile.ZipFile(memory_file, 'w') as zf:
for ext in file_exts:
#data = zipfile.ZipInfo(individualFile)
data = zipfile.ZipInfo("resultado" + ext)
data.date_time = time.localtime(time.time())[:6]
data.compress_type = zipfile.ZIP_DEFLATED
zf.writestr(data, open(file_path + ext, "r").read())
memory_file.seek(0)
# , encoding="ISO-8859-1"
# Delete files.
for ext in file_exts:
os.remove(file_path + ext)
# Return zip file to client.
return send_file(
memory_file,
mimetype = "zip",
attachment_filename='resultados.zip',
as_attachment=True,
cache_timeout=0
)
Unfortunately, once I decompress the zip file, the Excel file is getting corrupted (CSV and JSON file can be read and opened without problems). I have tried several different types of encoding when writing the zip file, however I haven't been able to find a solution.
What is the problem and how can I do this correctly?
You opened the files in text mode, which worked for JSON and CSV, but not for a binary file like Excel.
open(file_path + ext, "r")
You need to open them in binary mode, that is rb = read binary.
The computer is toying with me, I know it!
I am creating a zip folder in Python. The individual files are generated in memory and then the whole thing is zipped and saved to a file. I am allowed to add 9 files to the zip. I am allowed to add 11 files to the zip. But 10, no, not 10 files. The zip file IS saved to my computer, but I'm not allowed to open it; Windows says that the compressed zipped folder is invalid.
I use the code below, which I got from another stackoverflow question. It appends 10 files and saves the zipped folder. When I click on the folder, I cannot extract it. BUT, remove one of the appends() and it's fine. Or, add another append and it works!
What am I missing here? How can I make this work every time?
imz = InMemoryZip()
imz.append("1a.txt", "a").append("2a.txt", "a").append("3a.txt", "a").append("4a.txt", "a").append("5a.txt", "a").append("6a.txt", "a").append("7a.txt", "a").append("8a.txt", "a").append("9a.txt", "a").append("10a.txt", "a")
imz.writetofile("C:/path/test.zip")
import zipfile
import StringIO
class InMemoryZip(object):
def __init__(self):
# Create the in-memory file-like object
self.in_memory_zip = StringIO.StringIO()
def append(self, filename_in_zip, file_contents):
'''Appends a file with name filename_in_zip and contents of
file_contents to the in-memory zip.'''
# Get a handle to the in-memory zip in append mode
zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False)
# Write the file to the in-memory zip
zf.writestr(filename_in_zip, file_contents)
# Mark the files as having been created on Windows so that
# Unix permissions are not inferred as 0000
for zfile in zf.filelist:
zfile.create_system = 0
return self
def read(self):
'''Returns a string with the contents of the in-memory zip.'''
self.in_memory_zip.seek(0)
return self.in_memory_zip.read()
def writetofile(self, filename):
'''Writes the in-memory zip to a file.'''
f = file(filename, "w")
f.write(self.read())
f.close()
You should use the 'wb' mode when creating the file you are saving to the file system. This will ensure that the file is written in binary.
Otherwise, any time a newline (\n) character happens to be encountered in the zip file python will replace it to match the windows line ending (\r\n). The reason 10 files is a problem is that 10 happens to be the code for \n.
So your write function should look like this:
def writetofile(self, filename):
'''Writes the in-memory zip to a file.'''
f = file(filename, 'wb')
f.write(self.read())
f.close()
This should fix your problem and work for the files in your example. Although, in your case you might find it easier to write the zip file directly to the file system like this code which includes some of the comments from above:
import StringIO
import zipfile
class ZipCreator:
buffer = None
def __init__(self, fileName=None):
if fileName:
self.zipFile = zipfile.ZipFile(fileName, 'w', zipfile.ZIP_DEFLATED, False)
return
self.buffer = StringIO.StringIO()
self.zipFile = zipfile.ZipFile(self.buffer, 'w', zipfile.ZIP_DEFLATED, False)
def addToZipFromFileSystem(self, filePath, filenameInZip):
self.zipFile.write(filePath, filenameInZip)
def addToZipFromMemory(self, filenameInZip, fileContents):
self.zipFile.writestr(filenameInZip, fileContents)
for zipFile in self.zipFile.filelist:
zipFile.create_system = 0
def write(self, fileName):
if not self.buffer: # If the buffer was not initialized the file is written by the ZipFile
self.zipFile.close()
return
f = file(fileName, 'wb')
f.write(self.buffer.getvalue())
f.close()
# Use File Handle
zipCreator = ZipCreator('C:/path/test.zip')
# Use Memory Buffer
# zipCreator = ZipCreator()
for i in range(1, 10):
zipCreator.addToZipFromMemory('test/%sa.txt' % i, 'a')
zipCreator.write('C:/path/test.zip')
Ideally, you would probably use separate classes for an in-memory zip and a zip that is tied to the file system from the beginning. I have also seem some issues with the in-memory zip when folders are added which are difficult to recreate and which I am still trying to track down.
I need to create a temporary file to send it, I have tried :
# Create a temporary file --> I think it is ok (file not seen)
temporaryfile = NamedTemporaryFile(delete=False, dir=COMPRESSED_ROOT)
# The path to archive --> It's ok
root_dir = "something"
# Create a compressed file --> It bugs
data = open(f.write(make_archive(f.name, 'zip', root_dir))).read()
# Send the file --> Its ok
response = HttpResponse(data, mimetype='application/zip')
response['Content-Disposition'] = 'attachment; filename="%s"' % unicode(downloadedassignment.name + '.zip')
return response
I don't know at all if it is the good approach..
I actually just needed to do something similar and I wanted to avoid file I/O entirely, if possible. Here's what I came up with:
import tempfile
import zipfile
with tempfile.SpooledTemporaryFile() as tmp:
with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as archive:
archive.writestr('something.txt', 'Some Content Here')
# Reset file pointer
tmp.seek(0)
# Write file data to response
return HttpResponse(tmp.read(), mimetype='application/x-zip-compressed')
It uses a SpooledTemporaryFile so it will remain in-memory, unless it exceeds the memory limits. Then, I set this tempory file as the stream for ZipFile to use. The filename passed to writestr is just the filename that the file will have inside the archive, it doesn't have anything to do with the server's filesystem. Then, I just need to rewind the file pointer (seek(0)) after ZipFile had done its thing and dump it to the response.
First of all, you don't need to create a NamedTemporaryFile to use make_archive; all you want is a unique filename for the make_archive file to create.
.write doesn't return a filename
To focus on that error: You are assuming that the return value of f.write is a filename you can open; just seek to the start of your file and read instead:
f.write(make_archive(f.name, 'zip', root_dir))
f.seek(0)
data = f.read()
Note that you'll also need to clean up the temporary file you created (you set delete=False):
import os
f.close()
os.unlink(f.name)
Alternatively, just omit the delete keyword to have it default to True again and only close your file afterwards, no need to unlink.
That just wrote the archive filename to a new file..
You are just writing the new archive name to your temporary file. You'd be better off just reading the archive directly:
data = open(make_archive(f.name, 'zip', root_dir), 'rb').read()
Note that now your temporary file isn't being written to at all.
Best way to do this
Avoid creating a NamedTemporaryFile altogether: Use tempfile.mkdtemp() instead, to generate a temporary directory in which to put your archive, then clean that up afterwards:
tmpdir = tempfile.mkdtemp()
try:
tmparchive = os.path.join(tmpdir, 'archive')
root_dir = "something"
data = open(make_archive(tmparchive, 'zip', root_dir), 'rb').read()
finally:
shutil.rmtree(tmpdir)
So I've created a form that includes the following item
<input type="file" name="form_file" multiple/>
This tells the browser to allow the user to select multiple files while browsing. The problem I am having is is that when reading / writing the files that are being uploaded, I can only see the last of the files, not all of them. I was pretty sure I've seen this done before, but had no luck searching. Here's generally what my read looks like
if request.FILES:
filename = parent_id + str(random.randrange(0,100))
output_file = open(settings.PROJECT_PATH + "static/img/inventory/" + filename + ".jpg", "w")
output_file.write(request.FILES["form_file"].read())
output_file.close()
Now, as you can see I'm not looping through each file, because I've tried a few different ways and can't seem to find the other files (in objects and such)
I added in this print(request.FILES["form_file"]) and was only getting the last filename, as expected. Is there some trick to get to the other files? Am I stuck with a single file upload? Thanks!
Based on your file element form_file, the value in request.FILES['form_file'] should be a list of files. So you can do something like:
for upfile in request.FILES.getlist('form_file'):
filename = upfile.name
# instead of "filename" specify the full path and filename of your choice here
fd = open(filename, 'w')
fd.write(upfile['content'])
fd.close()
Using chunks:
for upfile in request.FILES.getlist('form_file'):
filename = upfile.name
fd = open(filename, 'w+') # or 'wb+' for binary file
for chunk in upfile.chunks():
fd.write(chunk)
fd.close()