Page MenuHomePhabricator

the conduit API is awkward to use when one must send arrays
Closed, InvalidPublic

Description

I was trying to use the conduit API to create a task, using the Requests python library (python 2.7 on debian jessie); I suppose this is the simplest script that anybody would write:

import requests

s = requests.Session()
projectPHIDs = ["PHID-PROJ-cve7ps3jhhf5e2k4modo"]
data = {'api.token': '...',
        'projectPHIDs': projectPHIDs,
        'title': 'aaa',
        'description': 'bbb'}
url1 = 'https://phabricator.example.com/api/maniphest.createtask'
r1 = s.post(url1, data=data)
r1.raise_for_status()
results1 = r1.json()
print results1

but this results in:

{"error_code": "ERR-CONDUIT-CORE", "result": None, "error_info": "Argument 1 passed to ManiphestConduitAPIMethod::validatePHIDList() must be of the type array, string given, called in /opt/phabricator/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php on line 141 and defined"}

to see what Requests sends, I switched to a more verbose synthax:

req = requests.Request('POST', url, data=data)
prepped = req.prepare()
print 'body = ' + prepped.body

and here is the body:

title=aaa&api.token=...&description=bbb&projectPHIDs=%5B%27PHID-PROJ-cve7ps3jhhf5e2k4modo%27%5D

according to urlparse.parse_qs this decodes back to:

{"projectPHIDs": ["['PHID-PROJ-cve7ps3jhhf5e2k4modo']"], "api.token": ["..."], "description": ["bbb"], "title": ["aaa"]}

so actually it's sending a string indeed !

I then tested the same API endpoint using the conduit webconsole, intercepted the request and found this:

325\r\n\r\n__csrf__=B%40n2t2voygdd0372992a31838c&__form__=1&params%5Btitle%5D=%5B%22bbb%22%5D&params%5Bdescription%5D=&params%5BownerPHID%5D=&params%5BviewPolicy%5D=&params%5BeditPolicy%5D=&params%5BccPHIDs%5D=&params%5Bpriority%5D=&params%5BprojectPHIDs%5D=%5B%22PHID-PROJ-cve7ps3jhhf5e2k4modo%22%5D&params%5Bauxiliary%5D=&output=human"

this is what I see when I decode that with urlparse.parse_qs:

{'325\r\n\r\n__csrf__': ['B@n2t2voygdd0372992a31838c'], '__form__': ['1'], 'params[projectPHIDs]': ['["PHID-PROJ-cve7ps3jhhf5e2k4modo"]'], 'output': ['human"'], 'params[title]': ['["bbb"]']}

now this seems to me a non-standard way to send the parameters !

after some more tests, I just wanted to find a way to create the one task, so here is the solution:

import requests

s = requests.Session()
projectPHID = "PHID-PROJ-cve7ps3jhhf5e2k4modo"

data = {'api.token': '...',
        'title': 'aaa',
        'description': 'bbb'}
url = 'https://phabricator.example.com/api/maniphest.createtask'
req = requests.Request('POST', url, data=data)
prepped = s.prepare_request(req)
prepped.body = prepped.body + '&projectPHIDs=%5B%22' + projectPHID + '%22%5D'
print 'body = ' + prepped.body
resp = s.send(prepped)
resp.raise_for_status()
results = resp.json()
print results

this bug report is only to point out that the conduit API is awkward to use when one must send arrays; it should really be way less painful that that !

Event Timeline

This comment was removed by avivey.

This doesn't look like a bug report nor a feature request. I don't know what you want us to do with this.

epriestley added a subscriber: epriestley.

This is not a bug.

This amounts to "Conduit does not support the requests module serialization of Python objects over HTTP". It is not expected to, we don't claim it does, and we have no plans to support this serialization.

this is fine since the way python serializes over HTTP is certainly peculiar; but what about JSON ?

in the Conduit API overview I read: "It is roughly JSON-RPC: you usually pass a JSON blob, and usually get a JSON blob back, although both call and result formats are flexible in some cases."

of course I have tried JSON too in my tests ! here is the JSON script:

import requests

s = requests.Session()
projectPHIDs = ["PHID-PROJ-1", "PHID-PROJ-2"]

data = {'api.token': '...',
        'projectPHIDs': projectPHIDs,
        'title': 'aaa',
        'description': 'bbb'}
url = 'https://phabricator.example.com/api/maniphest.createtask'
req = requests.Request('POST', url, json=data)
prepped = s.prepare_request(req)
print 'body = ' + prepped.body
resp = s.send(prepped)
resp.raise_for_status()
results = resp.json()
print results

output:

body = {"title": "aaa", "api.token": "...", "description": "bbb", "projectPHIDs": ["PHID-PROJ-1", "PHID-PROJ-2"]}

{u'error_code': u'ERR-CONDUIT-CALL', u'result': None, u'error_info': u'API Method "maniphest.createtask" does not define these parameters: \'{"title": "aaa", "api.token": "...", "description": "bbb", "projectPHIDs": ["PHID-PROJ-1", "PHID-PROJ-2"]}\'.'}

I'm happy to rewrite your script so it works, but we charge $1,500/hr for custom development (see Consulting) because time spent helping individual users with unique problems takes away from developing and improving Phabricator. Are you interested in this?

we'll discuss internally next week and let you know. thanks for now

Turned out the fix above:

prepped.body = prepped.body + '&projectPHIDs=%5B%22' + projectPHID + '%22%5D'

does not really work. What works is this:

import requests

s = requests.Session()
projectPHID = "PHID-PROJ-aaaaaaaaaaaaaaaaaaaa"
data = {'api.token': 'api-aaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        'title': 'aaa',
        'description': 'bbb',
        'projectPHIDs[]': [projectPHID]}
url = 'https://phabricator.example.com/api/maniphest.createtask'
req = requests.Request('POST', url, data=data)
prepped = s.prepare_request(req)
resp = s.send(prepped)
resp.raise_for_status()
results = resp.json()
print results

The [] appended to the projectPHIDs key in the data object makes python Requests treat the array in a fashion that is more compatible with Conduit.

So our script was faulty but we did our homework and there was no need for your help; sorry for wasting your time and thanks for developing and improving the excellent Phabricator.

As a way to give back, we've open sourced the tool that is using this: a web application plugin for collecting user feedback and sending it straight to Phabricator, creating a task in Maniphest complete with screenshot and detailed info.

Should we list it in the Community Resources wiki page ?