Commit fe89b949 authored by Gauvain Pocentek's avatar Gauvain Pocentek
Browse files

Drop API v3 support

Drop the code, the tests, and update the documentation.
parent 70116944
Loading
Loading
Loading
Loading
+23 −69
Original line number Diff line number Diff line
@@ -2,13 +2,12 @@
Getting started with the API
############################

python-gitlab supports both GitLab v3 and v4 APIs.
python-gitlab supports both GitLab v3 and v4 APIs. To use the v3 make sure to

v3 being deprecated by GitLab, its support in python-gitlab will be minimal.
The development team will focus on v4.

v4 is the default API used by python-gitlab since version 1.3.0.
.. note::

   To use the v3 make sure to install python-gitlab 1.4. Only the v4 API is
   documented here. See the documentation of earlier version for the v3 API.

``gitlab.Gitlab`` class
=======================
@@ -60,23 +59,6 @@ https://gist.github.com/gpocentek/bd4c3fbf8a6ce226ebddc4aad6b46c0a.
See `issue 380 <https://github.com/python-gitlab/python-gitlab/issues/380>`_
for a detailed discussion.

API version
===========

``python-gitlab`` uses the v4 GitLab API by default. Use the ``api_version``
parameter to switch to v3:

.. code-block:: python

   import gitlab

   gl = gitlab.Gitlab('http://10.0.0.1', 'JVNSESs8EwWRx5yDxM5q', api_version=3)

.. warning::

   The python-gitlab API is not the same for v3 and v4. Make sure to read
   :ref:`switching_to_v4` if you are upgrading from v3.

Managers
========

@@ -103,10 +85,10 @@ Examples:
   user = gl.users.create(user_data)
   print(user)

You can list the mandatory and optional attributes for object creation
with the manager's ``get_create_attrs()`` method. It returns 2 tuples, the
first one is the list of mandatory attributes, the second one the list of
optional attribute:
You can list the mandatory and optional attributes for object creation and
update with the manager's ``get_create_attrs()`` and ``get_update_attrs()``
methods. They return 2 tuples, the first one is the list of mandatory
attributes, the second one the list of optional attribute:

.. code-block:: python

@@ -116,19 +98,11 @@ optional attribute:

The attributes of objects are defined upon object creation, and depend on the
GitLab API itself. To list the available information associated with an object
use the python introspection tools for v3, or the ``attributes`` attribute for
v4:
use the ``attributes`` attribute:

.. code-block:: python

   project = gl.projects.get(1)

   # v3
   print(vars(project))
   # or
   print(project.__dict__)

   # v4
   print(project.attributes)

Some objects also provide managers to access related GitLab resources:
@@ -171,32 +145,21 @@ The ``gitlab`` package provides some base types.

* ``gitlab.Gitlab`` is the primary class, handling the HTTP requests. It holds
  the GitLab URL and authentication information.

For v4 the following types are defined:

* ``gitlab.base.RESTObject`` is the base class for all the GitLab v4 objects.
  These objects provide an abstraction for GitLab resources (projects, groups,
  and so on).
* ``gitlab.base.RESTManager`` is the base class for v4 objects managers,
  providing the API to manipulate the resources and their attributes.

For v3 the following types are defined:

* ``gitlab.base.GitlabObject`` is the base class for all the GitLab v3 objects.
  These objects provide an abstraction for GitLab resources (projects, groups,
  and so on).
* ``gitlab.base.BaseManager`` is the base class for v3 objects managers,
  providing the API to manipulate the resources and their attributes.
Lazy objects
============

Lazy objects (v4 only)
======================

To avoid useless calls to the server API, you can create lazy objects. These
To avoid useless API calls to the server you can create lazy objects. These
objects are created locally using a known ID, and give access to other managers
and methods.

The following example will only make one API call to the GitLab server to star
a project:
a project (the previous example used 2 API calls):

.. code-block:: python

