Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The code in the document does not work #2069

Closed
imsgj opened this issue Feb 5, 2022 · 4 comments · Fixed by #2400
Closed

The code in the document does not work #2069

imsgj opened this issue Feb 5, 2022 · 4 comments · Fixed by #2400
Labels
bug Something isn't working docs Changes to the documentation

Comments

@imsgj
Copy link

imsgj commented Feb 5, 2022

Hi. The code in the document does not work

import httpx
files = {'upload-file': (None, 'text content', 'text/plain')}
r = httpx.post("https://httpbin.org/post", files=files)
print(r.text)

The error is

TypeError: Expected bytes or bytes-like object got: <class 'str'>

I found this problem has been raised but not solved

If the string is modified with b, it works fine

import httpx
files = {'upload-file': (None, b'text content', 'text/plain')}
r = httpx.post("https://httpbin.org/post", files=files)
print(r.text)

Compared to requests

import requests
import httpx

files = {'upload-file': (None, 'text content', 'text/plain')}
httpx.post("https://httpbin.org/post", files=files)  # error
requests.post("https://httpbin.org/post", files=files)  # works

files = {'upload-file': (None, b'text content', 'text/plain')}
httpx.post("https://httpbin.org/post", files=files)  # works
requests.post("https://httpbin.org/post", files=files)  # works
@tomchristie tomchristie added bug Something isn't working docs Changes to the documentation labels Feb 9, 2022
@tomchristie
Copy link
Member

Thanks for raising this @imsgj - you're absolutely correct.

We made a change here so that only binary content is accepted. However I'm not sure we really want to do that or not. The necessary case was that we only accept files opened in binary mode. But we could still accept the plain ol' str case if we wanted to.

We've got two options here.

  • Accept that we've got sensible behaviour, and update the docs to match the expected usage.
  • Change the behaviour so that plain str cases are accepted. (But files opened in text mode are not.)

@florimondmanca
Copy link
Member

If we decide to keep the behavior -- should we also try to sniff "have we got str content?" early on enough so that we display a friendlier error message, also explaining why we only accept bytes? I don't know where the TypeError comes from, probably not from us directly. I assume it might be a frequent enough attempt to pass str here assuming it would be accepted.

@jamesboehmer
Copy link

FWIW I am hopeful that we can allow non-files for two reasons:

  1. It would be consistent with requests
  2. I just ran into exactly this issue because the Cloudflare Images API recently fixed a long-standing issue, but in doing so changed to require multipart/form-data requests (see https://developers.cloudflare.com/images/cloudflare-images/upload-images/direct-creator-upload/). This used to take urlencoded JSON, so my client instantly broke. I worked around it by faking the files argument with an empty pydantic instance:
async with httpx.AsyncClient() as client:
    cloudflare_response = await client.post(
        url=url,
        data=data_dict,
        files=pydantic.BaseModel(),  # truthy+iterable+empty to fake httpx into doing multipart encoding for v2 API without actually sending any files
        headers={
            # 'Content-Type': application/json',  # only used for v1 of the API
            'Authorization': f"Bearer {api_token}"
        })

@ImBatou
Copy link

ImBatou commented Jun 20, 2022

FWIW I am hopeful that we can allow non-files for two reasons:

  1. It would be consistent with requests
  2. I just ran into exactly this issue because the Cloudflare Images API recently fixed a long-standing issue, but in doing so changed to require multipart/form-data requests (see https://developers.cloudflare.com/images/cloudflare-images/upload-images/direct-creator-upload/). This used to take urlencoded JSON, so my client instantly broke. I worked around it by faking the files argument with an empty pydantic instance:
async with httpx.AsyncClient() as client:
    cloudflare_response = await client.post(
        url=url,
        data=data_dict,
        files=pydantic.BaseModel(),  # truthy+iterable+empty to fake httpx into doing multipart encoding for v2 API without actually sending any files
        headers={
            # 'Content-Type': application/json',  # only used for v1 of the API
            'Authorization': f"Bearer {api_token}"
        })

You can use :

payload = (
    ('foo', (None, b'bar')),
    ('variable', (None, str.encode(variable))),
    ('bar', (None, b'foo')),
)
client.post("https://.../api/post", headers=headers, files=payload)

This will work.

The other problem that I have is that we are not able to set a "WebKitFormBoundary" for the form, this could be really useful because almost every website using multipart are using a boundary

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working docs Changes to the documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants