diff options
-rw-r--r-- | docs/gl_objects/users.rst | 7 | ||||
-rw-r--r-- | gitlab/__init__.py | 21 | ||||
-rw-r--r-- | gitlab/mixins.py | 39 | ||||
-rw-r--r-- | gitlab/types.py | 10 | ||||
-rw-r--r-- | gitlab/v4/objects.py | 9 | ||||
-rw-r--r-- | tools/avatar.png | bin | 0 -> 592 bytes | |||
-rw-r--r-- | tools/python_test_v4.py | 10 |
7 files changed, 76 insertions, 20 deletions
diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index a1d6dd6..fa966d1 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -61,6 +61,13 @@ Block/Unblock a user:: user.block() user.unblock() +Set the avatar image for a user:: + + # the avatar image can be passed as data (content of the file) or as a file + # object opened in binary mode + user.avatar = open('path/to/file.png', 'rb') + user.save() + User custom attributes ====================== diff --git a/gitlab/__init__.py b/gitlab/__init__.py index af38680..3a36bf2 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -356,20 +356,25 @@ class Gitlab(object): opts = self._get_session_opts(content_type='application/json') - # don't set the content-type header when uploading files - if files is not None: - del opts["headers"]["Content-type"] - verify = opts.pop('verify') timeout = opts.pop('timeout') + # We need to deal with json vs. data when uploading files + if files: + data = post_data + json = None + del opts["headers"]["Content-type"] + else: + json = post_data + data = None + # Requests assumes that `.` should not be encoded as %2E and will make # changes to urls using this encoding. Using a prepped request we can # get the desired behavior. # The Requests behavior is right but it seems that web servers don't # always agree with this decision (this is the case with a default # gitlab installation) - req = requests.Request(verb, url, json=post_data, params=params, + req = requests.Request(verb, url, json=json, data=data, params=params, files=files, **opts) prepped = self.session.prepare_request(req) prepped.url = sanitized_url(prepped.url) @@ -506,7 +511,8 @@ class Gitlab(object): error_message="Failed to parse the server message") return result - def http_put(self, path, query_data={}, post_data={}, **kwargs): + def http_put(self, path, query_data={}, post_data={}, files=None, + **kwargs): """Make a PUT request to the Gitlab server. Args: @@ -515,6 +521,7 @@ class Gitlab(object): query_data (dict): Data to send as query parameters post_data (dict): Data to send in the body (will be converted to json) + files (dict): The files to send to the server **kwargs: Extra data to make the query (e.g. sudo, per_page, page) Returns: @@ -525,7 +532,7 @@ class Gitlab(object): GitlabParsingError: If the json data could not be parsed """ result = self.http_request('put', path, query_data=query_data, - post_data=post_data, **kwargs) + post_data=post_data, files=files, **kwargs) try: return result.json() except Exception: diff --git a/gitlab/mixins.py b/gitlab/mixins.py index f940d60..17f1196 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -18,6 +18,7 @@ import gitlab from gitlab import base from gitlab import cli +from gitlab import types as g_types from gitlab import exceptions as exc @@ -171,21 +172,29 @@ class CreateMixin(object): GitlabCreateError: If the server cannot perform the request """ self._check_missing_create_attrs(data) + files = {} # We get the attributes that need some special transformation types = getattr(self, '_types', {}) - if types: # Duplicate data to avoid messing with what the user sent us data = data.copy() for attr_name, type_cls in types.items(): if attr_name in data.keys(): type_obj = type_cls(data[attr_name]) - data[attr_name] = type_obj.get_for_api() + + # if the type if FileAttribute we need to pass the data as + # file + if issubclass(type_cls, g_types.FileAttribute): + k = type_obj.get_file_name(attr_name) + files[attr_name] = (k, data.pop(attr_name)) + else: + data[attr_name] = type_obj.get_for_api() # Handle specific URL for creation path = kwargs.pop('path', self.path) - server_data = self.gitlab.http_post(path, post_data=data, **kwargs) + server_data = self.gitlab.http_post(path, post_data=data, files=files, + **kwargs) return self._obj_cls(self, server_data) @@ -232,15 +241,27 @@ class UpdateMixin(object): path = '%s/%s' % (self.path, id) self._check_missing_update_attrs(new_data) + files = {} # We get the attributes that need some special transformation types = getattr(self, '_types', {}) - for attr_name, type_cls in types.items(): - if attr_name in new_data.keys(): - type_obj = type_cls(new_data[attr_name]) - new_data[attr_name] = type_obj.get_for_api() - - return self.gitlab.http_put(path, post_data=new_data, **kwargs) + if types: + # Duplicate data to avoid messing with what the user sent us + new_data = new_data.copy() + for attr_name, type_cls in types.items(): + if attr_name in new_data.keys(): + type_obj = type_cls(new_data[attr_name]) + + # if the type if FileAttribute we need to pass the data as + # file + if issubclass(type_cls, g_types.FileAttribute): + k = type_obj.get_file_name(attr_name) + files[attr_name] = (k, new_data.pop(attr_name)) + else: + new_data[attr_name] = type_obj.get_for_api() + + return self.gitlab.http_put(path, post_data=new_data, files=files, + **kwargs) class SetMixin(object): diff --git a/gitlab/types.py b/gitlab/types.py index d361222..b32409f 100644 --- a/gitlab/types.py +++ b/gitlab/types.py @@ -44,3 +44,13 @@ class ListAttribute(GitlabAttribute): class LowercaseStringAttribute(GitlabAttribute): def get_for_api(self): return str(self._value).lower() + + +class FileAttribute(GitlabAttribute): + def get_file_name(self, attr_name=None): + return attr_name + + +class ImageAttribute(FileAttribute): + def get_file_name(self, attr_name=None): + return '%s.png' % attr_name if attr_name else 'image.png' diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 14bad5a..2d9a6bf 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -307,16 +307,19 @@ class UserManager(CRUDMixin, RESTManager): ('email', 'username', 'name', 'password', 'reset_password', 'skype', 'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider', 'bio', 'admin', 'can_create_group', 'website_url', - 'skip_confirmation', 'external', 'organization', 'location') + 'skip_confirmation', 'external', 'organization', 'location', 'avatar') ) _update_attrs = ( ('email', 'username', 'name'), ('password', 'skype', 'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider', 'bio', 'admin', 'can_create_group', 'website_url', 'skip_confirmation', 'external', 'organization', - 'location') + 'location', 'avatar') ) - _types = {'confirm': types.LowercaseStringAttribute} + _types = { + 'confirm': types.LowercaseStringAttribute, + 'avatar': types.ImageAttribute, + } class CurrentUserEmail(ObjectDeleteMixin, RESTObject): diff --git a/tools/avatar.png b/tools/avatar.png Binary files differnew file mode 100644 index 0000000..a3a767c --- /dev/null +++ b/tools/avatar.png diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 62e1499..c11e567 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -1,6 +1,9 @@ import base64 +import os import time +import requests + import gitlab LOGIN = 'root' @@ -49,6 +52,7 @@ nxs4TLO3kZjUTgWKdhpgRNF5hwaz51ZjpebaRf/ZqRuNyX4lIRolDxzOn/+O1o8L qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ== =5OGa -----END PGP PUBLIC KEY BLOCK-----''' +AVATAR_PATH = os.path.join(os.path.dirname(__file__), 'avatar.png') # token authentication from config file @@ -81,7 +85,11 @@ assert(settings.default_projects_limit == 42) # users new_user = gl.users.create({'email': 'foo@bar.com', 'username': 'foo', - 'name': 'foo', 'password': 'foo_password'}) + 'name': 'foo', 'password': 'foo_password', + 'avatar': open(AVATAR_PATH, 'rb')}) +avatar_url = new_user.avatar_url.replace('gitlab.test', 'localhost:8080') +uploaded_avatar = requests.get(avatar_url).content +assert(uploaded_avatar == open(AVATAR_PATH, 'rb').read()) users_list = gl.users.list() for user in users_list: if user.username == 'foo': |