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

Cannot make URL query string with a parameter without a value #2651

Closed
agilevic opened this issue Jun 24, 2015 · 34 comments
Closed

Cannot make URL query string with a parameter without a value #2651

agilevic opened this issue Jun 24, 2015 · 34 comments

Comments

@agilevic
Copy link

URL query string may contain a parameter, which has no value i.e. http://host/path/?foo or http://host/path/?a=1&foo. Currently Requests does not provide support for that.

In [68]: d
Out[68]: {'a': 1, 'foo': None}

In [69]: tl
Out[69]: [('a', 1), ('foo',)]

In [70]: RequestEncodingMixin._encode_params(d)
Out[70]: 'a=1'

In [71]: RequestEncodingMixin._encode_params(tl)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-71-5d4dac855108> in <module>()
----> 1 RequestEncodingMixin._encode_params(tl)

/home/f557010/jpm/local/lib/python2.7/site-packages/requests/models.pyc in _encode_params(data)
     87         elif hasattr(data, '__iter__'):
     88             result = []
---> 89             for k, vs in to_key_val_list(data):
     90                 if isinstance(vs, basestring) or not hasattr(vs, '__iter__'):
     91                     vs = [vs]

ValueError: need more than 1 value to unpack

Expected:

'a=1&foo'
@Lukasa
Copy link
Member

Lukasa commented Jun 25, 2015

I can see some value in this. For API reasons it could only ever work with the 'list of tuples' approach, but I'd be ok with us adding support for this. @sigmavirus24?

@sigmavirus24
Copy link
Contributor

I don't think we use it (yet) but it seems that urllib3 uses urlencode and so do we

If we examine how that behaves, you can see that it doesn't like either of the proposed ways of working with this. {'foo': None} will "work" but does not do the right thing. This is probably why we avoided this before. That said, RFC 3986 has a very ... loose definition of the query part of a URI, so in my opinion we should handle it. That said, I'm not sure there's any tools to readily allow us to handle it. =/

