async-mega-py 2.1.1__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/PKG-INFO +1 -1
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/pyproject.toml +1 -1
- async_mega_py-2.2.1/src/mega/__init__.py +8 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/__main__.py +6 -2
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/api.py +76 -50
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/client.py +7 -4
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/data_structures.py +3 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/transfer_it.py +6 -2
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/utils.py +2 -1
- async_mega_py-2.1.1/src/mega/__init__.py +0 -4
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/LICENSE +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/README.md +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/auth.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/chunker.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/core.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/crypto.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/download.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/env.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/errors.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/filesystem.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/progress/__init__.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/progress/rich_progress.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/py.typed +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/upload.py +0 -0
- {async_mega_py-2.1.1 → async_mega_py-2.2.1}/src/mega/vault.py +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
from contextvars import ContextVar
|
|
3
|
+
|
|
4
|
+
_package_name_ = "async-mega-py"
|
|
5
|
+
__version__ = importlib.metadata.version(_package_name_)
|
|
6
|
+
|
|
7
|
+
LOG_FILE_PROGRESS: ContextVar[bool] = ContextVar("LOG_FILE_PROGRESS", default=True)
|
|
8
|
+
LOG_HTTP_TRAFFIC: ContextVar[bool] = ContextVar("LOG_HTTP_TRAFFIC", default=False)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# ruff: noqa: E402
|
|
1
2
|
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
import contextlib
|
|
@@ -6,11 +7,14 @@ import logging
|
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import TYPE_CHECKING, Annotated
|
|
8
9
|
|
|
10
|
+
from rich.traceback import install as install_rich_traceback
|
|
11
|
+
|
|
12
|
+
_ = install_rich_traceback(width=None, max_frames=2)
|
|
13
|
+
|
|
9
14
|
import yarl
|
|
10
15
|
from cyclopts import App, Parameter
|
|
11
16
|
|
|
12
|
-
from mega import __version__, env
|
|
13
|
-
from mega.api import LOG_HTTP_TRAFFIC
|
|
17
|
+
from mega import LOG_HTTP_TRAFFIC, __version__, env
|
|
14
18
|
from mega.client import MegaNzClient
|
|
15
19
|
from mega.transfer_it import TransferItClient
|
|
16
20
|
from mega.utils import Site, setup_logger
|
|
@@ -3,17 +3,18 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
5
|
import dataclasses
|
|
6
|
+
import json
|
|
6
7
|
import logging
|
|
8
|
+
import uuid
|
|
7
9
|
from collections.abc import Mapping, Sequence
|
|
8
|
-
from contextvars import ContextVar
|
|
9
10
|
from functools import wraps
|
|
10
|
-
from
|
|
11
|
-
from typing import TYPE_CHECKING, Any, ClassVar, Literal, ParamSpec, Self, TypeVar
|
|
11
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, ParamSpec, Self, TypeVar
|
|
12
12
|
|
|
13
13
|
import aiohttp
|
|
14
14
|
import yarl
|
|
15
15
|
from aiolimiter import AsyncLimiter
|
|
16
16
|
|
|
17
|
+
from mega import LOG_HTTP_TRAFFIC, __version__, _package_name_
|
|
17
18
|
from mega.crypto import generate_hashcash
|
|
18
19
|
from mega.errors import RequestError, RetryRequestError
|
|
19
20
|
from mega.utils import random_id, random_u32int
|
|
@@ -25,11 +26,6 @@ if TYPE_CHECKING:
|
|
|
25
26
|
_R = TypeVar("_R")
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0"
|
|
29
|
-
_DEFAULT_HEADERS: MappingProxyType[str, str] = MappingProxyType({"User-Agent": _UA})
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
LOG_HTTP_TRAFFIC: ContextVar[bool] = ContextVar("LOG_HTTP_TRAFFIC", default=False)
|
|
33
29
|
logger = logging.getLogger(__name__)
|
|
34
30
|
|
|
35
31
|
|
|
@@ -67,25 +63,23 @@ def retry(
|
|
|
67
63
|
return wrapper
|
|
68
64
|
|
|
69
65
|
|
|
70
|
-
@dataclasses.dataclass(slots=True, weakref_slot=True
|
|
66
|
+
@dataclasses.dataclass(slots=True, weakref_slot=True)
|
|
71
67
|
class MegaAPI:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
_session: aiohttp.ClientSession | None = None
|
|
69
|
+
|
|
70
|
+
user_agent: str = f"{_package_name_}/{__version__}"
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
session_id: str | None = dataclasses.field(init=False, default=None)
|
|
73
|
+
_request_id: int = dataclasses.field(init=False, default_factory=random_u32int)
|
|
74
|
+
_client_id: str = dataclasses.field(init=False, default_factory=lambda: random_id(10))
|
|
79
75
|
|
|
80
|
-
|
|
76
|
+
_auto_close_session: bool = dataclasses.field(init=False)
|
|
77
|
+
_rate_limiter: AsyncLimiter = dataclasses.field(init=False, default_factory=lambda: AsyncLimiter(100, 60))
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
self.
|
|
86
|
-
self.__session = session
|
|
87
|
-
self._auto_close_session = session is None
|
|
88
|
-
self._rate_limiter = AsyncLimiter(100, 60)
|
|
79
|
+
_entrypoint: ClassVar[yarl.URL] = dataclasses.field(init=False, default=yarl.URL("https://g.api.mega.co.nz/cs"))
|
|
80
|
+
|
|
81
|
+
def __post_init__(self) -> None:
|
|
82
|
+
self._auto_close_session = self._session is None
|
|
89
83
|
|
|
90
84
|
@property
|
|
91
85
|
def entrypoint(self) -> yarl.URL:
|
|
@@ -95,22 +89,12 @@ class MegaAPI:
|
|
|
95
89
|
def client_id(self) -> str:
|
|
96
90
|
return self._client_id
|
|
97
91
|
|
|
98
|
-
@property
|
|
99
|
-
def request_id(self) -> int:
|
|
100
|
-
return self._request_id
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def _session(self) -> aiohttp.ClientSession:
|
|
104
|
-
if self.__session is None:
|
|
105
|
-
self.__session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(sock_connect=160, sock_read=60))
|
|
106
|
-
return self.__session
|
|
107
|
-
|
|
108
92
|
def __repr__(self) -> str:
|
|
109
93
|
return f"<{type(self).__name__}>(session_id={self.session_id!r}, client_id={self.client_id!r}, auto_close_session={self._auto_close_session!r})"
|
|
110
94
|
|
|
111
95
|
async def aclose(self) -> None:
|
|
112
|
-
if self._auto_close_session and self.
|
|
113
|
-
await self.
|
|
96
|
+
if self._auto_close_session and self._session:
|
|
97
|
+
await self._session.close()
|
|
114
98
|
|
|
115
99
|
async def __enter__(self) -> Self:
|
|
116
100
|
return self
|
|
@@ -174,19 +158,40 @@ class MegaAPI:
|
|
|
174
158
|
headers: Mapping[str, str] | None = None,
|
|
175
159
|
**kwargs: Any,
|
|
176
160
|
) -> AsyncGenerator[aiohttp.ClientResponse]:
|
|
177
|
-
kwargs["headers"] =
|
|
161
|
+
kwargs["headers"] = {"User-Agent": self.user_agent, **(headers or {})}
|
|
162
|
+
request_id = str(uuid.uuid4())
|
|
178
163
|
if LOG_HTTP_TRAFFIC.get():
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
logger.debug(
|
|
165
|
+
"Starting %s request [id=%s] to %s \n%s",
|
|
166
|
+
method,
|
|
167
|
+
request_id,
|
|
168
|
+
url,
|
|
169
|
+
kwargs,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
resp = None
|
|
173
|
+
if self._session is None:
|
|
174
|
+
self._session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(sock_connect=160, sock_read=60))
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
async with self._rate_limiter, self._session.request(method, url, **kwargs) as resp:
|
|
178
|
+
yield resp
|
|
179
|
+
except RetryRequestError:
|
|
180
|
+
logger.warning("Request [id=%s] failed, retrying", request_id)
|
|
181
|
+
raise
|
|
182
|
+
finally:
|
|
183
|
+
if resp and LOG_HTTP_TRAFFIC.get():
|
|
184
|
+
logger.debug(
|
|
185
|
+
"Finished %s request [id=%s]\n%s",
|
|
186
|
+
method,
|
|
187
|
+
request_id,
|
|
188
|
+
_LazyResponseLog(resp),
|
|
189
|
+
)
|
|
183
190
|
|
|
184
191
|
@staticmethod
|
|
185
192
|
async def _parse_response(response: aiohttp.ClientResponse) -> Any:
|
|
186
193
|
json_resp = await response.json()
|
|
187
194
|
resp = json_resp
|
|
188
|
-
if LOG_HTTP_TRAFFIC.get():
|
|
189
|
-
logger.debug(f"Got response [{response.status}] json={json_resp!r}")
|
|
190
195
|
|
|
191
196
|
if isinstance(json_resp, list) and len(json_resp) == 1:
|
|
192
197
|
resp = json_resp[0]
|
|
@@ -196,27 +201,48 @@ class MegaAPI:
|
|
|
196
201
|
return resp
|
|
197
202
|
|
|
198
203
|
if resp == -3:
|
|
199
|
-
msg = "Request failed, retrying"
|
|
200
|
-
logger.warning(msg)
|
|
201
204
|
raise RetryRequestError
|
|
202
205
|
raise RequestError(resp)
|
|
203
206
|
|
|
204
207
|
return resp
|
|
205
208
|
|
|
206
209
|
|
|
207
|
-
class
|
|
208
|
-
|
|
210
|
+
class _LazyResponseLog:
|
|
211
|
+
def __init__(self, resp: aiohttp.ClientResponse) -> None:
|
|
212
|
+
self.resp = resp
|
|
213
|
+
|
|
214
|
+
def __json__(self) -> dict[str, Any]:
|
|
215
|
+
me = {
|
|
216
|
+
"url": str(self.resp.url),
|
|
217
|
+
"status_code": self.resp.status,
|
|
218
|
+
"response_headers": dict(self.resp.headers),
|
|
219
|
+
"content": None,
|
|
220
|
+
}
|
|
221
|
+
if self.resp._body:
|
|
222
|
+
stripped = self.resp._body.strip()
|
|
223
|
+
if stripped:
|
|
224
|
+
content = json.loads(stripped.decode(self.resp.get_encoding()))
|
|
225
|
+
me.update(content=content)
|
|
226
|
+
|
|
227
|
+
return me
|
|
228
|
+
|
|
229
|
+
def __str__(self) -> str:
|
|
230
|
+
return str(self.__json__())
|
|
231
|
+
|
|
209
232
|
|
|
210
|
-
|
|
211
|
-
|
|
233
|
+
_API_T = TypeVar("_API_T", bound=MegaAPI, covariant=True)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class APIContextManager(Generic[_API_T]):
|
|
237
|
+
__slots__ = ("_api",)
|
|
212
238
|
|
|
213
239
|
async def __aenter__(self) -> Self:
|
|
214
240
|
return self
|
|
215
241
|
|
|
216
242
|
async def __aexit__(self, *_) -> None:
|
|
217
|
-
await self.
|
|
243
|
+
await self.aclose()
|
|
218
244
|
|
|
219
245
|
async def aclose(self) -> None:
|
|
220
|
-
await self._api.
|
|
246
|
+
await self._api.aclose()
|
|
221
247
|
|
|
222
248
|
close = aclose
|
|
@@ -7,7 +7,7 @@ from pathlib import Path, PurePosixPath
|
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
from mega import progress
|
|
10
|
-
from mega.api import APIContextManager
|
|
10
|
+
from mega.api import APIContextManager, MegaAPI
|
|
11
11
|
from mega.core import MegaCore
|
|
12
12
|
from mega.crypto import a32_to_base64, b64_to_a32, b64_url_encode, encrypt_attr, encrypt_key
|
|
13
13
|
from mega.data_structures import (
|
|
@@ -38,13 +38,16 @@ logger = logging.getLogger(__name__)
|
|
|
38
38
|
_DOMAIN = Site.MEGA.value
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
class MegaNzClient(APIContextManager):
|
|
41
|
+
class MegaNzClient(APIContextManager[MegaAPI]):
|
|
42
42
|
"""Interface with all the public methods of the API"""
|
|
43
43
|
|
|
44
44
|
__slots__ = ("_core",)
|
|
45
45
|
|
|
46
|
-
def __init__(self, session: aiohttp.ClientSession | None = None) -> None:
|
|
47
|
-
|
|
46
|
+
def __init__(self, session: aiohttp.ClientSession | None = None, *, user_agent: str | None = None) -> None:
|
|
47
|
+
self._api = MegaAPI(session)
|
|
48
|
+
if user_agent:
|
|
49
|
+
self._api.user_agent = user_agent
|
|
50
|
+
|
|
48
51
|
self._core: MegaCore = MegaCore(self._api)
|
|
49
52
|
if hasattr(sys, "ps1"):
|
|
50
53
|
setup_logger(logging.DEBUG)
|
|
@@ -134,6 +134,9 @@ class _DictDumper:
|
|
|
134
134
|
"""Get a JSONable dict representation of this object"""
|
|
135
135
|
return dataclasses.asdict(self)
|
|
136
136
|
|
|
137
|
+
def __json__(self) -> dict[str, Any]:
|
|
138
|
+
return self.dump()
|
|
139
|
+
|
|
137
140
|
def _shallow_dump(self) -> dict[str, Any]:
|
|
138
141
|
return {name: getattr(self, name) for name in _fields(type(self))}
|
|
139
142
|
|
|
@@ -45,9 +45,13 @@ class TransferItAPI(MegaAPI):
|
|
|
45
45
|
return await super().post(json, params)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class TransferItClient(APIContextManager):
|
|
49
|
-
|
|
48
|
+
class TransferItClient(APIContextManager[TransferItAPI]):
|
|
49
|
+
__slots__ = ()
|
|
50
|
+
|
|
51
|
+
def __init__(self, session: aiohttp.ClientSession | None = None, *, user_agent: str | None = None) -> None:
|
|
50
52
|
self._api = TransferItAPI(session)
|
|
53
|
+
if user_agent:
|
|
54
|
+
self._api.user_agent = user_agent
|
|
51
55
|
|
|
52
56
|
@property
|
|
53
57
|
def progress_bar(self) -> _GeneratorContextManager[None, None, None]:
|
|
@@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Literal, TypeVar, overload
|
|
|
14
14
|
import aiohttp
|
|
15
15
|
import yarl
|
|
16
16
|
|
|
17
|
+
from mega import LOG_FILE_PROGRESS
|
|
17
18
|
from mega.errors import ValidationError
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
@@ -63,7 +64,7 @@ def setup_logger(level: int = logging.INFO) -> None:
|
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def progress_logger(output_path: Path, file_size: int, *, download: bool) -> Callable[[float], None]:
|
|
66
|
-
if not logger.isEnabledFor(
|
|
67
|
+
if not (LOG_FILE_PROGRESS.get() and logger.isEnabledFor(logging.DEBUG)):
|
|
67
68
|
return lambda _: None
|
|
68
69
|
|
|
69
70
|
from mega.data_structures import ByteSize
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|