async-mega-py 2.1.0__tar.gz → 2.2.0__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.
Files changed (24) hide show
  1. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/PKG-INFO +1 -1
  2. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/pyproject.toml +1 -1
  3. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/__main__.py +5 -0
  4. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/api.py +57 -17
  5. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/chunker.py +6 -2
  6. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/client.py +2 -2
  7. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/data_structures.py +3 -0
  8. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/transfer_it.py +2 -2
  9. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/LICENSE +0 -0
  10. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/README.md +0 -0
  11. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/__init__.py +0 -0
  12. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/auth.py +0 -0
  13. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/core.py +0 -0
  14. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/crypto.py +0 -0
  15. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/download.py +0 -0
  16. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/env.py +0 -0
  17. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/errors.py +0 -0
  18. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/filesystem.py +0 -0
  19. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/progress/__init__.py +0 -0
  20. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/progress/rich_progress.py +0 -0
  21. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/py.typed +0 -0
  22. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/upload.py +0 -0
  23. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/utils.py +0 -0
  24. {async_mega_py-2.1.0 → async_mega_py-2.2.0}/src/mega/vault.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: async-mega.py
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Python library for the Mega.nz and Transfer.it API
5
5
  Keywords: api,downloader,mega,mega.nz,transfer.it
6
6
  Author: NTFSvolume
@@ -34,7 +34,7 @@ license = "Apache-2.0"
34
34
  license-files = ["LICENSE"]
35
35
  readme = "README.md"
36
36
  requires-python = ">=3.11"
37
- version = "2.1.0"
37
+ version = "2.2.0"
38
38
 
39
39
  [project.optional-dependencies]
40
40
  cli = [
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: E402
1
2
  from __future__ import annotations
2
3
 
3
4
  import contextlib
@@ -6,6 +7,10 @@ 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
 
@@ -3,17 +3,19 @@ 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
10
  from contextvars import ContextVar
9
11
  from functools import wraps
10
- from types import MappingProxyType
11
12
  from typing import TYPE_CHECKING, Any, ClassVar, Literal, ParamSpec, Self, TypeVar
12
13
 
13
14
  import aiohttp
14
15
  import yarl
15
16
  from aiolimiter import AsyncLimiter
16
17
 
18
+ from mega import __version__, _package_name_
17
19
  from mega.crypto import generate_hashcash
18
20
  from mega.errors import RequestError, RetryRequestError
19
21
  from mega.utils import random_id, random_u32int
@@ -25,11 +27,8 @@ if TYPE_CHECKING:
25
27
  _R = TypeVar("_R")
26
28
 
27
29
 
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
30
  LOG_HTTP_TRAFFIC: ContextVar[bool] = ContextVar("LOG_HTTP_TRAFFIC", default=False)
31
+
33
32
  logger = logging.getLogger(__name__)
34
33
 
35
34
 
@@ -78,14 +77,16 @@ class MegaAPI:
78
77
  _rate_limiter: AsyncLimiter
79
78
 
80
79
  _entrypoint: ClassVar[yarl.URL] = yarl.URL("https://g.api.mega.co.nz/cs")
80
+ user_agent: str
81
81
 
82
- def __init__(self, session: aiohttp.ClientSession | None = None) -> None:
82
+ def __init__(self, session: aiohttp.ClientSession | None = None, user_agent: str | None = None) -> None:
83
83
  self.session_id = None
84
84
  self._request_id = random_u32int()
85
85
  self._client_id = random_id(10)
86
86
  self.__session = session
87
87
  self._auto_close_session = session is None
88
88
  self._rate_limiter = AsyncLimiter(100, 60)
89
+ self.user_agent = user_agent or f"{_package_name_}/{__version__}"
89
90
 
90
91
  @property
91
92
  def entrypoint(self) -> yarl.URL:
@@ -174,19 +175,37 @@ class MegaAPI:
174
175
  headers: Mapping[str, str] | None = None,
175
176
  **kwargs: Any,
176
177
  ) -> AsyncGenerator[aiohttp.ClientResponse]:
177
- kwargs["headers"] = _DEFAULT_HEADERS | (headers or {})
178
+ kwargs["headers"] = {"User-Agent": self.user_agent} | (headers or {})
179
+ request_id = str(uuid.uuid4())
178
180
  if LOG_HTTP_TRAFFIC.get():
179
- params = ", ".join(f"{name} = {value!r}" for name, value in kwargs.items())
180
- logger.debug(f"Making {method} request to {url!s} with {params}")
181
- async with self._rate_limiter, self._session.request(method, url, **kwargs) as resp:
182
- yield resp
181
+ logger.debug(
182
+ "Starting %s request [id=%s] to %s \n%s",
183
+ method,
184
+ request_id,
185
+ url,
186
+ kwargs,
187
+ )
188
+
189
+ resp = None
190
+ try:
191
+ async with self._rate_limiter, self._session.request(method, url, **kwargs) as resp:
192
+ yield resp
193
+ except RetryRequestError:
194
+ logger.warning("Request [id=%s] failed, retrying", request_id)
195
+ raise
196
+ finally:
197
+ if resp and LOG_HTTP_TRAFFIC.get():
198
+ logger.debug(
199
+ "Finished %s request [id=%s]\n%s",
200
+ method,
201
+ request_id,
202
+ _LazyResponseLog(resp),
203
+ )
183
204
 
