Updates URL parsing, updates API error code handling
This commit is contained in:
parent
631ca606ff
commit
aa89af0324
4 changed files with 101 additions and 55 deletions
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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={})
|
||||||
|
|
Loading…
Reference in a new issue