>>> u = urlparse.urlparse('http://example.com/foo?bar')
>>> u
ParseResult(scheme='http', netloc='example.com', path='/foo', params='', query='bar', fragment='')
>>> urlparse.parse_qs(u.query)
{}
>>> urllib.urlencode({'foo': None})
'foo=None'
>>> urllib.urlencode([('foo',)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 1336, in urlencode
    for k, v in query:
ValueError: need more than 1 value to unpack

@piotr-dobrogost
Copy link

Related: "urlencode of a None value uses the string 'None'" – https://bugs.python.org/issue18857

@Lukasa
Copy link
Member

Lukasa commented Jun 25, 2015

Give @piotr-dobrogost's input, @agilevic have you tried using the empty string as the value there? Does it work with your API server?

@Lukasa
Copy link
Member

Lukasa commented Jul 9, 2015

Bump. =)

@sigmavirus24
Copy link
Contributor

>>> import requests
>>> r = requests.get('https://httpbin.org/get', params={'foo': ''})
>>> r.request.url
'https://httpbin.org/get?foo='

@agilevic
Copy link
Author

It's not the same as having a parameter without a value. Yours renders the = sign after the parameter name. Some applications will work, but as a matter of providing a complete solution this exact case should be addressed. That urllib doesn't do it is of no significance. Requests does many things better than standard libraries for HTTP - that is its reason to exist.

@Lukasa
Copy link
Member

Lukasa commented Jul 28, 2015

@agilevic What would your proposed API design for this feature be?

@frnhr
Copy link

frnhr commented Sep 17, 2016

Here is a crazy thought:

>>> import requests
>>> r = requests.get('https://httpbin.org/get', params={'foo': None})
>>> r.request.url
'https://httpbin.org/get?foo'

That is what I think should be happening.

What is actually happening:

'https://httpbin.org/get'

:(

@Lukasa
Copy link
Member

Lukasa commented Sep 17, 2016

@frnhr So the reason that doesn't work with our API is that setting a key to None is the signal for "please remove this key from the map". We have that signal because some parameters can be persisted on a Session object itself, and users occasionally want to be able to suppress those parameters on a per-request basis.

@frnhr
Copy link

frnhr commented Sep 17, 2016

I'm afraid I don't see what the session object has to do with this bit of API, sincerely. But ok, maybeFalse then, or even some SpecialImportableObject instead of None?

On 17 Sep 2016, at 07:47, Cory Benfield notifications@github.com wrote:

@frnhr So the reason that doesn't work with our API is that setting a key to None is the signal for "please remove this key from the map". We have that signal because some parameters can be persisted on a Session object itself, and users occasionally want to be able to suppress those parameters on a per-request basis.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@Lukasa
Copy link
Member

Lukasa commented Sep 17, 2016

@frnhr The Session API is relevant because the requests. API is built on top of the Session API: it's a subset of that functionality, a convenience wrapper.

So we certainly could do it, but I'm not sure to what extent it's worthwhile. When not using key-value mapping, you should just pass a string to the params field: params="foo".

@whitej125
Copy link

What if we were to make a specific typed argument that could indicate to requests that the param is to be added without a value?

# defined somewhere in requests
class ValuelessParam(object):
    pass
....
....

params = {'foo': 'some_param', 'bar': requests.ValuelessParam()}

requests.get('http://something', params=params)

# url should be 'http://something?foo=some_param&bar'

Its not None, its not a scalar constant... so it should be backwards compatible. Under the hood... we could check for that value type and specially append this parameter to the constructed url.

@Lukasa
Copy link
Member

Lukasa commented Oct 27, 2016

So while that will definitely work, I don't think that API will get past Kenneth.

@kennethreitz
Copy link
Contributor

Yeah, No. I'd rather support a built-in like None, and I'm not sure that's the best idea — but it could work well. False would not.

@Lukasa
Copy link
Member

Lukasa commented Nov 1, 2016

None won't work either: the codebase already gives it the meaning of "unset the value set at the Session level.

@kennethreitz
Copy link
Contributor

That would be a pretty major change though, and I don't think it would benefit many people. Perhaps an empty tuple could be considered. (e.g. (,).

@nellson-CSC
Copy link

Not sure if this was resolved, but I found this thread trying to do the exact thing of adding a key with out a value. "QueryAll" in my case, but I have had a number of causes in my RestAPI Automation to make use of this kind of function.

@kennethreitz
Copy link
Contributor

what happens if you pass {'QueryAll': ''}?

@nellson-CSC
Copy link

I get &QueryAll= at the end.

@nellson-CSC
Copy link

Looks like it's kinda up to the API on how it handles the open "=" then, PasswordState's API took it as long as it was at the end of my params list, if I moved it to the beginning it error-ed.

@kennethreitz
Copy link
Contributor

in 3.0, i think we can consider making emptry string not result in an =

@nellson-CSC
Copy link

That would be great :-D So far my Passwordstate project can move forwward with the QueryAll= format at the end of teh params string so I am back on track. I'll watch this thread :-D

Thanks Kenneth!

@nellson-CSC
Copy link

nellson-CSC commented Sep 24, 2018 via email

@roncrdb
Copy link

roncrdb commented Sep 26, 2018

I'm running into this problem at the moment as well. Has anyone else found any way around this problem?

@samuraii
Copy link

I believe there is still no solution in requests for this simple, but very common feature. weird.

@gmcabiato
Copy link

Unfortunately facing the same issue.
Wouldn't it be a better pattern or option to be able to inject/pass an optional custom formatter/encoder or a specific flag for handling None behaviour?

@Lukasa You seem to know the codebase better than most of us, what do you think about it?

Wouldn't that resolve in a non-breaking-change, @kennethreitz? Defaults could be set to mimic 2.x behaviour.

@flisz
Copy link

flisz commented Feb 5, 2019

It appears that this is still adding the = when an empty string is passed in. Time to bang out a work-around!

@bwilgus
Copy link

bwilgus commented Feb 28, 2019

So I'm guessing this is still a problem?

@joewesch
Copy link

If you have parameters that have no value, you can list them as part of the url and any additional parameters will append with & instead of starting with ?:

In [3]: r = requests.get("http://www.google.com", params={'test': 'true'})

In [4]: r.url
Out[4]: 'http://www.google.com/?test=true'

In [5]: r = requests.get("http://www.google.com?test2", params={'test': 'true'})

In [6]: r.url
Out[6]: 'http://www.google.com/?test2&test=true'

@KES777
Copy link

KES777 commented May 31, 2019

How about this rule?

{ key: undefined } to ? (not included)
{ key: null } to ?key
{ key: '' } to ?key=
{ key: 'null' } to ?key=null

@VeNoMouS
Copy link

VeNoMouS commented May 7, 2020

2020... and still an issue.... SMH

@ymagoon

This comment has been minimized.

@idkq
Copy link

idkq commented Feb 7, 2021

What's the solution folks?

@psf psf locked as resolved and limited conversation to collaborators Feb 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests