I want to save multiple pdf files from datatable into a zip file

Hi, I want to save multiple pdf files from datatable into a zip file and be able to download it. Please assist:

:

Code Sample:

@anvil.server.callable
def get_all_file_into_Zip():
  cv_list = []
  for row in app_tables.cv_applicants_table.search(tables.order_by('name',ascending=True)):
    cv = row['cv']
    cv_list.append(cv)
    
    with ZipFile('someZipFile.zip', 'w') as img_zip:
      for image_url in cv_list:
        img_name = os.path.basename(image_url)
        img_data = requests.get(image_url).content
        img_zip.writestr(img_name, img_data)
  return img_zip` 

Clone link:
share a copy of your app

Welcome to the Forum!

It looks like your code was written to return an open file-handle across the Internet to your browser. There are several ways this code is broken.

  1. At the end of the with block, the file will be closed, so the variable img_zip won’t contain anything useful.
  2. Even if img_zip remained open, it refers to local storage on a Server computer, which may be thousands of miles away, across the internet, from the PC that invoked get_all_file_into_zip. That file-handle is useless on any other computer.
  3. Even if img_zip actually contained the data, its value is not of a type that Anvil knows how to transmit across the wire. The types that it does know about are here: Valid arguments and return values.

And there we see something that can help: Media objects. An Anvil Media Object is essentially the contents of a binary file, such as your zipfile, with some additional information for transit, like the file’s name and type. Anvil can return such an object from its Servers to your user’s browser.

I suggest that you take a look at Media objects. There you will find methods and tools for converting your someZipeFile.zip into a Media Object, so that the contents of your zipfile can be returned.

Also use Search, in the Documentation, and at the top of this web page, to find Tutorials, Examples, and relating to Media Objects. You’ll find plenty of examples, a few of which involve zip files.

Caution: as written, by creating your zipfile on disk, you are likely to have two different users trying to update the same someZipFile.zip at the same time. They will then get a mix of each other’s data. Probably not what you want.

Solution: In Python, you can create binary “files” in memory, instead. (See Python’s BytesIO.) This guarantees that each user sees only their own data.

Alternatively, see tempfile — Generate temporary files and directories — Python 3.7.16 documentation for how to create a uniquely-named temporary file, to hold your data. The file ceases to exist once its with block ends, so you’d want your for loop to live inside the with, instead of vice versa.

2 Likes

Thank you for your reply, I will try that and look into it and give an update. Much appreciated.

Here is the updated code from my server side. It works successfully by writing all the pdf files from row[‘pdfcolumn’] into newzip and the function returns archive as a media object. I can also successfully download the zip file from the client side and open it successfully but the issue is how the pdf files are saved and presented on my local machine.

@anvil.server.callable
def get_multiple_pdf_into_ZipFile_from_datatable():
  pdf_from_table = app_tables.my_table.search(tables.order_by('name',ascending=True))
  with zipfile.ZipFile('archive.zip','w') as newzip:
    for row in pdf_from_table:
      with anvil.media.TempFile(row["pdfcolumn"]) as filename:
        newzip.write(filename)
        
  archive = anvil.media.from_file("archive.zip")
  return archive

I can open the files as pdf on my local machine and it opens successfully but I want the pdf files from the datatable to already be saved into the zip file as pdf with the correct file name and extension. May you please assist me with that.

This is how the files look after downloading zipfile from anvil.
image

From the Python docs on ZipFile, these are the arguments for ZipFile.write:

ZipFile.write(filename, arcname=None, compress_type=None, compresslevel=None)

Looks like if you pass the arcname parameter you can say what the name of the file should be inside the archived.

2 Likes

Thank you so much, it works well.