Source code for githubissuesbot.github_bot

import requests
import json
import configparser
import re
import logging


logging.basicConfig(filename='github_bot.log', level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')


[docs]class GitHubBot: """ The class realizes GitHub bot functionality such as labeling all issues at once or processing them by one. Attributes: url (str): Url of issues in the specified repo (ex.: `https://api.github.com/repos/<username>/<repo>/issues`)). default_label (str): If no rule may be applied to issue, an issue will be labeled by this string If you have token in *auth.cfg.sample file* and labeling rules in *label.cfg* file, you may call this class the following way .. testsetup:: from githubissuesbot import github_bot import os auth_cfg_file = os.path.abspath(os.path.join(os.path.dirname(github_bot.__file__))) + '/config/auth.cfg.sample' label_cfg_file = os.path.abspath(os.path.join(os.path.dirname(github_bot.__file__))) + '/config/label.cfg' .. testcode:: from githubissuesbot import github_bot bot = github_bot.GitHubBot(auth_cfg_file, label_cfg_file, "https://api.github.com/repos/my-username/my-repo/issues", "default") And access its url and default label by invoking >>> bot.url 'https://api.github.com/repos/my-username/my-repo/issues' >>> bot.default_label 'default' You can also send GitHub personal access token as parameter and in this case doesn't matter what you have in *auth_cfg_file* .. testcode:: from githubissuesbot import github_bot bot = github_bot.GitHubBot(auth_cfg_file, label_cfg_file, "https://api.github.com/repos/my-username/my-repo/issues", "default", auth_token='mypersonalaccesstoken') print(bot._token) .. testoutput:: mypersonalaccesstoken """ def __init__(self, auth_file, label_file, url, default_label, session=None, auth_token=None): """ A constructor. Args: auth_file (str): path to file with authorization info. If you don't want to read file, you may use *auth_token*. label_file (str): path to file with issue labels and their rules. url (str): Url of issues in repo (ex.: `https://api.github.com/repos/<username>/<repo>/issues`). If you want to label ALL issues in this repo, you MUST specify this parameter. Otherwise, set it to None in case of labeling only one issue. default_label (str): If no rule may be applied to issue, an issue will be labeled by this string. session (betamax_session or requests.Session()): Session for handling network communication. If it is None, requests.Session() will be invoked. auth_token (str): GitHub Personal Access Token. If it is None, the *auth_file* will be read. Otherwise, the value of this variable will be used for authorization and *auth_file* parameter will be ignored. """ self._read_config(auth_file, auth_token, label_file) self.url = url self.default_label = default_label self._session = session or requests.Session() self._session.headers = {'Authorization': 'token ' + self._token, 'User-Agent': 'Python'} def _read_config(self, auth_file, auth_token, label_file): """ The function reads GitHub Personal Access token (from *auth_file* or use *auth_token* if it is given) and labeling rules. Args: auth_file (str): File with GitHub Personal Access token. auth_token (str): GitHub Personal Access token. If None, *auth_file* will be read. Otherwise, the program will use the *auth_token*. label_file (str): File with available labels (applied on issues) and their rules. """ conf = configparser.ConfigParser() if auth_token: conf.read(label_file) self._token = auth_token else: conf.read([auth_file, label_file]) self._token = conf['github']['token'] self._label_list = list(map(str.strip, conf['list']['labels'].split(','))) logging.debug("List of defined labels:", self._label_list) self._label_rules = [] for label in self._label_list: self._label_rules.append(conf['rules'][label])
[docs] def label_all_issues(self, label_comments): """ Call this function to label all unlabeled issues at once. Args: label_comments (bool): Set it to True, if the program must use comments (in addition to issues themselves) for labeling. Returns: dict: Dictionary with numbers of issues as keys and lists of assigned labels together with POST HTTP status code as values (ex.: results[15][0] gives labels of the 15-th issue, and results[15][1] gives POST status code of the same issue (if labeling on GitHub was successful or not)). """ r = self._session.get(self.url) r.raise_for_status() results = {} for issue_info in r.json(): if issue_info['labels']: continue results[issue_info['number']] = self._set_labels(self.url + '/' + str(issue_info['number']), issue_info['title'], issue_info['body'], label_comments) return results
[docs] def label_issue(self, issue_info): """ Call this function to label only the given unlabeled issue. Args: issue_info (json): Issue information in JSON format. Example issue JSON: https://developer.github.com/v3/activity/events/types/#issuesevent Returns: list: List of assigned labels together with POST HTTP status code (if labeling on GitHub was successful or not). Example: results[0] gives labels of the issue, and results[1] gives POST status code of the same issue). """ if not issue_info['labels']: return self._set_labels(issue_info['url'], issue_info['title'], issue_info['body'], False)
def _set_labels(self, issue_url, title, body, label_comments): """ The function labels an issue on the given url by its title, body and comments. Args: issue_url (str): Url of the issue. Example: `https://api.github.com/repos/<username>/<repo>/issues/<issue-number> title (str): Issue's title. body (str): Issue's body. label_comments (bool): Set it to True, if the program must use comments (in addition to issue's title and body) for labeling. Returns: list: The first value contains assigned labels, the second one - HTTP POST status code (if labeling on GitHub was successful or not). """ text = title + " " + body if label_comments: # add comments' body to issue's text r = self._session.get(issue_url + '/comments') for comm in r.json(): text += ' ' + comm['body'] labels = [] for rule, label_text in zip(self._label_rules, self._label_list): if re.search(rule, text): labels.append(label_text) logging.debug(10 * '-') logging.debug('Issue url:', issue_url) logging.debug('Issue title:', title) if not labels: labels.append(self.default_label) logging.debug("Labeled as:", labels) r = self._session.post(issue_url + '/labels', data=json.dumps(labels)) logging.debug('Status code:', r.status_code) return [labels, r.status_code]