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

File tree

8 files changed

+489
-45
lines changed

8 files changed

+489
-45
lines changed

docs/switching-to-v4.rst

Lines changed: 8 additions & 2 deletions
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

Lines changed: 22 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 115 additions & 38 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy