Returning List of Media Objects From Background Task

What I’m trying to do:

I am trying to run a background task which processes data table rows and generates pdf invoices to be downloaded later. However my list of media objects are not being serialised properly.

What I’ve tried and what’s not working:

The task is completing ,but I am receiving a serialization error when attempting to retrieve the return object of the task ( which should be a list of media objects).
Return Object
This is a print statement of the return object in the background task:

image

The Error
image

Code Sample:

#
# CLIENT SIDE
#
  def timer_exporter_tick(self, **event_args):
    """This method is called Every [interval] seconds. Does not trigger if [interval] is 0.
       This timer will handle all background task exports. Since these task can take very 
       long, they will be sent to a background task. The timer will be triggered by the 
       published message. For the timer to stop,all export task must be inactive in the 
       class function export task/active mapping, which keeps track of the active exports.
    """
    if not self.export_map:
      self.timer_exporter.interval = 0.0
    else:
      for _type,(active,task) in self.export_map.items():
        if active:
          with anvil.server.no_loading_indicator:
          term_status = task.get_termination_status()
          if term_status == "completed":
            app_logger.debug("DOWNLOADS READY!")
            self.export_map.pop(_type)
            download_files = task.get_return_value() "---> THIS IS WHERE IT BREAKS"
            result = alert(content="Files are ready to download, download?",
                  buttons=[
                    ("Yes",True),
                    ("No",False)
                  ]
                 )
            if result:
              for file in download_files:
                anvil.media.download(file)


#
# SERVER SIDE
#
@anvil.server.callable
def export_invoices(data):
  task = anvil.server.launch_background_task('get_invoices',data)
  return task

@anvil.server.background_task 
def get_invoices(data):
   invoices = []
   for row in data:
   
      #
      # Save Rate Con
      #
      invoice = create_invoice(row,row['name'])
      invoices.append(invoice)
   print(invoices)
   return invoices
   

def create_invoice(row,name):
    pdf = PDFRenderer(filename=name).render_form('app.utils.forms.Invoice',row)
    return pdf

I have looked at zipping looking at this example:

but can’t get my head around how to zip a list of media objects that are not apart of any “path”

Any suggestions would be greatly appreciated!

A background task runs in a new process different from the one that the client is interacting with. Perhaps that’s the reason for the serialization error. So why don’t you change the design such that the background task saves its output to an app_table and then fetch your media objects from the app_table?

1 Like

In the code you’ve shown, the background task doesn’t return anything (and ‘invoices’ remains an empty list).

You’ll need to show your actual code for us to have a chance of helping without guess work.

1 Like

Edited, my apologies. Thanks!

I hope that’s not the case. I’ll have to read up on serialization between sessions.

The worry I have with this approach is having multiple clients exporting at the same time. Thinking out loud , I could add some sort of ID for the commit to the data table, but I was hoping to not have a new table to maintain and just keep the everything in that single work flow.

Thank you very much! I’ll give this approach some thought.

A reproducible example always helps with these types of problems. I hacked one together:

https://anvil.works/build#clone:SOGRN7IO2UXEPVMN=L7374SVP6F2P6DAAU5XYYGZJ

A few observations:

  • This fails with a single media object, so the problem has nothing to do with the fact that you are returning a list of media objects.
  • This still fails when we force the object to be a standard anvil.BlobMedia object, so the fact that you are ending up with a anvil._serialise.StreamingMedia object is also not relevant.

So, it seems to me that media objects cannot be returned from a background task. This is a little surprising (maybe a bug?). I haven’t tried but I bet this would also fail even if you zipped the files. The obvious workaround is to stash in a datatable with a unique identifier, and a link to the user who launched the task so you guarantee they get back their data, and retrieve by separate server call.

2 Likes

Dan,

I really appreciate your example and thorough debugging.

I’ll give zipping a go, if not looks like my hand is forced.

Again, appreciate it a lot!

For Completeness, I updated the clone @danbolinson created. (Thank you very much) to test if zipping made a difference (spoiler, it did not) and have a working solution.

The solution being, passing the object to an export table to be downloaded once the task is complete. I will also add a column of “date created”, so I can have a scheduler come and clean the table periodically, in production.

I also zipped the files, for anyone who wanted an example of that.

Thank you everyone for your help and comments!

https://anvil.works/build#clone:G5KAIR2GAMPDOFMF=XQASYNYSLWCZZ5IJTEE66IGP

Hi all!

The answer to this is that background tasks cannot return media objects directly (as @danbolinson theorised) - you’ll have to store them in Data Tables and retrieve them from there.

The reasoning behind this is that the return values from background tasks can be accessed for a long time, and therefore it would be possible for large media objects to exhaust your data storage quota in a way that’s not very visible to you, or easy to fix. I hope this helps to clarify the situation here!

3 Likes

Thank you for the clarification!

Thank you for clarifying @eli-anvil! Quick question, is this documented anywhere? Thanks!

It’s not, but I added it to our list of needed fixes so it should be there soon!

1 Like

This is now documented here:

1 Like