diff options
Diffstat (limited to 'gitlab')
-rw-r--r-- | gitlab/__init__.py | 889 | ||||
-rw-r--r-- | gitlab/exceptions.py | 84 | ||||
-rw-r--r-- | gitlab/objects.py | 801 |
3 files changed, 900 insertions, 874 deletions
diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 9e9fac7..920c97a 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -20,14 +20,14 @@ from __future__ import division from __future__ import absolute_import import itertools import json -import sys import warnings import requests import six import gitlab.config - +from gitlab.exceptions import * # noqa +from gitlab.objects import * # noqa __title__ = 'python-gitlab' __version__ = '0.10' @@ -39,99 +39,14 @@ __copyright__ = 'Copyright 2013-2015 Gauvain Pocentek' warnings.simplefilter('always', DeprecationWarning) -class jsonEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, GitlabObject): - return obj.__dict__ - elif isinstance(obj, Gitlab): - return {'url': obj._url} - return json.JSONEncoder.default(self, obj) - - -class GitlabError(Exception): - def __init__(self, error_message="", response_code=None, - response_body=None): - - Exception.__init__(self, error_message) - # Http status code - self.response_code = response_code - # Full http response - self.response_body = response_body - # Parsed error message from gitlab - self.error_message = error_message - - def __str__(self): - if self.response_code is not None: - return "{0}: {1}".format(self.response_code, self.error_message) - else: - return "{0}".format(self.error_message) - - -class GitlabAuthenticationError(GitlabError): - pass - - -class GitlabConnectionError(GitlabError): - pass - - -class GitlabOperationError(GitlabError): - pass - - -class GitlabListError(GitlabOperationError): - pass - - -class GitlabGetError(GitlabOperationError): - pass - - -class GitlabCreateError(GitlabOperationError): - pass - - -class GitlabUpdateError(GitlabOperationError): - pass - - -class GitlabDeleteError(GitlabOperationError): - pass - - -class GitlabProtectError(GitlabOperationError): - pass - - -class GitlabTransferProjectError(GitlabOperationError): - pass - - -def _raise_error_from_response(response, error, expected_code=200): - """Tries to parse gitlab error message from response and raises error. - - Do nothing if the response status is the expected one. - - If response status code is 401, raises instead GitlabAuthenticationError. - - response: requests response object - error: Error-class to raise. Should be inherited from GitLabError - """ - - if expected_code == response.status_code: - return - - try: - message = response.json()['message'] - except (KeyError, ValueError): - message = response.content +def _sanitize(value): + if isinstance(value, six.string_types): + return value.replace('/', '%2F') + return value - if response.status_code == 401: - error = GitlabAuthenticationError - raise error(error_message=message, - response_code=response.status_code, - response_body=response.content) +def _sanitize_dict(src): + return dict((k, _sanitize(v)) for k, v in src.items()) class Gitlab(object): @@ -188,7 +103,7 @@ class Gitlab(object): data = json.dumps({'email': self.email, 'password': self.password}) r = self._raw_post('/session', data, content_type='application/json') - _raise_error_from_response(r, GitlabAuthenticationError, 201) + raise_error_from_response(r, GitlabAuthenticationError, 201) self.user = CurrentUser(self, r.json()) self.set_token(self.user.private_token) @@ -314,7 +229,7 @@ class Gitlab(object): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - _raise_error_from_response(r, GitlabListError) + raise_error_from_response(r, GitlabListError) cls = obj_class if obj_class._returnClass: @@ -370,7 +285,7 @@ class Gitlab(object): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - _raise_error_from_response(r, GitlabGetError) + raise_error_from_response(r, GitlabGetError) return r.json() def delete(self, obj, **kwargs): @@ -404,7 +319,7 @@ class Gitlab(object): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - _raise_error_from_response(r, GitlabDeleteError) + raise_error_from_response(r, GitlabDeleteError) return True def create(self, obj, **kwargs): @@ -434,7 +349,7 @@ class Gitlab(object): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - _raise_error_from_response(r, GitlabCreateError, 201) + raise_error_from_response(r, GitlabCreateError, 201) return r.json() def update(self, obj, **kwargs): @@ -463,7 +378,7 @@ class Gitlab(object): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % self._url) - _raise_error_from_response(r, GitlabUpdateError) + raise_error_from_response(r, GitlabUpdateError) return r.json() def Hook(self, id=None, **kwargs): @@ -502,7 +417,7 @@ class Gitlab(object): def _list_projects(self, url, **kwargs): r = self._raw_get(url, **kwargs) - _raise_error_from_response(r, GitlabListError) + raise_error_from_response(r, GitlabListError) l = [] for o in r.json(): @@ -577,777 +492,3 @@ class Gitlab(object): to write it on the server. """ return Team._get_list_or_object(self, id, **kwargs) - - -def _get_display_encoding(): - return sys.stdout.encoding or sys.getdefaultencoding() - - -def _sanitize(value): - if isinstance(value, six.string_types): - return value.replace('/', '%2F') - return value - - -def _sanitize_dict(src): - return dict((k, _sanitize(v)) for k, v in src.items()) - - -class GitlabObject(object): - """Base class for all classes that interface with GitLab - - Args: - gl (gitlab.Gitlab): GitLab server connection - data: If data is integer or string type, get object from GitLab - data: If data is dictionary, create new object locally. To save object - in GitLab, call save-method - kwargs: Arbitrary keyword arguments - """ - #: Url to use in GitLab for this object - _url = None - # Some objects (e.g. merge requests) have different urls for singular and - # plural - _urlPlural = None - _returnClass = None - _constructorTypes = None - #: Whether _get_list_or_object should return list or object when id is None - getListWhenNoId = True - - #: Tells if GitLab-api allows retrieving single objects - canGet = True - #: Tells if GitLab-api allows listing of objects - canList = True - #: Tells if GitLab-api allows creation of new objects - canCreate = True - #: Tells if GitLab-api allows updating object - canUpdate = True - #: Tells if GitLab-api allows deleting object - canDelete = True - #: Attributes that are required for constructing url - requiredUrlAttrs = [] - #: Attributes that are required when retrieving list of objects - requiredListAttrs = [] - #: Attributes that are required when retrieving single object - requiredGetAttrs = [] - #: Attributes that are required when deleting object - requiredDeleteAttrs = [] - #: Attributes that are required when creating a new object - requiredCreateAttrs = [] - #: Attributes that are optional when creating a new object - optionalCreateAttrs = [] - #: Attributes that are required when updating an object - requiredUpdateAttrs = None - #: Attributes that are optional when updating an object - optionalUpdateAttrs = None - #: Whether the object ID is required in the GET url - getRequiresId = True - - idAttr = 'id' - shortPrintAttr = None - - def _data_for_gitlab(self, extra_parameters={}): - data = {} - for attribute in itertools.chain(self.requiredCreateAttrs, - self.optionalCreateAttrs): - if hasattr(self, attribute): - data[attribute] = getattr(self, attribute) - - data.update(extra_parameters) - - return json.dumps(data) - - @classmethod - def list(cls, gl, **kwargs): - if not cls.canList: - raise NotImplementedError - - if not cls._url: - raise NotImplementedError - - return gl.list(cls, **kwargs) - - @classmethod - def get(cls, gl, id, **kwargs): - if cls.canGet is False: - raise NotImplementedError - elif cls.canGet is True: - return cls(gl, id, **kwargs) - elif cls.canGet == 'from_list': - for obj in cls.list(gl, **kwargs): - obj_id = getattr(obj, obj.idAttr) - if str(obj_id) == str(id): - return obj - - raise GitlabGetError("Object not found") - - @classmethod - def _get_list_or_object(cls, gl, id, **kwargs): - if id is None and cls.getListWhenNoId: - return cls.list(gl, **kwargs) - else: - return cls.get(gl, id, **kwargs) - - def _get_object(self, k, v): - if self._constructorTypes and k in self._constructorTypes: - return globals()[self._constructorTypes[k]](self.gitlab, v) - else: - return v - - def _set_from_dict(self, data): - for k, v in data.items(): - if isinstance(v, list): - self.__dict__[k] = [] - for i in v: - self.__dict__[k].append(self._get_object(k, i)) - elif v is None: - self.__dict__[k] = None - else: - self.__dict__[k] = self._get_object(k, v) - - def _create(self, **kwargs): - if not self.canCreate: - raise NotImplementedError - - json = self.gitlab.create(self, **kwargs) - self._set_from_dict(json) - self._from_api = True - - def _update(self, **kwargs): - if not self.canUpdate: - raise NotImplementedError - - json = self.gitlab.update(self, **kwargs) - self._set_from_dict(json) - - def save(self, **kwargs): - if self._from_api: - self._update(**kwargs) - else: - self._create(**kwargs) - - def delete(self, **kwargs): - if not self.canDelete: - raise NotImplementedError - - if not self._from_api: - raise GitlabDeleteError("Object not yet created") - - return self.gitlab.delete(self, **kwargs) - - @classmethod - def create(cls, gl, data, **kwargs): - if not cls.canCreate: - raise NotImplementedError - - obj = cls(gl, data, **kwargs) - obj.save() - - return obj - - def __init__(self, gl, data=None, **kwargs): - self._from_api = False - self.gitlab = gl - - if (data is None or isinstance(data, six.integer_types) or - isinstance(data, six.string_types)): - if not self.canGet: - raise NotImplementedError - data = self.gitlab.get(self.__class__, data, **kwargs) - self._from_api = True - - self._set_from_dict(data) - - if kwargs: - for k, v in kwargs.items(): - self.__dict__[k] = v - - # Special handling for api-objects that don't have id-number in api - # responses. Currently only Labels and Files - if not hasattr(self, "id"): - self.id = None - - def __str__(self): - return '%s => %s' % (type(self), str(self.__dict__)) - - def display(self, pretty): - if pretty: - self.pretty_print() - else: - self.short_print() - - def short_print(self, depth=0): - id = self.__dict__[self.idAttr] - print("%s%s: %s" % (" " * depth * 2, self.idAttr, id)) - if self.shortPrintAttr: - print("%s%s: %s" % (" " * depth * 2, - self.shortPrintAttr.replace('_', '-'), - self.__dict__[self.shortPrintAttr])) - - @staticmethod - def _obj_to_str(obj): - if isinstance(obj, dict): - s = ", ".join(["%s: %s" % - (x, GitlabObject._obj_to_str(y)) - for (x, y) in obj.items()]) - return "{ %s }" % s - elif isinstance(obj, list): - s = ", ".join([GitlabObject._obj_to_str(x) for x in obj]) - return "[ %s ]" % s - elif six.PY2 and isinstance(obj, six.text_type): - return obj.encode(_get_display_encoding(), "replace") - else: - return str(obj) - - def pretty_print(self, depth=0): - id = self.__dict__[self.idAttr] - print("%s%s: %s" % (" " * depth * 2, self.idAttr, id)) - for k in sorted(self.__dict__.keys()): - if k == self.idAttr or k == 'id': - continue - if k[0] == '_': - continue - v = self.__dict__[k] - pretty_k = k.replace('_', '-') - if six.PY2: - pretty_k = pretty_k.encode(_get_display_encoding(), "replace") - if isinstance(v, GitlabObject): - if depth == 0: - print("%s:" % pretty_k) - v.pretty_print(1) - else: - print("%s: %s" % (pretty_k, v.id)) - else: - if isinstance(v, Gitlab): - continue - v = GitlabObject._obj_to_str(v) - print("%s%s: %s" % (" " * depth * 2, pretty_k, v)) - - def json(self): - return json.dumps(self.__dict__, cls=jsonEncoder) - - -class UserKey(GitlabObject): - _url = '/users/%(user_id)s/keys' - canGet = 'from_list' - canUpdate = False - requiredUrlAttrs = ['user_id'] - requiredCreateAttrs = ['title', 'key'] - - -class User(GitlabObject): - _url = '/users' - shortPrintAttr = 'username' - # FIXME: password is required for create but not for update - requiredCreateAttrs = ['email', 'username', 'name'] - optionalCreateAttrs = ['password', 'skype', 'linkedin', 'twitter', - 'projects_limit', 'extern_uid', 'provider', - 'bio', 'admin', 'can_create_group', 'website_url', - 'confirm'] - - def _data_for_gitlab(self, extra_parameters={}): - if hasattr(self, 'confirm'): - self.confirm = str(self.confirm).lower() - return super(User, self)._data_for_gitlab(extra_parameters) - - def Key(self, id=None, **kwargs): - return UserKey._get_list_or_object(self.gitlab, id, - user_id=self.id, - **kwargs) - - -class CurrentUserKey(GitlabObject): - _url = '/user/keys' - canUpdate = False - shortPrintAttr = 'title' - requiredCreateAttrs = ['title', 'key'] - - -class CurrentUser(GitlabObject): - _url = '/user' - canList = False - canCreate = False - canUpdate = False - canDelete = False - shortPrintAttr = 'username' - - def Key(self, id=None, **kwargs): - return CurrentUserKey._get_list_or_object(self.gitlab, id, **kwargs) - - -class GroupMember(GitlabObject): - _url = '/groups/%(group_id)s/members' - canGet = 'from_list' - requiredUrlAttrs = ['group_id'] - requiredCreateAttrs = ['access_level', 'user_id'] - requiredUpdateAttrs = ['access_level'] - shortPrintAttr = 'username' - - def _update(self, **kwargs): - self.user_id = self.id - super(GroupMember, self)._update(**kwargs) - - -class Group(GitlabObject): - _url = '/groups' - canUpdate = False - _constructorTypes = {'projects': 'Project'} - requiredCreateAttrs = ['name', 'path'] - shortPrintAttr = 'name' - - GUEST_ACCESS = 10 - REPORTER_ACCESS = 20 - DEVELOPER_ACCESS = 30 - MASTER_ACCESS = 40 - OWNER_ACCESS = 50 - - def Member(self, id=None, **kwargs): - return GroupMember._get_list_or_object(self.gitlab, id, - group_id=self.id, - **kwargs) - - def transfer_project(self, id, **kwargs): - url = '/groups/%d/projects/%d' % (self.id, id) - r = self.gitlab._raw_post(url, None, **kwargs) - _raise_error_from_response(r, GitlabTransferProjectError, 201) - - -class Hook(GitlabObject): - _url = '/hooks' - canUpdate = False - requiredCreateAttrs = ['url'] - shortPrintAttr = 'url' - - -class Issue(GitlabObject): - _url = '/issues' - _constructorTypes = {'author': 'User', 'assignee': 'User', - 'milestone': 'ProjectMilestone'} - canGet = 'from_list' - canDelete = False - canUpdate = False - canCreate = False - shortPrintAttr = 'title' - - -class ProjectBranch(GitlabObject): - _url = '/projects/%(project_id)s/repository/branches' - _constructorTypes = {'author': 'User', "committer": "User"} - - idAttr = 'name' - canUpdate = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['branch_name', 'ref'] - _constructorTypes = {'commit': 'ProjectCommit'} - - def protect(self, protect=True, **kwargs): - url = self._url % {'project_id': self.project_id} - action = 'protect' if protect else 'unprotect' - url = "%s/%s/%s" % (url, self.name, action) - r = self.gitlab._raw_put(url, data=None, content_type=None, **kwargs) - _raise_error_from_response(r, GitlabProtectError) - - if protect: - self.protected = protect - else: - del self.protected - - def unprotect(self, **kwargs): - self.protect(False, **kwargs) - - -class ProjectCommit(GitlabObject): - _url = '/projects/%(project_id)s/repository/commits' - canDelete = False - canUpdate = False - canCreate = False - requiredUrlAttrs = ['project_id'] - shortPrintAttr = 'title' - - def diff(self, **kwargs): - url = ('/projects/%(project_id)s/repository/commits/%(commit_id)s/diff' - % {'project_id': self.project_id, 'commit_id': self.id}) - r = self.gitlab._raw_get(url, **kwargs) - _raise_error_from_response(r, GitlabGetError) - - return r.json() - - def blob(self, filepath, **kwargs): - url = ('/projects/%(project_id)s/repository/blobs/%(commit_id)s' % - {'project_id': self.project_id, 'commit_id': self.id}) - url += '?filepath=%s' % filepath - r = self.gitlab._raw_get(url, **kwargs) - - _raise_error_from_response(r, GitlabGetError) - - return r.content - - -class ProjectKey(GitlabObject): - _url = '/projects/%(project_id)s/keys' - canUpdate = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['title', 'key'] - - -class ProjectEvent(GitlabObject): - _url = '/projects/%(project_id)s/events' - canGet = 'from_list' - canDelete = False - canUpdate = False - canCreate = False - requiredUrlAttrs = ['project_id'] - shortPrintAttr = 'target_title' - - -class ProjectHook(GitlabObject): - _url = '/projects/%(project_id)s/hooks' - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['url'] - optionalCreateAttrs = ['push_events', 'issues_events', - 'merge_requests_events', 'tag_push_events'] - shortPrintAttr = 'url' - - -class ProjectIssueNote(GitlabObject): - _url = '/projects/%(project_id)s/issues/%(issue_id)s/notes' - _constructorTypes = {'author': 'User'} - canUpdate = False - canDelete = False - requiredUrlAttrs = ['project_id', 'issue_id'] - requiredCreateAttrs = ['body'] - - -class ProjectIssue(GitlabObject): - _url = '/projects/%(project_id)s/issues/' - _constructorTypes = {'author': 'User', 'assignee': 'User', - 'milestone': 'ProjectMilestone'} - canDelete = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['title'] - # FIXME: state_event is only valid with update - optionalCreateAttrs = ['description', 'assignee_id', 'milestone_id', - 'labels', 'state_event'] - - shortPrintAttr = 'title' - - def _data_for_gitlab(self, extra_parameters={}): - # Gitlab-api returns labels in a json list and takes them in a - # comma separated list. - if hasattr(self, "labels"): - if (self.labels is not None and - not isinstance(self.labels, six.string_types)): - labels = ", ".join(self.labels) - extra_parameters['labels'] = labels - - return super(ProjectIssue, self)._data_for_gitlab(extra_parameters) - - def Note(self, id=None, **kwargs): - return ProjectIssueNote._get_list_or_object(self.gitlab, id, - project_id=self.project_id, - issue_id=self.id, - **kwargs) - - -class ProjectMember(GitlabObject): - _url = '/projects/%(project_id)s/members' - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['access_level', 'user_id'] - shortPrintAttr = 'username' - - -class ProjectNote(GitlabObject): - _url = '/projects/%(project_id)s/notes' - _constructorTypes = {'author': 'User'} - canUpdate = False - canDelete = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['body'] - - -class ProjectTag(GitlabObject): - _url = '/projects/%(project_id)s/repository/tags' - idAttr = 'name' - canGet = 'from_list' - canDelete = False - canUpdate = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['tag_name', 'ref'] - optionalCreateAttrs = ['message'] - shortPrintAttr = 'name' - - -class ProjectMergeRequestNote(GitlabObject): - _url = '/projects/%(project_id)s/merge_requests/%(merge_request_id)s/notes' - _constructorTypes = {'author': 'User'} - canDelete = False - requiredUrlAttrs = ['project_id', 'merge_request_id'] - requiredCreateAttrs = ['body'] - - -class ProjectMergeRequest(GitlabObject): - _url = '/projects/%(project_id)s/merge_request' - _urlPlural = '/projects/%(project_id)s/merge_requests' - _constructorTypes = {'author': 'User', 'assignee': 'User'} - canDelete = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['source_branch', 'target_branch', 'title'] - optionalCreateAttrs = ['assignee_id'] - - def Note(self, id=None, **kwargs): - return ProjectMergeRequestNote._get_list_or_object( - self.gitlab, id, project_id=self.project_id, - merge_request_id=self.id, **kwargs) - - -class ProjectMilestone(GitlabObject): - _url = '/projects/%(project_id)s/milestones' - canDelete = False - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['title'] - optionalCreateAttrs = ['description', 'due_date', 'state_event'] - shortPrintAttr = 'title' - - -class ProjectLabel(GitlabObject): - _url = '/projects/%(project_id)s/labels' - requiredUrlAttrs = ['project_id'] - idAttr = 'name' - requiredDeleteAttrs = ['name'] - requiredCreateAttrs = ['name', 'color'] - requiredUpdateAttrs = [] - # FIXME: new_name is only valid with update - optionalCreateAttrs = ['new_name'] - - -class ProjectFile(GitlabObject): - _url = '/projects/%(project_id)s/repository/files' - canList = False - requiredUrlAttrs = ['project_id'] - requiredGetAttrs = ['file_path', 'ref'] - requiredCreateAttrs = ['file_path', 'branch_name', 'content', - 'commit_message'] - optionalCreateAttrs = ['encoding'] - requiredDeleteAttrs = ['branch_name', 'commit_message'] - getListWhenNoId = False - shortPrintAttr = 'file_path' - getRequiresId = False - - -class ProjectSnippetNote(GitlabObject): - _url = '/projects/%(project_id)s/snippets/%(snippet_id)s/notes' - _constructorTypes = {'author': 'User'} - canUpdate = False - canDelete = False - requiredUrlAttrs = ['project_id', 'snippet_id'] - requiredCreateAttrs = ['body'] - - -class ProjectSnippet(GitlabObject): - _url = '/projects/%(project_id)s/snippets' - _constructorTypes = {'author': 'User'} - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['title', 'file_name', 'code'] - optionalCreateAttrs = ['lifetime'] - shortPrintAttr = 'title' - - def Content(self, **kwargs): - url = ("/projects/%(project_id)s/snippets/%(snippet_id)s/raw" % - {'project_id': self.project_id, 'snippet_id': self.id}) - r = self.gitlab._raw_get(url, **kwargs) - _raise_error_from_response(r, GitlabGetError) - return r.content - - def Note(self, id=None, **kwargs): - return ProjectSnippetNote._get_list_or_object( - self.gitlab, id, - project_id=self.project_id, - snippet_id=self.id, - **kwargs) - - -class UserProject(GitlabObject): - _url = '/projects/user/%(user_id)s' - _constructorTypes = {'owner': 'User', 'namespace': 'Group'} - canUpdate = False - canDelete = False - canList = False - canGet = False - requiredUrlAttrs = ['user_id'] - requiredCreateAttrs = ['name'] - optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', - 'merge_requests_enabled', 'wiki_enabled', - 'snippets_enabled', 'public', 'visibility_level', - 'description'] - - -class Project(GitlabObject): - _url = '/projects' - _constructorTypes = {'owner': 'User', 'namespace': 'Group'} - requiredCreateAttrs = ['name'] - requiredUpdateAttrs = [] - optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', - 'merge_requests_enabled', 'wiki_enabled', - 'snippets_enabled', 'public', 'visibility_level', - 'namespace_id', 'description', 'path', 'import_url'] - - shortPrintAttr = 'path' - - def Branch(self, id=None, **kwargs): - return ProjectBranch._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Commit(self, id=None, **kwargs): - return ProjectCommit._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Event(self, id=None, **kwargs): - return ProjectEvent._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Hook(self, id=None, **kwargs): - return ProjectHook._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Key(self, id=None, **kwargs): - return ProjectKey._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Issue(self, id=None, **kwargs): - return ProjectIssue._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Member(self, id=None, **kwargs): - return ProjectMember._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def MergeRequest(self, id=None, **kwargs): - return ProjectMergeRequest._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Milestone(self, id=None, **kwargs): - return ProjectMilestone._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Note(self, id=None, **kwargs): - return ProjectNote._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Snippet(self, id=None, **kwargs): - return ProjectSnippet._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Label(self, id=None, **kwargs): - return ProjectLabel._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def File(self, id=None, **kwargs): - return ProjectFile._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def Tag(self, id=None, **kwargs): - return ProjectTag._get_list_or_object(self.gitlab, id, - project_id=self.id, - **kwargs) - - def tree(self, path='', ref_name='', **kwargs): - url = "%s/%s/repository/tree" % (self._url, self.id) - url += '?path=%s&ref_name=%s' % (path, ref_name) - r = self.gitlab._raw_get(url, **kwargs) - _raise_error_from_response(r, GitlabGetError) - return r.json() - - def blob(self, sha, filepath, **kwargs): - url = "%s/%s/repository/blobs/%s" % (self._url, self.id, sha) - url += '?filepath=%s' % (filepath) - r = self.gitlab._raw_get(url, **kwargs) - _raise_error_from_response(r, GitlabGetError) - return r.content - - def archive(self, sha=None, **kwargs): - url = '/projects/%s/repository/archive' % self.id - if sha: - url += '?sha=%s' % sha - r = self.gitlab._raw_get(url, **kwargs) - _raise_error_from_response(r, GitlabGetError) - return r.content - - def create_file(self, path, branch, content, message, **kwargs): - """Creates file in project repository - - Args: - path (str): Full path to new file - branch (str): The name of branch - content (str): Content of the file - message (str): Commit message - kwargs: Arbitrary keyword arguments - - Raises: - GitlabCreateError: Operation failed - GitlabConnectionError: Connection to GitLab-server failed - """ - url = "/projects/%s/repository/files" % self.id - url += ("?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % - (path, branch, content, message)) - r = self.gitlab._raw_post(url, data=None, content_type=None, **kwargs) - _raise_error_from_response(r, GitlabCreateError, 201) - - def update_file(self, path, branch, content, message, **kwargs): - url = "/projects/%s/repository/files" % self.id - url += ("?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % - (path, branch, content, message)) - r = self.gitlab._raw_put(url, data=None, content_type=None, **kwargs) - _raise_error_from_response(r, GitlabUpdateError) - - def delete_file(self, path, branch, message, **kwargs): - url = "/projects/%s/repository/files" % self.id - url += ("?file_path=%s&branch_name=%s&commit_message=%s" % - (path, branch, message)) - r = self.gitlab._raw_delete(url, **kwargs) - _raise_error_from_response(r, GitlabDeleteError) - - -class TeamMember(GitlabObject): - _url = '/user_teams/%(team_id)s/members' - canUpdate = False - requiredUrlAttrs = ['teamd_id'] - requiredCreateAttrs = ['access_level'] - shortPrintAttr = 'username' - - -class TeamProject(GitlabObject): - _url = '/user_teams/%(team_id)s/projects' - _constructorTypes = {'owner': 'User', 'namespace': 'Group'} - canUpdate = False - requiredCreateAttrs = ['greatest_access_level'] - requiredUrlAttrs = ['team_id'] - shortPrintAttr = 'name' - - -class Team(GitlabObject): - _url = '/user_teams' - shortPrintAttr = 'name' - requiredCreateAttrs = ['name', 'path'] - canUpdate = False - - def Member(self, id=None, **kwargs): - return TeamMember._get_list_or_object(self.gitlab, id, - team_id=self.id, - **kwargs) - - def Project(self, id=None, **kwargs): - return TeamProject._get_list_or_object(self.gitlab, id, - team_id=self.id, - **kwargs) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py new file mode 100644 index 0000000..f538031 --- /dev/null +++ b/gitlab/exceptions.py @@ -0,0 +1,84 @@ +class GitlabError(Exception): + def __init__(self, error_message="", response_code=None, + response_body=None): + + Exception.__init__(self, error_message) + # Http status code + self.response_code = response_code + # Full http response + self.response_body = response_body + # Parsed error message from gitlab + self.error_message = error_message + + def __str__(self): + if self.response_code is not None: + return "{0}: {1}".format(self.response_code, self.error_message) + else: + return "{0}".format(self.error_message) + + +class GitlabAuthenticationError(GitlabError): + pass + + +class GitlabConnectionError(GitlabError): + pass + + +class GitlabOperationError(GitlabError): + pass + + +class GitlabListError(GitlabOperationError): + pass + + +class GitlabGetError(GitlabOperationError): + pass + + +class GitlabCreateError(GitlabOperationError): + pass + + +class GitlabUpdateError(GitlabOperationError): + pass + + +class GitlabDeleteError(GitlabOperationError): + pass + + +class GitlabProtectError(GitlabOperationError): + pass + + +class GitlabTransferProjectError(GitlabOperationError): + pass + + +def raise_error_from_response(response, error, expected_code=200): + """Tries to parse gitlab error message from response and raises error. + + Do nothing if the response status is the expected one. + + If response status code is 401, raises instead GitlabAuthenticationError. + + response: requests response object + error: Error-class to raise. Should be inherited from GitLabError + """ + + if expected_code == response.status_code: + return + + try: + message = response.json()['message'] + except (KeyError, ValueError): + message = response.content + + if response.status_code == 401: + error = GitlabAuthenticationError + + raise error(error_message=message, + response_code=response.status_code, + response_body=response.content) diff --git a/gitlab/objects.py b/gitlab/objects.py new file mode 100644 index 0000000..1060564 --- /dev/null +++ b/gitlab/objects.py @@ -0,0 +1,801 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2015 Gauvain Pocentek <gauvain@pocentek.net> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +import itertools +import json +import sys + +import six + +from gitlab.exceptions import * # noqa + + +class jsonEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, GitlabObject): + return obj.__dict__ + elif isinstance(obj, Gitlab): + return {'url': obj._url} + return json.JSONEncoder.default(self, obj) + + +class GitlabObject(object): + """Base class for all classes that interface with GitLab + + Args: + gl (gitlab.Gitlab): GitLab server connection + data: If data is integer or string type, get object from GitLab + data: If data is dictionary, create new object locally. To save object + in GitLab, call save-method + kwargs: Arbitrary keyword arguments + """ + #: Url to use in GitLab for this object + _url = None + # Some objects (e.g. merge requests) have different urls for singular and + # plural + _urlPlural = None + _returnClass = None + _constructorTypes = None + #: Whether _get_list_or_object should return list or object when id is None + getListWhenNoId = True + + #: Tells if GitLab-api allows retrieving single objects + canGet = True + #: Tells if GitLab-api allows listing of objects + canList = True + #: Tells if GitLab-api allows creation of new objects + canCreate = True + #: Tells if GitLab-api allows updating object + canUpdate = True + #: Tells if GitLab-api allows deleting object + canDelete = True + #: Attributes that are required for constructing url + requiredUrlAttrs = [] + #: Attributes that are required when retrieving list of objects + requiredListAttrs = [] + #: Attributes that are required when retrieving single object + requiredGetAttrs = [] + #: Attributes that are required when deleting object + requiredDeleteAttrs = [] + #: Attributes that are required when creating a new object + requiredCreateAttrs = [] + #: Attributes that are optional when creating a new object + optionalCreateAttrs = [] + #: Attributes that are required when updating an object + requiredUpdateAttrs = None + #: Attributes that are optional when updating an object + optionalUpdateAttrs = None + #: Whether the object ID is required in the GET url + getRequiresId = True + + idAttr = 'id' + shortPrintAttr = None + + def _data_for_gitlab(self, extra_parameters={}): + data = {} + for attribute in itertools.chain(self.requiredCreateAttrs, + self.optionalCreateAttrs): + if hasattr(self, attribute): + data[attribute] = getattr(self, attribute) + + data.update(extra_parameters) + + return json.dumps(data) + + @classmethod + def list(cls, gl, **kwargs): + if not cls.canList: + raise NotImplementedError + + if not cls._url: + raise NotImplementedError + + return gl.list(cls, **kwargs) + + @classmethod + def get(cls, gl, id, **kwargs): + if cls.canGet is False: + raise NotImplementedError + elif cls.canGet is True: + return cls(gl, id, **kwargs) + elif cls.canGet == 'from_list': + for obj in cls.list(gl, **kwargs): + obj_id = getattr(obj, obj.idAttr) + if str(obj_id) == str(id): + return obj + + raise GitlabGetError("Object not found") + + @classmethod + def _get_list_or_object(cls, gl, id, **kwargs): + if id is None and cls.getListWhenNoId: + return cls.list(gl, **kwargs) + else: + return cls.get(gl, id, **kwargs) + + def _get_object(self, k, v): + if self._constructorTypes and k in self._constructorTypes: + return globals()[self._constructorTypes[k]](self.gitlab, v) + else: + return v + + def _set_from_dict(self, data): + for k, v in data.items(): + if isinstance(v, list): + self.__dict__[k] = [] + for i in v: + self.__dict__[k].append(self._get_object(k, i)) + elif v is None: + self.__dict__[k] = None + else: + self.__dict__[k] = self._get_object(k, v) + + def _create(self, **kwargs): + if not self.canCreate: + raise NotImplementedError + + json = self.gitlab.create(self, **kwargs) + self._set_from_dict(json) + self._from_api = True + + def _update(self, **kwargs): + if not self.canUpdate: + raise NotImplementedError + + json = self.gitlab.update(self, **kwargs) + self._set_from_dict(json) + + def save(self, **kwargs): + if self._from_api: + self._update(**kwargs) + else: + self._create(**kwargs) + + def delete(self, **kwargs): + if not self.canDelete: + raise NotImplementedError + + if not self._from_api: + raise GitlabDeleteError("Object not yet created") + + return self.gitlab.delete(self, **kwargs) + + @classmethod + def create(cls, gl, data, **kwargs): + if not cls.canCreate: + raise NotImplementedError + + obj = cls(gl, data, **kwargs) + obj.save() + + return obj + + def __init__(self, gl, data=None, **kwargs): + self._from_api = False + self.gitlab = gl + + if (data is None or isinstance(data, six.integer_types) or + isinstance(data, six.string_types)): + if not self.canGet: + raise NotImplementedError + data = self.gitlab.get(self.__class__, data, **kwargs) + self._from_api = True + + self._set_from_dict(data) + + if kwargs: + for k, v in kwargs.items(): + self.__dict__[k] = v + + # Special handling for api-objects that don't have id-number in api + # responses. Currently only Labels and Files + if not hasattr(self, "id"): + self.id = None + + def __str__(self): + return '%s => %s' % (type(self), str(self.__dict__)) + + def display(self, pretty): + if pretty: + self.pretty_print() + else: + self.short_print() + + def short_print(self, depth=0): + id = self.__dict__[self.idAttr] + print("%s%s: %s" % (" " * depth * 2, self.idAttr, id)) + if self.shortPrintAttr: + print("%s%s: %s" % (" " * depth * 2, + self.shortPrintAttr.replace('_', '-'), + self.__dict__[self.shortPrintAttr])) + + @staticmethod + def _get_display_encoding(): + return sys.stdout.encoding or sys.getdefaultencoding() + + @staticmethod + def _obj_to_str(obj): + if isinstance(obj, dict): + s = ", ".join(["%s: %s" % + (x, GitlabObject._obj_to_str(y)) + for (x, y) in obj.items()]) + return "{ %s }" % s + elif isinstance(obj, list): + s = ", ".join([GitlabObject._obj_to_str(x) for x in obj]) + return "[ %s ]" % s + elif six.PY2 and isinstance(obj, six.text_type): + return obj.encode(GitlabObject._get_display_encoding(), "replace") + else: + return str(obj) + + def pretty_print(self, depth=0): + id = self.__dict__[self.idAttr] + print("%s%s: %s" % (" " * depth * 2, self.idAttr, id)) + for k in sorted(self.__dict__.keys()): + if k == self.idAttr or k == 'id': + continue + if k[0] == '_': + continue + v = self.__dict__[k] + pretty_k = k.replace('_', '-') + if six.PY2: + pretty_k = pretty_k.encode( + GitlabObject._get_display_encoding(), "replace") + if isinstance(v, GitlabObject): + if depth == 0: + print("%s:" % pretty_k) + v.pretty_print(1) + else: + print("%s: %s" % (pretty_k, v.id)) + else: + if hasattr(v, __name__) and v.__name__ == 'Gitlab': + continue + v = GitlabObject._obj_to_str(v) + print("%s%s: %s" % (" " * depth * 2, pretty_k, v)) + + def json(self): + return json.dumps(self.__dict__, cls=jsonEncoder) + + +class UserKey(GitlabObject): + _url = '/users/%(user_id)s/keys' + canGet = 'from_list' + canUpdate = False + requiredUrlAttrs = ['user_id'] + requiredCreateAttrs = ['title', 'key'] + + +class User(GitlabObject): + _url = '/users' + shortPrintAttr = 'username' + # FIXME: password is required for create but not for update + requiredCreateAttrs = ['email', 'username', 'name'] + optionalCreateAttrs = ['password', 'skype', 'linkedin', 'twitter', + 'projects_limit', 'extern_uid', 'provider', + 'bio', 'admin', 'can_create_group', 'website_url', + 'confirm'] + + def _data_for_gitlab(self, extra_parameters={}): + if hasattr(self, 'confirm'): + self.confirm = str(self.confirm).lower() + return super(User, self)._data_for_gitlab(extra_parameters) + + def Key(self, id=None, **kwargs): + return UserKey._get_list_or_object(self.gitlab, id, + user_id=self.id, + **kwargs) + + +class CurrentUserKey(GitlabObject): + _url = '/user/keys' + canUpdate = False + shortPrintAttr = 'title' + requiredCreateAttrs = ['title', 'key'] + + +class CurrentUser(GitlabObject): + _url = '/user' + canList = False + canCreate = False + canUpdate = False + canDelete = False + shortPrintAttr = 'username' + + def Key(self, id=None, **kwargs): + return CurrentUserKey._get_list_or_object(self.gitlab, id, **kwargs) + + +class GroupMember(GitlabObject): + _url = '/groups/%(group_id)s/members' + canGet = 'from_list' + requiredUrlAttrs = ['group_id'] + requiredCreateAttrs = ['access_level', 'user_id'] + requiredUpdateAttrs = ['access_level'] + shortPrintAttr = 'username' + + def _update(self, **kwargs): + self.user_id = self.id + super(GroupMember, self)._update(**kwargs) + + +class Group(GitlabObject): + _url = '/groups' + canUpdate = False + _constructorTypes = {'projects': 'Project'} + requiredCreateAttrs = ['name', 'path'] + shortPrintAttr = 'name' + + GUEST_ACCESS = 10 + REPORTER_ACCESS = 20 + DEVELOPER_ACCESS = 30 + MASTER_ACCESS = 40 + OWNER_ACCESS = 50 + + def Member(self, id=None, **kwargs): + return GroupMember._get_list_or_object(self.gitlab, id, + group_id=self.id, + **kwargs) + + def transfer_project(self, id, **kwargs): + url = '/groups/%d/projects/%d' % (self.id, id) + r = self.gitlab._raw_post(url, None, **kwargs) + raise_error_from_response(r, GitlabTransferProjectError, 201) + + +class Hook(GitlabObject): + _url = '/hooks' + canUpdate = False + requiredCreateAttrs = ['url'] + shortPrintAttr = 'url' + + +class Issue(GitlabObject): + _url = '/issues' + _constructorTypes = {'author': 'User', 'assignee': 'User', + 'milestone': 'ProjectMilestone'} + canGet = 'from_list' + canDelete = False + canUpdate = False + canCreate = False + shortPrintAttr = 'title' + + +class ProjectBranch(GitlabObject): + _url = '/projects/%(project_id)s/repository/branches' + _constructorTypes = {'author': 'User', "committer": "User"} + + idAttr = 'name' + canUpdate = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['branch_name', 'ref'] + _constructorTypes = {'commit': 'ProjectCommit'} + + def protect(self, protect=True, **kwargs): + url = self._url % {'project_id': self.project_id} + action = 'protect' if protect else 'unprotect' + url = "%s/%s/%s" % (url, self.name, action) + r = self.gitlab._raw_put(url, data=None, content_type=None, **kwargs) + raise_error_from_response(r, GitlabProtectError) + + if protect: + self.protected = protect + else: + del self.protected + + def unprotect(self, **kwargs): + self.protect(False, **kwargs) + + +class ProjectCommit(GitlabObject): + _url = '/projects/%(project_id)s/repository/commits' + canDelete = False + canUpdate = False + canCreate = False + requiredUrlAttrs = ['project_id'] + shortPrintAttr = 'title' + + def diff(self, **kwargs): + url = ('/projects/%(project_id)s/repository/commits/%(commit_id)s/diff' + % {'project_id': self.project_id, 'commit_id': self.id}) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + + return r.json() + + def blob(self, filepath, **kwargs): + url = ('/projects/%(project_id)s/repository/blobs/%(commit_id)s' % + {'project_id': self.project_id, 'commit_id': self.id}) + url += '?filepath=%s' % filepath + r = self.gitlab._raw_get(url, **kwargs) + + raise_error_from_response(r, GitlabGetError) + + return r.content + + +class ProjectKey(GitlabObject): + _url = '/projects/%(project_id)s/keys' + canUpdate = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title', 'key'] + + +class ProjectEvent(GitlabObject): + _url = '/projects/%(project_id)s/events' + canGet = 'from_list' + canDelete = False + canUpdate = False + canCreate = False + requiredUrlAttrs = ['project_id'] + shortPrintAttr = 'target_title' + + +class ProjectHook(GitlabObject): + _url = '/projects/%(project_id)s/hooks' + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['url'] + optionalCreateAttrs = ['push_events', 'issues_events', + 'merge_requests_events', 'tag_push_events'] + shortPrintAttr = 'url' + + +class ProjectIssueNote(GitlabObject): + _url = '/projects/%(project_id)s/issues/%(issue_id)s/notes' + _constructorTypes = {'author': 'User'} + canUpdate = False + canDelete = False + requiredUrlAttrs = ['project_id', 'issue_id'] + requiredCreateAttrs = ['body'] + + +class ProjectIssue(GitlabObject): + _url = '/projects/%(project_id)s/issues/' + _constructorTypes = {'author': 'User', 'assignee': 'User', + 'milestone': 'ProjectMilestone'} + canDelete = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title'] + # FIXME: state_event is only valid with update + optionalCreateAttrs = ['description', 'assignee_id', 'milestone_id', + 'labels', 'state_event'] + + shortPrintAttr = 'title' + + def _data_for_gitlab(self, extra_parameters={}): + # Gitlab-api returns labels in a json list and takes them in a + # comma separated list. + if hasattr(self, "labels"): + if (self.labels is not None and + not isinstance(self.labels, six.string_types)): + labels = ", ".join(self.labels) + extra_parameters['labels'] = labels + + return super(ProjectIssue, self)._data_for_gitlab(extra_parameters) + + def Note(self, id=None, **kwargs): + return ProjectIssueNote._get_list_or_object(self.gitlab, id, + project_id=self.project_id, + issue_id=self.id, + **kwargs) + + +class ProjectMember(GitlabObject): + _url = '/projects/%(project_id)s/members' + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['access_level', 'user_id'] + shortPrintAttr = 'username' + + +class ProjectNote(GitlabObject): + _url = '/projects/%(project_id)s/notes' + _constructorTypes = {'author': 'User'} + canUpdate = False + canDelete = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['body'] + + +class ProjectTag(GitlabObject): + _url = '/projects/%(project_id)s/repository/tags' + idAttr = 'name' + canGet = 'from_list' + canDelete = False + canUpdate = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['tag_name', 'ref'] + optionalCreateAttrs = ['message'] + shortPrintAttr = 'name' + + +class ProjectMergeRequestNote(GitlabObject): + _url = '/projects/%(project_id)s/merge_requests/%(merge_request_id)s/notes' + _constructorTypes = {'author': 'User'} + canDelete = False + requiredUrlAttrs = ['project_id', 'merge_request_id'] + requiredCreateAttrs = ['body'] + + +class ProjectMergeRequest(GitlabObject): + _url = '/projects/%(project_id)s/merge_request' + _urlPlural = '/projects/%(project_id)s/merge_requests' + _constructorTypes = {'author': 'User', 'assignee': 'User'} + canDelete = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['source_branch', 'target_branch', 'title'] + optionalCreateAttrs = ['assignee_id'] + + def Note(self, id=None, **kwargs): + return ProjectMergeRequestNote._get_list_or_object( + self.gitlab, id, project_id=self.project_id, + merge_request_id=self.id, **kwargs) + + +class ProjectMilestone(GitlabObject): + _url = '/projects/%(project_id)s/milestones' + canDelete = False + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title'] + optionalCreateAttrs = ['description', 'due_date', 'state_event'] + shortPrintAttr = 'title' + + +class ProjectLabel(GitlabObject): + _url = '/projects/%(project_id)s/labels' + requiredUrlAttrs = ['project_id'] + idAttr = 'name' + requiredDeleteAttrs = ['name'] + requiredCreateAttrs = ['name', 'color'] + requiredUpdateAttrs = [] + # FIXME: new_name is only valid with update + optionalCreateAttrs = ['new_name'] + + +class ProjectFile(GitlabObject): + _url = '/projects/%(project_id)s/repository/files' + canList = False + requiredUrlAttrs = ['project_id'] + requiredGetAttrs = ['file_path', 'ref'] + requiredCreateAttrs = ['file_path', 'branch_name', 'content', + 'commit_message'] + optionalCreateAttrs = ['encoding'] + requiredDeleteAttrs = ['branch_name', 'commit_message'] + getListWhenNoId = False + shortPrintAttr = 'file_path' + getRequiresId = False + + +class ProjectSnippetNote(GitlabObject): + _url = '/projects/%(project_id)s/snippets/%(snippet_id)s/notes' + _constructorTypes = {'author': 'User'} + canUpdate = False + canDelete = False + requiredUrlAttrs = ['project_id', 'snippet_id'] + requiredCreateAttrs = ['body'] + + +class ProjectSnippet(GitlabObject): + _url = '/projects/%(project_id)s/snippets' + _constructorTypes = {'author': 'User'} + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['title', 'file_name', 'code'] + optionalCreateAttrs = ['lifetime'] + shortPrintAttr = 'title' + + def Content(self, **kwargs): + url = ("/projects/%(project_id)s/snippets/%(snippet_id)s/raw" % + {'project_id': self.project_id, 'snippet_id': self.id}) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return r.content + + def Note(self, id=None, **kwargs): + return ProjectSnippetNote._get_list_or_object( + self.gitlab, id, + project_id=self.project_id, + snippet_id=self.id, + **kwargs) + + +class UserProject(GitlabObject): + _url = '/projects/user/%(user_id)s' + _constructorTypes = {'owner': 'User', 'namespace': 'Group'} + canUpdate = False + canDelete = False + canList = False + canGet = False + requiredUrlAttrs = ['user_id'] + requiredCreateAttrs = ['name'] + optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', + 'merge_requests_enabled', 'wiki_enabled', + 'snippets_enabled', 'public', 'visibility_level', + 'description'] + + +class Project(GitlabObject): + _url = '/projects' + _constructorTypes = {'owner': 'User', 'namespace': 'Group'} + requiredCreateAttrs = ['name'] + requiredUpdateAttrs = [] + optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', + 'merge_requests_enabled', 'wiki_enabled', + 'snippets_enabled', 'public', 'visibility_level', + 'namespace_id', 'description', 'path', 'import_url'] + + shortPrintAttr = 'path' + + def Branch(self, id=None, **kwargs): + return ProjectBranch._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Commit(self, id=None, **kwargs): + return ProjectCommit._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Event(self, id=None, **kwargs): + return ProjectEvent._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Hook(self, id=None, **kwargs): + return ProjectHook._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Key(self, id=None, **kwargs): + return ProjectKey._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Issue(self, id=None, **kwargs): + return ProjectIssue._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Member(self, id=None, **kwargs): + return ProjectMember._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def MergeRequest(self, id=None, **kwargs): + return ProjectMergeRequest._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Milestone(self, id=None, **kwargs): + return ProjectMilestone._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Note(self, id=None, **kwargs): + return ProjectNote._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Snippet(self, id=None, **kwargs): + return ProjectSnippet._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Label(self, id=None, **kwargs): + return ProjectLabel._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def File(self, id=None, **kwargs): + return ProjectFile._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def Tag(self, id=None, **kwargs): + return ProjectTag._get_list_or_object(self.gitlab, id, + project_id=self.id, + **kwargs) + + def tree(self, path='', ref_name='', **kwargs): + url = "%s/%s/repository/tree" % (self._url, self.id) + url += '?path=%s&ref_name=%s' % (path, ref_name) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return r.json() + + def blob(self, sha, filepath, **kwargs): + url = "%s/%s/repository/blobs/%s" % (self._url, self.id, sha) + url += '?filepath=%s' % (filepath) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return r.content + + def archive(self, sha=None, **kwargs): + url = '/projects/%s/repository/archive' % self.id + if sha: + url += '?sha=%s' % sha + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return r.content + + def create_file(self, path, branch, content, message, **kwargs): + """Creates file in project repository + + Args: + path (str): Full path to new file + branch (str): The name of branch + content (str): Content of the file + message (str): Commit message + kwargs: Arbitrary keyword arguments + + Raises: + GitlabCreateError: Operation failed + GitlabConnectionError: Connection to GitLab-server failed + """ + url = "/projects/%s/repository/files" % self.id + url += ("?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % + (path, branch, content, message)) + r = self.gitlab._raw_post(url, data=None, content_type=None, **kwargs) + raise_error_from_response(r, GitlabCreateError, 201) + + def update_file(self, path, branch, content, message, **kwargs): + url = "/projects/%s/repository/files" % self.id + url += ("?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % + (path, branch, content, message)) + r = self.gitlab._raw_put(url, data=None, content_type=None, **kwargs) + raise_error_from_response(r, GitlabUpdateError) + + def delete_file(self, path, branch, message, **kwargs): + url = "/projects/%s/repository/files" % self.id + url += ("?file_path=%s&branch_name=%s&commit_message=%s" % + (path, branch, message)) + r = self.gitlab._raw_delete(url, **kwargs) + raise_error_from_response(r, GitlabDeleteError) + + +class TeamMember(GitlabObject): + _url = '/user_teams/%(team_id)s/members' + canUpdate = False + requiredUrlAttrs = ['teamd_id'] + requiredCreateAttrs = ['access_level'] + shortPrintAttr = 'username' + + +class TeamProject(GitlabObject): + _url = '/user_teams/%(team_id)s/projects' + _constructorTypes = {'owner': 'User', 'namespace': 'Group'} + canUpdate = False + requiredCreateAttrs = ['greatest_access_level'] + requiredUrlAttrs = ['team_id'] + shortPrintAttr = 'name' + + +class Team(GitlabObject): + _url = '/user_teams' + shortPrintAttr = 'name' + requiredCreateAttrs = ['name', 'path'] + canUpdate = False + + def Member(self, id=None, **kwargs): + return TeamMember._get_list_or_object(self.gitlab, id, + team_id=self.id, + **kwargs) + + def Project(self, id=None, **kwargs): + return TeamProject._get_list_or_object(self.gitlab, id, + team_id=self.id, + **kwargs) |