Skip to content
Advertisement

Convert PHP curl POST request with array of parameters to Python code

I want to convert my php code to python code.

$u = 'http://httpbin.org/post';

$req = array(
  'check' => 'command',
  'parameters' => array(
    0 => array('parameter' => '1', 'description' => '2'),
    1 => array('parameter' => '3', 'description' => '4')
  )
);

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $u);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($req));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($curl);

php test.php
{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "check": "command",
    "parameters[0][parameter]": "1",
    "parameters[0][description]": "2",
    "parameters[1][parameter]": "3",
    "parameters[1][description]": "4"
  },

this code successfully fetching data from remote api, but when i try to write it with Python requests – array of parameters is sending with wrong data.

url = 'http://httpbin.org/post'
req = {'check': 'command', 'parameters': ({'parameter': '1', 'description': '2'}, {'parameter': '3', 'description': '4'})}

try:
    fetch = requests.post(url, data = req)
except requests.exceptions.RequestException as e:
    print(e)
print(fetch.text)

python3 test.py
    {
      "args": {},
      "data": "",
      "files": {},
      "form": {
        "check": "command",
        "parameters": [
            "parameter",
            "description",
            "parameter",
            "description"
        ]
      },

“Parameters”, sended by my Python script is invalid

Advertisement

Answer

PHPs http_build_query and it’s corresponding $_GET and $_POST parsing are completely arbitrary in how they work. Thus you must implement this functionality yourself.

Let’s compare the outputs of PHPs http_build_query to pythons urlencode (which requests uses internally to build the parameters)

PHP

$req = array(
  'check' => 'command',
  'parameters' => array(
    0 => array('parameter' => '1', 'description' => '2'),
    1 => array('parameter' => '3', 'description' => '4')
  )
);
$query = http_build_query($req);
$query_parsed = urldecode($query);
echo $query;
echo $query_parsed;

Result:

check=command&parameters%5B0%5D%5Bparameter%5D=1&parameters%5B0%5D%5Bdescription%5D=2&parameters%5B1%5D%5Bparameter%5D=3&parameters%5B1%5D%5Bdescription%5D=4
check=command&parameters[0][parameter]=1&parameters[0][description]=2&parameters[1][parameter]=3&parameters[1][description]=4

Python

from urllib.parse import urlencode, unquote
req = {'check': 'command', 'parameters': ({'parameter': '1', 'description': '2'}, {'parameter': '3', 'description': '4'})}
query = urlencode(req)
query_parsed = unquote(query)
print(query)
print(query_parsed)

Result:

check=command&parameters=%28%7B%27parameter%27%3A+%271%27%2C+%27description%27%3A+%272%27%7D%2C+%7B%27parameter%27%3A+%273%27%2C+%27description%27%3A+%274%27%7D%29
check=command&parameters=({'parameter':+'1',+'description':+'2'},+{'parameter':+'3',+'description':+'4'})

This looks quite a bit different but apparently conforms to the standard and thus httpbin interprets this correctly.

To make python behave the same as PHP, I’ve adapted this answer to create the following:

from collections.abc import MutableMapping
from urllib.parse import urlencode, unquote

def flatten(dictionary, parent_key=False, separator='.', separator_suffix=''):
    """
    Turn a nested dictionary into a flattened dictionary
    :param dictionary: The dictionary to flatten
    :param parent_key: The string to prepend to dictionary's keys
    :param separator: The string used to separate flattened keys
    :return: A flattened dictionary
    """

    items = []
    for key, value in dictionary.items():
        new_key = str(parent_key) + separator + key + separator_suffix if parent_key else key
        if isinstance(value, MutableMapping):
            items.extend(flatten(value, new_key, separator, separator_suffix).items())
        elif isinstance(value, list) or isinstance(value, tuple):
            for k, v in enumerate(value):
                items.extend(flatten({str(k): v}, new_key, separator, separator_suffix).items())
        else:
            items.append((new_key, value))
    return dict(items)


req = {'check': 'command', 'parameters': ({'parameter': '1', 'description': '2'}, {'parameter': '3', 'description': '4'})}
req = flatten(req, False, '[', ']')
query = urlencode(req)
query_parsed = unquote(query)
print(query)
print(query_parsed)

Which outputs

check=command&parameters%5B0%5D%5Bparameter%5D=1&parameters%5B0%5D%5Bdescription%5D=2&parameters%5B1%5D%5Bparameter%5D=3&parameters%5B1%5D%5Bdescription%5D=4
check=command&parameters[0][parameter]=1&parameters[0][description]=2&parameters[1][parameter]=3&parameters[1][description]=4

which seems to be what you want.

Now you should be able to pass the result of flatten as data to get the desired behaviour.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement