Completed
Push — pyup-update-toml-0.9.2-to-0.9.... ( 3e288d )
by Michael
21:07 queued 21:02
created

Release.title()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1 3
import re
2 3
import shlex
3
4 3
import attr
0 ignored issues
show
Configuration introduced by
The import attr could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
5 3
import semantic_version
0 ignored issues
show
Configuration introduced by
The import semantic_version could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
6 3
import uritemplate
0 ignored issues
show
Configuration introduced by
The import uritemplate could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
7 3
import requests
8 3
import giturlparse
0 ignored issues
show
Configuration introduced by
The import giturlparse could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
9 3
from plumbum.cmd import git
0 ignored issues
show
Configuration introduced by
The import plumbum.cmd could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
10
11 3
MERGED_PULL_REQUEST = re.compile(
12
    r'^([0-9a-f]{5,40}) Merge pull request #(\w+)'
13
)
14
15 3
GITHUB_PULL_REQUEST_API = (
16
    'https://api.github.com/repos{/owner}{/repo}/issues{/number}'
17
)
18 3
GITHUB_LABEL_API = (
19
    'https://api.github.com/repos{/owner}{/repo}/labels'
20
)
21
22
23 3
def changes_to_release_type(repository):
24 3
    pull_request_labels = set()
25 3
    changes = repository.changes_since_last_version
26
27 3
    for change in changes:
28 3
        for label in change.labels:
29 3
            pull_request_labels.add(label)
30
31 3
    change_descriptions = [
32
        '\n'.join([change.title, change.description]) for change in changes
33
    ]
34
35 3
    current_version = repository.latest_version
36 3
    if 'BREAKING CHANGE' in change_descriptions:
37
        return 'major', Release.BREAKING_CHANGE, current_version.next_major()
38 3
    elif 'enhancement' in pull_request_labels:
39
        return 'minor', Release.FEATURE, current_version.next_minor()
40 3
    elif 'bug' in pull_request_labels:
41 3
        return 'patch', Release.FIX, current_version.next_patch()
42
    else:
43 3
        return None, Release.NO_CHANGE, current_version
44
45
46 3
@attr.s
47
class Release:
48 3
    NO_CHANGE = 'nochanges'
49 3
    BREAKING_CHANGE = 'breaking'
50 3
    FEATURE = 'feature'
51 3
    FIX = 'fix'
52
53 3
    release_date = attr.ib()
54 3
    version = attr.ib()
55 3
    description = attr.ib()
56 3
    name = attr.ib(default=attr.Factory(str))
57 3
    changes = attr.ib(default=attr.Factory(dict))
58
59
60 3
@attr.s
61
class PullRequest:
62 3
    number = attr.ib()
63 3
    title = attr.ib()
64 3
    description = attr.ib()
65
    # default is 'body' key
66 3
    author = attr.ib()
67 3
    labels = attr.ib(default=attr.Factory(list))
68
69 3
    @classmethod
70
    def from_github(cls, api_response):