@@ -214,9 +177,9 @@ listing methods support the ``page`` and ``per_page`` parameters:

   ten_first_groups = gl.groups.list(page=1, per_page=10)

.. note::
.. warning::

   The first page is page 1, not page 0, except for project commits in v3 API.
   The first page is page 1, not page 0.

By default GitLab does not return the complete list of items. Use the ``all``
parameter to get all the items when using listing methods:
@@ -226,18 +189,9 @@ parameter to get all the items when using listing methods:
   all_groups = gl.groups.list(all=True)
   all_owned_projects = gl.projects.owned(all=True)

.. warning::

   With API v3 python-gitlab will iterate over the list by calling the
   corresponding API multiple times. This might take some time if you have a
   lot of items to retrieve. This might also consume a lot of memory as all the
   items will be stored in RAM. If you're encountering the python recursion
   limit exception, use ``safe_all=True`` to stop pagination automatically if
   the recursion limit is hit.

With API v4, ``list()`` methods can also return a generator object which will
handle the next calls to the API when required. This is the recommended way to
iterate through a large number of items:
``list()`` methods can also return a generator object which will handle the
next calls to the API when required. This is the recommended way to iterate
through a large number of items:

.. code-block:: python

@@ -331,12 +285,12 @@ http://docs.python-requests.org/en/master/user/advanced/#client-side-certificate
Rate limits
-----------

python-gitlab will obey the rate limit of the GitLab server by default.
On receiving a 429 response (Too Many Requests), python-gitlab will sleep for the amount of time
in the Retry-After header, that GitLab sends back.
python-gitlab obeys the rate limit of the GitLab server by default.  On
receiving a 429 response (Too Many Requests), python-gitlab sleeps for the
amount of time in the Retry-After header that GitLab sends back.

If you don't want to wait, you can disable the rate-limiting feature, by supplying the
``obey_rate_limit`` argument.
If you don't want to wait, you can disable the rate-limiting feature, by
supplying the ``obey_rate_limit`` argument.

.. code-block:: python

+0 −1
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@ Subpackages

.. toctree::

    gitlab.v3
    gitlab.v4

Submodules

docs/api/gitlab.v3.rst

deleted100644 → 0
+0 −22
Original line number Diff line number Diff line
gitlab.v3 package
=================

Submodules
----------

gitlab.v3.objects module
------------------------

.. automodule:: gitlab.v3.objects
    :members:
    :undoc-members:
    :show-inheritance:


Module contents
---------------

.. automodule:: gitlab.v3
    :members:
    :undoc-members:
    :show-inheritance:
+16 −341
Original line number Diff line number Diff line
@@ -19,10 +19,6 @@
from __future__ import print_function
from __future__ import absolute_import
import importlib
import inspect
import itertools
import json
import re
import time
import warnings

@@ -32,7 +28,6 @@ import six
import gitlab.config
from gitlab.const import *  # noqa
from gitlab.exceptions import *  # noqa
from gitlab.v3.objects import *  # noqa

