# Copyright (c) 2020 The Regents of the University of California # All Rights Reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import copy import json import requests from types import SimpleNamespace from urllib.parse import urljoin class GerritResponseParser: @staticmethod def get_json_content(response): assert(isinstance(response, requests.Response)) # If the status code is not in the 200s range, it doesn't have content. if response.status_code >= 300: return None # Transform response.content to a Python3 string. # response.content is a byte array containing the response. # The first 4 bytes are b")]}\", which doesn't belong to JSON content. # The byte array is encoded by utf-8. content = response.content[4:].decode("utf-8") json_content = json.loads(content) return json_content # TODO: parsing method for each Gerrit data structure @staticmethod def parse(response): json_content = GerritResponseParser.get_json_content(response) if not json_content: return None return SimpleNamespace(**json_content) class GerritRestAPI: def __init__(self, auth, api_entry_point, timeout): self.username = auth[0] self.password = auth[1] self.api_entry_point = api_entry_point self.timeout = timeout # helper methods for sending GET and POST requests def _get(self, endpoint, params = None): request_url = urljoin(self.api_entry_point, endpoint) return requests.get(request_url, params = params, timeout = self.timeout, auth = (self.username, self.password)) def _post(self, endpoint, json_content): request_url = urljoin(self.api_entry_point, endpoint) return requests.post(request_url, json = json_content, timeout = self.timeout, auth = (self.username, self.password)) # --------------- Account Endpoints --------------- # https://gerrit-review.googlesource.com/Documentation/ # rest-api-accounts.html#get-account def get_account(self, account_id="self"): """ get an account detail from an account_id """ return self._get(f"/accounts/{account_id}") # https://gerrit-review.googlesource.com/Documentation/ # rest-api-accounts.html#query-account def query_account(self, query, limit = None): """ get accounts based on the query """ params = { "q": query } if limit: params["n"] = str(limit) return self._get(f"/accounts/", params) # --------------- Changes Endpoints --------------- # https://gerrit-review.googlesource.com/Documentation/ # rest-api-changes.html#list-changes def query_changes(self, query, limit=None, optional_field=None): """ query changes with maximum limit returned queries """ endpoint = f"/changes/" params = { "q": query } if limit: params["n"] = str(limit) if optional_field: params["o"] = optional_field return self._get(endpoint, params) # --------------- Reviewer Endpoints --------------- # https://gerrit-review.googlesource.com/Documentation/ # rest-api-changes.html#list-reviewers def list_reviewers(self, change_id): """ list reviewers of a change """ return self._get(f"/changes/{change_id}/reviewers") def add_reviewer(self, change_id, reviewer_email): """ add a reviewer using an email address """ data = {"reviewer": reviewer_email} return self._post(f"/changes/{change_id}/reviewers/", data)