NotImplementedError while using DownloadResult

Hi all,
DownloadResult seems to be working in the case of:

def download_stuff(self, params, **kwargs):
    return DownloadResult(file_content='Content of file 1', file_name="my_file_1.txt")

But not in the case of:

def download_stuff(self, params, **kwargs):
    return DownloadResult(zipped_files={'my_file_1.txt': 'Content of file 1', 'my_file_2.txt': 'Content of file 2'}, file_name="my_file.zip")

Could you explain why? I do receive the following error in the latter case:

Traceback (most recent call last):
  File "viktor_connector\connector.pyx", line 295, in connector.Job.execute
  File "viktor\core.pyx", line 1918, in viktor.core._handle_job
  File "viktor\core.pyx", line 1870, in viktor.core._handle_job._handle_button
  File "viktor\result.pyx", line 109, in viktor.result.DownloadResult._serialize
  File "viktor\result.pyx", line 116, in viktor.result.DownloadResult._serialize
NotImplementedError

Thanks.

Hi,

Welcome to the forum.

The zipped_files argument allows for BytesIO or File type, not str, so this should work:

def download_stuff(self, params, **kwargs):
    return DownloadResult(zipped_files={'my_file_1.txt': File.from_data('Content of file 1'), 'my_file_2.txt': File.from_data('Content of file 2')}, file_name="my_file.zip")

Thanks for the prompt reply. Would it be possible to somehow use 2d numpy arrays? Does it always has to be somehow like this:

def download_stuff(self, params, **kwargs):
    DownloadResult(zipped_files={'my_file_1.txt': File.from_data(array2string(my_array)), 'my_file_2.txt': File.from_data(array2string(my_array))}, file_name="my_file.zip")

In the end the information is transferred as bytes. File.from_data is just a convenient way to convert str (or even bytes) to a File object (which will convert to bytes under the hood). If you need to convert a Numpy array to bytes, there are (Numpy) commands to do this. For example, this seems to be one way to do it (there might be others):

The bytes representation can the be passed to DownloadResult as:

def download_stuff(self, params, **kwargs):
    DownloadResult(zipped_files={'my_file_1.txt': File.from_data(bytes_array), 'my_file_2.txt': File.from_data(bytes_array)}, file_name="my_file.zip")

or

def download_stuff(self, params, **kwargs):
    DownloadResult(zipped_files={'my_file_1.txt': BytesIO(bytes_array), 'my_file_2.txt': BytesIO(bytes_array)}, file_name="my_file.zip")

Thank you for the explanation. Could it be possible to download the figures then?

Also, I am wondering how to store the results from particular methods, e.g., calculation results from an action button. In particular, I would like to learn whether I can store objects or dictionaries.

Hi Volkanozos,

To download something:

you do need to “create” the bytes that are saved in the file. A plotly or mathpoltlib object is not something you can download as-is. You will need to convert those to an image first, for example an svg, png…

Here is an example based on the Docs on how to convert a mathplotlib plot to the file content of the image:

  def create_download_result(self, params, **kwargs):
        # initialize figure
        fig = plt.figure()

        # create plot data
        x = np.random.randn(10)
        plt.plot(x)

        # save figure
        svg_data = StringIO()
        fig.savefig(svg_data, format='svg')
        plt.close()

        return DownloadResult(file_content=svg_data, file_name="my_file.svg")

To store a dictionary:

You can not save a dictionary as-is, a dictionary is an object that can “do” things such as items to be added, items to be removed. It can feel like a minute distinction, but you want to save the data inside the dictionary. the easiest way to save a dictionary is to save a JSON. This is a file type that is very easy to convert to and from a dictionary.

to create the bytes for a json file:

import json  # python built-in, no need to add it to the requirements
json_data = json.dumps(my_dictionary)

to convert back to a dictionary:

import json  # python built-in, no need to add it to the requirements
my_dictionary= json.dumps(json_data)

Not all objects data can be easely saved as JSONs. We do have some additional tooling to save objects as a json, such as numpy arrays. More about that: SDK Reference - Core | VIKTOR Documentation

Once you have your results as a json, you can use Storage to save the results. More about storage can be found here: Results & visualizations - Storing results | VIKTOR Documentation

Alternatively, if it is a little amount of data only, you can save it in a JSONField User input, fields and buttons - Other fields | VIKTOR Documentation