2

I need to send HTTP POST request with data as follow:

data = {'id[]': '1', 'id[]': '2', 'id[]': '3'}

Values list is actually unknown, but let it be values_list = ['1', '2', '3']

Of course if to try

for value in values_list:
    data["id[]"] = value

I get {'id[]': '3'} as key-value pair will be overwritten on each iteration...

I used this solution:

data = {}

class data_keys(object):
    def __init__(self, data_key):
        self.data_key = data_key

for value in values_list:
    data[data_keys('id[]')] = value

But my data looks like

{<__main__.data_keys object at 0x0000000004BAE518>: '2',
 <__main__.data_keys object at 0x0000000004BAED30>: '1',
 <__main__.data_keys object at 0x0000000004B9C748>: '3'}

What is wrong with my code? How else can I simply create dict with single key?

UPDATED

This how my HTTP request looks like:

requests.post(url, data={"id[]": '1', "id[]": '2', "id[]": '3'}, auth=HTTPBasicAuth(user_name, user_passw))

Title updated

Andersson
  • 51,635
  • 17
  • 77
  • 129
  • Just out of curiosity. Why do you construct such a dictionary? How will you get the values afterwards? I try out your code right now... – Ohumeronen Jun 24 '16 at 11:40
  • 4
    You mean create a dictionary where the same key appears more than once? You can't, hash maps like a `dict` **require** that each key appears only once. – jonrsharpe Jun 24 '16 at 11:40
  • Answer may depend on how are you sending an HTTP request. You'll have to take care of serialization step (as native dict cannot have duplicate keys). Depending on used library required actions may differ. – Łukasz Rogalski Jun 24 '16 at 11:40
  • 1
    Each key in a doctionnary have to be different (different hash) in order to be accessed, ie: if you have `d = {'a':1,'a':2}`, what will `d['a']` be? – Paul Jun 24 '16 at 11:42
  • 1
    To all commenting people, question is somewhat convoluted and misleading, but it's clearly not about creating a dict with duplicate keys, only about creating HTTP request with serialized data of some fixed form. Second one is technically possible without creating a dictionary with duplicate keys (which is clearly impossible). Why such form is required - I'm not sure, but for what we know we may assume server has broken API and OP is unable to change it... – Łukasz Rogalski Jun 24 '16 at 11:46
  • Too many comments :) As far as I know `data` for `HTTP` request is not actually a `Python` dictionary, but a `JSON`. So probably it could have same keys... Anyway this is exact form of `data` server require – Andersson Jun 24 '16 at 11:46
  • Can you post a working example of the structure of the data? You really need the same key for multiple values? You could try `collections.defaultdict` and set a list of values to the key: `d=collections.defaultdict([]) d['id[]'].append('1') d['id[]'].append('2') d['id[]'].append('3')` – Gábor Fekete Jun 24 '16 at 11:52
  • Ok, this is weird, but it's do-able. FWIW, I'd probably just construct the parameter string directly, rather than via a dict. But I'll post some code in a few minutes. – PM 2Ring Jun 24 '16 at 11:54
  • I've tried to do something like that once in my life just to keep my code simple but what I get is just break my head. As an advice I think is better if you use a list of dicts. – Arnold Gandarillas Jun 24 '16 at 11:59
  • Reason of downvote? – Andersson Jun 24 '16 at 11:59
  • 1
    I think you may find that the post data is one id key with a list of values, can you share the link? – Padraic Cunningham Jun 24 '16 at 12:08
  • @Andersson: _Maybe_ you got a downvote because someone thought you were asking to do something that's impossible, and that you should have known it was impossible by reading the docs... – PM 2Ring Jun 24 '16 at 12:17
  • 1
    @PadraicCunningham yep, that's likely. So, another [XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)? – Łukasz Rogalski Jun 24 '16 at 12:19
  • @Rogalski, yep, I have a very similar post requests here http://stackoverflow.com/questions/36289331/scraping-images-injected-by-javascript-in-python-with-selenium/36291204#36291204 if you look at the `i[]` paramater you see a list of data – Padraic Cunningham Jun 24 '16 at 12:25
  • 1
    It may be worth updating the title of the question to be about embedding the same parameter multiple times in post data. I don't think this is a bad question, but the title is going to lead to downvotes. – Jared Goguen Jun 24 '16 at 12:28
  • @JaredGoguen: It _is_ a bit of an XY problem, and poke has posted a sensible answer to the real problem. But I still think there's _some_ value in showing that it's possible to make a `dict` with multiple pseudo-identical keys, even if such a thing should probably not be used in real code. – PM 2Ring Jun 24 '16 at 12:49

3 Answers3

6

While you can hack dictionary keys in order to allow seemingly “equal” keys, this is probably not a good idea, as this relies on the implementation detail on how the key is transformed into a string. Furthermore, it will definitely cause confusion if you ever need to debug this situation.

A much easier and supported solution is actually built into the form data encode mechanism: You can simply pass a list of values:

data = {
    'id[]': ['1', '2', '3']
}

