POST body: argument 'data' of type 'str' works, except when it doesn't

In python3, if you pass `data=<str>` to `requests.request` then often it works - except when `<str>` contains high codepoints and then it barfs. 

[Documentation](http://docs.python-requests.org/en/master/api/#requests.request) says that `data` must be "Dictionary or list of tuples, bytes, or file-like object", so passing `str` is technically invalid; it would be helpful to catch the error sooner though.

## Expected Result

I understood the python3 philosophy to be that if you use `str` where `bytes` is expected, or vice versa, then it should "fail fast" and raise an exception immediately.  I was (wrongly) passing str to the data arg, but it worked fine...  until the first time it happened to include a high unicode character.

So I think that either:

* `requests` should fail if passed data=str; or
* it should be documented that data=str is permitted, but only if str can be encoded into iso-latin-1; or
* it should be documented that data=str is permitted, along with a way to specify the encoding (if there is one, I couldn't find it - if Content-Type header specifies the charset, this is ignored)

Aside 1: I see that the automatic iso-8859-1 fallback for response bodies was removed in #2086, so this could be considered a corresponding issue for request bodies.

Aside 2: in the real application, the data I was sending was some XML generated by `xmltodict.unparse()`, which generates a `str`.

## Actual Result

* `data="hello"` is fine
* `data="hello\ufffd"` barfs with `UnicodeEncodeError: 'latin-1' codec can't encode character`
* `json="hello\ufffd"` is fine

## Reproduction Steps

```python
>>> import requests
>>> requests.post("http://localhost", data="hello world")
(it works)

>>> v = "!@#$%^&*()+_\ufffd"
>>> requests.post("http://localhost", json=v)
(it works)

>>> requests.post("http://localhost", data=v)
...
UnicodeEncodeError: 'latin-1' codec can't encode character '\ufffd' in position 12: Body ('�') is not valid Latin-1. Use body.encode('utf-8') if you want to send it encoded in UTF-8.

>>> requests.post("http://localhost", data=v, headers={"Content-Type": "text/xml; charset=utf-8"})
...
UnicodeEncodeError: 'latin-1' codec can't encode character '\ufffd' in position 12: Body ('�') is not valid Latin-1. Use body.encode('utf-8') if you want to send it encoded in UTF-8.
```

## System Information

    $ python -m requests.help

```
{
  "chardet": {
    "version": "3.0.4"
  },
  "cryptography": {
    "version": ""
  },
  "idna": {
    "version": "2.6"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.5.2"
  },
  "platform": {
    "release": "4.4.0-109-generic",
    "system": "Linux"
  },
  "pyOpenSSL": {
    "openssl_version": "",
    "version": null
  },
  "requests": {
    "version": "2.18.4"
  },
  "system_ssl": {
    "version": "1000207f"
  },
  "urllib3": {
    "version": "1.22"
  },
  "using_pyopenssl": false
}
```


This is a change already slated for 3.0 if I remember correctly. @nateprewitt can you confirm?

@sigmavirus24 it doesn't look like we have anything merged into 3.0.0 and I couldn't find an issue around that off hand. Looking at the stack trace, this is a problem in http/client.py assuming any string it receives is encodable as latin-1, which is why we specify `bytes` as a requirement rather than a unicode string.

The reason `json` works is because we [force it into bytes](https://github.com/requests/requests/blob/654da610e1137246d9f5e5ccb9979bbfeffc6949/requests/models.py#L459) before sending it. I'm not sure if that's the right assumption for any string passed to `data` though. I think an exception if the string isn't latin-1 encodable in 3.0.0 may be helpful, but I'm not sure if he want to hard fail on any string. It's a question of how strictly we enforce the API, maybe we should require bytes and only bytes everywhere for 3.0.0 to avoid ambiguity.

> The reason json works is because we force it into bytes before sending it.

... with a hard-coded assumption that the desired encoding is utf-8.  I'd agree that is likely to be what the user wants;  utf-16 and utf-32 encodings of JSON *are* permitted, but I doubt anyone uses them much.

It therefore might be reasonable to encode str data as utf-8 by default as well, which will also be right most of the time.  If someone wants anything else they can encode it themselves.

That's not backwards-compatible for anyone who happens to rely on the existing default of encoding to iso-latin-1; but restricting data to being bytes-only is similarly not backwards-compatible.

> It therefore might be reasonable to encode str data as utf-8 by default as well, which will also be right most of the time. If someone wants anything else they can encode it themselves.

Once upon a time this was what was discussed for requests 3.0

> That's not backwards-compatible for anyone who happens to rely on the existing default of encoding to iso-latin-1; but restricting data to being bytes-only is similarly not backwards-compatible.

I agree with @candlerb, it's a big break to require only bytes. But, on the other hand, the doc states that string should not be used. I think the most reasonable way is to mark the "feature" as deprecated and show a warning if `str` is used.

Thanks folks above for this very educational conversation, and especially @nateprewitt 's clarification on

> which is why we specify bytes as a requirement rather than a unicode string

For the sake of completeness, I guess the same philosophy would also apply to when the data parameter is a dict? Currently, [the documentation](http://docs.python-requests.org/en/master/api/#requests.request) says that data must be "Dictionary or list of tuples, bytes, or file-like object", but does not clarify the type of dictionary values (or tuples values, for that matter). Based on the aforementioned philosophy, those values should better be `bytes` too. And [the underlying implementation actually does convert possible string input into `bytes`](https://github.com/requests/requests/blob/v2.21.0/requests/models.py#L103-L104).

On a side note, none of the examples in the [Quickstart document](http://docs.python-requests.org/en/master/user/quickstart/) uses `bytes` in the data parameter. This technically does not align with the requirement. I know, writing an extra `b` prefix would become "not for Human".

> requests.post('https://...', data = {'key': **b**'value'})

So, this is it. I guess the conclusion is to stick with `bytes` whenever you can.

Good point, there is definitely an inconsistent example in the Quickstart document which passes a str for data:

```
>>> r = requests.post(url, data=json.dumps(payload))
```

(which currently would fail if the payload contained a string with high codepoints)

In the case of dict or tuple list (to generate `application/x-www-form-urlencoded`), remember that dict keys can be binary too.  You found that the implementation allows strings as both keys and values, which is very helpful: it would be painful if you had to encode every key and every value.  But it should be documented that this is allowed, and that the encoding used is utf-8.

Given all of this, I think allowing str for data with implicit encoding as utf-8 would be the consistent and useful thing to do.

You can always send data as bytes if you are concerned about the encoding. Closing this issue.