summaryrefslogtreecommitdiff
path: root/gitlab
diff options
context:
space:
mode:
authorGauvain Pocentek <gauvain.pocentek@objectif-libre.com>2015-05-11 19:51:21 +0200
committerGauvain Pocentek <gauvain.pocentek@objectif-libre.com>2015-05-11 19:56:21 +0200
commitd2e5700c68f33b0872616fedc6a3320c84c81de6 (patch)
treeccc04bca14c092f6d14eeaaf4db81a5dda1f8da8 /gitlab
parent105896f59bd3399c7d76934e515dab57bcd4f594 (diff)
downloadgitlab-d2e5700c68f33b0872616fedc6a3320c84c81de6.tar.gz
gitlab is now a package
Diffstat (limited to 'gitlab')
-rwxr-xr-xgitlab363
-rw-r--r--gitlab/__init__.py1297
2 files changed, 1297 insertions, 363 deletions
diff --git a/gitlab b/gitlab
deleted file mode 100755
index 5e4d44a..0000000
--- a/gitlab
+++ /dev/null
@@ -1,363 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013-2014 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 argparse
-from inspect import getmro
-import os
-import re
-import sys
-try:
- from ConfigParser import ConfigParser
-except ImportError:
- from configparser import ConfigParser
-
-import gitlab
-
-camel_re = re.compile('(.)([A-Z])')
-LIST = 'list'
-GET = 'get'
-CREATE = 'create'
-UPDATE = 'update'
-DELETE = 'delete'
-PROTECT = 'protect'
-UNPROTECT = 'unprotect'
-SEARCH = 'search'
-OWNED = 'owned'
-ALL = 'all'
-ACTIONS = [LIST, GET, CREATE, UPDATE, DELETE]
-EXTRA_ACTION = [PROTECT, UNPROTECT, SEARCH, OWNED, ALL]
-
-extra_actions = {
- gitlab.ProjectBranch: {PROTECT: {'requiredAttrs': ['id', 'project-id']},
- UNPROTECT: {'requiredAttrs': ['id', 'project-id']}},
- gitlab.Project: {SEARCH: {'requiredAttrs': ['query']},
- OWNED: {'requiredAttrs': []},
- ALL: {'requiredAttrs': []}},
-}
-
-
-def die(msg):
- sys.stderr.write(msg + "\n")
- sys.exit(1)
-
-
-def whatToCls(what):
- return "".join([s.capitalize() for s in what.split("-")])
-
-
-def clsToWhat(cls):
- return camel_re.sub(r'\1-\2', cls.__name__).lower()
-
-
-def populate_sub_parser_by_class(cls, sub_parser):
- sub_parser_class = sub_parser.add_subparsers(
- dest='action',
- title="positional argument",
- description='action with %s' % cls.__name__,
- help='action to do'
- )
- for action_name in ACTIONS:
- attr = 'can' + action_name.capitalize()
- try:
- y = cls.__dict__[attr]
- except AttributeError:
- y = gitlab.GitlabObject.__dict__[attr]
- if not y:
- continue
- sub_parser_action = sub_parser_class.add_parser(action_name)
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=True)
- for x in cls.requiredUrlAttrs]
-
- if action_name == LIST:
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=True)
- for x in cls.requiredListAttrs]
- sub_parser_action.add_argument("--page", required=False)
- sub_parser_action.add_argument("--per-page", required=False)
-
- elif action_name in [GET, DELETE]:
- if cls not in [gitlab.CurrentUser]:
- sub_parser_action.add_argument("--id", required=True)
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=True)
- for x in cls.requiredGetAttrs]
-
- elif action_name == CREATE:
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=True)
- for x in cls.requiredCreateAttrs]
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=False)
- for x in cls.optionalCreateAttrs]
-
- elif action_name == UPDATE:
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=True)
- for x in cls.requiredCreateAttrs]
- [sub_parser_action.add_argument("--%s" % x.replace('_', '-'),
- required=False)
- for x in cls.optionalCreateAttrs]
-
- if cls in extra_actions:
- for action_name in sorted(extra_actions[cls]):
- sub_parser_action = sub_parser_class.add_parser(action_name)
- d = extra_actions[cls][action_name]
- [sub_parser_action.add_argument("--%s" % arg, required=True)
- for arg in d['requiredAttrs']]
-
-
-def do_auth():
- try:
- gl = gitlab.Gitlab(gitlab_url, private_token=gitlab_token,
- ssl_verify=ssl_verify, timeout=timeout)
- gl.auth()
- return gl
- except Exception as e:
- die("Could not connect to GitLab %s (%s)" % (gitlab_url, str(e)))
-
-
-def get_id():
- try:
- id = d.pop('id')
- except Exception:
- die("Missing --id argument")
-
- return id
-
-
-def do_create(cls, d):
- if not cls.canCreate:
- die("%s objects can't be created" % what)
-
- try:
- o = cls(gl, d)
- o.save()
- except Exception as e:
- die("Impossible to create object (%s)" % str(e))
-
- return o
-
-
-def do_list(cls, d):
- if not cls.canList:
- die("%s objects can't be listed" % what)
-
- try:
- l = cls.list(gl, **d)
- except Exception as e:
- die("Impossible to list objects (%s)" % str(e))
-
- return l
-
-
-def do_get(cls, d):
- if not cls.canGet:
- die("%s objects can't be retrieved" % what)
-
- id = None
- if cls not in [gitlab.CurrentUser]:
- id = get_id()
-
- try:
- o = cls(gl, id, **d)
- except Exception as e:
- die("Impossible to get object (%s)" % str(e))
-
- return o
-
-
-def do_delete(cls, d):
- if not cls.canDelete:
- die("%s objects can't be deleted" % what)
-
- o = do_get(cls, d)
- try:
- o.delete()
- except Exception as e:
- die("Impossible to destroy object (%s)" % str(e))
-
-
-def do_update(cls, d):
- if not cls.canUpdate:
- die("%s objects can't be updated" % what)
-
- o = do_get(cls, d)
- try:
- for k, v in d.items():
- o.__dict__[k] = v
- o.save()
- except Exception as e:
- die("Impossible to update object (%s)" % str(e))
-
- return o
-
-
-def do_project_search(d):
- try:
- return gl.search_projects(d['query'])
- except Exception as e:
- die("Impossible to search projects (%s)" % str(e))
-
-
-def do_project_all():
- try:
- return gl.all_projects()
- except Exception as e:
- die("Impossible to list all projects (%s)" % str(e))
-
-
-def do_project_owned():
- try:
- return gl.owned_projects()
- except Exception as e:
- die("Impossible to list owned projects (%s)" % str(e))
-
-
-if __name__ == "__main__":
- ssl_verify = True
- timeout = 60
- parser = argparse.ArgumentParser(
- description='GitLab API Command Line Interface')
- parser.add_argument("-v", "--verbosity", "--fancy",
- help="increase output verbosity",
- action="store_true")
- parser.add_argument("--gitlab", metavar='gitlab',
- help=("Specifies which python-gitlab.cfg "
- "configuration section should be used. "
- "If not defined, the default selection "
- "will be used."),
- required=False)
- subparsers = parser.add_subparsers(
- dest='what',
- title="positional argument",
- description='GitLab object',
- help='GitLab object'
- )
-
- # populate argparse for all Gitlab Object
- for cls in gitlab.__dict__.values():
- try:
- if gitlab.GitlabObject in getmro(cls):
- sub_parser = subparsers.add_parser(clsToWhat(cls))
- populate_sub_parser_by_class(cls, sub_parser)
- except Exception:
- pass
-
- arg = parser.parse_args()
- d = arg.__dict__
-
- # read the config
- config = ConfigParser()
- config.read(['/etc/python-gitlab.cfg',
- os.path.expanduser('~/.python-gitlab.cfg')])
- gitlab_id = arg.gitlab
- # conflicts with "gitlab" attribute from GitlabObject class
- d.pop("gitlab")
- verbose = arg.verbosity
- action = arg.action
- what = arg.what
-
- if gitlab_id is None:
- try:
- gitlab_id = config.get('global', 'default')
- except Exception:
- die("Impossible to get the gitlab id "
- "(not specified in config file)")
-
- try:
- gitlab_url = config.get(gitlab_id, 'url')
- gitlab_token = config.get(gitlab_id, 'private_token')
- except Exception:
- die("Impossible to get gitlab informations from configuration "
- "(%s)" % gitlab_id)
-
- try:
- ssl_verify = config.getboolean('global', 'ssl_verify')
- except Exception:
- pass
- try:
- ssl_verify = config.getboolean(gitlab_id, 'ssl_verify')
- except Exception:
- pass
-
- try:
- timeout = config.getint('global', 'timeout')
- except Exception:
- pass
- try:
- timeout = config.getint(gitlab_id, 'timeout')
- except Exception:
- pass
-
- cls = None
- try:
- cls = gitlab.__dict__[whatToCls(what)]
- except Exception:
- die("Unknown object: %s" % what)
-
- gl = do_auth()
-
- if action == CREATE or action == GET:
- o = globals()['do_%s' % action.lower()](cls, d)
- o.display(verbose)
-
- elif action == LIST:
- for o in do_list(cls, d):
- o.display(verbose)
- print("")
-
- elif action == DELETE or action == UPDATE:
- o = globals()['do_%s' % action.lower()](cls, d)
-
- elif action == PROTECT or action == UNPROTECT:
- if cls != gitlab.ProjectBranch:
- die("%s objects can't be protected" % what)
-
- o = do_get(cls, d)
- getattr(o, action)()
-
- elif action == SEARCH:
- if cls != gitlab.Project:
- die("%s objects don't support this request" % what)
-
- for o in do_project_search(d):
- o.display(verbose)
-
- elif action == OWNED:
- if cls != gitlab.Project:
- die("%s objects don't support this request" % what)
-
- for o in do_project_owned():
- o.display(verbose)
-
- elif action == ALL:
- if cls != gitlab.Project:
- die("%s objects don't support this request" % what)
-
- for o in do_project_all():
- o.display(verbose)
-
- else:
- die("Unknown action: %s. Use \"gitlab -h %s\" to get details." %
- (action, what))
-
- sys.exit(0)
diff --git a/gitlab/__init__.py b/gitlab/__init__.py
new file mode 100644
index 0000000..3466127
--- /dev/null
+++ b/gitlab/__init__.py
@@ -0,0 +1,1297 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013-2014 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/>.
+"""Package for interfacing with GitLab-api """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from itertools import chain
+import json
+import sys
+
+import requests
+import six
+
+__title__ = 'python-gitlab'
+__version__ = '0.8.1'
+__author__ = 'Gauvain Pocentek'
+__email__ = 'gauvain@pocentek.net'
+__license__ = 'LGPL3'
+__copyright__ = 'Copyright 2013-2014 Gauvain Pocentek'
+
+
+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 _raiseErrorFromResponse(response, error):
+ """Tries to parse gitlab error message from response and raises error.
+
+ If response status code is 401, raises instead GitlabAuthenticationError.
+
+ response: requests response object
+ error: Error-class to raise. Should be inherited from GitLabError
+ """
+
+ 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)
+
+
+class Gitlab(object):
+ """Represents a GitLab server connection
+
+ Args:
+ url (str): the URL of the Gitlab server
+ private_token (str): the user private token
+ email (str): the user email/login
+ password (str): the user password (associated with email)
+ ssl_verify (bool): (Passed to requests-library)
+ timeout (float or tuple(float,float)): (Passed to
+ requests-library). Timeout to use for requests to gitlab server
+ """
+
+ def __init__(self, url, private_token=None,
+ email=None, password=None, ssl_verify=True, timeout=None):
+
+ self._url = '%s/api/v3' % url
+ #: Timeout to use for requests to gitlab server
+ self.timeout = timeout
+ #: Headers that will be used in request to GitLab
+ self.headers = {}
+ self.setToken(private_token)
+ #: the user email
+ self.email = email
+ #: the user password (associated with email)
+ self.password = password
+ #: (Passed to requests-library)
+ self.ssl_verify = ssl_verify
+
+ def auth(self):
+ """Performs an authentication.
+
+ Uses either the private token, or the email/password pair.
+
+ The user attribute will hold a CurrentUser object on success.
+ """
+ if self.private_token:
+ self.token_auth()
+ else:
+ self.credentials_auth()
+
+ def credentials_auth(self):
+ if not self.email or not self.password:
+ raise GitlabAuthenticationError("Missing email/password")
+
+ data = json.dumps({'email': self.email, 'password': self.password})
+ r = self.rawPost('/session', data, content_type='application/json')
+
+ if r.status_code == 201:
+ self.user = CurrentUser(self, r.json())
+ else:
+ _raiseErrorFromResponse(r, GitlabAuthenticationError)
+
+ self.setToken(self.user.private_token)
+
+ def token_auth(self):
+ self.user = CurrentUser(self)
+
+ def setUrl(self, url):
+ """Updates the gitlab URL"""
+ self._url = '%s/api/v3' % url
+
+ def constructUrl(self, id_, obj, parameters):
+ args = _sanitize_dict(parameters)
+ url = obj._url % args
+ if id_ is not None:
+ url = '%s%s/%s' % (self._url, url, str(id_))
+ else:
+ url = '%s%s' % (self._url, url)
+ return url
+
+ def _createHeaders(self, content_type=None, headers={}):
+ request_headers = self.headers.copy()
+ request_headers.update(headers)
+ if content_type is not None:
+ request_headers['Content-type'] = content_type
+ return request_headers
+
+ def setToken(self, token):
+ """Sets the private token for authentication"""
+ self.private_token = token if token else None
+ if token:
+ self.headers["PRIVATE-TOKEN"] = token
+ elif "PRIVATE-TOKEN" in self.headers:
+ del self.headers["PRIVATE-TOKEN"]
+
+ def setCredentials(self, email, password):
+ """Sets the email/login and password for authentication"""
+ self.email = email
+ self.password = password
+
+ def rawGet(self, path, content_type=None, **kwargs):
+ url = '%s%s' % (self._url, path)
+ headers = self._createHeaders(content_type)
+
+ try:
+ return requests.get(url,
+ params=kwargs,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ def rawPost(self, path, data=None, content_type=None, **kwargs):
+ url = '%s%s' % (self._url, path)
+ headers = self._createHeaders(content_type)
+ try:
+ return requests.post(url, params=kwargs, data=data,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ def rawPut(self, path, data=None, content_type=None, **kwargs):
+ url = '%s%s' % (self._url, path)
+ headers = self._createHeaders(content_type)
+
+ try:
+ return requests.put(url, data=data, params=kwargs,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ def rawDelete(self, path, content_type=None, **kwargs):
+ url = '%s%s' % (self._url, path)
+ headers = self._createHeaders(content_type)
+
+ try:
+ return requests.delete(url,
+ params=kwargs,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ def list(self, obj_class, **kwargs):
+ missing = []
+ for k in chain(obj_class.requiredUrlAttrs,
+ obj_class.requiredListAttrs):
+ if k not in kwargs:
+ missing.append(k)
+ if missing:
+ raise GitlabListError('Missing attribute(s): %s' %
+ ", ".join(missing))
+
+ url = self.constructUrl(id_=None, obj=obj_class, parameters=kwargs)
+ headers = self._createHeaders()
+
+ # Remove attributes that are used in url so that there is only
+ # url-parameters left
+ params = kwargs.copy()
+ for attribute in obj_class.requiredUrlAttrs:
+ del params[attribute]
+
+ try:
+ r = requests.get(url, params=params, headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ if r.status_code == 200:
+ cls = obj_class
+ if obj_class._returnClass:
+ cls = obj_class._returnClass
+
+ cls_kwargs = kwargs.copy()
+
+ # Add _created manually, because we are not creating objects
+ # through normal path
+ cls_kwargs['_created'] = True
+
+ # Remove parameters from kwargs before passing it to constructor
+ for key in ['page', 'per_page', 'sudo']:
+ if key in cls_kwargs:
+ del cls_kwargs[key]
+
+ return [cls(self, item, **cls_kwargs) for item in r.json()
+ if item is not None]
+ else:
+ _raiseErrorFromResponse(r, GitlabListError)
+
+ def get(self, obj_class, id=None, **kwargs):
+ missing = []
+ for k in chain(obj_class.requiredUrlAttrs,
+ obj_class.requiredGetAttrs):
+ if k not in kwargs:
+ missing.append(k)
+ if missing:
+ raise GitlabGetError('Missing attribute(s): %s' %
+ ", ".join(missing))
+
+ url = self.constructUrl(id_=id, obj=obj_class, parameters=kwargs)
+ headers = self._createHeaders()
+
+ # Remove attributes that are used in url so that there is only
+ # url-parameters left
+ params = kwargs.copy()
+ for attribute in obj_class.requiredUrlAttrs:
+ del params[attribute]
+
+ try:
+ r = requests.get(url, params=params, headers=headers,
+ verify=self.ssl_verify, timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ if r.status_code == 200:
+ return r.json()
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+ def delete(self, obj, **kwargs):
+ params = obj.__dict__.copy()
+ params.update(kwargs)
+ missing = []
+ for k in chain(obj.requiredUrlAttrs, obj.requiredDeleteAttrs):
+ if k not in params:
+ missing.append(k)
+ if missing:
+ raise GitlabDeleteError('Missing attribute(s): %s' %
+ ", ".join(missing))
+
+ url = self.constructUrl(id_=obj.id, obj=obj, parameters=params)
+ headers = self._createHeaders()
+
+ # Remove attributes that are used in url so that there is only
+ # url-parameters left
+ for attribute in obj.requiredUrlAttrs:
+ del params[attribute]
+
+ try:
+ r = requests.delete(url,
+ params=params,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ if r.status_code == 200:
+ return True
+ else:
+ _raiseErrorFromResponse(r, GitlabDeleteError)
+
+ def create(self, obj, **kwargs):
+ params = obj.__dict__.copy()
+ params.update(kwargs)
+ missing = []
+ for k in chain(obj.requiredUrlAttrs, obj.requiredCreateAttrs):
+ if k not in params:
+ missing.append(k)
+ if missing:
+ raise GitlabCreateError('Missing attribute(s): %s' %
+ ", ".join(missing))
+
+ url = self.constructUrl(id_=None, obj=obj, parameters=params)
+ headers = self._createHeaders(content_type="application/json")
+
+ # build data that can really be sent to server
+ data = obj._dataForGitlab(extra_parameters=kwargs)
+
+ try:
+ r = requests.post(url, data=data,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ if r.status_code == 201:
+ return r.json()
+ else:
+ _raiseErrorFromResponse(r, GitlabCreateError)
+
+ def update(self, obj, **kwargs):
+ params = obj.__dict__.copy()
+ params.update(kwargs)
+ missing = []
+ for k in chain(obj.requiredUrlAttrs, obj.requiredCreateAttrs):
+ if k not in params:
+ missing.append(k)
+ if missing:
+ raise GitlabUpdateError('Missing attribute(s): %s' %
+ ", ".join(missing))
+ url = self.constructUrl(id_=obj.id, obj=obj, parameters=params)
+ headers = self._createHeaders(content_type="application/json")
+
+ # build data that can really be sent to server
+ data = obj._dataForGitlab(extra_parameters=kwargs)
+
+ try:
+ r = requests.put(url, data=data,
+ headers=headers,
+ verify=self.ssl_verify,
+ timeout=self.timeout)
+ except Exception:
+ raise GitlabConnectionError(
+ "Can't connect to GitLab server (%s)" % self._url)
+
+ if r.status_code == 200:
+ return r.json()
+ else:
+ _raiseErrorFromResponse(r, GitlabUpdateError)
+
+ def Hook(self, id=None, **kwargs):
+ """Creates/tests/lists system hook(s) known by the GitLab server.
+
+ If id is None, returns a list of hooks.
+
+ If id is an integer, tests the matching hook.
+
+ If id is a dict, creates a new object using attributes provided. The
+ object is NOT saved on the server. Use the save() method on the object
+ to write it on the server.
+ """
+ return Hook._getListOrObject(self, id, **kwargs)
+
+ def Project(self, id=None, **kwargs):
+ """Creates/gets/lists project(s) known by the GitLab server.
+
+ If id is None, returns a list of projects.
+
+ If id is an integer, returns the matching project (or raises a
+ GitlabGetError if not found)
+
+ If id is a dict, creates a new object using attributes provided. The
+ object is NOT saved on the server. Use the save() method on the object
+ to write it on the server.
+ """
+ return Project._getListOrObject(self, id, **kwargs)
+
+ def UserProject(self, id=None, **kwargs):
+ """Creates a project for a user.
+
+ id must be a dict.
+ """
+ return UserProject._getListOrObject(self, id, **kwargs)
+
+ def _list_projects(self, url, **kwargs):
+ r = self.rawGet(url, **kwargs)
+ if r.status_code != 200:
+ _raiseErrorFromResponse(r, GitlabListError)
+
+ l = []
+ for o in r.json():
+ l.append(Project(self, o))
+
+ return l
+
+ def search_projects(self, query, **kwargs):
+ """Searches projects by name.
+
+ Returns a list of matching projects.
+ """
+ return self._list_projects("/projects/search/" + query, **kwargs)
+
+ def all_projects(self, **kwargs):
+ """Lists all the projects (need admin rights)."""
+ return self._list_projects("/projects/all", **kwargs)
+
+ def owned_projects(self, **kwargs):
+ """Lists owned projects."""
+ return self._list_projects("/projects/owned", **kwargs)
+
+ def Group(self, id=None, **kwargs):
+ """Creates/gets/lists group(s) known by the GitLab server
+
+ Args:
+ id: If id is None, returns a list of groups.
+ id: If id is an integer,
+ returns the matching group (or raises a GitlabGetError if not
+ found).
+ id: If id is a dict, creates a new object using attributes
+ provided. The object is NOT saved on the server. Use the
+ save() method on the object to write it on the server.
+ kwargs: Arbitrary keyword arguments
+ """
+ return Group._getListOrObject(self, id, **kwargs)
+
+ def Issue(self, id=None, **kwargs):
+ """Lists issues(s) known by the GitLab server.
+
+ Does not support creation or getting a single issue unlike other
+ methods in this class yet.
+ """
+ return Issue._getListOrObject(self, id, **kwargs)
+
+ def User(self, id=None, **kwargs):
+ """Creates/gets/lists users(s) known by the GitLab server.
+
+ If id is None, returns a list of users.
+
+ If id is an integer, returns the matching user (or raises a
+ GitlabGetError if not found)
+
+ If id is a dict, creates a new object using attributes provided. The
+ object is NOT saved on the server. Use the save() method on the object
+ to write it on the server.
+ """
+ return User._getListOrObject(self, id, **kwargs)
+
+ def Team(self, id=None, **kwargs):
+ """Creates/gets/lists team(s) known by the GitLab server.
+
+ If id is None, returns a list of teams.
+
+ If id is an integer, returns the matching team (or raises a
+ GitlabGetError if not found)
+
+ If id is a dict, create a new object using attributes provided. The
+ object is NOT saved on the server. Use the save() method on the object
+ to write it on the server.
+ """
+ return Team._getListOrObject(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
+ _returnClass = None
+ _constructorTypes = None
+ #: Tells if _getListOrObject 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 = []
+ idAttr = 'id'
+ shortPrintAttr = None
+
+ def _dataForGitlab(self, extra_parameters={}):
+ data = {}
+ for attribute in 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 _getListOrObject(cls, gl, id, **kwargs):
+ if id is None and cls.getListWhenNoId:
+ return cls.list(gl, **kwargs)
+ else:
+ return cls(gl, id, **kwargs)
+
+ def _getObject(self, k, v):
+ if self._constructorTypes and k in self._constructorTypes:
+ return globals()[self._constructorTypes[k]](self.gitlab, v)
+ else:
+ return v
+
+ def _setFromDict(self, data):
+ for k, v in data.items():
+ if isinstance(v, list):
+ self.__dict__[k] = []
+ for i in v:
+ self.__dict__[k].append(self._getObject(k, i))
+ elif v is None:
+ self.__dict__[k] = None
+ else:
+ self.__dict__[k] = self._getObject(k, v)
+
+ def _create(self, **kwargs):
+ if not self.canCreate:
+ raise NotImplementedError
+
+ json = self.gitlab.create(self, **kwargs)
+ self._setFromDict(json)
+ self._created = True
+
+ def _update(self, **kwargs):
+ if not self.canUpdate:
+ raise NotImplementedError
+
+ json = self.gitlab.update(self, **kwargs)
+ self._setFromDict(json)
+
+ def save(self, **kwargs):
+ if self._created:
+ self._update(**kwargs)
+ else:
+ self._create(**kwargs)
+
+ def delete(self, **kwargs):
+ if not self.canDelete:
+ raise NotImplementedError
+
+ if not self._created:
+ raise GitlabDeleteError("Object not yet created")
+
+ return self.gitlab.delete(self, **kwargs)
+
+ def __init__(self, gl, data=None, **kwargs):
+ self._created = 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)
+ # Object is created because we got it from api
+ self._created = True
+
+ self._setFromDict(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 = False
+ 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']
+
+ def Key(self, id=None, **kwargs):
+ return UserKey._getListOrObject(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._getListOrObject(self.gitlab, id, **kwargs)
+
+
+class GroupMember(GitlabObject):
+ _url = '/groups/%(group_id)s/members'
+ canGet = False
+ canUpdate = False
+ requiredUrlAttrs = ['group_id']
+ requiredCreateAttrs = ['access_level', 'user_id']
+ shortPrintAttr = 'username'
+
+
+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._getListOrObject(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.rawPost(url, None, **kwargs)
+ if r.status_code != 201:
+ _raiseErrorFromResponse(r, GitlabTransferProjectError)
+
+
+class Hook(GitlabObject):
+ _url = '/hooks'
+ canUpdate = False
+ requiredCreateAttrs = ['url']
+ shortPrintAttr = 'url'
+
+
+class Issue(GitlabObject):
+ _url = '/issues'
+ _constructorTypes = {'author': 'User', 'assignee': 'User',
+ 'milestone': 'ProjectMilestone'}
+ canGet = False
+ 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.rawPut(url, data=None, content_type=None, **kwargs)
+
+ if r.status_code == 200:
+ if protect:
+ self.protected = protect
+ else:
+ del self.protected
+ else:
+ _raiseErrorFromResponse(r, GitlabProtectError)
+
+ 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.rawGet(url, **kwargs)
+ if r.status_code == 200:
+ return r.json()
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+ 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.rawGet(url, **kwargs)
+ if r.status_code == 200:
+ return r.content
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+
+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 = False
+ 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 _dataForGitlab(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)._dataForGitlab(extra_parameters)
+
+ def Note(self, id=None, **kwargs):
+ return ProjectIssueNote._getListOrObject(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 = False
+ 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'}
+ canUpdate = False
+ canDelete = False
+ requiredUrlAttrs = ['project_id', 'merge_request_id']
+ requiredCreateAttrs = ['body']
+
+
+class ProjectMergeRequest(GitlabObject):
+ _url = '/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._getListOrObject(
+ 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']
+ # 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 = 'name'
+
+
+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.rawGet(url, **kwargs)
+
+ if r.status_code == 200:
+ return r.content
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+ def Note(self, id=None, **kwargs):
+ return ProjectSnippetNote._getListOrObject(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'}
+ canUpdate = False
+ requiredCreateAttrs = ['name']
+ 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._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Commit(self, id=None, **kwargs):
+ return ProjectCommit._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Event(self, id=None, **kwargs):
+ return ProjectEvent._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Hook(self, id=None, **kwargs):
+ return ProjectHook._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Key(self, id=None, **kwargs):
+ return ProjectKey._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Issue(self, id=None, **kwargs):
+ return ProjectIssue._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Member(self, id=None, **kwargs):
+ return ProjectMember._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def MergeRequest(self, id=None, **kwargs):
+ return ProjectMergeRequest._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Milestone(self, id=None, **kwargs):
+ return ProjectMilestone._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Note(self, id=None, **kwargs):
+ return ProjectNote._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Snippet(self, id=None, **kwargs):
+ return ProjectSnippet._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Label(self, id=None, **kwargs):
+ return ProjectLabel._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def File(self, id=None, **kwargs):
+ return ProjectFile._getListOrObject(self.gitlab, id,
+ project_id=self.id,
+ **kwargs)
+
+ def Tag(self, id=None, **kwargs):
+ return ProjectTag._getListOrObject(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.rawGet(url, **kwargs)
+ if r.status_code == 200:
+ return r.json()
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+ def blob(self, sha, filepath, **kwargs):
+ url = "%s/%s/repository/blobs/%s" % (self._url, self.id, sha)
+ url += '?filepath=%s' % (filepath)
+ r = self.gitlab.rawGet(url, **kwargs)
+ if r.status_code == 200:
+ return r.content
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+ def archive(self, sha=None, **kwargs):
+ url = '/projects/%s/repository/archive' % self.id
+ if sha:
+ url += '?sha=%s' % sha
+ r = self.gitlab.rawGet(url, **kwargs)
+ if r.status_code == 200:
+ return r.content
+ else:
+ _raiseErrorFromResponse(r, GitlabGetError)
+
+ 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.rawPost(url, data=None, content_type=None, **kwargs)
+ if r.status_code != 201:
+ _raiseErrorFromResponse(r, GitlabCreateError)
+
+ 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.rawPut(url, data=None, content_type=None, **kwargs)
+ if r.status_code != 200:
+ _raiseErrorFromResponse(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.rawDelete(url, **kwargs)
+ if r.status_code != 200:
+ _raiseErrorFromResponse(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._getListOrObject(self.gitlab, id,
+ team_id=self.id,
+ **kwargs)
+
+ def Project(self, id=None, **kwargs):
+ return TeamProject._getListOrObject(self.gitlab, id,
+ team_id=self.id,
+ **kwargs)