diff --git a/requirements-dev.txt b/requirements-dev.txt index e36326f..43861ef 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,3 @@ pytest -isort \ No newline at end of file +isort +pytest-mock \ No newline at end of file diff --git a/tests/test_fetch.py b/tests/test_fetch.py new file mode 100644 index 0000000..711b724 --- /dev/null +++ b/tests/test_fetch.py @@ -0,0 +1,31 @@ +import io + +import vk +from vk.fetch import Session + + +def test_url_open(mocker): + mocker.patch('vk.fetch.urlopen') + + mock_json = mocker.patch('json.load') + mock_json.return_value = {'response': [{ + "id": 1, + "first_name": "Павел", + "last_name": "Дуров", + "domain": "durov", + }]} + + api = vk.Api('TOKEN') + user = api.get_user('durov') + + assert user.domain == 'durov' + + +def test_upload_photo(): + file_obj = io.BytesIO(b'Python developer and blogger.') + data, boundary = Session()._file_upload(file_obj) + + assert b'Content-Disposition: file; name="photo"; filename="photo.jpg"' in data + assert b'Content-Type: application/octet-stream' in data + assert b'Python developer and blogger.' in data + assert boundary.encode() in data diff --git a/vk/fetch.py b/vk/fetch.py index 09aff2d..34c8cff 100644 --- a/vk/fetch.py +++ b/vk/fetch.py @@ -1,34 +1,23 @@ # coding=utf-8 +import io import json +import uuid from .error import VKError, VKParseJsonError try: from urllib.parse import urlencode - from urllib.request import urlopen + from urllib.request import urlopen, Request except ImportError: from urllib import urlencode, urlopen - class Session(object): def __init__(self, access_token=None, lang='ru', version_api='5.63'): self.access_token = access_token self.lang = lang self.version_api = version_api - @staticmethod - def url_open(url, data=None): - data = data or {} - res = urlopen(url, data=data) - - try: - data_json = json.loads(res.read()) - except ValueError: - raise VKParseJsonError - - return data_json - def fetch(self, method_name, **params): url = "https://api.vk.com/method/{method_name}".format(method_name=method_name) params['v'] = self.version_api @@ -40,9 +29,13 @@ def fetch(self, method_name, **params): params = {key: value for key, value in params.items() if value is not None} - url = url + "?" + urlencode(params) + request = Request(url, data=urlencode(params).encode()) + res = urlopen(request) - data_json = self.url_open(url) + try: + data_json = json.load(res) + except ValueError: + raise VKParseJsonError if 'error' in data_json: error = data_json['error'] @@ -80,8 +73,19 @@ def fetch_items(self, method_name, constructor_from_json, count, **params): offset += count - def fetch_post(self, url, **kwargs): - return self.url_open(url, data=kwargs) + def fetch_photo(self, url, file_obj): + data, boundary = self._file_upload(file_obj) + + req = Request(url, data=data) + req.add_header('Content-type', 'multipart/form-data; boundary={0}'.format(boundary)) + req.add_header('Content-length', len(data)) + + res = urlopen(req) + + try: + return json.load(res) + except ValueError: + raise VKParseJsonError def _convert_list2str(self, fields): """ @@ -91,3 +95,17 @@ def _convert_list2str(self, fields): if isinstance(fields, tuple) or isinstance(fields, list): return ','.join(fields) return fields + + def _file_upload(self, file_obj): + boundary = uuid.uuid4().hex + + buffer = io.BytesIO() + buffer.write('--{0}\r\n'.format(boundary).encode()) + buffer.write('Content-Disposition: file; name="photo"; filename="photo.jpg"\r\n'.encode()) + buffer.write('Content-Type: application/octet-stream\r\n'.encode()) + buffer.write(b'\r\n') + buffer.write(file_obj.read()) + buffer.write(b'\r\n') + buffer.write('--{0}--\r\n'.format(boundary).encode()) + + return buffer.getvalue(), boundary diff --git a/vk/groups.py b/vk/groups.py index d02c832..e6f7a70 100644 --- a/vk/groups.py +++ b/vk/groups.py @@ -67,8 +67,7 @@ def get_walls_count(self): def set_cover_photo(self, file_like, width, height): upload_url = Photo._get_owner_cover_photo_upload_server(self._session, self.id, crop_x2=width, crop_y2=height) - files = {'photo': file_like} - response_json = self._session.fetch_post(upload_url, files=files) + response_json = self._session.fetch_photo(upload_url, file_like) Photo._save_owner_cover_photo(self._session, response_json['hash'], response_json['photo']) diff --git a/vk/photos.py b/vk/photos.py index e38c744..25a6b78 100644 --- a/vk/photos.py +++ b/vk/photos.py @@ -81,7 +81,7 @@ def _upload_wall_photos_for_group(session, group_id, image_files): attachments = [] for image_fd in image_files: - response_json = session.fetch_post(upload_url, files={'photo': image_fd}) + response_json = session.fetch_photo(upload_url, image_fd) photo, server, _hash = response_json['photo'], response_json['server'], response_json['hash'] photo_id, owner_id = Photo._get_save_wall_photo(session, photo, server, _hash, group_id=group_id) attachments.append("photo{0}_{1}".format(owner_id, photo_id)) @@ -110,7 +110,7 @@ def _upload_messages_photos_for_group(session, user_id, image_files): attachments = [] for image_fd in image_files: - response_json = session.fetch_post(upload_url, files={'photo': image_fd}) + response_json = session.fetch_photo(upload_url, image_fd) photo, server, _hash = response_json['photo'], response_json['server'], response_json['hash'] photo_id, owner_id = Photo._get_save_messages_photo(session, photo, server, _hash) attachments.append("photo{0}_{1}".format(owner_id, photo_id))