__title__ = 'python-gitlab'
__version__ = '1.4.0'
@@ -69,7 +64,7 @@ class Gitlab(object):
        timeout (float): Timeout to use for requests to the GitLab server.
        http_username (str): Username for HTTP authentication
        http_password (str): Password for HTTP authentication
        api_version (str): Gitlab API version to use (3 or 4)
        api_version (str): Gitlab API version to use (support for 4 only)
    """

    def __init__(self, url, private_token=None, oauth_token=None, email=None,
@@ -123,32 +118,12 @@ class Gitlab(object):
        self.snippets = objects.SnippetManager(self)
        self.users = objects.UserManager(self)
        self.todos = objects.TodoManager(self)
        if self._api_version == '3':
            self.teams = objects.TeamManager(self)
        else:
        self.dockerfiles = objects.DockerfileManager(self)
        self.events = objects.EventManager(self)
        self.features = objects.FeatureManager(self)
        self.pagesdomains = objects.PagesDomainManager(self)
        self.user_activities = objects.UserActivitiesManager(self)

        if self._api_version == '3':
            # build the "submanagers"
            for parent_cls in six.itervalues(vars(objects)):
                if (not inspect.isclass(parent_cls)
                    or not issubclass(parent_cls, objects.GitlabObject)
                        or parent_cls == objects.CurrentUser):
                    continue

                if not parent_cls.managers:
                    continue

                for var, cls_name, attrs in parent_cls.managers:
                    prefix = self._cls_to_manager_prefix(parent_cls)
                    var_name = '%s_%s' % (prefix, var)
                    manager = getattr(objects, cls_name)(self)
                    setattr(self, var_name, manager)

    def __enter__(self):
        return self

@@ -178,17 +153,9 @@ class Gitlab(object):

    @property
    def api_version(self):
        """The API version used (3 or 4)."""
        """The API version used (4 only)."""
        return self._api_version

    def _cls_to_manager_prefix(self, cls):
        # Manage bad naming decisions
        camel_case = (cls.__name__
                      .replace('NotificationSettings', 'Notificationsettings')
                      .replace('MergeRequest', 'Mergerequest')
                      .replace('AccessRequest', 'Accessrequest'))
        return re.sub(r'(.)([A-Z])', r'\1_\2', camel_case).lower()

    @staticmethod
    def from_config(gitlab_id=None, config_files=None):
        """Create a Gitlab connection from configuration files.
@@ -227,12 +194,6 @@ class Gitlab(object):

    def _credentials_auth(self):
        data = {'email': self.email, 'password': self.password}
        if self.api_version == '3':
            r = self._raw_post('/session', json.dumps(data),
                               content_type='application/json')
            raise_error_from_response(r, GitlabAuthenticationError, 201)
            self.user = self._objects.CurrentUser(self, r.json())
        else:
        r = self.http_post('/session', data)
        manager = self._objects.CurrentUserManager(self)
        self.user = self._objects.CurrentUser(manager, r)
@@ -240,9 +201,6 @@ class Gitlab(object):
        self._set_auth_info()

    def _token_auth(self):
        if self.api_version == '3':
            self.user = self._objects.CurrentUser(self)
        else:
        self.user = self._objects.CurrentUserManager(self).get()

    def version(self):
