Repository: encode/httpx

Issue:
httpx is deleting url query parameters that do not explicitly specify a value
The following program
```python
import asyncio
import httpx

async def main():
    async with httpx.AsyncClient() as client:
        r = await client.get("https://httpbin.org/get?foobar", params={'hello': "world"})
        print(r.url)

asyncio.run(main())
```
should print `https://httpbin.org/get?hello=world&foobar`

However, the actual output is `https://httpbin.org/get?hello=world`

It appears that httpx is deleting url query parameters that do not explicitly specify a value.

Thanks for having a look


Discussion:
Good afternoon!

This case reproduce in `httpx.Client`. If such behaviour is bug, I want to fix it
> It appears that httpx is deleting url query parameters that do not explicitly specify a value.

To determine if your assessment here is correct, try the same example but with `foobar=something`.
@tomchristie you're right if use key-value pair in url, it's work. But what to do in case of query param without value (only key). In this [discussion](https://stackoverflow.com/questions/4557387/is-a-url-query-parameter-valid-if-it-has-no-value) I found the opinion, that query can contain query param wiithout value
> > It appears that httpx is deleting url query parameters that do not explicitly specify a value.
> 
> To determine if your assessment here is correct, try the same example but with `foobar=something`.

It works fine?  That isn't related to the bug I'm reporting however.  Have you tried running my example?  Url parameters that _do not_ have an explicit value are being deleted.
> To determine if your assessment here is correct, try the same example but with foobar=something.

> It works fine? That isn't related to the bug I'm reporting however.

Great, so we've determined that your assessment *is* correct. This is *specifically* occurring for parameters without a value.

So, yes, that looks broken to me.

Confirmed this to myself with...

```python
import httpx

r = httpx.get("https://httpbin.org/get?foobar", params={'hello': "world"})
print(r.request.url)  # https://httpbin.org/get?hello=world

r = httpx.get("https://httpbin.org/get?foobar=abc", params={'hello': "world"})
print(r.request.url)  # https://httpbin.org/get?foobar=abc&hello=world
```



Resolving patch:
diff --git a/httpx/_urls.py b/httpx/_urls.py
--- a/httpx/_urls.py
+++ b/httpx/_urls.py
@@ -540,7 +540,7 @@ def __init__(
         items: typing.Sequence[typing.Tuple[str, PrimitiveData]]
         if value is None or isinstance(value, (str, bytes)):
             value = value.decode("ascii") if isinstance(value, bytes) else value
-            self._dict = parse_qs(value)
+            self._dict = parse_qs(value, keep_blank_values=True)
         elif isinstance(value, QueryParams):
             self._dict = {k: list(v) for k, v in value._dict.items()}
         else:


Test patch:
diff --git a/tests/models/test_queryparams.py b/tests/models/test_queryparams.py
--- a/tests/models/test_queryparams.py
+++ b/tests/models/test_queryparams.py
@@ -76,6 +76,17 @@ def test_queryparam_types():
     assert str(q) == "a=1&a=2"
 
 
+def test_empty_query_params():
+    q = httpx.QueryParams({"a": ""})
+    assert str(q) == "a="
+
+    q = httpx.QueryParams("a=")
+    assert str(q) == "a="
+
+    q = httpx.QueryParams("a")
+    assert str(q) == "a="
+
+
 def test_queryparam_update_is_hard_deprecated():
     q = httpx.QueryParams("a=123")
     with pytest.raises(RuntimeError):