Skip to content

Commit ccb23b6

Browse files
committed
Merge pull request #6 from dctrwatson/phab-query
Update interfaces.json and provide conduit.query support
2 parents d8dfe6e + 7e44156 commit ccb23b6

File tree

4 files changed

+135
-1239
lines changed

4 files changed

+135
-1239
lines changed

README.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ Parameters are passed as keyword arguments to the resource call::
2323

2424
Documentation on all methods is located at https://secure.phabricator.com/conduit/
2525

26-
Updating interfaces.json
27-
------------------------
26+
Interface out-of-date
27+
---------------------
2828

29-
Copy ``gen_api_interfaces.php`` to ``scripts/util`` of your Phabricator installation and run it::
29+
If Phabricator modifies Conduit and the included ``interfaces.json`` is out-of-date or to make sure
30+
to always have the latest interfaces::
3031

31-
$ ./gen_api_interfaces.php > interfaces.json
32+
from phabricator import Phabricator
33+
phab = Phabricator()
34+
phab.update_interfaces()
35+
phab.user.whoami()

phabricator/__init__.py

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@
1414
except:
1515
__version__ = 'unknown'
1616

17+
import copy
1718
import hashlib
1819
import httplib
1920
import json
2021
import os.path
22+
import re
2123
import socket
2224
import time
2325
import urllib
2426
import urlparse
2527

28+
from collections import defaultdict
29+
2630
__all__ = ['Phabricator']
2731

2832
# Default phabricator interfaces
@@ -34,6 +38,111 @@
3438
except IOError:
3539
ARCRC = None
3640

41+
# Map Phabricator types to Python types
42+
PARAM_TYPE_MAP = {
43+
# int types
44+
'int': 'int',
45+
'uint': 'int',
46+
'revisionid': 'int',
47+
'revision_id': 'int',
48+
'diffid': 'int',
49+
'diff_id': 'int',
50+
'id': 'int',
51+
'enum': 'int',
52+
53+
# bool types
54+
'bool': 'bool',
55+
56+
# dict types
57+
'map': 'dict',
58+
'dict': 'dict',
59+
60+
# list types
61+
'list': 'list',
62+
63+
# tuple types
64+
'pair': 'tuple',
65+
66+
# str types
67+
'str': 'str',
68+
'string': 'str',
69+
'phid': 'str',
70+
'guids': 'str',
71+
'type': 'str',
72+
}
73+
74+
STR_RE = re.compile(r'([a-zA-Z_]+)')
75+
76+
77+
def map_param_type(param_type):
78+
"""
79+
Perform param type mapping
80+
This requires a bit of logic since this isn't standardized.
81+
If a type doesn't map, assume str
82+
"""
83+
m = STR_RE.match(param_type)
84+
main_type = m.group(0)
85+
86+
if main_type in ('list', 'array'):
87+
info = param_type.replace(' ', '').split('<', 1)
88+
89+
# Handle no sub-type: "required list"
90+
sub_type = info[1] if len(info) > 1 else 'str'
91+
92+
# Handle list of pairs: "optional list<pair<callsign, path>>"
93+
sub_match = STR_RE.match(sub_type)
94+
sub_type = sub_match.group(0).lower()
95+
96+
return [PARAM_TYPE_MAP.setdefault(sub_type, 'str')]
97+
98+
return PARAM_TYPE_MAP.setdefault(main_type, 'str')
99+
100+
101+
def parse_interfaces(interfaces):
102+
"""
103+
Parse the conduit.query json dict response
104+
This performs the logic of parsing the non-standard params dict
105+
and then returning a dict Resource can understand
106+
"""
107+
parsed_interfaces = defaultdict(dict)
108+
109+
for m, d in interfaces.iteritems():
110+
app, func = m.split('.')
111+
112+
method = parsed_interfaces[app][func] = {}
113+
114+
# Make default assumptions since these aren't provided by Phab
115+
method['formats'] = ['json', 'human']
116+
method['method'] = 'POST'
117+
118+
method['optional'] = {}
119+
method['required'] = {}
120+
121+
for name, type_info in dict(d['params']).iteritems():
122+
# Usually in the format: <optionality> <param_type>
123+
info_pieces = type_info.split(' ', 1)
124+
125+
# If optionality isn't specified, assume required
126+
if info_pieces[0] not in ('optional', 'required'):
127+
optionality = 'required'
128+
param_type = info_pieces[0]
129+
# Just make an optional string for "ignored" params
130+
elif info_pieces[0] == 'ignored':
131+
optionality = 'optional'
132+
param_type = 'string'
133+
else:
134+
optionality = info_pieces[0]
135+
param_type = info_pieces[1]
136+
137+
# This isn't validated by the client
138+
if param_type.startswith('nonempty'):
139+
optionality = 'required'
140+
param_type = param_type[9:]
141+
142+
method[optionality][name] = map_param_type(param_type)
143+
144+
return dict(parsed_interfaces)
145+
37146

38147
class InterfaceNotDefined(NotImplementedError):
39148
pass
@@ -75,11 +184,19 @@ def __len__(self):
75184
def keys(self):
76185
return self.response.keys()
77186

187+
def iteritems(self):
188+
for k, v in self.response.iteritems():
189+
yield k, v
190+
191+
def itervalues(self):
192+
for v in self.response.itervalues():
193+
yield v
194+
78195

79196
class Resource(object):
80-
def __init__(self, api, interface=INTERFACES, endpoint=None, method=None):
197+
def __init__(self, api, interface=None, endpoint=None, method=None):
81198
self.api = api
82-
self.interface = interface
199+
self.interface = interface or copy.deepcopy(parse_interfaces(INTERFACES))
83200
self.endpoint = endpoint
84201
self.method = method
85202

@@ -208,3 +325,10 @@ def connect(self):
208325

209326
def generate_hash(self, token):
210327
return hashlib.sha1(token + self.api.certificate).hexdigest()
328+
329+
def update_interfaces(self):
330+
query = Resource(api=self, method='conduit', endpoint='query')
331+
332+
interfaces = query()
333+
334+
self.interface = parse_interfaces(interfaces)

phabricator/gen_api_interfaces.php

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)