Overcoming Challenges: Uploading WeChat Temporary Media with Python Requests

Project Goal

When a user uploads an image, the system receives it, performs array operations, and returns the processed image—all entirely in memory without touching the file system.

Project Analysis

WeChat's official upload API uses the following curl command:

curl -F media=@{image_name} "https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image"

Using a filename in the command line requires file system access, which conflicts with the all-in-memory requirement. Therefore, we must convert the curl command to a requests call.

Using curlconverter.com, the curl can be easily transformed into a requests snippet:

import requests

files = {
    'media': open('{image_name}', 'rb'),
}

response = requests.post('https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image', files=files)

This still necessitates file system access, so we need to create a binary stream manually.

The naive approach fails:

files = {'media': BytesIO(image_bytes)}
response = requests.post(url, files=files)

This returns an error:

{'errcode': 40005, 'errmsg': 'invalid file type hint: [vIQHea01094248] rid: 66193184-3f53f282-7178c05f'}

To determine whether the issue lies with the curl-to-requests conversion or with BytesIO, I tested a file-system-based example, which worked. Then I investigated the differences. The open() function returns a BufferedReader, so I tried wrapping the stream:

f1 = BufferedReader(BytesIO(image_name, image_bytes), image_bytes.__sizeof__())

This also failed. I printed the two handles to compare:

f1 = BytesIO(image_bytes)
f2 = open(f'{image_name}', 'rb')
print(f1, '\n', f2)

<_io.BufferedReader>
<_io.BufferedReader name='./tmp/6777248.png'>

The name property was missing. Since BytesIO's name attribute is read-only, I derived a custom class:

class NamedBytesIO(io.BytesIO):
    def __init__(self, name, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.name = name

<_io.BufferedReader name='./tmp/4365267.png'>
<_io.BufferedReader name='./tmp/4365267.png'>

Now the name appeared, but the upload still failed, leading to frustration.

I wondered: why does saving to the filesystem work, but sending the byte stream directly from memory does not?

Could the save operation alter the image?

image_name = './tmp/' + str(random.randint(1000000, 9999999)) + img_suffix
# cv2.imwrite(image_name, image)
image.save(image_name)

Recalling the error message again:

{'errcode': 40005, 'errmsg': 'invalid file type hint: [vIQHea01094248] rid: 66193184-3f53f282-7178c05f'}

Invalid file type? Could it be that the bytes are not in a proper image format?

Then it struck me: in the image processing logic, I first convert a JPEG image into a numpy array (pixel operations on np.ndarray). The array is not a valid JPEG—the compression algorithm produces different data.

def cut_white_border(image_bytes: bytes):
    image_pil = Image.open(BytesIO(image_bytes))
    if image_pil.format == 'PNG':
        img_suffix = '.png'
    elif image_pil.format == 'JPG':
        img_suffix = '.jpg'
    else:
        return 'error'

    image_array = np.array(bytearray(image_bytes), dtype=np.uint8)
    image = cv2.imdecode(image_array, cv2.IMREAD_UNCHANGED)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

So the pixel data must be compressed back to JPEG format:

yes, image_tobytes = cv2.imencode('.jpg', image.astype(np.uint8))

Problem solved after two hours of debugging—sometimes the simplest oversight is the culprit.

{'type': 'image', 'media_id': '6Pym7GRK0gDsNikc6iUlsLcFD3I1cQuyEAAJZz9y6CUwh5-znSw7biPXbqOhLmEv', 'created_at': 1712927076, 'item': []}

The final working solution requires specifying a filename; otherwise, issues persist. The name property is indeed necessary.

files = {'media': (image_name, BytesIO(image_bytes))}
response = requests.post(url, files=files)

I did not test whether the custom-derived class would work, though it likely does. The internal processing of the requests library accepts tuples with 2, 3, or 4 elements—details can be found in the source code comments.

Tags: python Requests wechat-api image-processing memory-stream

Posted on Mon, 28 Sep 2026 16:20:27 +0000 by dawieharmse