184
205
  @staticmethod
185
206
  async def _parse_response(response: aiohttp.ClientResponse) -> Any:
186
207
  json_resp = await response.json()
187
208
  resp = json_resp
188
- if LOG_HTTP_TRAFFIC.get():
189
- logger.debug(f"Got response [{response.status}] json={json_resp!r}")
190
209
 
191
210
  if isinstance(json_resp, list) and len(json_resp) == 1:
192
211
  resp = json_resp[0]
@@ -196,19 +215,40 @@ class MegaAPI:
196
215
  return resp
197
216
 
198
217
  if resp == -3:
199
- msg = "Request failed, retrying"
200
- logger.warning(msg)
201
218
  raise RetryRequestError
202
219
  raise RequestError(resp)
203
220
 
204
221
  return resp
205
222
 
206
223
 
224
+ class _LazyResponseLog:
225
+ def __init__(self, resp: aiohttp.ClientResponse) -> None:
226
+ self.resp = resp
227
+
228
+ def __json__(self) -> dict[str, Any]:
229
+ me = {
230
+ "url": str(self.resp.url),
231
+ "status_code": self.resp.status,
232
+ "response_headers": dict(self.resp.headers),
233
+ "content": None,
234
+ }
235
+ if self.resp._body:
236
+ stripped = self.resp._body.strip()
237
+ if stripped:
238
+ content = json.loads(stripped.decode(self.resp.get_encoding()))
239
+ me.update(content=content)
240
+
241
+ return me
242
+
243
+ def __str__(self) -> str:
244
+ return str(self.__json__())
245
+
246
+
207
247
  class APIContextManager:
208
248
  __slots__ = ("_api",)
209
249
 
210
- def __init__(self, session: aiohttp.ClientSession | None = None) -> None:
211
- self._api: MegaAPI = MegaAPI(session)
250
+ def __init__(self, session: aiohttp.ClientSession | None = None, *, user_agent: str | None = None) -> None:
251
+ self._api: MegaAPI = MegaAPI(session, user_agent)
212
252
 
213
253
  async def __aenter__(self) -> Self:
214
254
  return self
@@ -90,11 +90,13 @@ def _iter_chunks(
90
90
 
91
91
  aes = AES.new(key_bytes, AES.MODE_CTR, counter=Counter.new(128, initial_value=nonce))
92
92
  mac_cypher = AES.new(key_bytes, AES.MODE_CBC, EMPTY_IV)
93
- chunk_cypher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
94
-
95
93
  data_in: bytes | None = yield b""
96
94
 
97
95
  while data_in is not None:
96
+ if not data_in:
97
+ data_in = yield b""
98
+ continue
99
+
98
100
  if decrypt:
99
101
  decrypted_data = data_out = aes.decrypt(data_in)
100
102
  else:
@@ -104,6 +106,8 @@ def _iter_chunks(
104
106
  mem_view = memoryview(decrypted_data)
105
107
  last_block_index = (len(decrypted_data) % AES.block_size) or AES.block_size
106
108
  last_block = pad_bytes(mem_view[-last_block_index:])
109
+
110
+ chunk_cypher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
107
111
  chunk_cypher.encrypt(mem_view[:-last_block_index])
108
112
  mac_bytes = mac_cypher.encrypt(chunk_cypher.encrypt(last_block))
109
113
  data_in = yield data_out
@@ -43,8 +43,8 @@ class MegaNzClient(APIContextManager):
43
43
 
44
44
  __slots__ = ("_core",)
45
45
 
46
- def __init__(self, session: aiohttp.ClientSession | None = None) -> None:
47
- super().__init__(session)
46
+ def __init__(self, session: aiohttp.ClientSession | None = None, *, user_agent: str | None = None) -> None:
47
+ super().__init__(session, user_agent=user_agent)
48
48
  self._core: MegaCore = MegaCore(self._api)
49
49
  if hasattr(sys, "ps1"):
50
50
  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
 
@@ -46,8 +46,8 @@ class TransferItAPI(MegaAPI):
46
46
 
47
47
 
48
48
  class TransferItClient(APIContextManager):
49
- def __init__(self, session: aiohttp.ClientSession | None = None) -> None: # pyright: ignore[reportMissingSuperCall]
50
- self._api = TransferItAPI(session)
49
+ def __init__(self, session: aiohttp.ClientSession | None = None, *, user_agent: str | None = None) -> None: # pyright: ignore[reportMissingSuperCall]
50
+ self._api = TransferItAPI(session, user_agent=user_agent)
51
51
 
52
52
  @property
53
53
  def progress_bar(self) -> _GeneratorContextManager[None, None, None]:
File without changes
File without changes