We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fix Pickle support: dump() after load() fails
This is a one-liner fix to pickle support: pickle.dump() fails after pickle.load()
```
  File "/home/bourgesl/.local/lib/python3.6/site-packages/gitlab/base.py", line 50, in __getstate__
    module = state.pop("_module")
KeyError: '_module'
```

Reason:
Former self._module call invokes ```set_attr()``` that stores '_module' attribute in ```self.__dict__["_updated_attrs"]["_module"]``` that is not its correct place: ```self.__dict__["_module"]``` as expected by getstate()

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gitlab/base.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 import importlib
19 
20 
21 class RESTObject(object):
22     """Represents an object built from server data.
23 
24     It holds the attributes know from the server, and the updated attributes in
25     another. This allows smart updates, if the object allows it.
26 
27     You can redefine ``_id_attr`` in child classes to specify which attribute
28     must be used as uniq ID. ``None`` means that the object can be updated
29     without ID in the url.
30     """
31 
32     _id_attr = "id"
33 
34     def __init__(self, manager, attrs):
35         self.__dict__.update(
36             {
37                 "manager": manager,
38                 "_attrs": attrs,
39                 "_updated_attrs": {},
40                 "_module": importlib.import_module(self.__module__),
41             }
42         )
43         self.__dict__["_parent_attrs"] = self.manager.parent_attrs
44         self._create_managers()
45 
46     def __getstate__(self):
47         state = self.__dict__.copy()
48         module = state.pop("_module")
49         state["_module_name"] = module.__name__
50         return state
51 
52     def __setstate__(self, state):
53         module_name = state.pop("_module_name")
54         self.__dict__.update(state)
55         self._module = importlib.import_module(module_name)
56 
57     def __getattr__(self, name):
58         try:
59             return self.__dict__["_updated_attrs"][name]
60         except KeyError:
61             try:
62                 value = self.__dict__["_attrs"][name]
63 
64                 # If the value is a list, we copy it in the _updated_attrs dict
65                 # because we are not able to detect changes made on the object
66                 # (append, insert, pop, ...). Without forcing the attr
67                 # creation __setattr__ is never called, the list never ends up
68                 # in the _updated_attrs dict, and the update() and save()
69                 # method never push the new data to the server.
70                 # See https://github.com/python-gitlab/python-gitlab/issues/306
71                 #
72                 # note: _parent_attrs will only store simple values (int) so we
73                 # don't make this check in the next except block.
74                 if isinstance(value, list):
75                     self.__dict__["_updated_attrs"][name] = value[:]
76                     return self.__dict__["_updated_attrs"][name]
77 
78                 return value
79 
80             except KeyError:
81                 try:
82                     return self.__dict__["_parent_attrs"][name]
83                 except KeyError:
84                     raise AttributeError(name)
85 
86     def __setattr__(self, name, value):
87         self.__dict__["_updated_attrs"][name] = value
88 
89     def __str__(self):
90         data = self._attrs.copy()
91         data.update(self._updated_attrs)
92         return "%s => %s" % (type(self), data)
93 
94     def __repr__(self):
95         if self._id_attr:
96             return "<%s %s:%s>" % (
97                 self.__class__.__name__,
98                 self._id_attr,
99                 self.get_id(),
100             )
101         else:
102             return "<%s>" % self.__class__.__name__
103 
104     def __eq__(self, other):
105         if self.get_id() and other.get_id():
106             return self.get_id() == other.get_id()
107         return super(RESTObject, self) == other
108 
109     def __ne__(self, other):
110         if self.get_id() and other.get_id():
111             return self.get_id() != other.get_id()
112         return super(RESTObject, self) != other
113 
114     def __hash__(self):
115         if not self.get_id():
116             return super(RESTObject, self).__hash__()
117         return hash(self.get_id())
118 
119     def _create_managers(self):
120         managers = getattr(self, "_managers", None)
121         if managers is None:
122             return
123 
124         for attr, cls_name in self._managers:
125             cls = getattr(self._module, cls_name)
126             manager = cls(self.manager.gitlab, parent=self)
127             self.__dict__[attr] = manager
128 
129     def _update_attrs(self, new_attrs):
130         self.__dict__["_updated_attrs"] = {}
131         self.__dict__["_attrs"].update(new_attrs)
132 
133     def get_id(self):
134         """Returns the id of the resource."""
135         if self._id_attr is None or not hasattr(self, self._id_attr):
136             return None
137         return getattr(self, self._id_attr)
138 
139     @property
140     def attributes(self):
141         d = self.__dict__["_updated_attrs"].copy()
142         d.update(self.__dict__["_attrs"])
143         d.update(self.__dict__["_parent_attrs"])
144         return d
145 
146 
147 class RESTObjectList(object):
148     """Generator object representing a list of RESTObject's.
149 
150     This generator uses the Gitlab pagination system to fetch new data when
151     required.
152 
153     Note: you should not instanciate such objects, they are returned by calls
154     to RESTManager.list()
155 
156     Args:
157         manager: Manager to attach to the created objects
158         obj_cls: Type of objects to create from the json data
159         _list: A GitlabList object
160     """
161 
162     def __init__(self, manager, obj_cls, _list):
163         """Creates an objects list from a GitlabList.
164 
165         You should not create objects of this type, but use managers list()
166         methods instead.
167 
168         Args:
169             manager: the RESTManager to attach to the objects
170             obj_cls: the class of the created objects
171             _list: the GitlabList holding the data
172         """
173         self.manager = manager
174         self._obj_cls = obj_cls
175         self._list = _list
176 
177     def __iter__(self):
178         return self
179 
180     def __len__(self):
181         return len(self._list)
182 
183     def __next__(self):
184         return self.next()
185 
186     def next(self):
187         data = self._list.next()
188         return self._obj_cls(self.manager, data)
189 
190     @property
191     def current_page(self):
192         """The current page number."""
193         return self._list.current_page
194 
195     @property
196     def prev_page(self):
197         """The next page number.
198 
199         If None, the current page is the last.
200         """
201         return self._list.prev_page
202 
203     @property
204     def next_page(self):
205         """The next page number.
206 
207         If None, the current page is the last.
208         """
209         return self._list.next_page
210 
211     @property
212     def per_page(self):
213         """The number of items per page."""
214         return self._list.per_page
215 
216     @property
217     def total_pages(self):
218         """The total number of pages."""
219         return self._list.total_pages
220 
221     @property
222     def total(self):
223         """The total number of items."""
224         return self._list.total
225 
226 
227 class RESTManager(object):
228     """Base class for CRUD operations on objects.
229 
230     Derivated class must define ``_path`` and ``_obj_cls``.
231 
232     ``_path``: Base URL path on which requests will be sent (e.g. '/projects')
233     ``_obj_cls``: The class of objects that will be created
234     """
235 
236     _path = None
237     _obj_cls = None
238 
239     def __init__(self, gl, parent=None):
240         """REST manager constructor.
241 
242         Args:
243             gl (Gitlab): :class:`~gitlab.Gitlab` connection to use to make
244                          requests.
245             parent: REST object to which the manager is attached.
246         """
247         self.gitlab = gl
248         self._parent = parent  # for nested managers
249         self._computed_path = self._compute_path()
250 
251     @property
252     def parent_attrs(self):
253         return self._parent_attrs
254 
255     def _compute_path(self, path=None):
256         self._parent_attrs = {}
257         if path is None:
258             path = self._path
259         if self._parent is None or not hasattr(self, "_from_parent_attrs"):
260             return path
261 
262         data = {
263             self_attr: getattr(self._parent, parent_attr, None)
264             for self_attr, parent_attr in self._from_parent_attrs.items()
265         }
266         self._parent_attrs = data
267         return path % data
268 
269     @property
270     def path(self):
271         return self._computed_path
272
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gitlab/base.py b/gitlab/base.py
--- a/gitlab/base.py
+++ b/gitlab/base.py
@@ -52,7 +52,7 @@
     def __setstate__(self, state):
         module_name = state.pop("_module_name")
         self.__dict__.update(state)