@@ -252,18 +210,16 @@ class Gitlab(object):
        object.

        Returns:
            tuple (str, str): The server version and server revision, or
            tuple (str, str): The server version and server revision.
                              ('unknown', 'unknwown') if the server doesn't
                              support this API call (gitlab < 8.13.0)
                              perform as expected.
        """
        if self._server_version is None:
            r = self._raw_get('/version')
            try:
                raise_error_from_response(r, GitlabGetError, 200)
                data = r.json()
                data = self.http_get('/version')
                self._server_version = data['version']
                self._server_revision = data['revision']
            except GitlabGetError:
            except Exception:
                self._server_version = self._server_revision = 'unknown'

        return self._server_version, self._server_revision
@@ -279,12 +235,6 @@ class Gitlab(object):
            if hasattr(obj, attr):
                url_attr = attr
        obj_url = getattr(obj, url_attr)

        # TODO(gpocentek): the following will need an update when we have
        # object with both urlPlural and _ACTION_url attributes
        if id_ is None and obj._urlPlural is not None:
            url = obj._urlPlural % args
        else:
        url = obj_url % args

        if id_ is not None:
@@ -345,287 +295,12 @@ class Gitlab(object):
            'verify': self.ssl_verify
        }

    def _raw_get(self, path_, content_type=None, streamed=False, **kwargs):
        if path_.startswith('http://') or path_.startswith('https://'):
            url = path_
        else:
            url = '%s%s' % (self._url, path_)

        opts = self._get_session_opts(content_type)
        try:
            return self.session.get(url, params=kwargs, stream=streamed,
                                    **opts)
        except Exception as e:
            raise GitlabConnectionError(
                "Can't connect to GitLab server (%s)" % e)

    def _raw_list(self, path_, cls, **kwargs):
        params = kwargs.copy()

        catch_recursion_limit = kwargs.get('safe_all', False)
        get_all_results = (kwargs.get('all', False) is True
                           or catch_recursion_limit)

        # Remove these keys to avoid breaking the listing (urls will get too
        # long otherwise)
        for key in ['all', 'next_url', 'safe_all']:
            if key in params:
                del params[key]

        r = self._raw_get(path_, **params)
        raise_error_from_response(r, GitlabListError)

        # These attributes are not needed in the object
        for key in ['page', 'per_page', 'sudo']:
            if key in params:
                del params[key]

        # Add _from_api manually, because we are not creating objects
        # through normal path_
        params['_from_api'] = True

        results = [cls(self, item, **params) for item in r.json()
                   if item is not None]
        try:
            if ('next' in r.links and 'url' in r.links['next']
                    and get_all_results):
                args = kwargs.copy()
                args['next_url'] = r.links['next']['url']
                results.extend(self.list(cls, **args))
        except Exception as e:
            # Catch the recursion limit exception if the 'safe_all'
            # kwarg was provided
            if not (catch_recursion_limit and
                    "maximum recursion depth exceeded" in str(e)):
                raise e

        return results

    def _raw_post(self, path_, data=None, content_type=None,
                  files=None, **kwargs):
        url = '%s%s' % (self._url, path_)
        opts = self._get_session_opts(content_type)
        try:
            return self.session.post(url, params=kwargs, data=data,
                                     files=files, **opts)
        except Exception as e:
            raise GitlabConnectionError(
                "Can't connect to GitLab server (%s)" % e)

    def _raw_put(self, path_, data=None, content_type=None, **kwargs):
        url = '%s%s' % (self._url, path_)
        opts = self._get_session_opts(content_type)
        try:
            return self.session.put(url, data=data, params=kwargs, **opts)
        except Exception as e:
            raise GitlabConnectionError(
                "Can't connect to GitLab server (%s)" % e)

    def _raw_delete(self, path_, content_type=None, **kwargs):
        url = '%s%s' % (self._url, path_)
        opts = self._get_session_opts(content_type)
        try:
            return self.session.delete(url, params=kwargs, **opts)
        except Exception as e:
            raise GitlabConnectionError(
                "Can't connect to GitLab server (%s)" % e)

    def list(self, obj_class, **kwargs):
        """Request the listing of GitLab resources.

        Args:
            obj_class (object): The class of resource to request.
            **kwargs: Additional arguments to send to GitLab.

        Returns:
            list(obj_class): A list of objects of class `obj_class`.

        Raises:
            GitlabConnectionError: If the server cannot be reached.
            GitlabListError: If the server fails to perform the request.
        """
        missing = []
        for k in itertools.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._construct_url(id_=None, obj=obj_class, parameters=kwargs)

        return self._raw_list(url, obj_class, **kwargs)

    def get(self, obj_class, id=None, **kwargs):
        """Request a GitLab resources.

        Args:
            obj_class (object): The class of resource to request.
            id: The object ID.
            **kwargs: Additional arguments to send to GitLab.

        Returns:
            obj_class: An object of class `obj_class`.

        Raises:
            GitlabConnectionError: If the server cannot be reached.
            GitlabGetError: If the server fails to perform the request.
        """
        missing = []
        for k in itertools.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._construct_url(id_=_sanitize(id), obj=obj_class,
                                  parameters=kwargs)

        r = self._raw_get(url, **kwargs)
        raise_error_from_response(r, GitlabGetError)
        return r.json()

    def delete(self, obj, id=None, **kwargs):
        """Delete an object on the GitLab server.

        Args:
            obj (object or id): The object, or the class of the object to
                delete. If it is the class, the id of the object must be
                specified as the `id` arguments.
            id: ID of the object to remove. Required if `obj` is a class.
            **kwargs: Additional arguments to send to GitLab.

        Returns:
            bool: True if the operation succeeds.

        Raises:
            GitlabConnectionError: If the server cannot be reached.
            GitlabDeleteError: If the server fails to perform the request.
        """
        if inspect.isclass(obj):
            if not issubclass(obj, GitlabObject):
                raise GitlabError("Invalid class: %s" % obj)

        params = {obj.idAttr: id if id else getattr(obj, obj.idAttr)}
        params.update(kwargs)

        missing = []
        for k in itertools.chain(obj.requiredUrlAttrs,
                                 obj.requiredDeleteAttrs):
            if k not in params:
                try:
                    params[k] = getattr(obj, k)
                except KeyError:
                    missing.append(k)
        if missing:
            raise GitlabDeleteError('Missing attribute(s): %s' %
                                    ", ".join(missing))

        obj_id = params[obj.idAttr] if obj._id_in_delete_url else None
        url = self._construct_url(id_=obj_id, obj=obj, parameters=params)

        if obj._id_in_delete_url:
            # The ID is already built, no need to add it as extra key in query
            # string
            params.pop(obj.idAttr)

        r = self._raw_delete(url, **params)
        raise_error_from_response(r, GitlabDeleteError,
                                  expected_code=[200, 202, 204])
        return True

    def create(self, obj, **kwargs):
        """Create an object on the GitLab server.

        The object class and attributes define the request to be made on the
        GitLab server.

        Args:
            obj (object): The object to create.
            **kwargs: Additional arguments to send to GitLab.

        Returns:
            str: A json representation of the object as returned by the GitLab
                server

        Raises:
            GitlabConnectionError: If the server cannot be reached.
            GitlabCreateError: If the server fails to perform the request.
        """
        params = obj.__dict__.copy()
        params.update(kwargs)
        missing = []
        for k in itertools.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._construct_url(id_=None, obj=obj, parameters=params,
                                  action='create')

        # build data that can really be sent to server
        data = obj._data_for_gitlab(extra_parameters=kwargs)

        r = self._raw_post(url, data=data, content_type='application/json')
        raise_error_from_response(r, GitlabCreateError, 201)
        return r.json()

    def update(self, obj, **kwargs):
        """Update an object on the GitLab server.

        The object class and attributes define the request to be made on the
        GitLab server.

        Args:
            obj (object): The object to create.
            **kwargs: Additional arguments to send to GitLab.

        Returns:
            str: A json representation of the object as returned by the GitLab
                server

        Raises:
            GitlabConnectionError: If the server cannot be reached.
            GitlabUpdateError: If the server fails to perform the request.
        """
        params = obj.__dict__.copy()
        params.update(kwargs)
        missing = []
        if obj.requiredUpdateAttrs or obj.optionalUpdateAttrs:
            required_attrs = obj.requiredUpdateAttrs
        else:
            required_attrs = obj.requiredCreateAttrs
        for k in itertools.chain(obj.requiredUrlAttrs, required_attrs):
            if k not in params:
                missing.append(k)
        if missing:
            raise GitlabUpdateError('Missing attribute(s): %s' %
                                    ", ".join(missing))
        obj_id = params[obj.idAttr] if obj._id_in_update_url else None
        url = self._construct_url(id_=obj_id, obj=obj, parameters=params)

        # build data that can really be sent to server
        data = obj._data_for_gitlab(extra_parameters=kwargs, update=True)

        r = self._raw_put(url, data=data, content_type='application/json')
        raise_error_from_response(r, GitlabUpdateError)
        return r.json()

    def _build_url(self, path):
        """Returns the full url from path.

        If path is already a url, return it unchanged. If it's a path, append
        it to the stored url.

        This is a low-level method, different from _construct_url _build_url
        have no knowledge of GitlabObject's.

        Returns:
            str: The full URL
        """
+0 −526

File changed.

Preview size limit exceeded, changes collapsed.

Loading