8

I am trying to unit test my file uploading REST API. I found online some code generating the image with Pillow but it can't be serialized.

This is my code for generating the image :

image = Image.new('RGBA', size=(50, 50), color=(155, 0, 0))
file = BytesIO(image.tobytes())
file.name = 'test.png'
file.seek(0)

Then I try to upload this image fille :

return self.client.post("/api/images/", data=json.dumps({
     "image": file,
     "item": 1
}), content_type="application/json", format='multipart')

And I get the following error:

<ContentFile: Raw content> is not JSON serializable

How can I transform the Pillow image so it's serializable?

Elbbard
  • 2,064
  • 6
  • 30
  • 53
  • Have you tried omitting the `json.dumps` call? In my django project, I just post the data as a dictionary using the test client. – Brobin Nov 16 '16 at 21:06

2 Answers2

8

I wouldn't recommend submitting your data as JSON in this case, as it complicates the issue. Just make a POST request with the parameters and files you want to submit. Django REST Framework will handle it just fine without you needing to serialise it as JSON.

I wrote a test for uploading a file to an API endpoint a while back which looked like this:

def test_post_photo(self):
    """
    Test trying to add a photo
    """
    # Create an album
    album = AlbumFactory(owner=self.user)

    # Log user in
    self.client.login(username=self.user.username, password='password')

    # Create image
    image = Image.new('RGB', (100, 100))
    tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg')
    image.save(tmp_file)

    # Send data
    with open(tmp_file.name, 'rb') as data:
        response = self.client.post(reverse('photo-list'), {'album': 'http://testserver/api/albums/' + album.pk, 'image': data}, format='multipart')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

In this case, I used the tempfile module to store an image generated using Pillow. The with syntax used in the example allows you to pass the content of the file in the request body comparatively easily.

Based on this, something like this should work for your use case:

image = Image.new('RGBA', size=(50, 50), color=(155, 0, 0))
file = tempfile.NamedTemporaryFile(suffix='.png')
image.save(file)

with open(file.name, 'rb') as data:
    return self.client.post("/api/images/", {"image": data, "item": 1}, format='multipart')

Incidentally, depending on your use case it might be more convenient to accept the image data as a base 64 encoded string.

Matthew Daly
  • 9,212
  • 2
  • 42
  • 83
  • I implemented this solution and it works for a while in windows, but latter got error `PermissionError: [Errno 13] Permission denied: 'C:...` maybe it needs some additional functions like described in the answers for https://stackoverflow.com/q/130763/2343488 – juliocesar Jul 12 '18 at 17:10
  • Thank you for this solution. One hint: I had a big hustle when running multiple tests after another. I always got an error like `OSError: cannot identify image file "/tmp/tmpci3voa1h"`. Solution: You need to call seek(0) on the file object after running one test, e.g. `data.seek(0)` – user1383029 Sep 25 '18 at 21:11
  • where's the `Image` module coming from? –  Feb 10 '19 at 18:50
  • 1
    @RudolfOlah PIL or Pillow – Matthew Daly Feb 10 '19 at 19:37
0

You converted the file to bytes, which is not JSON serializable.

Without knowing what your API expects to receive, I'll have to take a guess that you have to encode file as a string: "image": file.decode('utf-8').

While there are many solutions to your general issue of unit testing image uploads to a REST API

fildred13
  • 2,280
  • 8
  • 28
  • 52