diff --git a/.gitignore b/.gitignore index 51593c3..f832a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.egg-info build/ dist/ +.idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ca2bed1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: +- '2.7' +- '3.5' +install: pip install .[tests] +script: python -m phabricator.tests.test_phabricator +deploy: + provider: pypi + user: disqus + password: + secure: AJ7zSLd6BgI4W8Kp3KEx5O40bUJA91PkgLTZb5MnCx4/8nUPlkk+LqvodaiiJQEGzpP8COPvRlzJ/swd8d0P38+Se6V83wA43MylimzrgngO6t3c/lXa/aMnrRzSpSfK5QznEMP2zcSU1ReD+2dNr7ATKajbGqTzrCFk/hQPMZ0= + on: + tags: true diff --git a/CHANGES b/CHANGES index 65e025e..8652a4e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,46 @@ +0.9.1 + +* Update requests version. + +0.9.0 + +* Add default retry to API calls. + +0.8.1 + +* Fixed bug where built-in interface dict overwrote `interface` methods from Almanac (`interface.search`, `interface.edit`). The `Resource` attribute `interface` is renamed `_interface`. + +0.8.0 + +* Switch to using requests +* Allow library to be used from a zip archive +* Deprecated parameters are now set to optional +* Fixed bug where instance attribute was shadowing API endpoint +* Update interfaces.json +* Update README + +0.7.0 + +* Allow for nested methods to support conduit calls such as diffusion.repository.edit +* Fix regular expressions that cause type parsing to fail + +0.6.1 + +* Fix Python 3 related issues + +0.6.0 + +* Python 3 support +* Fix "not JSON serializable" error when not using a token +* Better tests +* Updated interfaces +* Moved `conduit` to `_conduit` so `.conduit` API is available now +* Updated interfaces to the latest Phabricator version + +0.5.0 + +* Support new style token-based authentication + 0.4.0 * Update interfaces.json to Phab @ e75b389 diff --git a/README.rst b/README.rst index dff51a5..2ce845a 100644 --- a/README.rst +++ b/README.rst @@ -1,22 +1,29 @@ python-phabricator ================== +.. image:: https://travis-ci.org/disqus/python-phabricator.png?branch=master + :target: https://travis-ci.org/disqus/python-phabricator + Installation ------------ :: - $ python setup.py install + $ pip install phabricator Usage ----- Use the API by instantiating it, and then calling the method through dotted notation chaining:: - from phabricator import Phabricator - phab = Phabricator() # This will use your ~/.arcrc file - phab.user.whoami() + from phabricator import Phabricator + phab = Phabricator() # This will use your ~/.arcrc file + phab.user.whoami() + +You can also use:: + phab = Phabricator(host='https://my-phabricator.org/api/', token='api-mytoken') + Parameters are passed as keyword arguments to the resource call:: phab.user.find(aliases=["sugarc0de"]) @@ -29,7 +36,7 @@ Interface out-of-date If Phabricator modifies Conduit and the included ``interfaces.json`` is out-of-date or to make sure to always have the latest interfaces:: - from phabricator import Phabricator - phab = Phabricator() - phab.update_interfaces() - phab.user.whoami() + from phabricator import Phabricator + phab = Phabricator() + phab.update_interfaces() + phab.user.whoami() diff --git a/bin/publish b/bin/publish new file mode 100755 index 0000000..bf15421 --- /dev/null +++ b/bin/publish @@ -0,0 +1,4 @@ +#!/bin/bash +set -eo pipefail +python setup.py sdist bdist_wheel +twine upload dist/* \ No newline at end of file diff --git a/phabricator/__init__.py b/phabricator/__init__.py index 94d60c1..9277efa 100644 --- a/phabricator/__init__.py +++ b/phabricator/__init__.py @@ -14,44 +14,66 @@ except: __version__ = 'unknown' +import collections import copy import hashlib -import httplib import json import os.path import re import socket +import pkgutil import time -import urllib -import urlparse +import requests +from requests.adapters import HTTPAdapter, Retry + +from ._compat import ( + MutableMapping, iteritems, string_types, urlencode, +) -from collections import defaultdict __all__ = ['Phabricator'] -# Default phabricator interfaces -INTERFACES = json.loads(open(os.path.join(os.path.dirname(__file__), 'interfaces.json'), 'r').read()) + +ON_WINDOWS = os.name == 'nt' +CURRENT_DIR = os.getcwd() + + +# Default Phabricator interfaces +INTERFACES = {} +INTERFACES = json.loads( + pkgutil.get_data(__name__, 'interfaces.json') + .decode('utf-8')) + # Load arc config -ARC_CONFIGS = [ +ARC_CONFIGS = ( # System config - os.path.join(os.environ['ProgramData'], 'Phabricator', 'Arcanist', 'config') if os.name == 'nt' else - os.path.join('/etc', 'arcconfig'), + os.path.join( + os.environ['ProgramData'], + 'Phabricator', + 'Arcanist', + 'config' + ) if ON_WINDOWS else os.path.join('/etc', 'arcconfig'), # User config - os.path.join(os.environ['AppData'] if os.name == 'nt' else os.path.expanduser('~'), '.arcrc'), + os.path.join( + os.environ['AppData'] if ON_WINDOWS else os.path.expanduser('~'), + '.arcrc' + ), # Project config - os.path.join(os.getcwd(), '.arcconfig'), + os.path.join(CURRENT_DIR, '.arcconfig'), # Local project config - os.path.join(os.getcwd(), '.git', 'arc', 'config'), -] + os.path.join(CURRENT_DIR, '.git', 'arc', 'config'), +) ARCRC = {} for conf in ARC_CONFIGS: if os.path.exists(conf): - ARCRC.update(json.load(open(conf, 'r'))) + with open(conf, 'r') as fobj: + ARCRC.update(json.load(fobj)) + # Map Phabricator types to Python types PARAM_TYPE_MAP = { @@ -63,7 +85,6 @@ 'diffid': int, 'diff_id': int, 'id': int, - 'enum': int, # bool types 'bool': bool, @@ -79,14 +100,16 @@ 'pair': tuple, # str types - 'str': basestring, - 'string': basestring, - 'phid': basestring, - 'guids': basestring, - 'type': basestring, + 'str': string_types, + 'string': string_types, + 'phid': string_types, + 'guids': string_types, + 'type': string_types, } -STR_RE = re.compile(r'([a-zA-Z_]+)') +TYPE_INFO_COMMENT_RE = re.compile(r'\s*\([^)]+\)\s*$') +TYPE_INFO_SPLITTER_RE = re.compile(r'(\w+(?:<.+>)?)(?:\s+|$)') +TYPE_INFO_RE = re.compile(r']+>>?)?(?:.+|$)') def map_param_type(param_type): @@ -95,22 +118,24 @@ def map_param_type(param_type): This requires a bit of logic since this isn't standardized. If a type doesn't map, assume str """ - m = STR_RE.match(param_type) - main_type = m.group(0) + main_type, sub_type = TYPE_INFO_RE.match(param_type).groups() if main_type in ('list', 'array'): - info = param_type.replace(' ', '').split('<', 1) - # Handle no sub-type: "required list" - sub_type = info[1] if len(info) > 1 else 'str' + if sub_type is not None: + sub_type = sub_type.strip() + + if not sub_type: + sub_type = 'str' # Handle list of pairs: "optional list>" - sub_match = STR_RE.match(sub_type) - sub_type = sub_match.group(0).lower() + sub_match = TYPE_INFO_RE.match(sub_type) + if sub_match: + sub_type = sub_match.group(1).lower() - return [PARAM_TYPE_MAP.setdefault(sub_type, basestring)] + return [PARAM_TYPE_MAP.setdefault(sub_type, string_types)] - return PARAM_TYPE_MAP.setdefault(main_type, basestring) + return PARAM_TYPE_MAP.setdefault(main_type, string_types) def parse_interfaces(interfaces): @@ -119,9 +144,9 @@ def parse_interfaces(interfaces): This performs the logic of parsing the non-standard params dict and then returning a dict Resource can understand """ - parsed_interfaces = defaultdict(dict) + parsed_interfaces = collections.defaultdict(dict) - for m, d in interfaces.iteritems(): + for m, d in iteritems(interfaces): app, func = m.split('.', 1) method = parsed_interfaces[app][func] = {} @@ -133,33 +158,33 @@ def parse_interfaces(interfaces): method['optional'] = {} method['required'] = {} - for name, type_info in dict(d['params']).iteritems(): + for name, type_info in iteritems(dict(d['params'])): + # Set the defaults + optionality = 'required' + param_type = 'string' + # Usually in the format: - info_pieces = type_info.split(' ', 1) - - # If optionality isn't specified, assume required - if info_pieces[0] not in ('optional', 'required'): - optionality = 'required' - param_type = info_pieces[0] - # Just make an optional string for "ignored" params - elif info_pieces[0] == 'ignored': - optionality = 'optional' - param_type = 'string' - else: - optionality = info_pieces[0] - param_type = info_pieces[1] - - # This isn't validated by the client - if param_type.startswith('nonempty'): - optionality = 'required' - param_type = param_type[9:] + type_info = TYPE_INFO_COMMENT_RE.sub('', type_info) + info_pieces = TYPE_INFO_SPLITTER_RE.findall(type_info) + for info_piece in info_pieces: + if info_piece in ('optional', 'required'): + optionality = info_piece + elif info_piece == 'ignored': + optionality = 'optional' + param_type = 'string' + elif info_piece == 'nonempty': + optionality = 'required' + elif info_piece == 'deprecated': + optionality = 'optional' + else: + param_type = info_piece method[optionality][name] = map_param_type(param_type) return dict(parsed_interfaces) -class InterfaceNotDefined(NotImplementedError): +class ConfigurationError(Exception): pass @@ -172,61 +197,66 @@ def __str__(self): return '%s: %s' % (self.code, self.message) -class InvalidAccessToken(APIError): - pass - - -class Result(object): +class Result(MutableMapping): def __init__(self, response): self.response = response - def __repr__(self): - return '<%s: %s>' % (self.__class__.__name__, repr(self.response)) - - def __iter__(self): - for r in self.response: - yield r - def __getitem__(self, key): return self.response[key] - def __getattr__(self, key): - return self.response[key] + __getattr__ = __getitem__ - def __getstate__(self): - return self.response + def __setitem__(self, key, value): + self.response[key] = value - def __setstate__(self, state): - self.response = state + def __delitem__(self, key): + del self.response[key] - def __len__(self): - return len(self.response.keys()) - - def keys(self): - return self.response.keys() + def __iter__(self): + return iter(self.response) - def iteritems(self): - for k, v in self.response.iteritems(): - yield k, v + def __len__(self): + return len(self.response) - def itervalues(self): - for v in self.response.itervalues(): - yield v + def __repr__(self): + return '<%s: %s>' % (type(self).__name__, repr(self.response)) class Resource(object): - def __init__(self, api, interface=None, endpoint=None, method=None): + def __init__(self, api, interface=None, endpoint=None, method=None, nested=False): self.api = api - self.interface = interface or copy.deepcopy(parse_interfaces(INTERFACES)) + self._interface = interface or copy.deepcopy(parse_interfaces(INTERFACES)) self.endpoint = endpoint self.method = method + self.nested = nested + self.session = requests.Session() + adapter = HTTPAdapter(max_retries=Retry( + total=3, + connect=3, + allowed_methods=["HEAD", "GET", "POST", "PATCH", "PUT", "OPTIONS"] + )) + self.session.mount("https://", adapter) + self.session.mount("http://", adapter) + def __getattr__(self, attr): if attr in getattr(self, '__dict__'): return getattr(self, attr) - interface = self.interface - if attr not in interface: + interface = self._interface + if self.nested: + attr = "%s.%s" % (self.endpoint, attr) + submethod_exists = False + submethod_match = attr + '.' + for key in interface.keys(): + if key.startswith(submethod_match): + submethod_exists = True + break + if attr not in interface and submethod_exists: + return Resource(self.api, interface, attr, self.endpoint, nested=True) + elif attr not in interface: interface[attr] = {} + if self.nested: + return Resource(self.api, interface[attr], attr, self.method) return Resource(self.api, interface[attr], attr, self.endpoint) def __call__(self, **kwargs): @@ -234,25 +264,29 @@ def __call__(self, **kwargs): def _request(self, **kwargs): # Check for missing variables - resource = self.interface + resource = self._interface def validate_kwarg(key, target): # Always allow list - if isinstance(key, list): - return all([validate_kwarg(x, target[0]) for x in key]) - return isinstance(key, target) - - for k in resource.get('required', []): - if k not in [x.split(':')[0] for x in kwargs.keys()]: - raise ValueError('Missing required argument: %s' % k) - if isinstance(kwargs.get(k), list) and not isinstance(resource['required'][k], list): - raise ValueError('Wrong argument type: %s is not a list' % k) - elif not validate_kwarg(kwargs.get(k), resource['required'][k]): - if isinstance(resource['required'][k], list): - raise ValueError('Wrong arguemnt type: %s is not a list of %ss' % (k, resource['required'][k][0])) - raise ValueError('Wrong arguemnt type: %s is not a %s' % (k, resource['required'][k])) - - conduit = self.api.conduit + if isinstance(target, list): + return ( + isinstance(key, (list, tuple, set)) and + all(validate_kwarg(x, target[0]) for x in key) + ) + + return isinstance(key, tuple(target) if isinstance(target, list) else target) + + for key, val in resource.get('required', {}).items(): + if key not in [x.split(':')[0] for x in kwargs.keys()]: + raise ValueError('Missing required argument: %s' % key) + if isinstance(kwargs.get(key), list) and not isinstance(val, list): + raise ValueError('Wrong argument type: %s is not a list' % key) + elif not validate_kwarg(kwargs.get(key), val): + if isinstance(val, list): + raise ValueError('Wrong argument type: %s is not a list of %ss' % (key, val[0])) + raise ValueError('Wrong argument type: %s is not a %s' % (key, val)) + + conduit = self.api._conduit if conduit: # Already authenticated, add session key to json data @@ -265,30 +299,30 @@ def validate_kwarg(key, target): else: # Authorization is required, silently auth the user self.api.connect() - kwargs['__conduit__'] = self.api.conduit - - url = urlparse.urlparse(self.api.host) - if url.scheme == 'https': - conn = httplib.HTTPSConnection(url.netloc, timeout=self.api.timeout) - else: - conn = httplib.HTTPConnection(url.netloc, timeout=self.api.timeout) + kwargs['__conduit__'] = self.api._conduit - path = url.path + '%s.%s' % (self.method, self.endpoint) headers = { 'User-Agent': 'python-phabricator/%s' % str(self.api.clientVersion), 'Content-Type': 'application/x-www-form-urlencoded' } - body = urllib.urlencode({ + body = { "params": json.dumps(kwargs), "output": self.api.response_format - }) + } # TODO: Use HTTP "method" from interfaces.json - conn.request('POST', path, body, headers) - response = conn.getresponse() - data = self._parse_response(response.read()) + path = '%s%s.%s' % (self.api.host, self.method, self.endpoint) + response = self.session.post(path, data=body, headers=headers, timeout=self.api.timeout) + + # Make sure we got a 2xx response indicating success + if not response.status_code >= 200 or not response.status_code < 300: + raise requests.exceptions.HTTPError( + 'Bad response status: {0}'.format(response.status_code) + ) + + data = self._parse_response(response.text) return Result(data['result']) @@ -309,47 +343,62 @@ class Phabricator(Resource): } def __init__(self, username=None, certificate=None, host=None, - timeout=5, response_format='json', **kwargs): + timeout=5, response_format='json', token=None, **kwargs): - # Set values in ~/.arcrc as defaults - if ARCRC: - self.host = host if host else ARCRC['hosts'].keys()[0] - self.username = username if username else ARCRC['hosts'][self.host]['user'] - self.certificate = certificate if certificate else ARCRC['hosts'][self.host]['cert'] - else: - self.host = host - self.username = username - self.certificate = certificate + defined_hosts = ARCRC.get('hosts', {}) + + try: + self.host = host if host else list(defined_hosts.keys())[0] + except IndexError: + raise ConfigurationError("No host found or provided.") + + current_host_config = defined_hosts.get(self.host, {}) + self.conduit_token = token if token else current_host_config.get('token') + + if self.conduit_token is None: + self.username = username if username else current_host_config.get('user') + self.certificate = certificate if certificate else current_host_config.get('cert') self.timeout = timeout self.response_format = response_format self.client = 'python-phabricator' self.clientVersion = 1 self.clientDescription = socket.gethostname() + ':python-phabricator' - self.conduit = None + self._conduit = None - super(Phabricator, self).__init__(self) + super(Phabricator, self).__init__(self, **kwargs) def _request(self, **kwargs): raise SyntaxError('You cannot call the Conduit API without a resource.') def connect(self): + if self.conduit_token: + self._conduit = { + 'token': self.conduit_token + } + return + auth = Resource(api=self, method='conduit', endpoint='connect') - response = auth(user=self.username, host=self.host, - client=self.client, clientVersion=self.clientVersion) + response = auth( + user=self.username, + host=self.host, + client=self.client, + clientVersion=self.clientVersion + ) - self.conduit = { + self._conduit = { 'sessionKey': response.sessionKey, 'connectionID': response.connectionID } def generate_hash(self, token): - return hashlib.sha1(token + self.api.certificate).hexdigest() + source_string = (token + self.api.certificate).encode('utf-8') + return hashlib.sha1(source_string).hexdigest() def update_interfaces(self): query = Resource(api=self, method='conduit', endpoint='query') interfaces = query() - self.interface = parse_interfaces(interfaces) + self._interface = parse_interfaces(interfaces) diff --git a/phabricator/_compat.py b/phabricator/_compat.py new file mode 100644 index 0000000..c87f3a8 --- /dev/null +++ b/phabricator/_compat.py @@ -0,0 +1,26 @@ +import sys + +PY3 = sys.version_info[0] >= 3 + +try: + from collections.abc import MutableMapping +except ImportError: + from collections import MutableMapping + +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode + +if PY3: + str_type = str + string_types = str, + + def iteritems(d, **kw): + return iter(d.items(**kw)) +else: + str_type = unicode + string_types = basestring, + + def iteritems(d, **kw): + return d.iteritems(**kw) diff --git a/phabricator/interfaces.json b/phabricator/interfaces.json index 7163cd4..0bfa36a 100644 --- a/phabricator/interfaces.json +++ b/phabricator/interfaces.json @@ -1 +1 @@ -{"arcanist.projectinfo":{"params":{"name":"required string"}},"audit.query":{"params":{"auditorPHIDs":"optional list\u003cphid\u003e","commitPHIDs":"optional list\u003cphid\u003e","status":"optional enum\u003c\"status-any\", \"status-open\"\u003e (default = \"status-any\")","offset":"optional int","limit":"optional int (default = 100)"}},"chatlog.query":{"params":{"channels":"optional list\u003cstring\u003e","limit":"optional int (default = 100)"}},"chatlog.record":{"params":{"logs":"required list\u003cdict\u003e"}},"conduit.connect":{"params":{"client":"required string","clientVersion":"required int","clientDescription":"optional string","user":"optional string","authToken":"optional int","authSignature":"optional string","host":"required string"}},"conduit.getcertificate":{"params":{"token":"required string","host":"required string"}},"conduit.ping":{"params":[]},"conduit.query":{"params":[]},"conpherence.createthread":{"params":{"title":"optional string","message":"required string","participantPHIDs":"required list\u003cphids\u003e"}},"conpherence.querythread":{"params":{"ids":"optional array\u003cint\u003e","phids":"optional array\u003cphids\u003e","limit":"optional int","offset":"optional int"}},"conpherence.querytransaction":{"params":{"threadID":"optional int","threadPHID":"optional phid","limit":"optional int","offset":"optional int"}},"conpherence.updatethread":{"params":{"id":"optional int","phid":"optional phid","title":"optional string","message":"optional string","addParticipantPHIDs":"optional list\u003cphids\u003e","removeParticipantPHID":"optional phid"}},"differential.createinline":{"params":{"revisionID":"optional revisionid","diffID":"optional diffid","filePath":"required string","isNewFile":"required bool","lineNumber":"required int","lineLength":"optional int","content":"required string"}},"differential.createrawdiff":{"params":{"diff":"required string"}},"differential.getrevisioncomments":{"params":{"ids":"required list\u003cint\u003e","inlines":"optional bool"}},"differential.close":{"params":{"revisionID":"required int"}},"differential.createcomment":{"params":{"revision_id":"required revisionid","message":"optional string","action":"optional string","silent":"optional bool","attach_inlines":"optional bool"}},"differential.creatediff":{"params":{"changes":"required list\u003cdict\u003e","sourceMachine":"required string","sourcePath":"required string","branch":"required string","bookmark":"optional string","sourceControlSystem":"required enum\u003csvn, git\u003e","sourceControlPath":"required string","sourceControlBaseRevision":"required string","parentRevisionID":"optional revisionid","creationMethod":"optional string","authorPHID":"optional phid","arcanistProject":"optional string","repositoryUUID":"optional string","lintStatus":"required enum\u003cnone, skip, okay, warn, fail, postponed\u003e","unitStatus":"required enum\u003cnone, skip, okay, warn, fail, postponed\u003e"}},"differential.createrevision":{"params":{"user":"ignored","diffid":"required diffid","fields":"required dict"}},"differential.find":{"params":{"query":"required enum\u003copen, committable, revision-ids, phids\u003e","guids":"required nonempty list\u003cguids\u003e"}},"differential.finishpostponedlinters":{"params":{"diffID":"required diffID","linters":"required dict"}},"differential.getalldiffs":{"params":{"revision_ids":"required list\u003cint\u003e"}},"differential.getcommitmessage":{"params":{"revision_id":"optional revision_id","fields":"optional dict\u003cstring, wild\u003e","edit":"optional enum\u003c\"edit\", \"create\"\u003e"}},"differential.getcommitpaths":{"params":{"revision_id":"required int"}},"differential.getdiff":{"params":{"revision_id":"optional id","diff_id":"optional id"}},"differential.getrawdiff":{"params":{"diffID":"required diffID"}},"differential.getrevision":{"params":{"revision_id":"required id"}},"differential.markcommitted":{"params":{"revision_id":"required revision_id"}},"differential.parsecommitmessage":{"params":{"corpus":"required string","partial":"optional bool"}},"differential.query":{"params":{"authors":"optional list\u003cphid\u003e","ccs":"optional list\u003cphid\u003e","reviewers":"optional list\u003cphid\u003e","paths":"optional list\u003cpair\u003ccallsign, path\u003e\u003e","commitHashes":"optional list\u003cpair\u003cenum\u003cgtcm, gttr, hgcm\u003e, string\u003e\u003e","status":"optional enum\u003cstatus-any, status-open, status-accepted, status-closed\u003e","order":"optional enum\u003corder-modified, order-created\u003e","limit":"optional uint","offset":"optional uint","ids":"optional list\u003cuint\u003e","phids":"optional list\u003cphid\u003e","subscribers":"optional list\u003cphid\u003e","responsibleUsers":"optional list\u003cphid\u003e","branches":"optional list\u003cstring\u003e","arcanistProjects":"optional list\u003cstring\u003e"}},"differential.querydiffs":{"params":{"ids":"optional list\u003cuint\u003e","revisionIDs":"optional list\u003cuint\u003e"}},"differential.setdiffproperty":{"params":{"diff_id":"required diff_id","name":"required string","data":"required string"}},"differential.updaterevision":{"params":{"id":"required revisionid","diffid":"required diffid","fields":"required dict","message":"required string"}},"differential.updateunitresults":{"params":{"diff_id":"required diff_id","file":"required string","name":"required string","link":"optional string","result":"required string","message":"required string","coverage":"optional map\u003cstring, string\u003e"}},"diffusion.branchquery":{"params":{"limit":"optional int","offset":"optional int","callsign":"required string","branch":"optional string"}},"diffusion.browsequery":{"params":{"path":"optional string","commit":"optional string","needValidityOnly":"optional bool","callsign":"required string","branch":"optional string"}},"diffusion.commitbranchesquery":{"params":{"commit":"required string","callsign":"required string","branch":"optional string"}},"diffusion.commitparentsquery":{"params":{"commit":"required string","callsign":"required string","branch":"optional string"}},"diffusion.diffquery":{"params":{"path":"required string","commit":"optional string","callsign":"required string","branch":"optional string"}},"diffusion.existsquery":{"params":{"commit":"required string","callsign":"required string","branch":"optional string"}},"diffusion.filecontentquery":{"params":{"path":"required string","commit":"required string","needsBlame":"optional bool","callsign":"required string","branch":"optional string"}},"diffusion.historyquery":{"params":{"commit":"required string","path":"required string","offset":"required int","limit":"required int","needDirectChanges":"optional bool","needChildChanges":"optional bool","callsign":"required string","branch":"optional string"}},"diffusion.lastmodifiedquery":{"params":{"commit":"required string","path":"required string","callsign":"required string","branch":"optional string"}},"diffusion.mergedcommitsquery":{"params":{"commit":"required string","limit":"optional int","callsign":"required string","branch":"optional string"}},"diffusion.rawdiffquery":{"params":{"commit":"required string","path":"optional string","timeout":"optional int","linesOfContext":"optional int","againstCommit":"optional string","callsign":"required string","branch":"optional string"}},"diffusion.readmequery":{"params":{"paths":"required array \u003cstring\u003e","callsign":"required string","branch":"optional string"}},"diffusion.refsquery":{"params":{"commit":"required string","callsign":"required string","branch":"optional string"}},"diffusion.resolverefs":{"params":{"refs":"required list\u003cstring\u003e","callsign":"required string","branch":"optional string"}},"diffusion.searchquery":{"params":{"path":"required string","stableCommitName":"required string","grep":"required string","limit":"optional int","offset":"optional int","callsign":"required string","branch":"optional string"}},"diffusion.tagsquery":{"params":{"names":"optional list\u003cstring\u003e","commit":"optional string","needMessages":"optional bool","offset":"optional int","limit":"optional int","callsign":"required string","branch":"optional string"}},"diffusion.createcomment":{"params":{"phid":"required string","action":"optional string","message":"required string"}},"diffusion.findsymbols":{"params":{"name":"optional string","namePrefix":"optional string","context":"optional string","language":"optional string","type":"optional string"}},"diffusion.getcommits":{"params":{"commits":"required list\u003cstring\u003e"}},"diffusion.getlintmessages":{"params":{"arcanistProject":"required string","branch":"optional string","commit":"optional string","files":"required list\u003cstring\u003e"}},"diffusion.getrecentcommitsbypath":{"params":{"callsign":"required string","path":"required string","branch":"optional string","limit":"optional int"}},"diffusion.looksoon":{"params":{"callsigns":"required list\u003cstring\u003e","urgency":"optional string"}},"feed.publish":{"params":{"type":"required string","data":"required dict","time":"optional int"}},"feed.query":{"params":{"filterPHIDs":"optional list \u003cphid\u003e","limit":"optional int (default 100)","after":"optional int","before":"optional int","view":"optional string (data, html, html-summary, text)"}},"file.download":{"params":{"phid":"required phid"}},"file.info":{"params":{"phid":"optional phid","id":"optional id"}},"file.upload":{"params":{"data_base64":"required nonempty base64-bytes","name":"optional string"}},"file.uploadhash":{"params":{"hash":"required nonempty string","name":"required nonempty string"}},"flag.delete":{"params":{"id":"optional id","objectPHID":"optional phid"}},"flag.edit":{"params":{"objectPHID":"required phid","color":"optional int","note":"optional string"}},"flag.query":{"params":{"ownerPHIDs":"optional list\u003cphid\u003e","types":"optional list\u003ctype\u003e","objectPHIDs":"optional list\u003cphid\u003e","offset":"optional int","limit":"optional int (default = 100)"}},"macro.creatememe":{"params":{"macroName":"string","upperText":"optional string","lowerText":"optional string"}},"macro.query":{"params":{"authorPHIDs":"optional list\u003cphid\u003e","phids":"optional list\u003cphid\u003e","ids":"optional list\u003cid\u003e","names":"optional list\u003cstring\u003e","nameLike":"optional string"}},"maniphest.createtask":{"params":{"title":"required string","description":"optional string","ownerPHID":"optional phid","ccPHIDs":"optional list\u003cphid\u003e","priority":"optional int","projectPHIDs":"optional list\u003cphid\u003e","filePHIDs":"optional list\u003cphid\u003e","auxiliary":"optional dict"}},"maniphest.gettasktransactions":{"params":{"ids":"required list\u003cint\u003e"}},"maniphest.info":{"params":{"task_id":"required id"}},"maniphest.query":{"params":{"ids":"optional list\u003cuint\u003e","phids":"optional list\u003cphid\u003e","ownerPHIDs":"optional list\u003cphid\u003e","authorPHIDs":"optional list\u003cphid\u003e","projectPHIDs":"optional list\u003cphid\u003e","ccPHIDs":"optional list\u003cphid\u003e","fullText":"optional string","status":"optional enum\u003cstatus-any, status-open, status-closed, status-resolved, status-wontfix, status-invalid, status-spite, status-duplicate\u003e","order":"optional enum\u003corder-priority, order-created, order-modified\u003e","limit":"optional int","offset":"optional int"}},"maniphest.find":{"params":{"ids":"optional list\u003cuint\u003e","phids":"optional list\u003cphid\u003e","ownerPHIDs":"optional list\u003cphid\u003e","authorPHIDs":"optional list\u003cphid\u003e","projectPHIDs":"optional list\u003cphid\u003e","ccPHIDs":"optional list\u003cphid\u003e","fullText":"optional string","status":"optional enum\u003cstatus-any, status-open, status-closed, status-resolved, status-wontfix, status-invalid, status-spite, status-duplicate\u003e","order":"optional enum\u003corder-priority, order-created, order-modified\u003e","limit":"optional int","offset":"optional int"}},"maniphest.update":{"params":{"id":"optional int","phid":"optional int","title":"optional string","description":"optional string","ownerPHID":"optional phid","ccPHIDs":"optional list\u003cphid\u003e","priority":"optional int","projectPHIDs":"optional list\u003cphid\u003e","filePHIDs":"optional list\u003cphid\u003e","auxiliary":"optional dict","status":"optional int","comments":"optional string"}},"owners.query":{"params":{"userOwner":"optional string","projectOwner":"optional string","userAffiliated":"optional string","repositoryCallsign":"optional string","path":"optional string"}},"paste.create":{"params":{"content":"required string","title":"optional string","language":"optional string"}},"paste.info":{"params":{"paste_id":"required id"}},"paste.query":{"params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","authorPHIDs":"optional list\u003cphid\u003e","after":"optional int","limit":"optional int, default = 100"}},"phame.query":{"params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","after":"optional int","before":"optional int","limit":"optional int"}},"phame.queryposts":{"params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","blogPHIDs":"optional list\u003cphid\u003e","bloggerPHIDs":"optional list\u003cphid\u003e","phameTitles":"optional list\u003cstring\u003e","published":"optional bool","publishedAfter":"optional date","before":"optional int","after":"optional int","limit":"optional int"}},"phid.info":{"params":{"phid":"required phid"}},"phid.lookup":{"params":{"names":"required list\u003cstring\u003e"}},"phid.query":{"params":{"phids":"required list\u003cphid\u003e"}},"phpast.getast":{"params":{"code":"required string"}},"phpast.version":{"params":[]},"phriction.edit":{"params":{"slug":"required string","title":"optional string","content":"optional string","description":"optional string"}},"phriction.history":{"params":{"slug":"required string"}},"phriction.info":{"params":{"slug":"required string"}},"project.query":{"params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","status":"optional enum\u003cstatus-any, status-open, status-closed, status-active, status-archived\u003e","members":"optional list\u003cphid\u003e","limit":"optional int","offset":"optional int"}},"releeph.getbranches":{"params":[]},"releeph.projectinfo":{"params":{"arcProjectName":"optional string"}},"releeph.queryrequests":{"params":{"revisionPHIDs":"optional list\u003cphid\u003e","requestedCommitPHIDs":"optional list\u003cphid\u003e"}},"releeph.request":{"params":{"branchPHID":"required string","things":"required string","fields":"dict\u003cstring, string\u003e"}},"releephwork.canpush":{"params":{"projectPHID":"required string"}},"releephwork.getauthorinfo":{"params":{"userPHID":"required string","vcsType":"required string"}},"releephwork.getbranch":{"params":{"branchPHID":"required string"}},"releephwork.getbranchcommitmessage":{"params":{"branchPHID":"required string"}},"releephwork.getcommitmessage":{"params":{"requestPHID":"required string","action":"required enum\u003c\"pick\", \"revert\"\u003e"}},"releephwork.getorigcommitmessage":{"params":{"commitPHID":"required string"}},"releephwork.nextrequest":{"params":{"branchPHID":"required int","seen":"required list\u003cstring, bool\u003e"}},"releephwork.record":{"params":{"requestPHID":"required string","action":"required enum\u003c\"pick\", \"revert\"\u003e","commitIdentifier":"required string"}},"releephwork.recordpickstatus":{"params":{"requestPHID":"required string","action":"required enum\u003c\"pick\", \"revert\"\u003e","ok":"required bool","dryRun":"optional bool","details":"optional dict\u003cstring, wild\u003e"}},"remarkup.process":{"params":{"context":"required enum\u003cphriction, maniphest, differential, phame, feed, diffusion\u003e","contents":"required list\u003cstring\u003e"}},"repository.create":{"params":{"name":"required string","vcs":"required enum\u003cgit, hg, svn\u003e","callsign":"required string","description":"optional string","encoding":"optional string","tracking":"optional bool","uri":"optional string","sshUser":"optional string","sshKey":"optional string","sshKeyFile":"optional string","httpUser":"optional string","httpPassword":"optional string","localPath":"optional string","svnSubpath":"optional string","branchFilter":"optional list\u003cstring\u003e","closeCommitsFilter":"optional list\u003cstring\u003e","pullFrequency":"optional int","defaultBranch":"optional string","heraldEnabled":"optional bool, default = true","autocloseEnabled":"optional bool, default = true","svnUUID":"optional string"}},"repository.query":{"params":[]},"slowvote.info":{"params":{"poll_id":"required id"}},"token.give":{"params":{"tokenPHID":"phid|null","objectPHID":"phid"}},"token.given":{"params":{"authorPHIDs":"list\u003cphid\u003e","objectPHIDs":"list\u003cphid\u003e","tokenPHIDs":"list\u003cphid\u003e"}},"token.query":{"params":[]},"user.addstatus":{"params":{"fromEpoch":"required int","toEpoch":"required int","status":"required enum\u003caway, sporadic\u003e","description":"optional string"}},"user.disable":{"params":{"phids":"required list\u003cphid\u003e"}},"user.enable":{"params":{"phids":"required list\u003cphid\u003e"}},"user.find":{"params":{"aliases":"required nonempty list\u003cstring\u003e"}},"user.info":{"params":{"phid":"required phid"}},"user.query":{"params":{"usernames":"optional list\u003cstring\u003e","emails":"optional list\u003cstring\u003e","realnames":"optional list\u003cstring\u003e","phids":"optional list\u003cphid\u003e","ids":"optional list\u003cuint\u003e","offset":"optional int","limit":"optional int (default = 100)"}},"user.removestatus":{"params":{"fromEpoch":"required int","toEpoch":"required int"}},"user.whoami":{"params":[]}} +{"almanac.device.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"almanac.service.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"audit.query":{"description":"Query audit requests.","params":{"auditorPHIDs":"optional list\u003cphid\u003e","commitPHIDs":"optional list\u003cphid\u003e","status":"optional string-constant\u003c\"audit-status-any\", \"audit-status-open\", \"audit-status-concern\", \"audit-status-accepted\", \"audit-status-partial\"\u003e (default = \"audit-status-any\")","offset":"optional int","limit":"optional int (default = 100)"},"return":"list\u003cdict\u003e"},"auth.logout":{"description":"Terminate all web login sessions. If called via OAuth, also terminate the current OAuth token.\n\nWARNING: This method does what it claims on the label. If you call this method via the test console in the web UI, it will log you out!","params":[],"return":"void"},"auth.querypublickeys":{"description":"Query public keys.","params":{"ids":"optional list\u003cid\u003e","phids":"optional list\u003cphid\u003e","objectPHIDs":"optional list\u003cphid\u003e","keys":"optional list\u003cstring\u003e","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"result-set"},"badges.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"badges.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"conduit.connect":{"description":"Connect a session-based client.","params":{"client":"required string","clientVersion":"required int","clientDescription":"optional string","user":"optional string","authToken":"optional int","authSignature":"optional string","host":"deprecated"},"return":"dict\u003cstring, any\u003e"},"conduit.getcapabilities":{"description":"List capabilities, wire formats, and authentication protocols available on this server.","params":[],"return":"dict\u003cstring, any\u003e"},"conduit.getcertificate":{"description":"Retrieve certificate information for a user.","params":{"token":"required string","host":"required string"},"return":"dict\u003cstring, any\u003e"},"conduit.ping":{"description":"Basic ping for monitoring or a health-check.","params":[],"return":"string"},"conduit.query":{"description":"Returns the parameters of the Conduit methods.","params":[],"return":"dict\u003cdict\u003e"},"conpherence.createthread":{"description":"Create a new conpherence thread.","params":{"title":"optional string","message":"required string","participantPHIDs":"required list\u003cphids\u003e"},"return":"nonempty dict"},"conpherence.querythread":{"description":"Query for Conpherence threads for the logged in user. You can query by IDs or PHIDs for specific Conpherence threads. Otherwise, specify limit and offset to query the most recently updated Conpherences for the logged in user.","params":{"ids":"optional array\u003cint\u003e","phids":"optional array\u003cphids\u003e","limit":"optional int","offset":"optional int"},"return":"nonempty dict"},"conpherence.querytransaction":{"description":"Query for transactions for the logged in user within a specific Conpherence room. You can specify the room by ID or PHID. Otherwise, specify limit and offset to query the most recent transactions within the Conpherence room for the logged in user.","params":{"roomID":"optional int","roomPHID":"optional phid","limit":"optional int","offset":"optional int"},"return":"nonempty dict"},"conpherence.updatethread":{"description":"Update an existing conpherence room.","params":{"id":"optional int","phid":"optional phid","title":"optional string","message":"optional string","addParticipantPHIDs":"optional list\u003cphids\u003e","removeParticipantPHID":"optional phid"},"return":"bool"},"dashboard.panel.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"differential.close":{"description":"Close a Differential revision.","params":{"revisionID":"required int"},"return":"void"},"differential.createcomment":{"description":"Add a comment to a Differential revision.","params":{"revision_id":"required revisionid","message":"optional string","action":"optional string","silent":"optional bool","attach_inlines":"optional bool"},"return":"nonempty dict"},"differential.creatediff":{"description":"Create a new Differential diff.","params":{"changes":"required list\u003cdict\u003e","sourceMachine":"required string","sourcePath":"required string","branch":"required string","bookmark":"optional string","sourceControlSystem":"required string-constant\u003c\"svn\", \"git\", \"hg\"\u003e","sourceControlPath":"required string","sourceControlBaseRevision":"required string","creationMethod":"optional string","lintStatus":"required string-constant\u003c\"none\", \"skip\", \"okay\", \"warn\", \"fail\"\u003e","unitStatus":"required string-constant\u003c\"none\", \"skip\", \"okay\", \"warn\", \"fail\"\u003e","repositoryPHID":"optional phid","parentRevisionID":"deprecated","authorPHID":"deprecated","repositoryUUID":"deprecated"},"return":"nonempty dict"},"differential.createinline":{"description":"Add an inline comment to a Differential revision.","params":{"revisionID":"optional revisionid","diffID":"optional diffid","filePath":"required string","isNewFile":"required bool","lineNumber":"required int","lineLength":"optional int","content":"required string"},"return":"nonempty dict"},"differential.createrawdiff":{"description":"Create a new Differential diff from a raw diff source.","params":{"diff":"required string","repositoryPHID":"optional string","viewPolicy":"optional string"},"return":"nonempty dict"},"differential.createrevision":{"description":"Create a new Differential revision.","params":{"user":"ignored","diffid":"required diffid","fields":"required dict"},"return":"nonempty dict"},"differential.getcommitmessage":{"description":"Retrieve Differential commit messages or message templates.","params":{"revision_id":"optional revision_id","fields":"optional dict\u003cstring, wild\u003e","edit":"optional string-constant\u003c\"edit\", \"create\"\u003e"},"return":"nonempty string"},"differential.getcommitpaths":{"description":"Query which paths should be included when committing a Differential revision.","params":{"revision_id":"required int"},"return":"nonempty list\u003cstring\u003e"},"differential.getrawdiff":{"description":"Retrieve a raw diff","params":{"diffID":"required diffID"},"return":"nonempty string"},"differential.parsecommitmessage":{"description":"Parse commit messages for Differential fields.","params":{"corpus":"required string","partial":"optional bool"},"return":"nonempty dict"},"differential.query":{"description":"Query Differential revisions which match certain criteria.","params":{"authors":"optional list\u003cphid\u003e","ccs":"optional list\u003cphid\u003e","reviewers":"optional list\u003cphid\u003e","paths":"optional list\u003cpair\u003ccallsign, path\u003e\u003e","commitHashes":"optional list\u003cpair\u003cstring-constant\u003c\"gtcm\", \"gttr\", \"hgcm\"\u003e, string\u003e\u003e","status":"optional string-constant\u003c\"status-any\", \"status-open\", \"status-accepted\", \"status-closed\"\u003e","order":"optional string-constant\u003c\"order-modified\", \"order-created\"\u003e","limit":"optional uint","offset":"optional uint","ids":"optional list\u003cuint\u003e","phids":"optional list\u003cphid\u003e","subscribers":"optional list\u003cphid\u003e","responsibleUsers":"optional list\u003cphid\u003e","branches":"optional list\u003cstring\u003e"},"return":"list\u003cdict\u003e"},"differential.querydiffs":{"description":"Query differential diffs which match certain criteria.","params":{"ids":"optional list\u003cuint\u003e","revisionIDs":"optional list\u003cuint\u003e"},"return":"list\u003cdict\u003e"},"differential.setdiffproperty":{"description":"Attach properties to Differential diffs.","params":{"diff_id":"required diff_id","name":"required string","data":"required string"},"return":"void"},"differential.updaterevision":{"description":"Update a Differential revision.","params":{"id":"required revisionid","diffid":"required diffid","fields":"required dict","message":"required string"},"return":"nonempty dict"},"differential.revision.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"differential.find":{"description":"Query Differential revisions which match certain criteria.","params":{"query":"required string-constant\u003c\"open\", \"committable\", \"revision-ids\", \"phids\"\u003e","guids":"required nonempty list\u003cguids\u003e"},"return":"nonempty list\u003cdict\u003e"},"differential.getalldiffs":{"description":"Load all diffs for given revisions from Differential.","params":{"revision_ids":"required list\u003cint\u003e"},"return":"dict"},"differential.getdiff":{"description":"Load the content of a diff from Differential by revision ID or diff ID.","params":{"revision_id":"optional id","diff_id":"optional id"},"return":"nonempty dict"},"differential.getrevision":{"description":"Load the content of a revision from Differential.","params":{"revision_id":"required id"},"return":"nonempty dict"},"differential.getrevisioncomments":{"description":"Retrieve Differential Revision Comments.","params":{"ids":"required list\u003cint\u003e","inlines":"optional bool (deprecated)"},"return":"nonempty list\u003cdict\u003cstring, wild\u003e\u003e"},"diffusion.commit.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https://secure.phabricator.com/diviner/find/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"after":"optional string","attachments":"optional map","before":"optional string","constraints":"optional map","limit":"optional int (default = 100)","order":"optional order","queryKey":"optional string"},"return":"list"},"diffusion.findsymbols":{"description":"Retrieve Diffusion symbol information.","params":{"name":"optional string","namePrefix":"optional string","context":"optional string","language":"optional string","type":"optional string","repositoryPHID":"optional string"},"return":"nonempty list\u003cdict\u003e"},"diffusion.getrecentcommitsbypath":{"description":"Get commit identifiers for recent commits affecting a given path.","params":{"callsign":"required string","path":"required string","branch":"optional string","limit":"optional int"},"return":"nonempty list\u003cstring\u003e"},"diffusion.querycommits":{"description":"Retrieve information about commits.","params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","names":"optional list\u003cstring\u003e","repositoryPHID":"optional phid","needMessages":"optional bool","bypassCache":"optional bool","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, dict\u003e"},"diffusion.blame":{"description":"Get blame information for a list of paths.","params":{"paths":"required list\u003cstring\u003e","commit":"required string","timeout":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"map\u003cstring, wild\u003e"},"diffusion.branchquery":{"description":"Determine what branches exist for a repository.","params":{"closed":"optional bool","limit":"optional int","offset":"optional int","contains":"optional string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"list\u003cdict\u003e"},"diffusion.browsequery":{"description":"File(s) information for a repository at an (optional) path and (optional) commit.","params":{"path":"optional string","commit":"optional string","needValidityOnly":"optional bool","limit":"optional int","offset":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.commitparentsquery":{"description":"Get the commit identifiers for a commit's parent or parents.","params":{"commit":"required string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"list\u003cstring\u003e"},"diffusion.diffquery":{"description":"Get diff information from a repository for a specific path at an (optional) commit.","params":{"path":"required string","commit":"optional string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.existsquery":{"description":"Determine if code exists in a version control system.","params":{"commit":"required string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"bool"},"diffusion.filecontentquery":{"description":"Retrieve file content from a repository.","params":{"path":"required string","commit":"required string","timeout":"optional int","byteLimit":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.getlintmessages":{"description":"Get lint messages for existing code.","params":{"repositoryPHID":"required phid","branch":"required string","commit":"optional string","files":"required list\u003cstring\u003e"},"return":"list\u003cdict\u003e"},"diffusion.historyquery":{"description":"Returns history information for a repository at a specific commit and path.","params":{"commit":"required string","path":"required string","offset":"required int","limit":"required int","needDirectChanges":"optional bool","needChildChanges":"optional bool","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.internal.gitrawdiffquery":{"description":"Internal method for getting raw diff information.","params":{"commit":"required string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"string"},"diffusion.lastmodifiedquery":{"description":"Get the commits at which paths were last modified.","params":{"paths":"required map\u003cstring, string\u003e","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"map\u003cstring, string\u003e"},"diffusion.looksoon":{"description":"Advises Phabricator to look for new commits in a repository as soon as possible. This advice is most useful if you have just pushed new commits to that repository.","params":{"callsigns":"optional list\u003cstring\u003e (deprecated)","repositories":"optional list\u003cstring\u003e","urgency":"optional string"},"return":"void"},"diffusion.mergedcommitsquery":{"description":"Merged commit information for a specific commit in a repository.","params":{"commit":"required string","limit":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.querypaths":{"description":"Filename search on a repository.","params":{"path":"required string","commit":"required string","pattern":"optional string","limit":"optional int","offset":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"list\u003cstring\u003e"},"diffusion.rawdiffquery":{"description":"Get raw diff information from a repository for a specific commit at an (optional) path.","params":{"commit":"required string","path":"optional string","timeout":"optional int","byteLimit":"optional int","linesOfContext":"optional int","againstCommit":"optional string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"string"},"diffusion.refsquery":{"description":"Query a git repository for ref information at a specific commit.","params":{"commit":"required string","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.repository.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"diffusion.repository.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"diffusion.resolverefs":{"description":"Resolve references into stable, canonical identifiers.","params":{"refs":"required list\u003cstring\u003e","types":"optional list\u003cstring\u003e","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"dict\u003cstring, list\u003cdict\u003cstring, wild\u003e\u003e\u003e"},"diffusion.searchquery":{"description":"Search (grep) a repository at a specific path and commit.","params":{"path":"required string","commit":"optional string","grep":"required string","limit":"optional int","offset":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.tagsquery":{"description":"Retrieve information about tags in a repository.","params":{"names":"optional list\u003cstring\u003e","commit":"optional string","needMessages":"optional bool","offset":"optional int","limit":"optional int","callsign":"optional string (deprecated)","repository":"optional string","branch":"optional string"},"return":"array"},"diffusion.updatecoverage":{"description":"Publish coverage information for a repository.","params":{"repositoryPHID":"required phid","branch":"required string","commit":"required string","coverage":"required map\u003cstring, string\u003e","mode":"optional string-constant\u003c\"overwrite\", \"update\"\u003e"},"return":"void"},"diffusion.uri.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"diffusion.createcomment":{"description":"Add a comment to a Diffusion commit. By specifying an action of \"concern\", \"accept\", \"resign\", or \"close\", auditing actions can be triggered. Defaults to \"comment\".","params":{"phid":"required string","action":"optional string","message":"required string","silent":"optional bool"},"return":"bool"},"feed.publish":{"description":"Publish a story to the feed.","params":{"type":"required string","data":"required dict","time":"optional int"},"return":"nonempty phid"},"feed.query":{"description":"Query the feed for stories","params":{"filterPHIDs":"optional list \u003cphid\u003e","limit":"optional int (default 100)","after":"optional int","before":"optional int","view":"optional string (data, html, html-summary, text)"},"return":"nonempty dict"},"file.allocate":{"description":"Prepare to upload a file.","params":{"name":"string","contentLength":"int","contentHash":"optional string","viewPolicy":"optional string","deleteAfterEpoch":"optional int"},"return":"map\u003cstring, wild\u003e"},"file.download":{"description":"Download a file from the server.","params":{"phid":"required phid"},"return":"nonempty base64-bytes"},"file.info":{"description":"Get information about a file.","params":{"phid":"optional phid","id":"optional id"},"return":"nonempty dict"},"file.querychunks":{"description":"Get information about file chunks.","params":{"filePHID":"phid"},"return":"list\u003cwild\u003e"},"file.upload":{"description":"Upload a file to the server.","params":{"data_base64":"required nonempty base64-bytes","name":"optional string","viewPolicy":"optional valid policy string or \u003cphid\u003e","canCDN":"optional bool"},"return":"nonempty guid"},"file.uploadchunk":{"description":"Upload a chunk of file data to the server.","params":{"filePHID":"phid","byteStart":"int","data":"string","dataEncoding":"string"},"return":"void"},"file.uploadhash":{"description":"Upload a file to the server using content hash.","params":{"hash":"required nonempty string","name":"required nonempty string"},"return":"phid or null"},"flag.delete":{"description":"Clear a flag.","params":{"id":"optional id","objectPHID":"optional phid"},"return":"dict | null"},"flag.edit":{"description":"Create or modify a flag.","params":{"objectPHID":"required phid","color":"optional int","note":"optional string"},"return":"dict"},"flag.query":{"description":"Query flag markers.","params":{"ownerPHIDs":"optional list\u003cphid\u003e","types":"optional list\u003ctype\u003e","objectPHIDs":"optional list\u003cphid\u003e","offset":"optional int","limit":"optional int (default = 100)"},"return":"list\u003cdict\u003e"},"harbormaster.createartifact":{"description":"Use this method to attach artifacts to build targets while running builds. Artifacts can be used to carry data through a complex build workflow, provide extra information to users, or store build results.\n\nWhen creating an artifact, you will choose an `artifactType` from this table. These types of artifacts are supported:\n| Artifact Type | Name | Summary |\n|-------------|--------------|--------------|\n| `host` | **Drydock Host** | References a host lease from Drydock. |\n| `working-copy` | **Drydock Working Copy** | References a working copy lease from Drydock. |\n| `file` | **File** | Stores a reference to file data which has been uploaded to Phabricator. |\n| `uri` | **URI** | Stores a URI. |\n\nEach artifact also needs an `artifactKey`, which names the artifact. Finally, you will provide some `artifactData` to fill in the content of the artifact. The data you provide depends on what type of artifact you are creating.\nDrydock Host\n--------------------------\n\nReferences a host lease from Drydock.\n\nCreate an artifact of this type by passing `host` as the `artifactType`. When creating an artifact of this type, provide these parameters as a dictionary to `artifactData`:\n| Key | Type | Description |\n|-------------|--------------|--------------|\n| `drydockLeasePHID` | \/\/string\/\/ | Drydock working copy lease to create an artifact from. |\nFor example:\n```lang=json\n{\n \"drydockLeasePHID\": \"PHID-DRYL-abcdefghijklmnopqrst\"\n}\n\n```\nDrydock Working Copy\n--------------------------\n\nReferences a working copy lease from Drydock.\n\nCreate an artifact of this type by passing `working-copy` as the `artifactType`. When creating an artifact of this type, provide these parameters as a dictionary to `artifactData`:\n| Key | Type | Description |\n|-------------|--------------|--------------|\n| `drydockLeasePHID` | \/\/string\/\/ | Drydock working copy lease to create an artifact from. |\nFor example:\n```lang=json\n{\n \"drydockLeasePHID\": \"PHID-DRYL-abcdefghijklmnopqrst\"\n}\n\n```\nFile\n--------------------------\n\nStores a reference to file data which has been uploaded to Phabricator.\n\nCreate an artifact of this type by passing `file` as the `artifactType`. When creating an artifact of this type, provide these parameters as a dictionary to `artifactData`:\n| Key | Type | Description |\n|-------------|--------------|--------------|\n| `filePHID` | \/\/string\/\/ | File to create an artifact from. |\nFor example:\n```lang=json\n{\n \"filePHID\": \"PHID-FILE-abcdefghijklmnopqrst\"\n}\n\n```\nURI\n--------------------------\n\nStores a URI.\n\nWith `ui.external`, you can use this artifact type to add links to build results in an external build system.\n\nCreate an artifact of this type by passing `uri` as the `artifactType`. When creating an artifact of this type, provide these parameters as a dictionary to `artifactData`:\n| Key | Type | Description |\n|-------------|--------------|--------------|\n| `uri` | \/\/string\/\/ | The URI to store. |\n| `name` | \/\/optional string\/\/ | Optional label for this URI. |\n| `ui.external` | \/\/optional bool\/\/ | If true, display this URI in the UI as an link to additional build details in an external build system. |\nFor example:\n```lang=json\n{\n \"uri\": \"https:\/\/buildserver.mycompany.com\/build\/123\/\",\n \"name\": \"View External Build Results\",\n \"ui.external\": true\n}\n\n```","params":{"buildTargetPHID":"phid","artifactKey":"string","artifactType":"string","artifactData":"map\u003cstring, wild\u003e"},"return":"wild"},"harbormaster.queryautotargets":{"description":"Load or create build autotargets.","params":{"objectPHID":"phid","targetKeys":"list\u003cstring\u003e"},"return":"map\u003cstring, phid\u003e"},"harbormaster.querybuildables":{"description":"Query Harbormaster buildables.","params":{"ids":"optional list\u003cid\u003e","phids":"optional list\u003cphid\u003e","buildablePHIDs":"optional list\u003cphid\u003e","containerPHIDs":"optional list\u003cphid\u003e","manualBuildables":"optional bool","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"wild"},"harbormaster.querybuilds":{"description":"Query Harbormaster builds.","params":{"ids":"optional list\u003cid\u003e","phids":"optional list\u003cphid\u003e","buildStatuses":"optional list\u003cstring\u003e","buildablePHIDs":"optional list\u003cphid\u003e","buildPlanPHIDs":"optional list\u003cphid\u003e","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"wild"},"harbormaster.sendmessage":{"description":"Send a message about the status of a build target to Harbormaster, notifying the application of build results in an external system.\n\nSending Messages\n================\nIf you run external builds, you can use this method to publish build results back into Harbormaster after the external system finishes work or as it makes progress.\n\nThe simplest way to use this method is to call it once after the build finishes with a `pass` or `fail` message. This will record the build result, and continue the next step in the build if the build was waiting for a result.\n\nWhen you send a status message about a build target, you can optionally include detailed `lint` or `unit` results alongside the message. See below for details.\n\nIf you want to report intermediate results but a build hasn't completed yet, you can use the `work` message. This message doesn't have any direct effects, but allows you to send additional data to update the progress of the build target. The target will continue waiting for a completion message, but the UI will update to show the progress which has been made.\n\nMessage Types\n=============\nWhen you send Harbormaster a message, you must include a `type`, which describes the overall state of the build. For example, use `pass` to tell Harbomaster that a build completed successfully.\n\nSupported message types are:\n\n| Type | Description |\n|--------------|--------------|\n| `pass` | Report that the target is complete, and the target has passed. |\n| `fail` | Report that the target is complete, and the target has failed. |\n| `work` | Report that work on the target is ongoing. This message can be used to report partial results during a build. |\n\nUnit Results\n============\nYou can report test results alongside a message. The simplest way to do this is to report all the results alongside a `pass` or `fail` message, but you can also send a `work` message to report intermediate results.\n\nTo provide unit test results, pass a list of results in the `unit` parameter. Each result shoud be a dictionary with these keys:\n\n| Key | Type | Description |\n|-------------|--------------|--------------|\n| `name` | \/\/string\/\/ | Short test name, like \"ExampleTest\". |\n| `result` | \/\/string\/\/ | Result of the test. |\n| `namespace` | \/\/optional string\/\/ | Optional namespace for this test. This is organizational and is often a class or module name, like \"ExampleTestCase\". |\n| `engine` | \/\/optional string\/\/ | Test engine running the test, like \"JavascriptTestEngine\". This primarily prevents collisions between tests with the same name in different test suites (for example, a Javascript test and a Python test). |\n| `duration` | \/\/optional float or int\/\/ | Runtime duration of the test, in seconds. |\n| `path` | \/\/optional string\/\/ | Path to the file where the test is declared, relative to the project root. |\n| `coverage` | \/\/optional map\u003cstring, wild\u003e\/\/ | Coverage information for this test. |\n| `details` | \/\/optional string\/\/ | Additional human-readable information about the failure. |\n\nThe `result` parameter recognizes these test results:\n\n| Key | Name | Description |\n|-------------|--------------|--------------|\n| `pass` | **Pass** | The test passed. |\n| `fail` | **Fail** | The test failed. |\n| `skip` | **Skip** | The test was not executed. |\n| `broken` | **Broken** | The test failed in an abnormal or severe way. For example, the harness crashed instead of reporting a failure. |\n| `unsound` | **Unsound** | The test failed, but this change is probably not what broke it. For example, it might have already been failing. |\n\nThis is a simple, valid value for the `unit` parameter. It reports one passing test and one failing test:\n\n\n\n```lang=json\n[\n {\n \"name\": \"PassingTest\",\n \"result\": \"pass\"\n },\n {\n \"name\": \"FailingTest\",\n \"result\": \"fail\"\n }\n]\n```\n\nLint Results\n============\nLike unit test results, you can report lint results alongside a message. The `lint` parameter should contain results as a list of dictionaries with these keys:\n\n| Key | Type | Description |\n|-------------|--------------|--------------|\n| `name` | \/\/string\/\/ | Short message name, like \"Syntax Error\". |\n| `code` | \/\/string\/\/ | Lint message code identifying the type of message, like \"ERR123\". |\n| `severity` | \/\/string\/\/ | Severity of the message. |\n| `path` | \/\/string\/\/ | Path to the file containing the lint message, from the project root. |\n| `line` | \/\/optional int\/\/ | Line number in the file where the text which triggered the message first appears. The first line of the file is line 1, not line 0. |\n| `char` | \/\/optional int\/\/ | Byte position on the line where the text which triggered the message starts. The first byte on the line is byte 1, not byte 0. This position is byte-based (not character-based) because not all lintable files have a valid character encoding. |\n| `description` | \/\/optional string\/\/ | Long explanation of the lint message. |\n\nThe `severity` parameter recognizes these severity levels:\n\n| Key | Name |\n|-------------|--------------|\n| `advice` | **Advice** |\n| `autofix` | **Auto-Fix** |\n| `warning` | **Warning** |\n| `error` | **Error** |\n| `disabled` | **Disabled** |\n\nThis is a simple, valid value for the `lint` parameter. It reports one error and one warning:\n\n```lang=json\n[\n {\n \"name\": \"Syntax Error\",\n \"code\": \"EXAMPLE1\",\n \"severity\": \"error\",\n \"path\": \"path\/to\/example.c\",\n \"line\": 17,\n \"char\": 3\n },\n {\n \"name\": \"Not A Haiku\",\n \"code\": \"EXAMPLE2\",\n \"severity\": \"error\",\n \"path\": \"path\/to\/source.cpp\",\n \"line\": 23,\n \"char\": 1,\n \"description\": \"This function definition is not a haiku.\"\n }\n]\n```\n\n","params":{"buildTargetPHID":"required phid","type":"required string-constant\u003c\"pass\", \"fail\", \"work\"\u003e","unit":"optional list\u003cwild\u003e","lint":"optional list\u003cwild\u003e"},"return":"void"},"macro.query":{"description":"Retrieve image macro information.","params":{"authorPHIDs":"optional list\u003cphid\u003e","phids":"optional list\u003cphid\u003e","ids":"optional list\u003cid\u003e","names":"optional list\u003cstring\u003e","nameLike":"optional string"},"return":"list\u003cdict\u003e"},"macro.creatememe":{"description":"Generate a meme.","params":{"macroName":"string","upperText":"optional string","lowerText":"optional string"},"return":"string"},"maniphest.createtask":{"description":"Create a new Maniphest task.","params":{"title":"required string","description":"optional string","ownerPHID":"optional phid","viewPolicy":"optional phid or policy string","editPolicy":"optional phid or policy string","ccPHIDs":"optional list\u003cphid\u003e","priority":"optional int","projectPHIDs":"optional list\u003cphid\u003e","auxiliary":"optional dict"},"return":"nonempty dict"},"maniphest.gettasktransactions":{"description":"Retrieve Maniphest task transactions.","params":{"ids":"required list\u003cint\u003e"},"return":"nonempty list\u003cdict\u003cstring, wild\u003e\u003e"},"maniphest.info":{"description":"Retrieve information about a Maniphest task, given its ID.","params":{"task_id":"required id"},"return":"nonempty dict"},"maniphest.query":{"description":"Execute complex searches for Maniphest tasks.","params":{"ids":"optional list\u003cuint\u003e","phids":"optional list\u003cphid\u003e","ownerPHIDs":"optional list\u003cphid\u003e","authorPHIDs":"optional list\u003cphid\u003e","projectPHIDs":"optional list\u003cphid\u003e","ccPHIDs":"optional list\u003cphid\u003e","fullText":"optional string","status":"optional string-constant\u003c\"status-any\", \"status-open\", \"status-closed\", \"status-resolved\", \"status-wontfix\", \"status-invalid\", \"status-spite\", \"status-duplicate\"\u003e","order":"optional string-constant\u003c\"order-priority\", \"order-created\", \"order-modified\"\u003e","limit":"optional int","offset":"optional int"},"return":"list"},"maniphest.querystatuses":{"description":"Retrieve information about possible Maniphest task status values.","params":[],"return":"nonempty dict\u003cstring, wild\u003e"},"maniphest.update":{"description":"Update an existing Maniphest task.","params":{"id":"optional int","phid":"optional int","title":"optional string","description":"optional string","ownerPHID":"optional phid","viewPolicy":"optional phid or policy string","editPolicy":"optional phid or policy string","ccPHIDs":"optional list\u003cphid\u003e","priority":"optional int","projectPHIDs":"optional list\u003cphid\u003e","auxiliary":"optional dict","status":"optional string","comments":"optional string"},"return":"nonempty dict"},"maniphest.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"maniphest.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"owners.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"owners.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"owners.query":{"description":"Query for Owners packages. Obsoleted by \"owners.search\".","params":{"userOwner":"optional string","projectOwner":"optional string","userAffiliated":"optional string","repositoryCallsign":"optional string","path":"optional string"},"return":"dict\u003cphid -\u003e dict of package info\u003e"},"passphrase.query":{"description":"Query credentials.","params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","needSecrets":"optional bool","needPublicKeys":"optional bool","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"list\u003cdict\u003e"},"paste.create":{"description":"Create a new paste.","params":{"content":"required string","title":"optional string","language":"optional string"},"return":"nonempty dict"},"paste.query":{"description":"Query Pastes.","params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","authorPHIDs":"optional list\u003cphid\u003e","after":"optional int","limit":"optional int, default = 100"},"return":"list\u003cdict\u003e"},"paste.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"paste.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"paste.info":{"description":"Retrieve an array of information about a paste.","params":{"paste_id":"required id"},"return":"nonempty dict"},"phame.blog.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"phame.blog.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"phame.post.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"phame.post.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"phid.lookup":{"description":"Look up objects by name.","params":{"names":"required list\u003cstring\u003e"},"return":"nonempty dict\u003cstring, wild\u003e"},"phid.query":{"description":"Retrieve information about arbitrary PHIDs.","params":{"phids":"required list\u003cphid\u003e"},"return":"nonempty dict\u003cstring, wild\u003e"},"phid.info":{"description":"Retrieve information about an arbitrary PHID.","params":{"phid":"required phid"},"return":"nonempty dict\u003cstring, wild\u003e"},"phragment.getpatch":{"description":"Retrieve the patches to apply for a given set of files.","params":{"path":"required string","state":"required dict\u003cstring, string\u003e"},"return":"nonempty dict"},"phragment.queryfragments":{"description":"Query fragments based on their paths.","params":{"paths":"required list\u003cstring\u003e"},"return":"nonempty dict"},"phriction.create":{"description":"Create a Phriction document.","params":{"slug":"required string","title":"required string","content":"required string","description":"optional string"},"return":"nonempty dict"},"phriction.edit":{"description":"Update a Phriction document.","params":{"slug":"required string","title":"optional string","content":"optional string","description":"optional string"},"return":"nonempty dict"},"phriction.history":{"description":"Retrieve history about a Phriction document.","params":{"slug":"required string"},"return":"nonempty list"},"phriction.info":{"description":"Retrieve information about a Phriction document.","params":{"slug":"required string"},"return":"nonempty dict"},"project.create":{"description":"Create a project.","params":{"name":"required string","members":"optional list\u003cphid\u003e","icon":"optional string","color":"optional string","tags":"optional list\u003cstring\u003e"},"return":"dict"},"project.query":{"description":"Execute searches for Projects.","params":{"ids":"optional list\u003cint\u003e","names":"optional list\u003cstring\u003e","phids":"optional list\u003cphid\u003e","slugs":"optional list\u003cstring\u003e","icons":"optional list\u003cstring\u003e","colors":"optional list\u003cstring\u003e","status":"optional string-constant\u003c\"status-any\", \"status-open\", \"status-closed\", \"status-active\", \"status-archived\"\u003e","members":"optional list\u003cphid\u003e","limit":"optional int","offset":"optional int"},"return":"list"},"project.edit":{"description":"This is a standard **ApplicationEditor** method which allows you to create and modify objects by applying transactions. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Edit+Endpoints&type=article&jump=1 | Conduit API: Using Edit Endpoints ]]**.","params":{"transactions":"list\u003cmap\u003cstring, wild\u003e\u003e","objectIdentifier":"optional id|phid|string"},"return":"map\u003cstring, wild\u003e"},"project.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"releeph.getbranches":{"description":"Return information about all active Releeph branches.","params":[],"return":"nonempty list\u003cdict\u003cstring, wild\u003e\u003e"},"releeph.querybranches":{"description":"Query information about Releeph branches.","params":{"ids":"optional list\u003cid\u003e","phids":"optional list\u003cphid\u003e","productPHIDs":"optional list\u003cphid\u003e","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"query-results"},"releeph.queryproducts":{"description":"Query information about Releeph products.","params":{"ids":"optional list\u003cid\u003e","phids":"optional list\u003cphid\u003e","repositoryPHIDs":"optional list\u003cphid\u003e","isActive":"optional bool","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"query-results"},"releeph.queryrequests":{"description":"Return information about all Releeph requests linked to the given ids.","params":{"revisionPHIDs":"optional list\u003cphid\u003e","requestedCommitPHIDs":"optional list\u003cphid\u003e"},"return":"dict\u003cstring, wild\u003e"},"releeph.request":{"description":"Request a commit or diff to be picked to a branch.","params":{"branchPHID":"required string","things":"required list\u003cstring\u003e","fields":"dict\u003cstring, string\u003e"},"return":"dict\u003cstring, wild\u003e"},"releephwork.canpush":{"description":"Return whether the conduit user is allowed to push.","params":{"projectPHID":"required string"},"return":"bool"},"releephwork.getauthorinfo":{"description":"Return a string to use as the VCS author.","params":{"userPHID":"required string","vcsType":"required string"},"return":"nonempty string"},"releephwork.getbranch":{"description":"Return information to help checkout \/ cut a Releeph branch.","params":{"branchPHID":"required string"},"return":"dict\u003cstring, wild\u003e"},"releephwork.getbranchcommitmessage":{"description":"Get a commit message for committing a Releeph branch.","params":{"branchPHID":"required string"},"return":"nonempty string"},"releephwork.getcommitmessage":{"description":"Get commit message components for building a ReleephRequest commit message.","params":{"requestPHID":"required string","action":"required string-constant\u003c\"pick\", \"revert\"\u003e"},"return":"dict\u003cstring, string\u003e"},"releephwork.nextrequest":{"description":"Return info required to cut a branch, and pick and revert ReleephRequests.","params":{"branchPHID":"required phid","seen":"required map\u003cstring, bool\u003e"},"return":""},"releephwork.record":{"description":"Record whether we committed a pick or revert to the upstream repository.","params":{"requestPHID":"required string","action":"required string-constant\u003c\"pick\", \"revert\"\u003e","commitIdentifier":"required string"},"return":"void"},"releephwork.recordpickstatus":{"description":"Record whether a pick or revert was successful or not.","params":{"requestPHID":"required string","action":"required string-constant\u003c\"pick\", \"revert\"\u003e","ok":"required bool","dryRun":"optional bool","details":"optional dict\u003cstring, wild\u003e"},"return":""},"remarkup.process":{"description":"Process text through remarkup in Phabricator context.","params":{"context":"required string-constant\u003c\"phriction\", \"maniphest\", \"differential\", \"phame\", \"feed\", \"diffusion\"\u003e","contents":"required list\u003cstring\u003e"},"return":"nonempty dict"},"repository.query":{"description":"Query repositories.","params":{"ids":"optional list\u003cint\u003e","phids":"optional list\u003cphid\u003e","callsigns":"optional list\u003cstring\u003e","vcsTypes":"optional list\u003cstring\u003e","remoteURIs":"optional list\u003cstring\u003e","uuids":"optional list\u003cstring\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"list\u003cdict\u003e"},"slowvote.info":{"description":"Retrieve an array of information about a poll.","params":{"poll_id":"required id"},"return":"nonempty dict"},"token.give":{"description":"Give or change a token.","params":{"tokenPHID":"phid|null","objectPHID":"phid"},"return":"void"},"token.given":{"description":"Query tokens given to objects.","params":{"authorPHIDs":"list\u003cphid\u003e","objectPHIDs":"list\u003cphid\u003e","tokenPHIDs":"list\u003cphid\u003e"},"return":"list\u003cdict\u003e"},"token.query":{"description":"Query tokens.","params":[],"return":"list\u003cdict\u003e"},"user.disable":{"description":"Permanently disable specified users (admin only).","params":{"phids":"required list\u003cphid\u003e"},"return":"void"},"user.enable":{"description":"Re-enable specified users (admin only).","params":{"phids":"required list\u003cphid\u003e"},"return":"void"},"user.query":{"description":"Query users.","params":{"usernames":"optional list\u003cstring\u003e","emails":"optional list\u003cstring\u003e","realnames":"optional list\u003cstring\u003e","phids":"optional list\u003cphid\u003e","ids":"optional list\u003cuint\u003e","offset":"optional int","limit":"optional int (default = 100)"},"return":"list\u003cdict\u003e"},"user.whoami":{"description":"Retrieve information about the logged-in user.","params":[],"return":"nonempty dict\u003cstring, wild\u003e"},"user.search":{"description":"This is a standard **ApplicationSearch** method which will let you list, query, or search for objects. For documentation on these endpoints, see **[[ https:\/\/secure.phabricator.com\/diviner\/find\/?name=Conduit+API%3A+Using+Search+Endpoints&type=article&jump=1 | Conduit API: Using Search Endpoints ]]**.","params":{"queryKey":"optional string","constraints":"optional map\u003cstring, wild\u003e","attachments":"optional map\u003cstring, bool\u003e","order":"optional order","before":"optional string","after":"optional string","limit":"optional int (default = 100)"},"return":"map\u003cstring, wild\u003e"},"user.find":{"description":"Lookup PHIDs by username. Obsoleted by \"user.query\".","params":{"aliases":"required list\u003cstring\u003e"},"return":"nonempty dict\u003cstring, phid\u003e"}} \ No newline at end of file diff --git a/phabricator/tests.py b/phabricator/tests.py deleted file mode 100644 index 7d0a157..0000000 --- a/phabricator/tests.py +++ /dev/null @@ -1,75 +0,0 @@ -import phabricator -import unittest -from StringIO import StringIO -from mock import patch, Mock - -RESPONSES = { - 'conduit.connect': '{"result":{"connectionID":1759,"sessionKey":"lwvyv7f6hlzb2vawac6reix7ejvjty72svnir6zy","userPHID":"PHID-USER-6ij4rnamb2gsfpdkgmny"},"error_code":null,"error_info":null}', - 'user.whoami': '{"result":{"phid":"PHID-USER-6ij4rnamz2gxfpbkamny","userName":"testaccount","realName":"Test Account"},"error_code":null,"error_info":null}', - 'maniphest.find': '{"result":{"PHID-TASK-4cgpskv6zzys6rp5rvrc":{"id":"722","phid":"PHID-TASK-4cgpskv6zzys6rp5rvrc","authorPHID":"PHID-USER-5022a9389121884ab9db","ownerPHID":"PHID-USER-5022a9389121884ab9db","ccPHIDs":["PHID-USER-5022a9389121884ab9db","PHID-USER-ba8aeea1b3fe2853d6bb"],"status":"3","priority":"Needs Triage","title":"Relations should be two-way","description":"When adding a differential revision you can specify Maniphest Tickets to add the relation. However, this doesnt add the relation from the ticket -> the differently.(This was added via the commit message)","projectPHIDs":["PHID-PROJ-358dbc2e601f7e619232","PHID-PROJ-f58a9ac58c333f106a69"],"uri":"https:\/\/secure.phabricator.com\/T722","auxiliary":[],"objectName":"T722","dateCreated":"1325553508","dateModified":"1325618490"}},"error_code":null,"error_info":null}' -} - -class PhabricatorTest(unittest.TestCase): - def setUp(self): - self.api = phabricator.Phabricator(username='test', certificate='test', host='http://localhost') - self.api.certificate = "fdhcq3zsyijnm4h6gmh43zue5umsmng5t4dlwodvmiz4cnc6fl6f" + \ - "zrvjbfg2ftktrcddan7b3xtgmfge2afbrh4uwam6pfxpq5dbkhbl" + \ - "6mgaijdzpq5efw2ynlnjhoeqyh6dakl4yg346gbhabzkcxreu7hc" + \ - "jhw6vo6wwa7ky2sjdk742khlgsakwtme6sr2dfkhlxxkcqw3jngy" + \ - "rq5zj7m6m7hnscuzlzsviawnvg47pe7l4hxiexpbb5k456r" - - def test_generate_hash(self): - token = '12345678' - hashed = self.api.generate_hash(token) - self.assertEquals(hashed, 'f8d3bea4e58a2b2967d93d5b307bfa7c693b2e7f') - - @patch('phabricator.httplib.HTTPConnection') - def test_connect(self, mock_connection): - mock = mock_connection.return_value = Mock() - mock.getresponse.return_value = StringIO(RESPONSES['conduit.connect']) - - api = phabricator.Phabricator(username='test', certificate='test', host='http://localhost') - api.connect() - self.assertTrue('sessionKey' in api.conduit.keys()) - self.assertTrue('connectionID' in api.conduit.keys()) - - @patch('phabricator.httplib.HTTPConnection') - def test_user_whoami(self, mock_connection): - mock = mock_connection.return_value = Mock() - mock.getresponse.return_value = StringIO(RESPONSES['user.whoami']) - - api = phabricator.Phabricator(username='test', certificate='test', host='http://localhost') - api.conduit = True - - self.assertEqual('testaccount', api.user.whoami()['userName']) - - @patch('phabricator.httplib.HTTPConnection') - def test_maniphest_find(self, mock_connection): - mock = mock_connection.return_value = Mock() - mock.getresponse.return_value = StringIO(RESPONSES['maniphest.find']) - - api = phabricator.Phabricator(username='test', certificate='test', host='http://localhost') - api.conduit = True - - result = api.maniphest.find(ownerphids=["PHID-USER-5022a9389121884ab9db"]) - self.assertEqual(1, len(result)) - - # Test iteration - self.assertTrue(isinstance([x for x in result], list)) - - # Test getattr - self.assertEqual("3", result["PHID-TASK-4cgpskv6zzys6rp5rvrc"]["status"]) - - def test_validation(self): - self.api.conduit = True - - with self.assertRaises(ValueError): - self.assertRaises(ValueError, self.api.differential.find()) - self.assertRaises(ValueError, self.api.differential.find(query=1)) - self.assertRaises(ValueError, self.api.differential.find(query="1")) - self.assertRaises(ValueError, self.api.differential.find(query="1", guids="1")) - self.assertRaises(ValueError, self.api.differential.find(query="1", guids=["1"])) - - -if __name__ == '__main__': - unittest.main() diff --git a/phabricator/tests/__init__.py b/phabricator/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/phabricator/tests/resources/__init__.py b/phabricator/tests/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/phabricator/tests/resources/certificate.txt b/phabricator/tests/resources/certificate.txt new file mode 100644 index 0000000..a3589fd --- /dev/null +++ b/phabricator/tests/resources/certificate.txt @@ -0,0 +1 @@ +fdhcq3zsyijnm4h6gmh43zue5umsmng5t4dlwodvmiz4cnc6fl6fzrvjbfg2ftktrcddan7b3xtgmfge2afbrh4uwam6pfxpq5dbkhbl6mgaijdzpq5efw2ynlnjhoeqyh6dakl4yg346gbhabzkcxreu7hcjhw6vo6wwa7ky2sjdk742khlgsakwtme6sr2dfkhlxxkcqw3jngyrq5zj7m6m7hnscuzlzsviawnvg47pe7l4hxiexpbb5k456r diff --git a/phabricator/tests/resources/responses.json b/phabricator/tests/resources/responses.json new file mode 100644 index 0000000..2bb85e9 --- /dev/null +++ b/phabricator/tests/resources/responses.json @@ -0,0 +1,5 @@ +{ + "conduit.connect": "{\"result\":{\"connectionID\":1759,\"sessionKey\":\"lwvyv7f6hlzb2vawac6reix7ejvjty72svnir6zy\",\"userPHID\":\"PHID-USER-6ij4rnamb2gsfpdkgmny\"},\"error_code\":null,\"error_info\":null}", + "user.whoami": "{\"result\":{\"phid\":\"PHID-USER-6ij4rnamz2gxfpbkamny\",\"userName\":\"testaccount\",\"realName\":\"Test Account\"},\"error_code\":null,\"error_info\":null}", + "maniphest.find": "{\"result\":{\"PHID-TASK-4cgpskv6zzys6rp5rvrc\":{\"id\":\"722\",\"phid\":\"PHID-TASK-4cgpskv6zzys6rp5rvrc\",\"authorPHID\":\"PHID-USER-5022a9389121884ab9db\",\"ownerPHID\":\"PHID-USER-5022a9389121884ab9db\",\"ccPHIDs\":[\"PHID-USER-5022a9389121884ab9db\",\"PHID-USER-ba8aeea1b3fe2853d6bb\"],\"status\":\"3\",\"priority\":\"Needs Triage\",\"title\":\"Relations should be two-way\",\"description\":\"When adding a differential revision you can specify Maniphest Tickets to add the relation. However, this doesnt add the relation from the ticket -> the differently.(This was added via the commit message)\",\"projectPHIDs\":[\"PHID-PROJ-358dbc2e601f7e619232\",\"PHID-PROJ-f58a9ac58c333f106a69\"],\"uri\":\"https://secure.phabricator.com/T722\",\"auxiliary\":[],\"objectName\":\"T722\",\"dateCreated\":\"1325553508\",\"dateModified\":\"1325618490\"}},\"error_code\":null,\"error_info\":null}" +} diff --git a/phabricator/tests/test_phabricator.py b/phabricator/tests/test_phabricator.py new file mode 100644 index 0000000..e12b285 --- /dev/null +++ b/phabricator/tests/test_phabricator.py @@ -0,0 +1,174 @@ +try: + import unittest2 as unittest +except ImportError: + import unittest + +import requests +import responses + +from pkg_resources import resource_string +import json + +import phabricator +phabricator.ARCRC = {} # overwrite any arcrc that might be read + + +RESPONSES = json.loads( + resource_string( + 'phabricator.tests.resources', + 'responses.json' + ).decode('utf8') +) +CERTIFICATE = resource_string( + 'phabricator.tests.resources', + 'certificate.txt' +).decode('utf8').strip() + + +# Protect against local user's .arcrc interference. +phabricator.ARCRC = {} + + +class PhabricatorTest(unittest.TestCase): + def setUp(self): + self.api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + self.api.certificate = CERTIFICATE + + def test_generate_hash(self): + token = '12345678' + hashed = self.api.generate_hash(token) + self.assertEqual(hashed, 'f8d3bea4e58a2b2967d93d5b307bfa7c693b2e7f') + + @responses.activate + def test_connect(self): + responses.add('POST', 'http://localhost/api/conduit.connect', + body=RESPONSES['conduit.connect'], status=200) + + api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + + api.connect() + keys = api._conduit.keys() + self.assertIn('sessionKey', keys) + self.assertIn('connectionID', keys) + assert len(responses.calls) == 1 + + @responses.activate + def test_user_whoami(self): + responses.add('POST', 'http://localhost/api/user.whoami', + body=RESPONSES['user.whoami'], status=200) + + api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + api._conduit = True + + self.assertEqual(api.user.whoami()['userName'], 'testaccount') + + def test_classic_resources(self): + api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + + self.assertEqual(api.user.whoami.method, 'user') + self.assertEqual(api.user.whoami.endpoint, 'whoami') + + def test_nested_resources(self): + api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + + self.assertEqual(api.diffusion.repository.edit.method, 'diffusion') + self.assertEqual( + api.diffusion.repository.edit.endpoint, 'repository.edit') + + @responses.activate + def test_bad_status(self): + responses.add( + 'POST', 'http://localhost/api/conduit.connect', status=400) + + api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + + with self.assertRaises(requests.exceptions.HTTPError): + api.user.whoami() + assert len(responses.calls) == 1 + + @responses.activate + def test_maniphest_find(self): + responses.add('POST', 'http://localhost/api/maniphest.find', + body=RESPONSES['maniphest.find'], status=200) + + api = phabricator.Phabricator( + username='test', + certificate='test', + host='http://localhost/api/' + ) + api._conduit = True + + result = api.maniphest.find( + ownerphids=['PHID-USER-5022a9389121884ab9db'] + ) + self.assertEqual(len(result), 1) + + # Test iteration + self.assertIsInstance([x for x in result], list) + + # Test getattr + self.assertEqual( + result['PHID-TASK-4cgpskv6zzys6rp5rvrc']['status'], + '3' + ) + + def test_validation(self): + self.api._conduit = True + + self.assertRaises(ValueError, self.api.differential.find) + with self.assertRaises(ValueError): + self.api.differential.find(query=1) + with self.assertRaises(ValueError): + self.api.differential.find(query='1') + with self.assertRaises(ValueError): + self.api.differential.find(query='1', guids='1') + + def test_map_param_type(self): + uint = 'uint' + self.assertEqual(phabricator.map_param_type(uint), int) + + list_bool = 'list' + self.assertEqual(phabricator.map_param_type(list_bool), [bool]) + + list_pair = 'list>' + self.assertEqual(phabricator.map_param_type(list_pair), [tuple]) + + complex_list_pair = 'list, string>>' + self.assertEqual(phabricator.map_param_type( + complex_list_pair), [tuple]) + + + def test_endpoint_shadowing(self): + shadowed_endpoints = [e for e in self.api._interface.keys() if e in self.api.__dict__] + self.assertEqual( + shadowed_endpoints, + [], + "The following endpoints are shadowed: {}".format(shadowed_endpoints) + ) + +if __name__ == '__main__': + unittest.main() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3c6e79c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py index b765db7..3ec4f1a 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,41 @@ #!/usr/bin/env python +import sys + from setuptools import setup, find_packages +tests_requires = ['responses>=0.12'] + +if sys.version_info[:2] < (2, 7): + tests_requires.append('unittest2') + +if sys.version_info[:2] <= (3, 3): + tests_requires.append('mock') + setup( name='phabricator', - version='0.4.0', - author='DISQUS', + version='0.9.1', + author='Disqus', author_email='opensource@disqus.com', url='http://github.com/disqus/python-phabricator', description='Phabricator API Bindings', packages=find_packages(), zip_safe=False, - test_suite='nose.collector', - install_requires=[''], - tests_require=['nose', 'unittest2', 'mock'], + install_requires=['requests>=2.26'], + test_suite='phabricator.tests.test_phabricator', + extras_require={ + 'tests': tests_requires, + }, include_package_data=True, classifiers=[ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Operating System :: OS Independent', - 'Topic :: Software Development' + 'Topic :: Software Development', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', ], )