Skip to content

Commit f418767

Browse files
author
Gauvain Pocentek
committed
Migrate all v4 objects to new API
Some things are probably broken. Next step is writting unit and functional tests. And fix.
1 parent 0467f77 commit f418767

File tree

5 files changed

+926
-1161
lines changed

5 files changed

+926
-1161
lines changed

gitlab/__init__.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ def http_get(self, path, query_data={}, streamed=False, **kwargs):
683683
raise GitlaParsingError(
684684
message="Failed to parse the server message")
685685
else:
686-
return r
686+
return result
687687

688688
def http_list(self, path, query_data={}, **kwargs):
689689
"""Make a GET request to the Gitlab server for list-oriented queries.
@@ -722,19 +722,23 @@ def http_post(self, path, query_data={}, post_data={}, **kwargs):
722722
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
723723
724724
Returns:
725-
The parsed json returned by the server.
725+
The parsed json returned by the server if json is return, else the
726+
raw content.
726727
727728
Raises:
728729
GitlabHttpError: When the return code is not 2xx
729730
GitlabParsingError: IF the json data could not be parsed
730731
"""
731732
result = self.http_request('post', path, query_data=query_data,
732733
post_data=post_data, **kwargs)
733-
try:
734-
return result.json()
735-
except Exception:
736-
raise GitlabParsingError(
737-
message="Failed to parse the server message")
734+
if result.headers.get('Content-Type', None) == 'application/json':
735+
try:
736+
return result.json()
737+
except Exception:
738+
raise GitlabParsingError(
739+
message="Failed to parse the server message")
740+
else:
741+
return result.content
738742

739743
def http_put(self, path, query_data={}, post_data={}, **kwargs):
740744
"""Make a PUT request to the Gitlab server.

gitlab/base.py

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -533,31 +533,6 @@ def __ne__(self, other):
533533
return not self.__eq__(other)
534534

535535

536-
class SaveMixin(object):
537-
"""Mixin for RESTObject's that can be updated."""
538-
def save(self, **kwargs):
539-
"""Saves the changes made to the object to the server.
540-
541-
Args:
542-
**kwargs: Extra option to send to the server (e.g. sudo)
543-
544-
The object is updated to match what the server returns.
545-
"""
546-
updated_data = {}
547-
required, optional = self.manager.get_update_attrs()
548-
for attr in required:
549-
# Get everything required, no matter if it's been updated
550-
updated_data[attr] = getattr(self, attr)
551-
# Add the updated attributes
552-
updated_data.update(self._updated_attrs)
553-
554-
# class the manager
555-
obj_id = self.get_id()
556-
server_data = self.manager.update(obj_id, updated_data, **kwargs)
557-
self._updated_attrs = {}
558-
self._attrs.update(server_data)
559-
560-
561536
class RESTObject(object):
562537
"""Represents an object built from server data.
563538
@@ -618,6 +593,10 @@ def _create_managers(self):
618593
manager = cls(self.manager.gitlab, parent=self)
619594
self.__dict__[attr] = manager
620595

596+
def _update_attrs(self, new_attrs):
597+
self._updated_attrs = {}
598+
self._attrs.update(new_attrs)
599+
621600
def get_id(self):
622601
if self._id_attr is None:
623602
return None
@@ -674,13 +653,15 @@ def __init__(self, gl, parent=None):
674653
self._parent = parent # for nested managers
675654
self._computed_path = self._compute_path()
676655

677-
def _compute_path(self):
656+
def _compute_path(self, path=None):
657+
if path is None:
658+
path = self._path
678659
if self._parent is None or not hasattr(self, '_from_parent_attrs'):
679-
return self._path
660+
return path
680661

681662
data = {self_attr: getattr(self._parent, parent_attr)
682663
for self_attr, parent_attr in self._from_parent_attrs.items()}
683-
return self._path % data
664+
return path % data
684665

685666
@property
686667
def path(self):

gitlab/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class GitlabAuthenticationError(GitlabError):
3939
pass
4040

4141

42+
class GitlabParsingError(GitlabError):
43+
pass
44+
45+
4246
class GitlabConnectionError(GitlabError):
4347
pass
4448

gitlab/mixins.py

Lines changed: 159 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# You should have received a copy of the GNU Lesser General Public License
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

18+
import gitlab
1819
from gitlab import base
1920

2021

@@ -70,7 +71,10 @@ def list(self, **kwargs):
7071
list(RESTObjectList).
7172
"""
7273

