Skip to content

Commit d7c7911

Browse files
author
Gauvain Pocentek
committed
functional tests for v4
Update the python tests for v4, and fix the problems raised when running those tests.
1 parent c15ba3b commit d7c7911

8 files changed

+489
-45
lines changed

docs/switching-to-v4.rst

+8-2
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,17 @@ following important changes in the python API:
115115
+ :attr:`~gitlab.Gitlab.http_put`
116116
+ :attr:`~gitlab.Gitlab.http_delete`
117117

118+
* The users ``get_by_username`` method has been removed. It doesn't exist in
119+
the GitLab API. You can use the ``username`` filter attribute when listing to
120+
get a similar behavior:
121+
122+
.. code-block:: python
123+
124+
user = list(gl.users.list(username='jdoe'))[0]
125+
118126
119127
Undergoing work
120128
===============
121129

122-
* The ``delete()`` method for objects is not yet available. For now you need to
123-
use ``manager.delete(obj.id)``.
124130
* The ``page`` and ``per_page`` arguments for listing don't behave as they used
125131
to. Their behavior will be restored.

gitlab/__init__.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -645,12 +645,32 @@ def http_request(self, verb, path, query_data={}, post_data={},
645645
Raises:
646646
GitlabHttpError: When the return code is not 2xx
647647
"""
648+
649+
def sanitized_url(url):
650+
parsed = six.moves.urllib.parse.urlparse(url)
651+
new_path = parsed.path.replace('.', '%2E')
652+
return parsed._replace(path=new_path).geturl()
653+
648654
url = self._build_url(path)
649655
params = query_data.copy()
650656
params.update(kwargs)
651657
opts = self._get_session_opts(content_type='application/json')
652-
result = self.session.request(verb, url, json=post_data,
653-
params=params, stream=streamed, **opts)
658+
verify = opts.pop('verify')
659+
timeout = opts.pop('timeout')
660+
661+
# Requests assumes that `.` should not be encoded as %2E and will make
662+
# changes to urls using this encoding. Using a prepped request we can
663+
# get the desired behavior.
664+
# The Requests behavior is right but it seems that web servers don't
665+
# always agree with this decision (this is the case with a default
666+
# gitlab installation)
667+
req = requests.Request(verb, url, json=post_data, params=params,
668+
**opts)
669+
prepped = self.session.prepare_request(req)
670+
prepped.url = sanitized_url(prepped.url)
671+
result = self.session.send(prepped, stream=streamed, verify=verify,
672+
timeout=timeout)
673+
654674
if 200 <= result.status_code < 300:
655675
return result
656676

gitlab/mixins.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def create(self, data, **kwargs):
152152
**kwargs: Extra options to send to the Gitlab server (e.g. sudo)
153153
154154
Returns:
155-
RESTObject: a new instance of the managed object class build with
155+
RESTObject: a new instance of the managed object class built with
156156
the data sent by the server
157157
158158
Raises:

gitlab/v4/objects.py

+115-38
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class UserKey(ObjectDeleteMixin, RESTObject):
126126

127127

128128
class UserKeyManager(GetFromListMixin, CreateMixin, DeleteMixin, RESTManager):
129-
_path = '/users/%(user_id)s/emails'
129+
_path = '/users/%(user_id)s/keys'
130130
_obj_cls = UserKey
131131
_from_parent_attrs = {'user_id': 'id'}
132132
_create_attrs = (('title', 'key'), tuple())
@@ -842,8 +842,8 @@ def enable(self, key_id, **kwargs):
842842
GitlabAuthenticationError: If authentication is not correct
843843
GitlabProjectDeployKeyError: If the key could not be enabled
844844
"""
845-
path = '%s/%s/enable' % (self.manager.path, key_id)
846-
self.manager.gitlab.http_post(path, **kwargs)
845+
path = '%s/%s/enable' % (self.path, key_id)
846+
self.gitlab.http_post(path, **kwargs)
847847

848848

849849
class ProjectEvent(RESTObject):
@@ -999,17 +999,19 @@ def set_release_description(self, description, **kwargs):
999999
data = {'description': description}
10001000
if self.release is None:
10011001
try:
1002-
result = self.manager.gitlab.http_post(path, post_data=data,
1003-
**kwargs)
1002+
server_data = self.manager.gitlab.http_post(path,
1003+
post_data=data,
1004+
**kwargs)
10041005
except exc.GitlabHttpError as e:
10051006
raise exc.GitlabCreateError(e.response_code, e.error_message)
10061007
else:
10071008
try:
1008-
result = self.manager.gitlab.http_put(path, post_data=data,
1009-
**kwargs)
1009+
server_data = self.manager.gitlab.http_put(path,
1010+
post_data=data,
1011+
**kwargs)
10101012
except exc.GitlabHttpError as e:
10111013
raise exc.GitlabUpdateError(e.response_code, e.error_message)
1012-
self.release = result.json()
1014+
self.release = server_data
10131015

10141016

10151017
class ProjectTagManager(GetFromListMixin, CreateMixin, DeleteMixin,
@@ -1223,8 +1225,7 @@ def merge_requests(self, **kwargs):
12231225
return RESTObjectList(manager, ProjectMergeRequest, data_list)
12241226

12251227

1226-
class ProjectMilestoneManager(RetrieveMixin, CreateMixin, DeleteMixin,
1227-
RESTManager):
1228+
class ProjectMilestoneManager(CRUDMixin, RESTManager):
12281229
_path = '/projects/%(project_id)s/milestones'
12291230
_obj_cls = ProjectMilestone
12301231
_from_parent_attrs = {'project_id': 'id'}
@@ -1239,6 +1240,26 @@ class ProjectLabel(SubscribableMixin, SaveMixin, ObjectDeleteMixin,
12391240
RESTObject):
12401241
_id_attr = 'name'
12411242

1243+
# Update without ID, but we need an ID to get from list.
1244+
@exc.on_http_error(exc.GitlabUpdateError)
1245+
def save(self, **kwargs):
1246+
"""Saves the changes made to the object to the server.
1247+
1248+
The object is updated to match what the server returns.
1249+
1250+
Args:
1251+
**kwargs: Extra options to send to the server (e.g. sudo)
1252+
1253+
Raises:
1254+
GitlabAuthenticationError: If authentication is not correct.
1255+
GitlabUpdateError: If the server cannot perform the request.
1256+
"""
1257+
updated_data = self._get_updated_data()
1258+
1259+
# call the manager
1260+
server_data = self.manager.update(None, updated_data, **kwargs)
1261+
self._update_attrs(server_data)
1262+
12421263

12431264
class ProjectLabelManager(GetFromListMixin, CreateMixin, UpdateMixin,
12441265
DeleteMixin, RESTManager):
@@ -1262,27 +1283,7 @@ def delete(self, name, **kwargs):
12621283
GitlabAuthenticationError: If authentication is not correct.
12631284
GitlabDeleteError: If the server cannot perform the request.
12641285
"""
1265-
self.gitlab.http_delete(path, query_data={'name': self.name}, **kwargs)
1266-
1267-
# Update without ID, but we need an ID to get from list.
1268-
@exc.on_http_error(exc.GitlabUpdateError)
1269-
def save(self, **kwargs):
1270-
"""Saves the changes made to the object to the server.
1271-
1272-
The object is updated to match what the server returns.
1273-
1274-
Args:
1275-
**kwargs: Extra options to send to the server (e.g. sudo)
1276-
1277-
Raises:
1278-
GitlabAuthenticationError: If authentication is not correct.
1279-
GitlabUpdateError: If the server cannot perform the request.
1280-
"""
1281-
updated_data = self._get_updated_data()
1282-
1283-
# call the manager
1284-
server_data = self.manager.update(None, updated_data, **kwargs)
1285-
self._update_attrs(server_data)
1286+
self.gitlab.http_delete(self.path, query_data={'name': name}, **kwargs)
12861287

12871288

12881289
class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject):
@@ -1297,6 +1298,38 @@ def decode(self):
12971298
"""
12981299
return base64.b64decode(self.content)
12991300

1301+
def save(self, branch, commit_message, **kwargs):
1302+
"""Save the changes made to the file to the server.
1303+
1304+
The object is updated to match what the server returns.
1305+
1306+
Args:
1307+
branch (str): Branch in which the file will be updated
1308+
commit_message (str): Message to send with the commit
1309+
**kwargs: Extra options to send to the server (e.g. sudo)
1310+
1311+
Raise:
1312+
GitlabAuthenticationError: If authentication is not correct
1313+
GitlabUpdateError: If the server cannot perform the request
1314+
"""
1315+
self.branch = branch
1316+
self.commit_message = commit_message
1317+
super(ProjectFile, self).save(**kwargs)
1318+
1319+
def delete(self, branch, commit_message, **kwargs):
1320+
"""Delete the file from the server.
1321+
1322+
Args:
1323+
branch (str): Branch from which the file will be removed
1324+
commit_message (str): Commit message for the deletion
1325+
**kwargs: Extra options to send to the server (e.g. sudo)
1326+
1327+
Raises:
1328+
GitlabAuthenticationError: If authentication is not correct
1329+
GitlabDeleteError: If the server cannot perform the request
1330+
"""
1331+
self.manager.delete(self.get_id(), branch, commit_message, **kwargs)
1332+
13001333

13011334
class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin,
13021335
RESTManager):
@@ -1308,11 +1341,12 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin,
13081341
_update_attrs = (('file_path', 'branch', 'content', 'commit_message'),
13091342
('encoding', 'author_email', 'author_name'))
13101343

1311-
def get(self, file_path, **kwargs):
1344+
def get(self, file_path, ref, **kwargs):
13121345
"""Retrieve a single file.
13131346
13141347
Args:
1315-
id (int or str): ID of the object to retrieve
1348+
file_path (str): Path of the file to retrieve
1349+
ref (str): Name of the branch, tag or commit
13161350
**kwargs: Extra options to send to the Gitlab server (e.g. sudo)
13171351
13181352
Raises:
@@ -1323,7 +1357,49 @@ def get(self, file_path, **kwargs):
13231357
object: The generated RESTObject
13241358
"""
13251359
file_path = file_path.replace('/', '%2F')
1326-
return GetMixin.get(self, file_path, **kwargs)
1360+
return GetMixin.get(self, file_path, ref=ref, **kwargs)
1361+
1362+
@exc.on_http_error(exc.GitlabCreateError)
1363+
def create(self, data, **kwargs):
1364+
"""Create a new object.
1365+
1366+
Args:
1367+
data (dict): parameters to send to the server to create the
1368+
resource
1369+
**kwargs: Extra options to send to the Gitlab server (e.g. sudo)
1370+
1371+
Returns:
1372+
RESTObject: a new instance of the managed object class built with
1373+
the data sent by the server
1374+
1375+
Raises:
1376+
GitlabAuthenticationError: If authentication is not correct
1377+
GitlabCreateError: If the server cannot perform the request
1378+
"""
1379+
1380+
self._check_missing_create_attrs(data)
1381+
file_path = data.pop('file_path')
1382+
path = '%s/%s' % (self.path, file_path)
1383+
server_data = self.gitlab.http_post(path, post_data=data, **kwargs)
1384+
return self._obj_cls(self, server_data)
1385+
1386+
@exc.on_http_error(exc.GitlabDeleteError)
1387+
def delete(self, file_path, branch, commit_message, **kwargs):
1388+
"""Delete a file on the server.
1389+
1390+
Args:
1391+
file_path (str): Path of the file to remove
1392+
branch (str): Branch from which the file will be removed
1393+
commit_message (str): Commit message for the deletion
1394+
**kwargs: Extra options to send to the Gitlab server (e.g. sudo)
1395+
1396+
Raises:
1397+
GitlabAuthenticationError: If authentication is not correct
1398+
GitlabDeleteError: If the server cannot perform the request
1399+
"""
1400+
path = '%s/%s' % (self.path, file_path.replace('/', '%2F'))
1401+
data = {'branch': branch, 'commit_message': commit_message}
1402+
self.gitlab.http_delete(path, query_data=data, **kwargs)
13271403

13281404
@exc.on_http_error(exc.GitlabGetError)
13291405
def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024,
@@ -1348,7 +1424,7 @@ def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024,
13481424
Returns:
13491425
str: The file content
13501426
"""
1351-
file_path = file_path.replace('/', '%2F')
1427+
file_path = file_path.replace('/', '%2F').replace('.', '%2E')
13521428
path = '%s/%s/raw' % (self.path, file_path)
13531429
query_data = {'ref': ref}
13541430
result = self.gitlab.http_get(path, query_data=query_data,
@@ -1489,8 +1565,8 @@ class ProjectVariableManager(CRUDMixin, RESTManager):
14891565
_path = '/projects/%(project_id)s/variables'
14901566
_obj_cls = ProjectVariable
14911567
_from_parent_attrs = {'project_id': 'id'}
1492-
_create_attrs = (('key', 'vaule'), tuple())
1493-
_update_attrs = (('key', 'vaule'), tuple())
1568+
_create_attrs = (('key', 'value'), tuple())
1569+
_update_attrs = (('key', 'value'), tuple())
14941570

14951571

14961572
class ProjectService(GitlabObject):
@@ -2016,7 +2092,8 @@ class ProjectManager(CRUDMixin, RESTManager):
20162092
'request_access_enabled')
20172093
)
20182094
_list_filters = ('search', 'owned', 'starred', 'archived', 'visibility',
2019-
'order_by', 'sort', 'simple', 'membership', 'statistics')
2095+
'order_by', 'sort', 'simple', 'membership', 'statistics',
2096+
'with_issues_enabled', 'with_merge_requests_enabled')
20202097

20212098

20222099
class GroupProject(Project):

tools/build_test_env.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,6 @@ log "Installing into virtualenv..."
154154
try pip install -e .
155155

156156
log "Pausing to give GitLab some time to finish starting up..."
157-
sleep 20
157+
sleep 30
158158

159159
log "Test environment initialized."

tools/py_functional_tests.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ setenv_script=$(dirname "$0")/build_test_env.sh || exit 1
1818
BUILD_TEST_ENV_AUTO_CLEANUP=true
1919
. "$setenv_script" "$@" || exit 1
2020

21-
try python "$(dirname "$0")"/python_test.py
21+
try python "$(dirname "$0")"/python_test_v${API_VER}.py
File renamed without changes.

0 commit comments

Comments
 (0)