diff --git a/bitbucket/bitbucket.py b/bitbucket/bitbucket.py index e7d7b2b..ea9e374 100644 --- a/bitbucket/bitbucket.py +++ b/bitbucket/bitbucket.py @@ -16,6 +16,7 @@ import requests from .issue import Issue +from .pullrequest import PullRequest from .repository import Repository from .service import Service from .ssh import SSH @@ -27,6 +28,7 @@ # ======== URLS = { 'BASE': 'https://bitbucket.org/!api/1.0/%s', + 'BASE_V2': 'https://bitbucket.org/!api/2.0/%s', # Get user profile and repos 'GET_USER': 'users/%(username)s/', 'GET_USER_PRIVILEGES': 'user/privileges', @@ -55,6 +57,7 @@ def __init__(self, username='', password='', repo_name_or_slug=''): self.service = Service(self) self.ssh = SSH(self) self.issue = Issue(self) + self.pullrequest = PullRequest(self) self.deploy_key = DeployKey(self) self.access_token = None @@ -227,23 +230,26 @@ def dispatch(self, method, url, auth=None, params=None, **kwargs): s = Session() resp = s.send(r.prepare()) status = resp.status_code - text = resp.text + content = resp.content # Includes binary + error = resp.reason if status >= 200 and status < 300: - if text: + if content: try: - return (True, json.loads(text)) + return (True, json.loads(content)) except TypeError: pass except ValueError: pass - return (True, text) + + return (True, content) elif status >= 300 and status < 400: return ( False, 'Unauthorized access, ' 'please check your credentials.') elif status >= 400 and status < 500: + import pdb; pdb.set_trace() return (False, 'Service not found.') elif status >= 500 and status < 600: return (False, 'Server error.') @@ -255,6 +261,11 @@ def url(self, action, **kwargs): # TODO : should be static method ? return self.URLS['BASE'] % self.URLS[action] % kwargs + def url_v2(self, action, **kwargs): + """ Construct and return the URL for a specific API service. """ + # TODO : should be static method ? + return self.URLS['BASE_V2'] % self.URLS[action] % kwargs + # ===================== # = General functions = # ===================== @@ -290,3 +301,42 @@ def get_privileges(self): """ Get privledges for this user. """ url = self.url('GET_USER_PRIVILEGES') return self.dispatch('GET', url, auth=self.auth) + + +class BitbucketTeam(Bitbucket): + """ This class lets you interact with the bitbucket public API. """ + def __init__(self, username='', password='', repo_name_or_slug='', team=None): + self.team = team + super(self.__class__, self).__init__(username=username, password=password, repo_name_or_slug=repo_name_or_slug) + + # =================== + # = Getters/Setters = + # =================== + + @property + def auth(self): + """ Return credentials for current Bitbucket user. """ + if self.oauth: + return self.oauth + return (self._username, self.password) + + @property + def username(self): + """Return your repository's username.""" + return self.team or Bitbucket.username.fget(self) + + @username.setter + def username(self, value): + try: + if isinstance(value, basestring): + self._username = unicode(value) + except NameError: + self._username = value + + if value is None: + self._username = None + + @username.deleter + def username(self): + del self._username + diff --git a/bitbucket/issue.py b/bitbucket/issue.py index 40be84b..e91fa3c 100644 --- a/bitbucket/issue.py +++ b/bitbucket/issue.py @@ -37,21 +37,23 @@ def issue_id(self, value): def issue_id(self): del self._issue_id - def all(self, repo_slug=None, params=None): + def all(self, repo_slug=None, params=None, owner=None): """ Get issues from one of your repositories. """ + owner = owner or self.bitbucket.username repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('GET_ISSUES', username=self.bitbucket.username, repo_slug=repo_slug) + url = self.bitbucket.url('GET_ISSUES', username=owner, repo_slug=repo_slug) return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth, params=params) - def get(self, issue_id, repo_slug=None): + def get(self, issue_id, repo_slug=None, owner=None): """ Get an issue from one of your repositories. """ repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('GET_ISSUE', username=self.bitbucket.username, repo_slug=repo_slug, issue_id=issue_id) + owner = owner or self.bitbucket.username + url = self.bitbucket.url('GET_ISSUE', username=owner, repo_slug=repo_slug, issue_id=issue_id) return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) - def create(self, repo_slug=None, **kwargs): + def create(self, repo_slug=None, owner=None, **kwargs): """ Add an issue to one of your repositories. Each issue require a different set of attributes, @@ -67,11 +69,12 @@ def create(self, repo_slug=None, **kwargs): * status: The status of the issue (new, open, resolved, on hold, invalid, duplicate, or wontfix). * kind: The kind of issue (bug, enhancement, or proposal). """ + owner = owner or self.bitbucket.username repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('CREATE_ISSUE', username=self.bitbucket.username, repo_slug=repo_slug) + url = self.bitbucket.url('CREATE_ISSUE', username=owner, repo_slug=repo_slug) return self.bitbucket.dispatch('POST', url, auth=self.bitbucket.auth, **kwargs) - def update(self, issue_id, repo_slug=None, **kwargs): + def update(self, issue_id, repo_slug=None, owner=None, **kwargs): """ Update an issue to one of your repositories. Each issue require a different set of attributes, @@ -87,13 +90,15 @@ def update(self, issue_id, repo_slug=None, **kwargs): * status: The status of the issue (new, open, resolved, on hold, invalid, duplicate, or wontfix). * kind: The kind of issue (bug, enhancement, or proposal). """ + owner = owner or self.bitbucket.username repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('UPDATE_ISSUE', username=self.bitbucket.username, repo_slug=repo_slug, issue_id=issue_id) + url = self.bitbucket.url('UPDATE_ISSUE', username=owner, repo_slug=repo_slug, issue_id=issue_id) return self.bitbucket.dispatch('PUT', url, auth=self.bitbucket.auth, **kwargs) - def delete(self, issue_id, repo_slug=None): + def delete(self, issue_id, repo_slug=None, owner=None): """ Delete an issue from one of your repositories. """ + owner = owner or self.bitbucket.username repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('DELETE_ISSUE', username=self.bitbucket.username, repo_slug=repo_slug, issue_id=issue_id) + url = self.bitbucket.url('DELETE_ISSUE', username=owner, repo_slug=repo_slug, issue_id=issue_id) return self.bitbucket.dispatch('DELETE', url, auth=self.bitbucket.auth) diff --git a/bitbucket/issue_comment.py b/bitbucket/issue_comment.py index f7c4661..2b742ef 100644 --- a/bitbucket/issue_comment.py +++ b/bitbucket/issue_comment.py @@ -18,63 +18,68 @@ def __init__(self, issue): self.bitbucket.URLS.update(URLS) self.issue_id = issue.issue_id - def all(self, issue_id=None, repo_slug=None): + def all(self, issue_id=None, repo_slug=None , owner=None): """ Get issue comments from one of your repositories. """ issue_id = issue_id or self.issue_id repo_slug = repo_slug or self.bitbucket.repo_slug or '' + owner = owner or self.bitbucket.username url = self.bitbucket.url('GET_COMMENTS', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, issue_id=issue_id) return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) - def get(self, comment_id, issue_id=None, repo_slug=None): + def get(self, comment_id, issue_id=None, repo_slug=None, owner=None): """ Get an issue from one of your repositories. """ issue_id = issue_id or self.issue_id + owner = owner or self.bitbucket.username repo_slug = repo_slug or self.bitbucket.repo_slug or '' url = self.bitbucket.url('GET_COMMENT', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, issue_id=issue_id, comment_id=comment_id) return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) - def create(self, issue_id=None, repo_slug=None, **kwargs): + def create(self, issue_id=None, repo_slug=None, owner=None, **kwargs): """ Add an issue comment to one of your repositories. Each issue comment require only the content data field the system autopopulate the rest. """ issue_id = issue_id or self.issue_id repo_slug = repo_slug or self.bitbucket.repo_slug or '' + owner = owner or self.bitbucket.username url = self.bitbucket.url('CREATE_COMMENT', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, issue_id=issue_id) return self.bitbucket.dispatch('POST', url, auth=self.bitbucket.auth, **kwargs) - def update(self, comment_id, issue_id=None, repo_slug=None, **kwargs): + def update(self, comment_id, issue_id=None, repo_slug=None, owner=None, **kwargs): """ Update an issue comment in one of your repositories. Each issue comment require only the content data field the system autopopulate the rest. """ issue_id = issue_id or self.issue_id repo_slug = repo_slug or self.bitbucket.repo_slug or '' + owner = owner or self.bitbucket.username url = self.bitbucket.url('UPDATE_COMMENT', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, issue_id=issue_id, comment_id=comment_id) return self.bitbucket.dispatch('PUT', url, auth=self.bitbucket.auth, **kwargs) - def delete(self, comment_id, issue_id=None, repo_slug=None): + def delete(self, comment_id, issue_id=None, repo_slug=None, owner=None): """ Delete an issue from one of your repositories. """ issue_id = issue_id or self.issue_id repo_slug = repo_slug or self.bitbucket.repo_slug or '' + owner = owner or self.bitbucket.username url = self.bitbucket.url('DELETE_COMMENT', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, issue_id=issue_id, comment_id=comment_id) diff --git a/bitbucket/pullrequest.py b/bitbucket/pullrequest.py new file mode 100644 index 0000000..400f75e --- /dev/null +++ b/bitbucket/pullrequest.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +from .pullrequest_comment import PullRequestComment + + +URLS = { + # Issues + 'GET_PULLREQUESTS': 'repositories/%(username)s/%(repo_slug)s/pullrequests/', + 'GET_PULLREQUEST': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(issue_id)s/', + 'CREATE_PULLREQUEST': 'repositories/%(username)s/%(repo_slug)s/pullrequests/', + 'UPDATE_PULLREQUEST': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(issue_id)s/', + 'DELETE_PULLREQUEST': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(issue_id)s/', +} + + +class PullRequest(object): + """ This class provide issue-related methods to Bitbucket objects.""" + + def __init__(self, bitbucket, pullrequest_id=None): + self.bitbucket = bitbucket + self.bitbucket.URLS.update(URLS) + self.pullrequest_id = pullrequest_id + self.comment = PullRequestComment(self) + + @property + def pullrequest_id(self): + """Your repository slug name.""" + return self._pullrequest_id + + @pullrequest_id.setter + def pullrequest_id(self, value): + if value: + self._pullrequest_id = int(value) + elif value is None: + self._pullrequest_id = None + + @pullrequest_id.deleter + def pullrequest_id(self): + del self._pullrequest_id + + def all(self, repo_slug=None, params=None, owner=None): + """ Get PullRequests from one of your repositories. + """ + owner = owner or self.bitbucket.username + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url_v2('GET_PULLREQUESTS', username=owner, repo_slug=repo_slug) + return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth, params=params) + + def get(self, pullrequest_id, repo_slug=None, owner=None): + """ Get a PullRequest from one of your repositories. + """ + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + owner = owner or self.bitbucket.username + url = self.bitbucket.url_v2('GET_PULLREQUEST', username=owner, repo_slug=repo_slug, issue_id=pullrequest_id) + return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) + + def create(self, repo_slug=None, owner=None, **kwargs): + """ + Add a PullRequest to one of your repositories. + Each issue require a different set of attributes, + you can pass them as keyword arguments (attributename='attributevalue'). + Attributes are: + + * title: A string representing the request title. + * description: The description of the pull request. + * name: + * milestone: The milestone associated with the issue. + * version: The version associated with the issue. + * responsible: The username of the person responsible for the issue. + * status: The status of the issue (new, open, resolved, on hold, invalid, duplicate, or wontfix). + * kind: The kind of issue (bug, enhancement, or proposal). + """ + owner = owner or self.bitbucket.username + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url_v2('CREATE_PULLREQUEST', username=owner, repo_slug=repo_slug) + return self.bitbucket.dispatch('POST', url, auth=self.bitbucket.auth, **kwargs) + + def update(self, issue_id, repo_slug=None, owner=None, **kwargs): + """ + Update an issue to one of your repositories. + Each issue require a different set of attributes, + you can pass them as keyword arguments (attributename='attributevalue'). + Attributes are: + + * title: The title of the new issue. + * content: The content of the new issue. + * component: The component associated with the issue. + * milestone: The milestone associated with the issue. + * version: The version associated with the issue. + * responsible: The username of the person responsible for the issue. + * status: The status of the issue (new, open, resolved, on hold, invalid, duplicate, or wontfix). + * kind: The kind of issue (bug, enhancement, or proposal). + """ + owner = owner or self.bitbucket.username + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url_v2('UPDATE_PULLREQUEST', username=owner, repo_slug=repo_slug, issue_id=issue_id) + return self.bitbucket.dispatch('PUT', url, auth=self.bitbucket.auth, **kwargs) + + def delete(self, issue_id, repo_slug=None, owner=None): + """ Delete an issue from one of your repositories. + """ + owner = owner or self.bitbucket.username + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url_v2('DELETE_PULLREQUEST', username=owner, repo_slug=repo_slug, issue_id=issue_id) + return self.bitbucket.dispatch('DELETE', url, auth=self.bitbucket.auth) diff --git a/bitbucket/pullrequest_comment.py b/bitbucket/pullrequest_comment.py new file mode 100644 index 0000000..8158a18 --- /dev/null +++ b/bitbucket/pullrequest_comment.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +URLS = { + # Issue comments + 'GET_COMMENTS': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(pullrequest_id)s/comments/', + 'GET_COMMENT': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(pullrequest_id)s/comments/%(comment_id)s/', + 'CREATE_COMMENT': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(pullrequest_id)s/comments/', + 'UPDATE_COMMENT': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(pullrequest_id)s/comments/%(comment_id)s/', + 'DELETE_COMMENT': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(pullrequest_id)s/comments/%(comment_id)s/', +} + + +class PullRequestComment(object): + """ This class provide PullRequest's comments related methods to Bitbucket objects.""" + + def __init__(self, pullrequest): + self.pullrequest = pullrequest + self.bitbucket = self.pullrequest.bitbucket + self.bitbucket.URLS.update(URLS) + self.pullrequest_id = pullrequest.pullrequest_id + + def all(self, pullrequest_id=None, repo_slug=None): + """ Get PullRequest comments from one of your repositories. + """ + pullrequest_id = pullrequest_id or self.pullrequest_id + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url('GET_COMMENTS', + username=self.bitbucket.username, + repo_slug=repo_slug, + pullrequest_id=pullrequest_id) + return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) + + def get(self, comment_id, pullrequest_id=None, repo_slug=None): + """ Get a PullRequest from one of your repositories. + """ + pullrequest_id = pullrequest_id or self.pullrequest_id + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url('GET_COMMENT', + username=self.bitbucket.username, + repo_slug=repo_slug, + pullrequest_id=pullrequest_id, + comment_id=comment_id) + return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) + + def create(self, pullrequest_id=None, repo_slug=None, **kwargs): + """ Add a PullRequest comment to one of your repositories. + Each PullRequest comment require only the content data field + the system autopopulate the rest. + """ + pullrequest_id = pullrequest_id or self.pullrequest_id + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url('CREATE_COMMENT', + username=self.bitbucket.username, + repo_slug=repo_slug, + pullrequest_id=pullrequest_id) + return self.bitbucket.dispatch('POST', url, auth=self.bitbucket.auth, **kwargs) + + def update(self, comment_id, pullrequest_id=None, repo_slug=None, **kwargs): + """ Update a PullRequest comment in one of your repositories. + Each PullRequest comment require only the content data field + the system autopopulate the rest. + """ + pullrequest_id = pullrequest_id or self.pullrequest_id + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url('UPDATE_COMMENT', + username=self.bitbucket.username, + repo_slug=repo_slug, + pullrequest_id=pullrequest_id, + comment_id=comment_id) + return self.bitbucket.dispatch('PUT', url, auth=self.bitbucket.auth, **kwargs) + + def delete(self, comment_id, pullrequest_id=None, repo_slug=None): + """ Delete a PullRequest from one of your repositories. + """ + pullrequest_id = pullrequest_id or self.pullrequest_id + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url('DELETE_COMMENT', + username=self.bitbucket.username, + repo_slug=repo_slug, + pullrequest_id=pullrequest_id, + comment_id=comment_id) + return self.bitbucket.dispatch('DELETE', url, auth=self.bitbucket.auth) diff --git a/bitbucket/pullrequest_diff.py b/bitbucket/pullrequest_diff.py new file mode 100644 index 0000000..aef6365 --- /dev/null +++ b/bitbucket/pullrequest_diff.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +URLS = { + # Get Diff + 'GET_DIFF': 'repositories/%(username)s/%(repo_slug)s/pullrequests/%(pullrequest_id)s/diff/', +} + + +class PullRequestDiff(object): + """ + This class provide PullRequest's changesets related methods + to Bitbucket objects. + """ + + def __init__(self, pullrequest): + self.pullrequest = pullrequest + self.bitbucket = self.pullrequest.bitbucket + self.bitbucket.URLS.update(URLS) + self.pullrequest_id = pullrequest.pullrequest_id + + def get(self, pullrequest_id=None, repo_slug=None): + """ + Get a PullRequest from one of your repositories. + """ + pullrequest_id = pullrequest_id or self.pullrequest_id + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + url = self.bitbucket.url_v2('GET_DIFF', + username=self.bitbucket.username, + repo_slug=repo_slug, + pullrequest_id=pullrequest_id) + + success, diff = self.bitbucket.dispatch('GET', url, + auth=self.bitbucket.auth) + if success: + # request succeed, return diff + return diff + else: + # request failed + return None diff --git a/bitbucket/repository.py b/bitbucket/repository.py index b900b9b..e8775b5 100644 --- a/bitbucket/repository.py +++ b/bitbucket/repository.py @@ -1,13 +1,18 @@ # -*- coding: utf-8 -*- +import json from tempfile import NamedTemporaryFile from zipfile import ZipFile +from pprint import pprint + URLS = { 'CREATE_REPO': 'repositories/', + 'CREATE_REPO_V2': 'repositories/%(username)s/%(repo_slug)s/', 'GET_REPO': 'repositories/%(username)s/%(repo_slug)s/', 'UPDATE_REPO': 'repositories/%(username)s/%(repo_slug)s/', 'DELETE_REPO': 'repositories/%(username)s/%(repo_slug)s/', + 'GET_USER_REPOS': 'user/repositories/', # Get archive 'GET_ARCHIVE': 'repositories/%(username)s/%(repo_slug)s/%(format)s/master/', } @@ -20,12 +25,13 @@ def __init__(self, bitbucket): self.bitbucket = bitbucket self.bitbucket.URLS.update(URLS) - def _get_files_in_dir(self, repo_slug=None, dir='/'): + def _get_files_in_dir(self, repo_slug=None, owner=None, dir='/'): repo_slug = repo_slug or self.bitbucket.repo_slug or '' + owner = owner or self.bitbucket.username dir = dir.lstrip('/') url = self.bitbucket.url( 'GET_ARCHIVE', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, format='src') dir_url = url + dir @@ -34,7 +40,7 @@ def _get_files_in_dir(self, repo_slug=None, dir='/'): repo_tree = response[1] url = self.bitbucket.url( 'GET_ARCHIVE', - username=self.bitbucket.username, + username=owner, repo_slug=repo_slug, format='raw') # Download all files in dir @@ -45,7 +51,7 @@ def _get_files_in_dir(self, repo_slug=None, dir='/'): # recursively download in dirs for directory in repo_tree['directories']: dir_path = '/'.join((dir, directory)) - self._get_files_in_dir(repo_slug=repo_slug, dir=dir_path) + self._get_files_in_dir(repo_slug=repo_slug, owner=owner, dir=dir_path) def public(self, username=None): """ Returns all public repositories from an user. @@ -60,9 +66,10 @@ def public(self, username=None): pass return response - def all(self): - """ Return own repositories.""" - url = self.bitbucket.url('GET_USER', username=self.bitbucket.username) + def all(self, owner=None): + """ Return all repositories owned by a given owner """ + owner = owner or self.bitbucket.username + url = self.bitbucket.url('GET_USER', username=owner) response = self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) try: return (response[0], response[1]['repositories']) @@ -70,45 +77,80 @@ def all(self): pass return response - def get(self, repo_slug=None): + def team(self, include_owned=True): + """Return all repositories for which the authenticated user is part of + the team. + + If include_owned is True (default), repos owned by the user are + included (and therefore is a superset of the repos returned by + all(). + + If include_owned is False, repositories only repositories + owned by other users are returned. + + """ + url = self.bitbucket.url('GET_USER_REPOS') + status, repos = self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) + if status and not include_owned: + return status,[r for r in repos if r['owner'] != self.bitbucket.username] + return status, repos + + def get(self, repo_slug=None, owner=None): """ Get a single repository on Bitbucket and return it.""" repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('GET_REPO', username=self.bitbucket.username, repo_slug=repo_slug) + owner = owner or self.bitbucket.username + url = self.bitbucket.url('GET_REPO', username=owner, repo_slug=repo_slug) return self.bitbucket.dispatch('GET', url, auth=self.bitbucket.auth) - def create(self, repo_name, scm='git', private=True, **kwargs): - """ Creates a new repository on own Bitbucket account and return it.""" - url = self.bitbucket.url('CREATE_REPO') + def create(self, repo_name=None, repo_slug=None, owner=None, scm='git', private=True, **kwargs): + """ Creates a new repository on a Bitbucket account and return it.""" + repo_slug = repo_slug or self.bitbucket.repo_slug or '' + + if owner: + url = self.bitbucket.url_v2('CREATE_REPO_V2', username=owner, repo_slug=repo_slug) + else: + owner = self.bitbucket.username + url = self.bitbucket.url('CREATE_REPO') + return self.bitbucket.dispatch('POST', url, auth=self.bitbucket.auth, name=repo_name, scm=scm, is_private=private, **kwargs) - def update(self, repo_slug=None, **kwargs): - """ Updates repository on own Bitbucket account and return it.""" + def update(self, repo_slug=None, owner=None, **kwargs): + """ Updates repository on a Bitbucket account and return it.""" repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('UPDATE_REPO', username=self.bitbucket.username, repo_slug=repo_slug) + owner = owner or self.bitbucket.username + url = self.bitbucket.url('UPDATE_REPO', username=owner, repo_slug=repo_slug) return self.bitbucket.dispatch('PUT', url, auth=self.bitbucket.auth, **kwargs) - def delete(self, repo_slug=None): + def delete(self, repo_slug=None, owner=None): """ Delete a repository on own Bitbucket account. Please use with caution as there is NO confimation and NO undo. """ repo_slug = repo_slug or self.bitbucket.repo_slug or '' - url = self.bitbucket.url('DELETE_REPO', username=self.bitbucket.username, repo_slug=repo_slug) + owner = owner or self.bitbucket.username + url = self.bitbucket.url_v2('DELETE_REPO', username=owner, repo_slug=repo_slug) return self.bitbucket.dispatch('DELETE', url, auth=self.bitbucket.auth) - def archive(self, repo_slug=None, format='zip', prefix=''): + def archive(self, repo_slug=None, owner=None, format='zip', prefix=''): """ Get one of your repositories and compress it as an archive. Return the path of the archive. format parameter is curently not supported. """ + owner = owner or self.bitbucket.username prefix = '%s'.lstrip('/') % prefix - self._get_files_in_dir(repo_slug=repo_slug, dir='/') + self._get_files_in_dir(repo_slug=repo_slug, owner=owner, dir='/') if self.bitbucket.repo_tree: with NamedTemporaryFile(delete=False) as archive: with ZipFile(archive, 'w') as zip_archive: - for name, file in self.bitbucket.repo_tree.items(): + for name, f in self.bitbucket.repo_tree.items(): with NamedTemporaryFile(delete=False) as temp_file: - temp_file.write(file.encode('utf-8')) + if isinstance(f, dict): + f = json.dumps(f) + + try: + temp_file.write(f.encode('utf-8')) + except UnicodeDecodeError: + temp_file.write(f) zip_archive.write(temp_file.name, prefix + name) return (True, archive.name) return (False, 'Could not archive your project.') diff --git a/bitbucket/tests/private/issue_comment.py b/bitbucket/tests/private/issue_comment.py index 822de94..5476640 100644 --- a/bitbucket/tests/private/issue_comment.py +++ b/bitbucket/tests/private/issue_comment.py @@ -13,6 +13,7 @@ def setUp(self): title=u'Test Issue Bitbucket API', content=u'Test Issue Bitbucket API', responsible=self.bb.username, + owner=self.bb.username, status=u'new', kind=u'bug',) # Save latest issue's id @@ -34,6 +35,7 @@ def _create_issue_comment(self): content = u'Test Issue comment Bitbucket API' # Test create an issue comment success, result = self.bb.issue.comment.create( + owner=self.bb.username, content=content) self.assertTrue(success) self.assertIsInstance(result, dict) @@ -43,7 +45,9 @@ def _create_issue_comment(self): def _get_issue_comment(self): # Test get an issue comment. - success, result = self.bb.issue.comment.get(comment_id=self.comment_id) + success, result = self.bb.issue.comment.get( + owner=self.bb.username, + comment_id=self.comment_id) self.assertTrue(success) self.assertIsInstance(result, dict) # Test get an invalid issue comment. @@ -54,6 +58,7 @@ def _update_issue_comment(self): # Test issue comment update. test_content = 'Test content' success, result = self.bb.issue.comment.update( + owner=self.bb.username, comment_id=self.comment_id, content=test_content) self.assertTrue(success) @@ -63,6 +68,7 @@ def _update_issue_comment(self): def _delete_issue_comment(self): # Test issue comment delete. success, result = self.bb.issue.comment.delete( + owner=self.bb.username, comment_id=self.comment_id) self.assertTrue(success) self.assertEqual(result, '')