Beispiel #1
0
def create_or_update_comment(ghrequest, comment,
                             ONLY_UPDATE_COMMENT_BUT_NOT_CREATE):
    query = "/repos/{}/issues/{}/comments"
    query = query.format(ghrequest.repository, str(ghrequest.pr_number))
    comments = utils._request(query).json()

    # Get the last comment id by the bot
    last_comment_id = None
    for old_comment in comments:
        if old_comment["user"]["id"] == 24736507:  # ID of @pep8speaks
            last_comment_id = old_comment["id"]
            break

    if last_comment_id is None and not ONLY_UPDATE_COMMENT_BUT_NOT_CREATE:  # Create a new comment
        response = utils._request(query=query,
                                  method='POST',
                                  json={"body": comment})
        ghrequest.comment_response = response.json()
    else:  # Update the last comment
        utc_time = datetime.datetime.utcnow()
        time_now = utc_time.strftime("%B %d, %Y at %H:%M Hours UTC")
        comment += "\n\n##### Comment last updated on {}"
        comment = comment.format(time_now)

        query = "/repos/{}/issues/comments/{}"
        query = query.format(ghrequest.repository, str(last_comment_id))
        response = utils._request(query,
                                  method='PATCH',
                                  json={"body": comment})

    return response
Beispiel #2
0
def delete_if_forked(ghrequest):
    FORKED = False
    query = "/user/repos"
    r = utils._request(query)
    for repo in r.json():
        if repo["description"]:
            if ghrequest.target_repo_fullname in repo["description"]:
                FORKED = True
                url = "/repos/{}"
                url = url.format(repo["full_name"])
                utils._request(url, method='DELETE')
    return FORKED
Beispiel #3
0
def autopep8(ghrequest, config):
    # Run pycodestyle

    r = utils._request(ghrequest.diff_url)
    ## All the python files with additions
    patch = unidiff.PatchSet(r.content.splitlines(), encoding=r.encoding)

    # A dictionary with filename paired with list of new line numbers
    py_files = {}

    for patchset in patch:
        if patchset.target_file[-3:] == '.py':
            py_file = patchset.target_file[1:]
            py_files[py_file] = []
            for hunk in patchset:
                for line in hunk.target_lines():
                    if line.is_added:
                        py_files[py_file].append(line.target_line_no)

    # Ignore errors and warnings specified in the config file
    to_ignore = ",".join(config["pycodestyle"]["ignore"])
    arg_to_ignore = ""
    if len(to_ignore) > 0:
        arg_to_ignore = "--ignore " + to_ignore

    for file in py_files:
        filename = file[1:]
        url = "https://raw.githubusercontent.com/{}/{}/{}"
        url = url.format(ghrequest.repository, ghrequest.sha, file)
        r = utils._request(url)
        with open("file_to_fix.py", 'w+', encoding=r.encoding) as file_to_fix:
            file_to_fix.write(r.text)

        cmd = 'autopep8 file_to_fix.py --diff {arg_to_ignore}'.format(
            arg_to_ignore=arg_to_ignore)
        proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
        stdout, _ = proc.communicate()
        ghrequest.diff[filename] = stdout.decode(r.encoding)

        # Fix the errors
        ghrequest.diff[filename] = ghrequest.diff[filename].replace(
            "file_to_check.py", filename)
        ghrequest.diff[filename] = ghrequest.diff[filename].replace(
            "\\", "\\\\")

        ## Store the link to the file
        url = "https://github.com/{}/blob/{}{}"
        ghrequest.links = {}
        ghrequest.links[filename + "_link"] = url.format(
            ghrequest.repository, ghrequest.sha, file)
        os.remove("file_to_fix.py")
Beispiel #4
0
 def test_request(self, mocker, query, method, json, data, headers, params):
     mock_func = mock.MagicMock(return_value=True)
     mocker.patch('requests.request', mock_func)
     _request(query, method, json, data, headers, params)
     assert mock_func.call_count == 1
     assert mock_func.call_args[0][0] == method
     assert mock_func.call_args[1]['headers'] == headers
     assert mock_func.call_args[1]['auth'] == ('', '')
     assert mock_func.call_args[1]['params'] == params
     assert mock_func.call_args[1]['json'] == json
     if query[0] == "/":
         assert mock_func.call_args[0][1] == BASE_URL + query
     else:
         assert mock_func.call_args[0][1] == query
