What is the difference among idioms for serving a Matplotlib image with Flask? What is the difference among idioms for serving a Matplotlib image with Flask? flask flask

What is the difference among idioms for serving a Matplotlib image with Flask?


what is the role played by StringIO; is it the only way to prepare to serve an image (of any kind)?

First of all, no, it is not the only way. The "classical" way would be to involve the file system:

  1. Let matplotlib create a plot.
  2. Persistently save the corresponding image data to a file in the file system (that involves context switches to the kernel which invokes system calls like write()).
  3. Read the contents of this file again (which lets the kernel read out the file system for you, via read()).
  4. Serve the contents to the client, in an HTTP response with well-defined data encoding as well as properly set headers.

Steps (3) and (4) involve file system interaction. That is, the kernel actually talks to hardware components. This takes time (with classical hrad drives, writing just a few bytes to the disc might take a couple of milliseconds, as of the long access times). Now, the question is: do you need to have the image data persisted to disk? If the answer is "no", then you can skip the entire interaction with the file system and save some time, by keeping the image data within the memory of your web application process. That is what StringIO is good for:

StringIO is a very generic tool in Python that provides file-like objects, whereas the actual data is never delegated to the kernel for writing it to the file system or reading it from the file system. It is kept in memory. That is why StringIO objects are also called in-memory files.

The point is that plt.savefig() wants to have an object as first argument that looks like an object that actually represents a real file in the file system. StringIO provides such an object, but -- under the hood -- writes data to a buffer in the heap of the current process, and reads it from there again if requested.

Reading/writing small portions of data via StringIO takes nanoseconds or microseconds, whereas the interaction with the file system usually is orders of magnitudes slower.

Now, don't get me wrong: usually, the file system is fast enough, and an operating system has its own techniques to make file system interaction as fast as possible. The real question is, as stated before: do you need the image data persisted? If you don't care about accessing this image data at some point later on, then do not involve the file system. This is what the creators of the three snippets you show decided.

Replacing real file system interaction with StringIO for performance reasons might be a very very valid decision. However, in your web application there surely are other bottlenecks. For instance, using StringIO may reduce the request-response latency by let's say 5 ms. But does this actually matter considering network latencies of 100 ms? Also, remember that a serious web application should better not be bothered with sending large file contents -- these are better served with a well-established web server which can also make use of the sendfile() system call. In this case, it might again be better performance-wise to let matplotlib write the file to the file system and then tell your web server (via an X-Sendfile header) to do the rest. So, performance is a complicated topic might not be the strongest argument. But only you know your requirements!

is there any significance to the use of seek plus read, vs. getvalue, or do these do essentially the same thing

Essentially the same thing. Does not make a conceptual difference, does not make a (significant) performance difference.

what governs the choice among approaches for what is returned: a tuple vs. html.format vs. a Response (with make_response); and, finally

No definite answer. There are many ways to get data to the client. There is no "correct" approach, just better or worse. Which approach to take best strongly depends on the web framework. With Flask, make_response() is the canonical way for creating a response object. html.format() might have some advantages I am not aware of -- you need to read about this yourself! But, read on, I think there is a method built into Flask which perfectly fits your scenario.

why do some approaches set the Content-type explicitly, while others set the encoding (to 'base64')?

There are proper and improper ways to send files to browsers via HTTP. Generally, an HTTP response should contain certain headers (also see What HTTP response headers are required). Just for your understanding, you might want to read about these details. Surely, binary data needs to be encoded with an encoding the client understands, and the encoding must be clarified in the response header. Also, a proper HTTP response should contain a MIME type (content type). The methods you have presented seem to not really take control of one or the other (no offense, quick & dirty examples often focus more on one thing than on the other).

I think you really should use Flask's send_file method which takes care of some important things for you. There are a couple of arguments to this method. I would explicitly define the MIME type via mimetype. The first argument can be a file-like object, so a StringIO object works fine. However, in this case you need to do seek(0) before:

Make sure that the file pointer is positioned at the start of data to send before calling send_file().

The following two approaches are semantically elegant (in my opinion) and should take proper care of encoding the file contents and setting HTTP response headers:

from flask import send_file 

1)

f = StringIO.StringIO()plt.savefig(f, format='png', dpi=300)f.seek(0)send_file(f, mimetype='image/png')

2)

plt.savefig('image.png', dpi=300)send_file('image.png', mimetype='image/png')

In the second case your webserver (e.g. nginx) can, if properly configured, transmit the file for you.