-        self._module = importlib.import_module(module_name)
+        self.__dict__["_module"] = importlib.import_module(module_name)
 
     def __getattr__(self, name):
         try:

{"golden_diff": "diff --git a/gitlab/base.py b/gitlab/base.py\n--- a/gitlab/base.py\n+++ b/gitlab/base.py\n@@ -52,7 +52,7 @@\n     def __setstate__(self, state):\n         module_name = state.pop(\"_module_name\")\n         self.__dict__.update(state)\n-        self._module = importlib.import_module(module_name)\n+        self.__dict__[\"_module\"] = importlib.import_module(module_name)\n \n     def __getattr__(self, name):\n         try:\n", "issue": "Fix Pickle support: dump() after load() fails\nThis is a one-liner fix to pickle support: pickle.dump() fails after pickle.load()\r\n```\r\n  File \"/home/bourgesl/.local/lib/python3.6/site-packages/gitlab/base.py\", line 50, in __getstate__\r\n    module = state.pop(\"_module\")\r\nKeyError: '_module'\r\n```\r\n\r\nReason:\r\nFormer self._module call invokes ```set_attr()``` that stores '_module' attribute in ```self.__dict__[\"_updated_attrs\"][\"_module\"]``` that is not its correct place: ```self.__dict__[\"_module\"]``` as expected by getstate()\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nimport importlib\n\n\nclass RESTObject(object):\n    \"\"\"Represents an object built from server data.\n\n    It holds the attributes know from the server, and the updated attributes in\n    another. This allows smart updates, if the object allows it.\n\n    You can redefine ``_id_attr`` in child classes to specify which attribute\n    must be used as uniq ID. ``None`` means that the object can be updated\n    without ID in the url.\n    \"\"\"\n\n    _id_attr = \"id\"\n\n    def __init__(self, manager, attrs):\n        self.__dict__.update(\n            {\n                \"manager\": manager,\n                \"_attrs\": attrs,\n                \"_updated_attrs\": {},\n                \"_module\": importlib.import_module(self.__module__),\n            }\n        )\n        self.__dict__[\"_parent_attrs\"] = self.manager.parent_attrs\n        self._create_managers()\n\n    def __getstate__(self):\n        state = self.__dict__.copy()\n        module = state.pop(\"_module\")\n        state[\"_module_name\"] = module.__name__\n        return state\n\n    def __setstate__(self, state):\n        module_name = state.pop(\"_module_name\")\n        self.__dict__.update(state)\n        self._module = importlib.import_module(module_name)\n\n    def __getattr__(self, name):\n        try:\n            return self.__dict__[\"_updated_attrs\"][name]\n        except KeyError:\n            try:\n                value = self.__dict__[\"_attrs\"][name]\n\n                # If the value is a list, we copy it in the _updated_attrs dict\n                # because we are not able to detect changes made on the object\n                # (append, insert, pop, ...). Without forcing the attr\n                # creation __setattr__ is never called, the list never ends up\n                # in the _updated_attrs dict, and the update() and save()\n                # method never push the new data to the server.\n                # See https://github.com/python-gitlab/python-gitlab/issues/306\n                #\n                # note: _parent_attrs will only store simple values (int) so we\n                # don't make this check in the next except block.\n                if isinstance(value, list):\n                    self.__dict__[\"_updated_attrs\"][name] = value[:]\n                    return self.__dict__[\"_updated_attrs\"][name]\n\n                return value\n\n            except KeyError:\n                try:\n                    return self.__dict__[\"_parent_attrs\"][name]\n                except KeyError:\n                    raise AttributeError(name)\n\n    def __setattr__(self, name, value):\n        self.__dict__[\"_updated_attrs\"][name] = value\n\n    def __str__(self):\n        data = self._attrs.copy()\n        data.update(self._updated_attrs)\n        return \"%s => %s\" % (type(self), data)\n\n    def __repr__(self):\n        if self._id_attr:\n            return \"<%s %s:%s>\" % (\n                self.__class__.__name__,\n                self._id_attr,\n                self.get_id(),\n            )\n        else:\n            return \"<%s>\" % self.__class__.__name__\n\n    def __eq__(self, other):\n        if self.get_id() and other.get_id():\n            return self.get_id() == other.get_id()\n        return super(RESTObject, self) == other\n\n    def __ne__(self, other):\n        if self.get_id() and other.get_id():\n            return self.get_id() != other.get_id()\n        return super(RESTObject, self) != other\n\n    def __hash__(self):\n        if not self.get_id():\n            return super(RESTObject, self).__hash__()\n        return hash(self.get_id())\n\n    def _create_managers(self):\n        managers = getattr(self, \"_managers\", None)\n        if managers is None:\n            return\n\n        for attr, cls_name in self._managers:\n            cls = getattr(self._module, cls_name)\n            manager = cls(self.manager.gitlab, parent=self)\n            self.__dict__[attr] = manager\n\n    def _update_attrs(self, new_attrs):\n        self.__dict__[\"_updated_attrs\"] = {}\n        self.__dict__[\"_attrs\"].update(new_attrs)\n\n    def get_id(self):\n        \"\"\"Returns the id of the resource.\"\"\"\n        if self._id_attr is None or not hasattr(self, self._id_attr):\n            return None\n        return getattr(self, self._id_attr)\n\n    @property\n    def attributes(self):\n        d = self.__dict__[\"_updated_attrs\"].copy()\n        d.update(self.__dict__[\"_attrs\"])\n        d.update(self.__dict__[\"_parent_attrs\"])\n        return d\n\n\nclass RESTObjectList(object):\n    \"\"\"Generator object representing a list of RESTObject's.\n\n    This generator uses the Gitlab pagination system to fetch new data when\n    required.\n\n    Note: you should not instanciate such objects, they are returned by calls\n    to RESTManager.list()\n\n    Args:\n        manager: Manager to attach to the created objects\n        obj_cls: Type of objects to create from the json data\n        _list: A GitlabList object\n    \"\"\"\n\n    def __init__(self, manager, obj_cls, _list):\n        \"\"\"Creates an objects list from a GitlabList.\n\n        You should not create objects of this type, but use managers list()\n        methods instead.\n\n        Args:\n            manager: the RESTManager to attach to the objects\n            obj_cls: the class of the created objects\n            _list: the GitlabList holding the data\n        \"\"\"\n        self.manager = manager\n        self._obj_cls = obj_cls\n        self._list = _list\n\n    def __iter__(self):\n        return self\n\n    def __len__(self):\n        return len(self._list)\n\n    def __next__(self):\n        return self.next()\n\n    def next(self):\n        data = self._list.next()\n        return self._obj_cls(self.manager, data)\n\n    @property\n    def current_page(self):\n        \"\"\"The current page number.\"\"\"\n        return self._list.current_page\n\n    @property\n    def prev_page(self):\n        \"\"\"The next page number.\n\n        If None, the current page is the last.\n        \"\"\"\n        return self._list.prev_page\n\n    @property\n    def next_page(self):\n        \"\"\"The next page number.\n\n        If None, the current page is the last.\n        \"\"\"\n        return self._list.next_page\n\n    @property\n    def per_page(self):\n        \"\"\"The number of items per page.\"\"\"\n        return self._list.per_page\n\n    @property\n    def total_pages(self):\n        \"\"\"The total number of pages.\"\"\"\n        return self._list.total_pages\n\n    @property\n    def total(self):\n        \"\"\"The total number of items.\"\"\"\n        return self._list.total\n\n\nclass RESTManager(object):\n    \"\"\"Base class for CRUD operations on objects.\n\n    Derivated class must define ``_path`` and ``_obj_cls``.\n\n    ``_path``: Base URL path on which requests will be sent (e.g. '/projects')\n    ``_obj_cls``: The class of objects that will be created\n    \"\"\"\n\n    _path = None\n    _obj_cls = None\n\n    def __init__(self, gl, parent=None):\n        \"\"\"REST manager constructor.\n\n        Args:\n            gl (Gitlab): :class:`~gitlab.Gitlab` connection to use to make\n                         requests.\n            parent: REST object to which the manager is attached.\n        \"\"\"\n        self.gitlab = gl\n        self._parent = parent  # for nested managers\n        self._computed_path = self._compute_path()\n\n    @property\n    def parent_attrs(self):\n        return self._parent_attrs\n\n    def _compute_path(self, path=None):\n        self._parent_attrs = {}\n        if path is None:\n            path = self._path\n        if self._parent is None or not hasattr(self, \"_from_parent_attrs\"):\n            return path\n\n        data = {\n            self_attr: getattr(self._parent, parent_attr, None)\n            for self_attr, parent_attr in self._from_parent_attrs.items()\n        }\n        self._parent_attrs = data\n        return path % data\n\n    @property\n    def path(self):\n        return self._computed_path\n", "path": "gitlab/base.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright (C) 2013-2017 Gauvain Pocentek <gauvain@pocentek.net>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nimport importlib\n\n\nclass RESTObject(object):\n    \"\"\"Represents an object built from server data.\n\n    It holds the attributes know from the server, and the updated attributes in\n    another. This allows smart updates, if the object allows it.\n\n    You can redefine ``_id_attr`` in child classes to specify which attribute\n    must be used as uniq ID. ``None`` means that the object can be updated\n    without ID in the url.\n    \"\"\"\n\n    _id_attr = \"id\"\n\n    def __init__(self, manager, attrs):\n        self.__dict__.update(\n            {\n                \"manager\": manager,\n                \"_attrs\": attrs,\n                \"_updated_attrs\": {},\n                \"_module\": importlib.import_module(self.__module__),\n            }\n        )\n        self.__dict__[\"_parent_attrs\"] = self.manager.parent_attrs\n        self._create_managers()\n\n    def __getstate__(self):\n        state = self.__dict__.copy()\n        module = state.pop(\"_module\")\n        state[\"_module_name\"] = module.__name__\n        return state\n\n    def __setstate__(self, state):\n        module_name = state.pop(\"_module_name\")\n        self.__dict__.update(state)\n        self.__dict__[\"_module\"] = importlib.import_module(module_name)\n\n    def __getattr__(self, name):\n        try:\n            return self.__dict__[\"_updated_attrs\"][name]\n        except KeyError:\n            try:\n                value = self.__dict__[\"_attrs\"][name]\n\n                # If the value is a list, we copy it in the _updated_attrs dict\n                # because we are not able to detect changes made on the object\n                # (append, insert, pop, ...). Without forcing the attr\n                # creation __setattr__ is never called, the list never ends up\n                # in the _updated_attrs dict, and the update() and save()\n                # method never push the new data to the server.\n                # See https://github.com/python-gitlab/python-gitlab/issues/306\n                #\n                # note: _parent_attrs will only store simple values (int) so we\n                # don't make this check in the next except block.\n                if isinstance(value, list):\n                    self.__dict__[\"_updated_attrs\"][name] = value[:]\n                    return self.__dict__[\"_updated_attrs\"][name]\n\n                return value\n\n            except KeyError:\n                try:\n                    return self.__dict__[\"_parent_attrs\"][name]\n                except KeyError:\n                    raise AttributeError(name)\n\n    def __setattr__(self, name, value):\n        self.__dict__[\"_updated_attrs\"][name] = value\n\n    def __str__(self):\n        data = self._attrs.copy()\n        data.update(self._updated_attrs)\n        return \"%s => %s\" % (type(self), data)\n\n    def __repr__(self):\n        if self._id_attr:\n            return \"<%s %s:%s>\" % (\n                self.__class__.__name__,\n                self._id_attr,\n                self.get_id(),\n            )\n        else:\n            return \"<%s>\" % self.__class__.__name__\n\n    def __eq__(self, other):\n        if self.get_id() and other.get_id():\n            return self.get_id() == other.get_id()\n        return super(RESTObject, self) == other\n\n    def __ne__(self, other):\n        if self.get_id() and other.get_id():\n            return self.get_id() != other.get_id()\n        return super(RESTObject, self) != other\n\n    def __hash__(self):\n        if not self.get_id():\n            return super(RESTObject, self).__hash__()\n        return hash(self.get_id())\n\n    def _create_managers(self):\n        managers = getattr(self, \"_managers\", None)\n        if managers is None:\n            return\n\n        for attr, cls_name in self._managers:\n            cls = getattr(self._module, cls_name)\n            manager = cls(self.manager.gitlab, parent=self)\n            self.__dict__[attr] = manager\n\n    def _update_attrs(self, new_attrs):\n        self.__dict__[\"_updated_attrs\"] = {}\n        self.__dict__[\"_attrs\"].update(new_attrs)\n\n    def get_id(self):\n        \"\"\"Returns the id of the resource.\"\"\"\n        if self._id_attr is None or not hasattr(self, self._id_attr):\n            return None\n        return getattr(self, self._id_attr)\n\n    @property\n    def attributes(self):\n        d = self.__dict__[\"_updated_attrs\"].copy()\n        d.update(self.__dict__[\"_attrs\"])\n        d.update(self.__dict__[\"_parent_attrs\"])\n        return d\n\n\nclass RESTObjectList(object):\n    \"\"\"Generator object representing a list of RESTObject's.\n\n    This generator uses the Gitlab pagination system to fetch new data when\n    required.\n\n    Note: you should not instanciate such objects, they are returned by calls\n    to RESTManager.list()\n\n    Args:\n        manager: Manager to attach to the created objects\n        obj_cls: Type of objects to create from the json data\n        _list: A GitlabList object\n    \"\"\"\n\n    def __init__(self, manager, obj_cls, _list):\n        \"\"\"Creates an objects list from a GitlabList.\n\n        You should not create objects of this type, but use managers list()\n        methods instead.\n\n        Args:\n            manager: the RESTManager to attach to the objects\n            obj_cls: the class of the created objects\n            _list: the GitlabList holding the data\n        \"\"\"\n        self.manager = manager\n        self._obj_cls = obj_cls\n        self._list = _list\n\n    def __iter__(self):\n        return self\n\n    def __len__(self):\n        return len(self._list)\n\n    def __next__(self):\n        return self.next()\n\n    def next(self):\n        data = self._list.next()\n        return self._obj_cls(self.manager, data)\n\n    @property\n    def current_page(self):\n        \"\"\"The current page number.\"\"\"\n        return self._list.current_page\n\n    @property\n    def prev_page(self):\n        \"\"\"The next page number.\n\n        If None, the current page is the last.\n        \"\"\"\n        return self._list.prev_page\n\n    @property\n    def next_page(self):\n        \"\"\"The next page number.\n\n        If None, the current page is the last.\n        \"\"\"\n        return self._list.next_page\n\n    @property\n    def per_page(self):\n        \"\"\"The number of items per page.\"\"\"\n        return self._list.per_page\n\n    @property\n    def total_pages(self):\n        \"\"\"The total number of pages.\"\"\"\n        return self._list.total_pages\n\n    @property\n    def total(self):\n        \"\"\"The total number of items.\"\"\"\n        return self._list.total\n\n\nclass RESTManager(object):\n    \"\"\"Base class for CRUD operations on objects.\n\n    Derivated class must define ``_path`` and ``_obj_cls``.\n\n    ``_path``: Base URL path on which requests will be sent (e.g. '/projects')\n    ``_obj_cls``: The class of objects that will be created\n    \"\"\"\n\n    _path = None\n    _obj_cls = None\n\n    def __init__(self, gl, parent=None):\n        \"\"\"REST manager constructor.\n\n        Args:\n            gl (Gitlab): :class:`~gitlab.Gitlab` connection to use to make\n                         requests.\n            parent: REST object to which the manager is attached.\n        \"\"\"\n        self.gitlab = gl\n        self._parent = parent  # for nested managers\n        self._computed_path = self._compute_path()\n\n    @property\n    def parent_attrs(self):\n        return self._parent_attrs\n\n    def _compute_path(self, path=None):\n        self._parent_attrs = {}\n        if path is None:\n            path = self._path\n        if self._parent is None or not hasattr(self, \"_from_parent_attrs\"):\n            return path\n\n        data = {\n            self_attr: getattr(self._parent, parent_attr, None)\n            for self_attr, parent_attr in self._from_parent_attrs.items()\n        }\n        self._parent_attrs = data\n        return path % data\n\n    @property\n    def path(self):\n        return self._computed_path\n", "path": "gitlab/base.py"}]}