Beispiel #5
0
def comment_permission_check(ghrequest):
    """
    Check for quite and resume status or duplicate comments
    """
    repository = ghrequest.repository

    # Check for duplicate comment
    url = "/repos/{}/issues/{}/comments"
    url = url.format(repository, str(ghrequest.pr_number))
    comments = utils._request(url).json()
    """
    # Get the last comment by the bot
    last_comment = ""
    for old_comment in reversed(comments):
        if old_comment["user"]["id"] == 24736507:  # ID of @pep8speaks
            last_comment = old_comment["body"]
            break

    # Disabling this because only a single comment is made per PR
    text1 = ''.join(BeautifulSoup(markdown(comment)).findAll(text=True))
    text2 = ''.join(BeautifulSoup(markdown(last_comment)).findAll(text=True))
    if text1 == text2.replace("submitting", "updating"):
        PERMITTED_TO_COMMENT = False
    """

    # Check if the bot is asked to keep quiet
    for old_comment in reversed(comments):
        if '@pep8speaks' in old_comment['body']:
            if 'resume' in old_comment['body'].lower():
                break
            elif 'quiet' in old_comment['body'].lower():
                return False

    # Check for [skip pep8]
    ## In commits
    commits = utils._request(ghrequest.commits_url).json()
    for commit in commits:
        if any(m in commit["commit"]["message"].lower()
               for m in ["[skip pep8]", "[pep8 skip]"]):
            return False
    ## PR title
    if any(m in ghrequest.pr_title.lower()
           for m in ["[skip pep8]", "[pep8 skip]"]):
        return False
    ## PR description
    if any(m in ghrequest.pr_desc.lower()
           for m in ["[skip pep8]", "[pep8 skip]"]):
        return False

    return True
Beispiel #6
0
def _create_diff(ghrequest, config):
    # Dictionary with filename matched with a string of diff
    ghrequest.diff = {}

    # Process the files and prepare the diff for the gist
    helpers.autopep8(ghrequest, config)

    # Create the gist
    helpers.create_gist(ghrequest, config)

    comment = "Here you go with [the gist]({}) !\n\n" + \
              "> You can ask me to create a PR against this branch " + \
              "with those fixes. Simply comment " + \
              "`@pep8speaks pep8ify`.\n\n"
    if ghrequest.reviewer == ghrequest.author:  # Both are the same person
        comment += "@{} "
        comment = comment.format(ghrequest.gist_url, ghrequest.reviewer)
    else:
        comment += "@{} @{} "
        comment = comment.format(ghrequest.gist_url, ghrequest.reviewer,
                                 ghrequest.author)

    query = "/repos/{}/issues/{}/comments"
    query = query.format(ghrequest.repository, str(ghrequest.pr_number))
    response = utils._request(query, method='POST', json={"body": comment})
    ghrequest.comment_response = response.json()

    if ghrequest.error:
        return utils.Response(ghrequest, status=400)

    return utils.Response(ghrequest)
Beispiel #7
0
def follow_user(user):
    """Follow the user of the service"""
    headers = {
        "Content-Length": "0",
    }
    query = "/user/following/{}".format(user)
    return utils._request(query=query, method='PUT', headers=headers)
Beispiel #8
0
def commit(ghrequest):
    fullname = ghrequest.fork_fullname

    for file, new_file in ghrequest.results.items():
        query = "/repos/{}/contents/{}"
        query = query.format(fullname, file)
        params = {"ref": ghrequest.new_branch}
        r = utils._request(query, params=params)
        sha_blob = r.json().get("sha")
        params["path"] = file
        content_code = base64.b64encode(new_file.encode()).decode("utf-8")
        request_json = {
            "path": file,
            "message": "Fix pep8 errors in {}".format(file),
            "content": content_code,
            "sha": sha_blob,
            "branch": ghrequest.new_branch,
        }
        r = utils._request(query, method='PUT', json=request_json)
Beispiel #9
0
def create_new_branch(ghrequest):
    query = "/repos/{}/git/refs/heads"
    query = query.format(ghrequest.fork_fullname)
    sha = None
    r = utils._request(query)
    for ref in r.json():
        if ref["ref"].split("/")[-1] == ghrequest.target_repo_branch:
            sha = ref["object"]["sha"]

    query = "/repos/{}/git/refs"
    query = query.format(ghrequest.fork_fullname)
    ghrequest.new_branch = "{}-pep8-patch".format(ghrequest.target_repo_branch)
    request_json = {
        "ref": "refs/heads/{}".format(ghrequest.new_branch),
        "sha": sha,
    }
    r = utils._request(query, method='POST', json=request_json)

    if r.status_code > 299:
        ghrequest.error = "Could not create new branch in the fork"
Beispiel #10
0
def fork_for_pr(ghrequest):
    query = "/repos/{}/forks"
    query = query.format(ghrequest.target_repo_fullname)
    r = utils._request(query, method='POST')

    if r.status_code == 202:
        ghrequest.fork_fullname = r.json()["full_name"]
        return True

    ghrequest.error = "Unable to fork"
    return False
Beispiel #11
0
def update_fork_desc(ghrequest):
    # Check if forked (takes time)
    query = "/repos/{}".format(ghrequest.fork_fullname)
    r = utils._request(query)
    ATTEMPT = 0
    while (r.status_code != 200):
        time.sleep(5)
        r = utils._request(query)
        ATTEMPT += 1
        if ATTEMPT > 10:
            ghrequest.error = "Forking is taking more than usual time"
            break

    full_name = ghrequest.target_repo_fullname
    author, name = full_name.split("/")
    request_json = {
        "name": name,
        "description": "Forked from @{}'s {}".format(author, full_name)
    }
    r = utils._request(query, method='PATCH', data=json.dumps(request_json))
    if r.status_code != 200:
        ghrequest.error = "Could not update description of the fork"
Beispiel #12
0
def run_pycodestyle(ghrequest, config):
    """
    Runs the pycodestyle cli tool on the files and update ghrequest
    """
    repo = ghrequest.repository
    pr_number = ghrequest.pr_number
    commit = ghrequest.after_commit_hash

    # Run pycodestyle
    ## All the python files with additions
    # A dictionary with filename paired with list of new line numbers
    files_to_exclude = config["pycodestyle"]["exclude"]
    py_files = get_py_files_in_pr(repo, pr_number, files_to_exclude)

    for file in py_files:
        filename = file[1:]
        query = "https://raw.githubusercontent.com/{}/{}/{}"
        query = query.format(repo, commit, file)
        r = utils._request(query)
        with open("file_to_check.py", 'w+',
                  encoding=r.encoding) as file_to_check:
            file_to_check.write(r.text)

        # Use the command line here
        cmd = 'pycodestyle {config[pycodestyle_cmd_config]} file_to_check.py'.format(
            config=config)
        proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
        stdout, _ = proc.communicate()
        ghrequest.extra_results[filename] = stdout.decode(
            r.encoding).splitlines()

        # Put only relevant errors in the ghrequest.results dictionary
        ghrequest.results[filename] = []
        for error in list(ghrequest.extra_results[filename]):
            if re.search("^file_to_check.py:\d+:\d+:\s[WE]\d+\s.*", error):
                ghrequest.results[filename].append(
                    error.replace("file_to_check.py", filename))
                ghrequest.extra_results[filename].remove(error)

        ## Remove errors in case of diff_only = True
        ## which are caused in the whole file
        for error in list(ghrequest.results[filename]):
            if config["scanner"]["diff_only"]:
                if not int(error.split(":")[1]) in py_files[file]:
                    ghrequest.results[filename].remove(error)

        ## Store the link to the file
        url = "https://github.com/{}/blob/{}{}"
        ghrequest.links = {}  # UI Link of each updated file in the PR
        ghrequest.links[filename + "_link"] = url.format(repo, commit, file)
        os.remove("file_to_check.py")
Beispiel #13
0
def create_pr(ghrequest):
    query = "/repos/{}/pulls"
    query = query.format(ghrequest.target_repo_fullname)
    request_json = {
        "title": "Fix pep8 errors",
        "head": "pep8speaks:{}".format(ghrequest.new_branch),
        "base": ghrequest.target_repo_branch,
        "body": "The changes are suggested by autopep8",
    }
    r = utils._request(query, method='POST', json=request_json)
    if r.status_code == 201:
        ghrequest.pr_url = r.json()["html_url"]
    else:
        ghrequest.error = "Pull request could not be created"
Beispiel #14
0
    def _get_pull_request(self, request, event):
        """
        Get data about the pull request created
        """
        if not self.OK:
            return None

        if event == "issue_comment":
            pr_url = request.json['issue']['pull_request']['url']
            pull_request = utils._request(pr_url).json()
        elif event in ("pull_request", "pull_request_review"):
            pull_request = request.json['pull_request']
        else:
            return None
        return pull_request
Beispiel #15
0
def create_gist(ghrequest, config):
    """Create gists for diff files"""
    request_json = {}
    request_json["public"] = True
    request_json["files"] = {}
    request_json["description"] = "In response to @{0}'s comment : {1}".format(
        ghrequest.reviewer, ghrequest.review_url)

    for file, diffs in ghrequest.diff.items():
        if len(diffs) != 0:
            request_json["files"][file.split("/")[-1] + ".diff"] = {
                "content": diffs
            }

    # Call github api to create the gist
    query = "/gists"
    response = utils._request(query, method='POST', json=request_json).json()
    ghrequest.gist_response = response
    ghrequest.gist_url = response["html_url"]
