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

16
  • 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. Commented Jun 24, 2016 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? Commented Jun 24, 2016 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... Commented Jun 24, 2016 at 11:46
  • 1
    I think you may find that the post data is one id key with a list of values, can you share the link? Commented Jun 24, 2016 at 12:08
  • 1
    @PadraicCunningham yep, that's likely. So, another XY Problem? Commented Jun 24, 2016 at 12:19

3 Answers 3

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'
Sign up to request clarification or add additional context in comments.

1 Comment

At Last The Voice Of Reason. :)
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...

6 Comments

Wow, interesting, built-in json module requires dict keys to be basestring subclasses. What is used by requests internally to serialize payload?
@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. :)
Oh, id is much cleaner than explicit counter, good catch. Nicely done.
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.
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.
|
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.

1 Comment

Nice work. I guess I ought to update my answer so it works with JSON too. ;)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.