71 3
        return PullRequest(
72
            number = api_response['number'],
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
number = api_response['number'],
^
Loading history...
73
            title = api_response['title'],
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
title = api_response['title'],
^
Loading history...
74
            description = api_response['body'],
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
description = api_response['body'],
^
Loading history...
75
            author = api_response['user']['login'],
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
author = api_response['user']['login'],
^
Loading history...
76
            labels = [
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
labels = [
^
Loading history...
77
                label['name']
78
                for label in api_response['labels']
79
                # label['colour'] => https://gist.github.com/MicahElliott/719710
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (80/79).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
80
            ],
81
            # labels need a description => map for default github tags
82
        )
83
84
85 3
@attr.s
86
class GitRepository:
87 3
    VERSION_ZERO = semantic_version.Version('0.0.0')
88
    # TODO: handle multiple remotes (cookiecutter [non-owner maintainer])
89 3
    REMOTE_NAME = 'origin'
90
91 3
    auth_token = attr.ib(default=None)
92
93 3
    @property
94
    def remote_url(self):
95 3
        return git(shlex.split('config --get remote.{}.url'.format(
96
            self.REMOTE_NAME
97
        )))
98
99 3
    @property
100
    def parsed_repo(self):
101 3
        return giturlparse.parse(self.remote_url)
102
103 3
    @property
104
    def repo(self):
105 3
        return self.parsed_repo.repo
106
107 3
    @property
108
    def owner(self):
109 3
        return self.parsed_repo.owner
110
111 3
    @property
112
    def platform(self):
113 3
        return self.parsed_repo.platform
114
115 3
    @property
116
    def is_github(self):
117 3
        return self.parsed_repo.github
118
119 3
    @property
120
    def is_bitbucket(self):
121
        return self.parsed_repo.bitbucket
122
123 3
    @property
124
    def commit_history(self):
125
        return [
126
            commit_message
127
            for commit_message in git(shlex.split(
128
                'log --oneline --no-color'
129
            )).split('\n')
130
            if commit_message
131
        ]
132
133 3
    @property
134
    def first_commit_sha(self):
135
        return git(
136
            'rev-list', '--max-parents=0', 'HEAD'
137
        )
138
139 3
    @property
140
    def tags(self):
141 3
        return git(shlex.split('tag --list')).split('\n')
142
143 3
    @property
144
    def versions(self):
145 3
        versions = []
146 3
        for tag in self.tags:
147 3
            try:
148 3
                versions.append(semantic_version.Version(tag))
149 3
            except ValueError:
150 3
                pass
151 3
        return versions
152
153 3
    @property
154 3
    def latest_version(self) -> semantic_version.Version:
155 3
        return max(self.versions) if self.versions else self.VERSION_ZERO
156
157 3
    def merges_since(self, version=None):
158 3
        if version == semantic_version.Version('0.0.0'):
159
            version = self.first_commit_sha
160
161 3
        revision_range = ' {}..HEAD'.format(version) if version else ''
162
163 3
        merge_commits = git(shlex.split(
164
            'log --oneline --merges --no-color{}'.format(revision_range)
165
        )).split('\n')
166 3
        return merge_commits
167
168
    # TODO: pull_requests_since(version=None)
169
    # TODO: cached_property
170 3
    @property
171
    def changes_since_last_version(self):
172 3
        pull_requests = []
173
174 3
        for index, commit_msg in enumerate(self.merges_since(self.latest_version)):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (83/79).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
175 3
            matches = MERGED_PULL_REQUEST.findall(commit_msg)
176
177 3
            if matches:
178 3
                _, pull_request_number = matches[0]
179
180 3
                pull_requests.append(PullRequest.from_github(
181
                    self.github_pull_request(pull_request_number)
182
                ))
183 3
        return pull_requests
184
185 3
    def github_pull_request(self, pr_num):
186 3
        pull_request_api_url = uritemplate.expand(
187
            GITHUB_PULL_REQUEST_API,
188
            dict(
189
                owner=self.owner,
190
                repo=self.repo,
191
                number=pr_num
192
            ),
193
        )
194
195 3
        return requests.get(
196
            pull_request_api_url,
197
            headers={
198
                'Authorization': 'token {}'.format(self.auth_token)
199
            },
200
        ).json()
201
202
    # TODO: cached_property
203
    # TODO: move to test fixture
204 3
    def github_labels(self):
205
206 3
        labels_api_url = uritemplate.expand(
207
            GITHUB_LABEL_API,
208
            dict(
209
                owner=self.owner,
210
                repo=self.repo,
211
            ),
212
        )
213
214 3
        return requests.get(
215
            labels_api_url,
216
            headers={
217
                'Authorization': 'token {}'.format(self.auth_token)
218
            },
219
        ).json()
220
221
222
223