Beispiel #16
0
def get_files_involved_in_pr(repo, pr_number):
    """
    Return a list of file names modified/added in the PR
    """
    headers = {"Accept": "application/vnd.github.VERSION.diff"}

    query = "/repos/{}/pulls/{}"
    query = query.format(repo, pr_number)
    r = utils._request(query, headers=headers)

    patch = unidiff.PatchSet(r.content.splitlines(), encoding=r.encoding)

    files = {}

    for patchset in patch:
        file = patchset.target_file[1:]
        files[file] = []
        for hunk in patchset:
            for line in hunk.target_lines():
                if line.is_added:
                    files[file].append(line.target_line_no)
    return files
Beispiel #17
0
def _pep8ify(ghrequest, config):
    ghrequest.target_repo_fullname = ghrequest.pull_request["head"]["repo"][
        "full_name"]
    ghrequest.target_repo_branch = ghrequest.pull_request["head"]["ref"]
    ghrequest.results = {}

    # Check if the fork of the target repo exists
    # If yes, then delete it
    helpers.delete_if_forked(ghrequest)
    # Fork the target repository
    helpers.fork_for_pr(ghrequest)
    # Update the fork description. This helps in fast deleting it
    helpers.update_fork_desc(ghrequest)
    # Create a new branch for the PR
    helpers.create_new_branch(ghrequest)
    # Fix the errors in the files
    helpers.autopep8ify(ghrequest, config)
    # Commit each change onto the branch
    helpers.commit(ghrequest)
    # Create a PR from the branch to the target repository
    helpers.create_pr(ghrequest)

    comment = "Here you go with [the Pull Request]({}) ! The fixes are " \
              "suggested by [autopep8](https://github.com/hhatto/autopep8).\n\n"
    if ghrequest.reviewer == ghrequest.author:  # Both are the same person
        comment += "@{} "
        comment = comment.format(ghrequest.pr_url, ghrequest.reviewer)
    else:
        comment += "@{} @{} "
        comment = comment.format(ghrequest.pr_url, ghrequest.reviewer,
                                 ghrequest.author)

    query = "/repos/{}/issues/{}/comments"
    query = query.format(ghrequest.repository, str(ghrequest.pr_number))
    response = utils._request(query, method='POST', json={"body": comment})
    ghrequest.comment_response = response.json()

    return utils.Response(ghrequest)
Beispiel #18
0
def get_config(repo, base_branch):
    """
    Get .pep8speaks.yml config file from the repository and return
    the config dictionary
    """

    # Default configuration parameters
    config = {
        "message": {
            "opened": {
                "header": "",
                "footer": ""
            },
            "updated": {
                "header": "",
                "footer": ""
            },
            "no_errors":
            "Cheers ! There are no PEP8 issues in this Pull Request. :beers: ",
        },
        "scanner": {
            "diff_only": False
        },
        "pycodestyle": {
            "ignore": [],
            "max-line-length": 79,
            "count": False,
            "first": False,
            "show-pep8": False,
            "filename": [],
            "exclude": [],
            "select": [],
            "show-source": False,
            "statistics": False,
            "hang-closing": False,
        },
        "no_blank_comment": True,
        "only_mention_files_with_errors": True,
        "descending_issues_order": False,
    }

    # Configuration file
    query = "https://raw.githubusercontent.com/{}/{}/.pep8speaks.yml"
    query = query.format(repo, base_branch)

    r = utils._request(query)

    if r.status_code == 200:
        try:
            new_config = yaml.load(r.text)
            # overloading the default configuration with the one specified
            config = utils.update_dict(config, new_config)
        except yaml.YAMLError:  # Bad YAML file
            pass

    # Create pycodestyle command line arguments
    arguments = []
    confs = config["pycodestyle"]
    for key, value in confs.items():
        if value:  # Non empty
            if isinstance(value, int):
                if isinstance(value, bool):
                    arguments.append("--{}".format(key))
                else:
                    arguments.append("--{}={}".format(key, value))
            elif isinstance(value, list):
                arguments.append("--{}={}".format(key, ','.join(value)))
    config["pycodestyle_cmd_config"] = ' {arguments}'.format(
        arguments=' '.join(arguments))

    # pycodestyle is case-sensitive
    config["pycodestyle"]["ignore"] = [
        e.upper() for e in list(config["pycodestyle"]["ignore"])
    ]

    return config