73-
obj = self.gitlab.http_list(self.path, **kwargs)
74+
# Allow to overwrite the path, handy for custom listings
75+
path = kwargs.pop('path', self.path)
76+
77+
obj = self.gitlab.http_list(path, **kwargs)
7478
if isinstance(obj, list):
7579
return [self._obj_cls(self, item) for item in obj]
7680
else:
@@ -102,7 +106,7 @@ class RetrieveMixin(ListMixin, GetMixin):
102106

103107

104108
class CreateMixin(object):
105-
def _check_missing_attrs(self, data):
109+
def _check_missing_create_attrs(self, data):
106110
required, optional = self.get_create_attrs()
107111
missing = []
108112
for attr in required:
@@ -119,13 +123,10 @@ def get_create_attrs(self):
119123
tuple: 2 items: list of required arguments and list of optional
120124
arguments for creation (in that order)
121125
"""
122-
if hasattr(self, '_create_attrs'):
123-
return (self._create_attrs['required'],
124-
self._create_attrs['optional'])
125-
return (tuple(), tuple())
126+
return getattr(self, '_create_attrs', (tuple(), tuple()))
126127

127128
def create(self, data, **kwargs):
128-
"""Created a new object.
129+
"""Creates a new object.
129130
130131
Args:
131132
data (dict): parameters to send to the server to create the
@@ -136,16 +137,17 @@ def create(self, data, **kwargs):
136137
RESTObject: a new instance of the manage object class build with
137138
the data sent by the server
138139
"""
139-
self._check_missing_attrs(data)
140+
self._check_missing_create_attrs(data)
140141
if hasattr(self, '_sanitize_data'):
141142
data = self._sanitize_data(data, 'create')
142-
server_data = self.gitlab.http_post(self.path, post_data=data,
143-
**kwargs)
143+
# Handle specific URL for creation
144+
path = kwargs.get('path', self.path)
145+
server_data = self.gitlab.http_post(path, post_data=data, **kwargs)
144146
return self._obj_cls(self, server_data)
145147

146148

147149
class UpdateMixin(object):
148-
def _check_missing_attrs(self, data):
150+
def _check_missing_update_attrs(self, data):
149151
required, optional = self.get_update_attrs()
150152
missing = []
151153
for attr in required:
@@ -162,10 +164,7 @@ def get_update_attrs(self):
162164
tuple: 2 items: list of required arguments and list of optional
163165
arguments for update (in that order)
164166
"""
165-
if hasattr(self, '_update_attrs'):
166-
return (self._update_attrs['required'],
167-
self._update_attrs['optional'])
168-
return (tuple(), tuple())
167+
return getattr(self, '_update_attrs', (tuple(), tuple()))
169168

170169
def update(self, id=None, new_data={}, **kwargs):
171170
"""Update an object on the server.
@@ -184,9 +183,11 @@ def update(self, id=None, new_data={}, **kwargs):
184183
else:
185184
path = '%s/%s' % (self.path, id)
186185

187-
self._check_missing_attrs(new_data)
186+
self._check_missing_update_attrs(new_data)
188187
if hasattr(self, '_sanitize_data'):
189188
data = self._sanitize_data(new_data, 'update')
189+
else:
190+
data = new_data
190191
server_data = self.gitlab.http_put(path, post_data=data, **kwargs)
191192
return server_data
192193

@@ -205,3 +206,145 @@ def delete(self, id, **kwargs):
205206

