44

I am using this awesome library called requests to maintain python 2 & 3 compatibility and simplify my application requests management.

I have a case where I need to parse a url and replace one of it's parameter. E.g:

http://example.com?param1=a&token=TOKEN_TO_REPLACE&param2=c

And I want to get this:

http://example.com?param1=a&token=NEW_TOKEN&param2=c

With the urllib I can achieve it this way:

from urllib.parse import urlparse
from urllib.parse import parse_qs
from urllib.parse import urlencode

url = 'http://example.com?param1=a&token=TOKEN_TO_REPLACE&param2=c'

o = urlparse(url)
query = parse_qs(o.query)
if query.get('token'):
    query['token'] = ['NEW_TOKEN', ]
    new_query = urlencode(query, doseq=True)
    url.split('?')[0] + '?' + new_query

>>> http://example.com?param2=c&param1=a&token=NEW_TOKEN

How can you achieve the same using the requests library?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Gab
  • 5,604
  • 6
  • 36
  • 52

3 Answers3

72

You cannot use requests for this; the library builds such URLs if passed a Python structure for the parameters, but does not offer any tools to parse them. That's not a goal of the project.

Stick to the urllib.parse method to parse out the parameters. Once you have a dictionary or list of key-value tuples, just pass that to requests to build the URL again:

try:
    # Python 3
    from urllib.parse import urlparse, parse_qs
except ImportError:
    # Python 2
    from urlparse import urlparse, parse_qs

o = urlparse(url)
query = parse_qs(o.query)
# extract the URL without query parameters
url = o._replace(query=None).geturl()

if 'token' in query:
    query['token'] = 'NEW_TOKEN'

requests.get(url, params=query)

You can get both the urlparse and parse_qs functions in both Python 2 and 3, all you need to do is adjust the import location if you get an exception.

Demo on Python 3 (without the import exception guard) to demonstrate the URL having been built:

>>> from urllib.parse import urlparse, parse_qs
>>> url = "http://httpbin.org/get?token=TOKEN_TO_REPLACE&param2=c"
>>> o = urlparse(url)
>>> query = parse_qs(o.query)
>>> url = o._replace(query=None).geturl()
>>> if 'token' in query:
...     query['token'] = 'NEW_TOKEN'
... 
>>> response = requests.get(url, params=query)
>>> print(response.text)
{
  "args": {
    "param2": "c", 
    "token": "NEW_TOKEN"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.5.1 CPython/3.4.2 Darwin/14.1.0"
  }, 
  "origin": "188.29.165.245", 
  "url": "http://httpbin.org/get?token=NEW_TOKEN&param2=c"
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    @Gab: I've updated the post to make it clear how you can maintain compatibility between Python 2 and 3; the same functionality exists in both versions, just in different locations. `requests` cannot help there. – Martijn Pieters Feb 04 '15 at 18:52
  • effectively, I checked more in details the requests website and github folder it appears you are right. +1 for showing a simplified version of the code using the `requests` library in the end! – Gab Feb 04 '15 at 19:04
  • Martijn, you should update this answer. This function is available as [`requests.utils.urlparse`](https://github.com/psf/requests/blob/eedd67462819f8dbf8c1c32e77f9070606605231/requests/utils.py#L30) (it's literally the same function, it comes from [`compat` which comes from this file](https://github.com/psf/requests/blob/eedd67462819f8dbf8c1c32e77f9070606605231/requests/compat.py#L58)) and has been since 2017 I think. – Boris Verkhovskiy Dec 28 '19 at 22:38
  • @Boris the `requests.compat` module is not part of the public API. It does what my answer does, essentially, but that doesn’t mean you can rely on it continuing to do so. It is only imported into `requests.utils` because the implementations in that module need it. In other words, the only reason it is there is *as an implementation detail*. – Martijn Pieters Dec 28 '19 at 22:56
  • @MartijnPieters I'm not sure about that. None of the functions in that module are documented as far as I can tell, but it says "This module provides utility functions that are used within Requests that are *also useful for external consumption*". I would think if that was the idea they would be imported with an underscore `import urlparse as _urlparse` – Boris Verkhovskiy Dec 28 '19 at 23:05
  • @Boris: the utilities useful for external consumption **are** documented: https://requests.readthedocs.io/en/master/api/#encodings. Nothing in the module is imported with an underscore, its *all* implementation detail. – Martijn Pieters Dec 28 '19 at 23:13
  • @Boris: if requests wanted to offer bridged url parsing tools they’d certainly have documented it **and included all the parsing functions**. `parse_qs` is not imported into that module, because that module doesn’t have a use for that function. The imports that *are* there have all been added as imports *when new functionality in the module required their use*. But why would they? The `six` library already offers bridging. And requests is a HTTP library, not a URL library. – Martijn Pieters Dec 28 '19 at 23:27
21

Using requests only:

query = requests.utils.urlparse(url).query
params = dict(x.split('=') for x in query.split('&'))

if 'token' in params:
    params['token'] = 'NEW_TOKEN'

requests.get(url, params=params)
vvvvv
  • 25,404
  • 19
  • 49
  • 81
Dorik1972
  • 211
  • 2
  • 3
  • I think a `requests.utils.unquote` might also be needed, to prevent sending characters like Space, as "%20" and such. – aliqandil Jan 18 '20 at 23:09
-1

This works for me:

params = request.args
my_param = params.get('my_param')
Juris
  • 407
  • 3
  • 14