req = requests.get(url='http://www.example.com', params=data)
print(req.url) # 'http://www.example.com/?id%5B%5D=1&id%5B%5D=2&id%5B%5D=3'

So you can just pass your values_list directly into the dictionary and everything will work properly without having to hack anything.


And if you find yourself in a situation where you think such a dictionary does not work, you can also supply an iterable of two-tuples (first value being the key, second the value):

data = [
    ('id[]', '1'),
    ('id[]', '2'),
    ('id[]', '3')
]

req = requests.get(url='http://www.example.com', params=data)
print(req.url) # 'http://www.example.com/?id%5B%5D=1&id%5B%5D=2&id%5B%5D=3'
poke
  • 369,085
  • 72
  • 557
  • 602
5

In order to make your data_keys class do what you want you need to give it an appropriate __repr__ or __str__ method. This will allow the class instances to be displayed in the desired fashion when you print the dict, or when some other code tries to serialize it.

Here's a short demo that uses the 3rd party requests module to build a URL. I've changed the class name to make it conform to the usual Python class naming convention. This code was tested on Python 2.6 and Python 3.6

from __future__ import print_function
import requests

class DataKey(object):
    def __init__(self, data_key):
        self.data_key = data_key

    def __repr__(self):
        return str(self.data_key)

data = {}
values_list = ['1', '2', '3']

for value in values_list:
    data[DataKey('id[]')] = value

print(data)

req = requests.get(url='http://www.example.com', params=data)
print(req.url)

output

{id[]: '1', id[]: '2', id[]: '3'}
http://www.example.com/?id%5B%5D=1&id%5B%5D=2&id%5B%5D=3

Here's a more robust version inspired by Rogalski's answer that is acceptable to the json module, .

from __future__ import print_function
import requests
import json

class DataKey(str):
    def __init__(self, data_key):
        self.data_key = data_key

    def __repr__(self):
        return str(self.data_key)

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return id(self)

data = {}
values_list = ['1', '2', '3']

for value in values_list:
    data[DataKey('id[]')] = value

print(data)

req = requests.get(url='http://www.example.com', params=data)
print(req.url)

print(json.dumps(data))

output

{id[]: '3', id[]: '1', id[]: '2'}
http://www.example.com/?id%5B%5D=3&id%5B%5D=1&id%5B%5D=2
{"id[]": "3", "id[]": "1", "id[]": "2"}

As I mentioned in my comment on the question, this is weird, and creating dictionaries with multiple (pseudo)identical keys is really not a very useful thing to do. There will almost always be a far better approach, eg using a dict of lists or tuples, or as in this case, an alternative way of supplying the data, as shown in Poke's answer.

However, I should mention that multiple identical keys are not prohibited in JSON objects, and so it may occasionally be necessary to deal with such JSON data. I'm not claiming that using one of these crazy dictionaries is a good way to do that but it is a possibility...

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Wow, interesting, built-in `json` module requires dict keys to be `basestring` subclasses. What is used by `requests` internally to serialize payload? – Łukasz Rogalski Jun 24 '16 at 12:02
  • @Rogalski: I have no idea what black magic `requests` is doing. To be frank, I was kind of amazed that `requests` didn't complain soundly. :) – PM 2Ring Jun 24 '16 at 12:11
  • Oh, `id` is much cleaner than explicit counter, good catch. Nicely done. – Łukasz Rogalski Jun 24 '16 at 12:18
  • `requests.get(url='http://www.example.com', params={"id[]":['1', '2', '3']})` would give you the exact same output, I think the OP does not actually quite understand what is required for the post request. – Padraic Cunningham Jun 24 '16 at 12:18
  • @PadraicCunningham: I'd forgotten about that; I haven't actually used `requests` for several months... but it was kinda fun getting the bizarre `dict` to work. :) FWIW, poke has just posted an answer that uses the same logic as the code in your comment. – PM 2Ring Jun 24 '16 at 12:46
  • 1
    The OP's confusion most likely comes from the fact that when you monitored the post request in firebug or chrome tools you would see `id[]:1` `id[]:2` `id[]:3` etc.. as individual post parameters which can be a bit confusing, I did not post an answer as while it is the correct solution, it does not really align with the title, there are also actually lots of dupes. – Padraic Cunningham Jun 24 '16 at 13:02
4

Don't do it in real code, but basic structure would be something like:

data = {}
values_list = [1,2,3]

class data_keys(str):
    unique = 0
    def __init__(self, val):
        super(data_keys, self).__init__(val)
        self.unique += 1
        self.my_hash = self.unique
    def __eq__(self, other):
        return False
    def __hash__(self):
        return self.my_hash


for value in values_list:
    data[data_keys('id[]')] = value

print json.dumps(data)
# '{"id[]": 1, "id[]": 3, "id[]": 2}'

As you can see, key objects has to inherit from basestring class. I've used str. Also, there is a dirty hack (class variable) to ensure unique hashes and inequality between any of pair of data_keys.

Only proof of concept, it certainly may be done better.

Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93