Updates URL parsing, updates API error code handling

This commit is contained in:
Richard O'Dwyer 2020-06-21 16:18:22 +01:00
parent 631ca606ff
commit aa89af0324
4 changed files with 101 additions and 55 deletions

View file

@ -1,17 +1,20 @@
-r requirements.txt -r requirements.txt
pytest pytest==5.4.3
ipdb ipdb==0.13.2
flake8 flake8==3.8.3
pep8-naming pep8-naming==0.11.1
autoflake autoflake==1.3.1
mccabe mccabe==0.6.1
yapf yapf==0.30.0
tox tox==3.15.2
coverage coverage==5.1
pytest-cov pytest-cov==2.10.0
zest.releaser zest.releaser==6.20.1
setuptools setuptools==47.3.1
twine twine==3.1.1
wheel wheel==0.34.2
rope rope==0.17.0
pytest-mock pytest-mock==3.1.1
brunette==0.1.5
lock-requirements==0.1.1
requests-mock

View file

@ -6,17 +6,16 @@ class ValidationError(Exception):
_CODE_TO_DESCRIPTIONS = { _CODE_TO_DESCRIPTIONS = {
0: ('UNKNOWN', 'API Returned 0'),
-1: ( -1: (
'EINTERNAL', 'EINTERNAL', (
(
'An internal error has occurred. Please submit a bug report, ' 'An internal error has occurred. Please submit a bug report, '
'detailing the exact circumstances in which this error occurred' 'detailing the exact circumstances in which this error occurred'
) )
), ),
-2: ('EARGS', 'You have passed invalid arguments to this command'), -2: ('EARGS', 'You have passed invalid arguments to this command'),
-3: ( -3: (
'EAGAIN', 'EAGAIN', (
(
'(always at the request level) A temporary congestion or server ' '(always at the request level) A temporary congestion or server '
'malfunction prevented your request from being processed. ' 'malfunction prevented your request from being processed. '
'No data was altered. Retry. Retries must be spaced with ' 'No data was altered. Retry. Retries must be spaced with '
@ -24,8 +23,7 @@ _CODE_TO_DESCRIPTIONS = {
) )
), ),
-4: ( -4: (
'ERATELIMIT', 'ERATELIMIT', (
(
'You have exceeded your command weight per time quota. Please ' 'You have exceeded your command weight per time quota. Please '
'wait a few seconds, then try again (this should never happen ' 'wait a few seconds, then try again (this should never happen '
'in sane real-life applications)' 'in sane real-life applications)'
@ -37,15 +35,13 @@ _CODE_TO_DESCRIPTIONS = {
'Too many concurrent IP addresses are accessing this upload target URL' 'Too many concurrent IP addresses are accessing this upload target URL'
), ),
-7: ( -7: (
'ERANGE', 'ERANGE', (
(
'The upload file packet is out of range or not starting and ' 'The upload file packet is out of range or not starting and '
'ending on a chunk boundary' 'ending on a chunk boundary'
) )
), ),
-8: ( -8: (
'EEXPIRED', 'EEXPIRED', (
(
'The upload target URL you are trying to access has expired. ' 'The upload target URL you are trying to access has expired. '
'Please request a fresh one' 'Please request a fresh one'
) )

View file

@ -174,24 +174,41 @@ class Mega:
data = [data] data = [data]
url = f'{self.schema}://g.api.{self.domain}/cs' url = f'{self.schema}://g.api.{self.domain}/cs'
req = requests.post( response = requests.post(
url, url,
params=params, params=params,
data=json.dumps(data), data=json.dumps(data),
timeout=self.timeout, timeout=self.timeout,
) )
json_resp = json.loads(req.text) json_resp = json.loads(response.text)
if isinstance(json_resp, int): try:
if json_resp == -3: if isinstance(json_resp, list):
int_resp = json_resp[0] if isinstance(
json_resp[0], int
) else None
elif isinstance(json_resp, int):
int_resp = json_resp
except IndexError:
int_resp = None
if int_resp is not None:
if int_resp == -3:
msg = 'Request failed, retrying' msg = 'Request failed, retrying'
logger.info(msg) logger.info(msg)
raise RuntimeError(msg) raise RuntimeError(msg)
raise RequestError(json_resp) raise RequestError(int_resp)
return json_resp[0] return json_resp[0]
def _parse_url(self, url): def _parse_url(self, url):
# parse file id and key from url """Parse file id and key from url."""
if '!' in url: if '/file/' in url:
# V2 URL structure
url = url.replace(' ', '')
file_id = re.findall(r'\W\w\w\w\w\w\w\w\w\W', url)[0][1:-1]
id_index = re.search(file_id, url).end()
key = url[id_index + 1:]
return f'{file_id}!{key}'
elif '!' in url:
# V1 URL structure
match = re.findall(r'/#!(.*)', url) match = re.findall(r'/#!(.*)', url)
path = match[0] path = match[0]
return path return path
@ -874,9 +891,12 @@ class Mega:
# update attributes # update attributes
data = self._api_request( data = self._api_request(
{ {
'a': 'p', 'a':
't': dest, 'p',
'i': self.request_id, 't':
dest,
'i':
self.request_id,
'n': [ 'n': [
{ {
'h': completion_file_handle, 'h': completion_file_handle,
@ -902,8 +922,10 @@ class Mega:
# update attributes # update attributes
data = self._api_request( data = self._api_request(
{ {
'a': 'p', 'a':
't': parent_node_id, 'p',
't':
parent_node_id,
'n': [ 'n': [
{ {
'h': 'xxxxxxxx', 'h': 'xxxxxxxx',
@ -912,7 +934,8 @@ class Mega:
'k': encrypted_key 'k': encrypted_key
} }
], ],
'i': self.request_id 'i':
self.request_id
} }
) )
return data return data
@ -1102,8 +1125,10 @@ class Mega:
encrypted_name = base64_url_encode(encrypt_attr({'n': dest_name}, k)) encrypted_name = base64_url_encode(encrypt_attr({'n': dest_name}, k))
return self._api_request( return self._api_request(
{ {
'a': 'p', 'a':
't': dest_node['h'], 'p',
't':
dest_node['h'],
'n': [ 'n': [
{ {
'ph': file_handle, 'ph': file_handle,

View file

@ -2,6 +2,7 @@ import random
from pathlib import Path from pathlib import Path
import os import os
import requests_mock
import pytest import pytest
from mega import Mega from mega import Mega
@ -11,6 +12,7 @@ TEST_PUBLIC_URL = (
'https://mega.nz/#!hYVmXKqL!r0d0-WRnFwulR_shhuEDwrY1Vo103-am1MyUy8oV6Ps' 'https://mega.nz/#!hYVmXKqL!r0d0-WRnFwulR_shhuEDwrY1Vo103-am1MyUy8oV6Ps'
) )
TEST_FILE = os.path.basename(__file__) TEST_FILE = os.path.basename(__file__)
MODULE = 'mega.mega'
@pytest.fixture @pytest.fixture
@ -32,9 +34,7 @@ def mega(folder_name):
def uploaded_file(mega, folder_name): def uploaded_file(mega, folder_name):
folder = mega.find(folder_name) folder = mega.find(folder_name)
dest_node_id = folder[1]['h'] dest_node_id = folder[1]['h']
mega.upload( mega.upload(__file__, dest=dest_node_id, dest_filename='test.py')
__file__, dest=dest_node_id, dest_filename='test.py'
)
path = f'{folder_name}/test.py' path = f'{folder_name}/test.py'
return mega.find(path) return mega.find(path)
@ -73,7 +73,6 @@ def test_get_link(mega, uploaded_file):
class TestExport: class TestExport:
def test_export_folder(self, mega, folder_name): def test_export_folder(self, mega, folder_name):
public_url = None public_url = None
for _ in range(2): for _ in range(2):
@ -103,9 +102,7 @@ class TestExport:
# Upload a single file into a folder # Upload a single file into a folder
folder = mega.find(folder_name) folder = mega.find(folder_name)
dest_node_id = folder[1]['h'] dest_node_id = folder[1]['h']
mega.upload( mega.upload(__file__, dest=dest_node_id, dest_filename='test.py')
__file__, dest=dest_node_id, dest_filename='test.py'
)
path = f'{folder_name}/test.py' path = f'{folder_name}/test.py'
assert mega.find(path) assert mega.find(path)
@ -143,13 +140,10 @@ class TestCreateFolder:
class TestFind: class TestFind:
def test_find_file(self, mega, folder_name): def test_find_file(self, mega, folder_name):
folder = mega.find(folder_name) folder = mega.find(folder_name)
dest_node_id = folder[1]['h'] dest_node_id = folder[1]['h']
mega.upload( mega.upload(__file__, dest=dest_node_id, dest_filename='test.py')
__file__, dest=dest_node_id, dest_filename='test.py'
)
path = f'{folder_name}/test.py' path = f'{folder_name}/test.py'
assert mega.find(path) assert mega.find(path)
@ -194,9 +188,7 @@ def test_download(mega, tmpdir, folder_name):
# Upload a single file into a folder # Upload a single file into a folder
folder = mega.find(folder_name) folder = mega.find(folder_name)
dest_node_id = folder[1]['h'] dest_node_id = folder[1]['h']
mega.upload( mega.upload(__file__, dest=dest_node_id, dest_filename='test.py')
__file__, dest=dest_node_id, dest_filename='test.py'
)
path = f'{folder_name}/test.py' path = f'{folder_name}/test.py'
file = mega.find(path) file = mega.find(path)
@ -222,3 +214,33 @@ def test_add_contact(mega):
def test_remove_contact(mega): def test_remove_contact(mega):
resp = mega.remove_contact(TEST_CONTACT) resp = mega.remove_contact(TEST_CONTACT)
assert isinstance(resp, int) assert isinstance(resp, int)
@pytest.mark.parametrize(
'url, expected_file_id_and_key', [
(
'https://mega.nz/#!Ue5VRSIQ!kC2E4a4JwfWWCWYNJovGFHlbz8F'
'N-ISsBAGPzvTjT6k',
'Ue5VRSIQ!kC2E4a4JwfWWCWYNJovGFHlbz8FN-ISsBAGPzvTjT6k'
),
(
'https://mega.nz/file/cH51DYDR#qH7QOfRcM-7N9riZWdSjsRq'
'5VDTLfIhThx1capgVA30',
'cH51DYDR!qH7QOfRcM-7N9riZWdSjsRq5VDTLfIhThx1capgVA30'
),
]
)
def test_parse_url(url, expected_file_id_and_key, mega):
assert mega._parse_url(url) == expected_file_id_and_key
class TestAPIRequest:
@pytest.mark.parametrize('response_text', ['-3', '-9'])
def test_when_api_returns_int_raises_exception(
self, mega, response_text,
):
with requests_mock.Mocker() as m:
m.post(
f'{mega.schema}://g.api.{mega.domain}/cs', text=response_text
)
mega._api_request(data={})