206207
class CRUDMixin(GetMixin, ListMixin, CreateMixin, UpdateMixin, DeleteMixin):
207208
pass
209+
210+
211+
class NoUpdateMixin(GetMixin, ListMixin, CreateMixin, DeleteMixin):
212+
pass
213+
214+
215+
class SaveMixin(object):
216+
"""Mixin for RESTObject's that can be updated."""
217+
def _get_updated_data(self):
218+
updated_data = {}
219+
required, optional = self.manager.get_update_attrs()
220+
for attr in required:
221+
# Get everything required, no matter if it's been updated
222+
updated_data[attr] = getattr(self, attr)
223+
# Add the updated attributes
224+
updated_data.update(self._updated_attrs)
225+
226+
return updated_data
227+
228+
def save(self, **kwargs):
229+
"""Saves the changes made to the object to the server.
230+
231+
Args:
232+
**kwargs: Extra option to send to the server (e.g. sudo)
233+
234+
The object is updated to match what the server returns.
235+
"""
236+
updated_data = self._get_updated_data()
237+
238+
# call the manager
239+
obj_id = self.get_id()
240+
server_data = self.manager.update(obj_id, updated_data, **kwargs)
241+
self._update_attrs(server_data)
242+
243+
244+
class AccessRequestMixin(object):
245+
def approve(self, access_level=gitlab.DEVELOPER_ACCESS, **kwargs):
246+
"""Approve an access request.
247+
248+
Attrs:
249+
access_level (int): The access level for the user.
250+
251+
Raises:
252+
GitlabConnectionError: If the server cannot be reached.
253+
GitlabUpdateError: If the server fails to perform the request.
254+
"""
255+
256+
path = '%s/%s/approve' % (self.manager.path, self.id)
257+
data = {'access_level': access_level}
258+
server_data = self.manager.gitlab.http_put(url, post_data=data,
259+
**kwargs)
260+
self._update_attrs(server_data)
261+
262+
263+
class SubscribableMixin(object):
264+
def subscribe(self, **kwarg):
265+
"""Subscribe to the object notifications.
266+
267+
raises:
268+
gitlabconnectionerror: if the server cannot be reached.
269+
gitlabsubscribeerror: if the subscription cannot be done
270+
"""
271+
path = '%s/%s/subscribe' % (self.manager.path, self.get_id())
272+
server_data = self.manager.gitlab.http_post(path, **kwargs)
273+
self._update_attrs(server_data)
274+
275+
def unsubscribe(self, **kwargs):
276+
"""Unsubscribe from the object notifications.
277+
278+
raises:
279+
gitlabconnectionerror: if the server cannot be reached.
280+
gitlabunsubscribeerror: if the unsubscription cannot be done
281+
"""
282+
path = '%s/%s/unsubscribe' % (self.manager.path, self.get_id())
283+
server_data = self.manager.gitlab.http_post(path, **kwargs)
284+
self._update_attrs(server_data)
285+
286+
287+
class TodoMixin(object):
288+
def todo(self, **kwargs):
289+
"""Create a todo associated to the object.
290+
291+
Raises:
292+
GitlabConnectionError: If the server cannot be reached.
293+
"""
294+
path = '%s/%s/todo' % (self.manager.path, self.get_id())
295+
self.manager.gitlab.http_post(path, **kwargs)
296+
297+
298+
class TimeTrackingMixin(object):
299+
def time_stats(self, **kwargs):
300+
"""Get time stats for the object.
301+
302+
Raises:
303+
GitlabConnectionError: If the server cannot be reached.
304+
"""
305+
path = '%s/%s/time_stats' % (self.manager.path, self.get_id())
306+
return self.manager.gitlab.http_get(path, **kwargs)
307+
308+
def time_estimate(self, duration, **kwargs):
309+
"""Set an estimated time of work for the object.
310+
311+
Args:
312+
duration (str): duration in human format (e.g. 3h30)
313+
314+
Raises:
315+
GitlabConnectionError: If the server cannot be reached.
316+
"""
317+
path = '%s/%s/time_estimate' % (self.manager.path, self.get_id())
318+
data = {'duration': duration}
319+
return self.manager.gitlab.http_post(path, post_data=data, **kwargs)
320+
321+
def reset_time_estimate(self, **kwargs):
322+
"""Resets estimated time for the object to 0 seconds.
323+
324+
Raises:
325+
GitlabConnectionError: If the server cannot be reached.
326+
"""
327+
path = '%s/%s/rest_time_estimate' % (self.manager.path, self.get_id())
328+
return self.manager.gitlab.http_post(path, **kwargs)
329+
330+
def add_spent_time(self, duration, **kwargs):
331+
"""Add time spent working on the object.
332+
333+
Args:
334+
duration (str): duration in human format (e.g. 3h30)
335+
336+
Raises:
337+
GitlabConnectionError: If the server cannot be reached.
338+
"""
339+
path = '%s/%s/add_spent_time' % (self.manager.path, self.get_id())
340+
data = {'duration': duration}
341+
return self.manager.gitlab.http_post(path, post_data=data, **kwargs)
342+
343+
def reset_spent_time(self, **kwargs):
344+
"""Resets the time spent working on the object.
345+
346+
Raises:
347+
GitlabConnectionError: If the server cannot be reached.
348+
"""
349+
path = '%s/%s/reset_spent_time' % (self.manager.path, self.get_id())
350+
return self.manager.gitlab.http_post(path, **kwargs)

0 commit comments

Comments
 (0)