Skip to content

Commit c5ad540

Browse files
author
Gauvain Pocentek
committed
Add lower-level methods for Gitlab()
Multiple goals: * Support making direct queries to the Gitlab server, without objects and managers. * Progressively remove the need to know about managers and objects in the Gitlab class; the Gitlab should only be an HTTP proxy to the gitlab server. * With this the objects gain control on how they should do requests. The complexities of dealing with object specifics will be moved in the object classes where they belong.
1 parent 88900e0 commit c5ad540

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

gitlab/__init__.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,3 +599,224 @@ def update(self, obj, **kwargs):
599599
r = self._raw_put(url, data=data, content_type='application/json')
600600
raise_error_from_response(r, GitlabUpdateError)
601601
return r.json()
602+
603+
def _build_url(self, path):
604+
"""Returns the full url from path.
605+
606+
If path is already a url, return it unchanged. If it's a path, append
607+
it to the stored url.
608+
609+
This is a low-level method, different from _construct_url _build_url
610+
have no knowledge of GitlabObject's.
611+
612+
Returns:
613+
str: The full URL
614+
"""
615+
if path.startswith('http://') or path.startswith('https://'):
616+
return path
617+
else:
618+
return '%s%s' % (self._url, path)
619+
620+
def http_request(self, verb, path, query_data={}, post_data={},
621+
streamed=False, **kwargs):
622+
"""Make an HTTP request to the Gitlab server.
623+
624+
Args:
625+
verb (str): The HTTP method to call ('get', 'post', 'put',
626+
'delete')
627+
path (str): Path or full URL to query ('/projects' or
628+
'http://whatever/v4/api/projecs')
629+
query_data (dict): Data to send as query parameters
630+
post_data (dict): Data to send in the body (will be converted to
631+
json)
632+
streamed (bool): Whether the data should be streamed
633+
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
634+
635+
Returns:
636+
A requests result object.
637+
638+
Raises:
639+
GitlabHttpError: When the return code is not 2xx
640+
"""
641+
url = self._build_url(path)
642+
params = query_data.copy()
643+
params.update(kwargs)
644+
opts = self._get_session_opts(content_type='application/json')
645+
result = self.session.request(verb, url, json=post_data,
646+
params=params, stream=streamed, **opts)
647+
if not (200 <= result.status_code < 300):
648+
raise GitlabHttpError(response_code=result.status_code)
649+
return result
650+
651+
def http_get(self, path, query_data={}, streamed=False, **kwargs):
652+
"""Make a GET request to the Gitlab server.
653+
654+
Args:
655+
path (str): Path or full URL to query ('/projects' or
656+
'http://whatever/v4/api/projecs')
657+
query_data (dict): Data to send as query parameters
658+
streamed (bool): Whether the data should be streamed
659+
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
660+
661+
Returns:
662+
A requests result object is streamed is True or the content type is
663+
not json.
664+
The parsed json data otherwise.
665+
666+
Raises:
667+
GitlabHttpError: When the return code is not 2xx
668+
GitlabParsingError: IF the json data could not be parsed
669+
"""
670+
result = self.http_request('get', path, query_data=query_data,
671+
streamed=streamed, **kwargs)
672+
if (result.headers['Content-Type'] == 'application/json' and
673+
not streamed):
674+
try:
675+
return result.json()
676+
except Exception as e:
677+
raise GitlaParsingError(
678+
message="Failed to parse the server message")
679+
else:
680+
return r
681+
682+
def http_list(self, path, query_data={}, **kwargs):
683+
"""Make a GET request to the Gitlab server for list-oriented queries.
684+
685+
Args:
686+
path (str): Path or full URL to query ('/projects' or
687+
'http://whatever/v4/api/projecs')
688+
query_data (dict): Data to send as query parameters
689+
**kwargs: Extra data to make the query (e.g. sudo, per_page, page,
690+
all)
691+
692+
Returns:
693+
GitlabList: A generator giving access to the objects. If an ``all``
694+
kwarg is defined and True, returns a list of all the objects (will
695+
possibly make numerous calls to the Gtilab server and eat a lot of
696+
memory)
697+
698+
Raises:
699+
GitlabHttpError: When the return code is not 2xx
700+
GitlabParsingError: IF the json data could not be parsed
701+
"""
702+
url = self._build_url(path)
703+
get_all = kwargs.pop('all', False)
704+
obj_gen = GitlabList(self, url, query_data, **kwargs)
705+
return list(obj_gen) if get_all else obj_gen
706+
707+
def http_post(self, path, query_data={}, post_data={}, **kwargs):
708+
"""Make a POST request to the Gitlab server.
709+
710+
Args:
711+
path (str): Path or full URL to query ('/projects' or
712+
'http://whatever/v4/api/projecs')
713+
query_data (dict): Data to send as query parameters
714+
post_data (dict): Data to send in the body (will be converted to
715+
json)
716+
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
717+
718+
Returns:
719+
The parsed json returned by the server.
720+
721+
Raises:
722+
GitlabHttpError: When the return code is not 2xx
723+
GitlabParsingError: IF the json data could not be parsed
724+
"""
725+
result = self.http_request('post', path, query_data=query_data,
726+
post_data=post_data, **kwargs)
727+
try:
728+
return result.json()
729+
except Exception as e:
730+
raise GitlabParsingError(message="Failed to parse the server message")
731+
732+
def http_put(self, path, query_data={}, post_data={}, **kwargs):
733+
"""Make a PUT request to the Gitlab server.
734+
735+
Args:
736+
path (str): Path or full URL to query ('/projects' or
737+
'http://whatever/v4/api/projecs')
738+
query_data (dict): Data to send as query parameters
739+
post_data (dict): Data to send in the body (will be converted to
740+
json)
741+
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
742+
743+
Returns:
744+
The parsed json returned by the server.
745+
746+
Raises:
747+
GitlabHttpError: When the return code is not 2xx
748+
GitlabParsingError: IF the json data could not be parsed
749+
"""
750+
result = self.hhtp_request('put', path, query_data=query_data,
751+
post_data=post_data, **kwargs)
752+
try:
753+
return result.json()
754+
except Exception as e:
755+
raise GitlabParsingError(message="Failed to parse the server message")
756+
757+
def http_delete(self, path, **kwargs):
758+
"""Make a PUT request to the Gitlab server.
759+
760+
Args:
761+
path (str): Path or full URL to query ('/projects' or
762+
'http://whatever/v4/api/projecs')
763+
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
764+
765+
Returns:
766+
True.
767+
768+
Raises:
769+
GitlabHttpError: When the return code is not 2xx
770+
"""
771+
result = self.http_request('delete', path, **kwargs)
772+
return True
773+
774+
775+
class GitlabList(object):
776+
"""Generator representing a list of remote objects.
777+
778+
The object handles the links returned by a query to the API, and will call
779+
the API again when needed.
780+
"""
781+
782+
def __init__(self, gl, url, query_data, **kwargs):
783+
self._gl = gl
784+
self._query(url, query_data, **kwargs)
785+
786+
def _query(self, url, query_data={}, **kwargs):
787+
result = self._gl.http_request('get', url, query_data=query_data,
788+
**kwargs)
789+
try:
790+
self._next_url = result.links['next']['url']
791+
except KeyError:
792+
self._next_url = None
793+
self._current_page = result.headers.get('X-Page')
794+
self._next_page = result.headers.get('X-Next-Page')
795+
self._per_page = result.headers.get('X-Per-Page')
796+
self._total_pages = result.headers.get('X-Total-Pages')
797+
self._total = result.headers.get('X-Total')
798+
799+
try:
800+
self._data = result.json()
801+
except Exception as e:
802+
raise GitlabParsingError(message="Failed to parse the server message")
803+
804+
self._current = 0
805+
806+
def __iter__(self):
807+
return self
808+
809+
def __next__(self):
810+
return self.next()
811+
812+
def next(self):
813+
try:
814+
item = self._data[self._current]
815+
self._current += 1
816+
return item
817+
except IndexError:
818+
if self._next_url:
819+
self._query(self._next_url)
820+
return self._data[self._current]
821+
822+
raise StopIteration

gitlab/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ class GitlabOperationError(GitlabError):
4747
pass
4848

4949

50+
class GitlabHttpError(GitlabError):
51+
pass
52+
53+
54+
class GitlaParsingError(GitlabHttpError):
55+
pass
56+
57+
5058
class GitlabListError(GitlabOperationError):
5159
pass
5260

0 commit comments

Comments
 (0)