async-mega-py 2.0.3.dev0__tar.gz → 2.0.4.dev0__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 (25) hide show
  1. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/PKG-INFO +1 -1
  2. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/pyproject.toml +1 -1
  3. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/client.py +3 -3
  4. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/progress.py +53 -35
  5. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/transfer_it.py +3 -3
  6. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/utils.py +3 -3
  7. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/LICENSE +0 -0
  8. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/README.md +0 -0
  9. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/__init__.py +0 -0
  10. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/__main__.py +0 -0
  11. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/api.py +0 -0
  12. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/auth.py +0 -0
  13. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/chunker.py +0 -0
  14. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/cli/__init__.py +0 -0
  15. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/cli/app.py +0 -0
  16. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/core.py +0 -0
  17. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/crypto.py +0 -0
  18. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/data_structures.py +0 -0
  19. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/download.py +0 -0
  20. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/env.py +0 -0
  21. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/errors.py +0 -0
  22. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/filesystem.py +0 -0
  23. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/py.typed +0 -0
  24. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/src/mega/upload.py +0 -0
  25. {async_mega_py-2.0.3.dev0 → async_mega_py-2.0.4.dev0}/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.0.3.dev0
3
+ Version: 2.0.4.dev0
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.0.3.dev"
37
+ version = "2.0.4.dev"
38
38
 
39
39
  [project.optional-dependencies]
40
40
  cli = [
@@ -22,7 +22,7 @@ from mega.crypto import (
22
22
  from mega.data_structures import AccountStats, Attributes, Crypto, FileInfo, Node, NodeID, NodeType, UserResponse
23
23
  from mega.download import DownloadResults
24
24
  from mega.filesystem import FileSystem
25
- from mega.utils import Site, throttled_gather
25
+ from mega.utils import Site, async_map
26
26
 
27
27
  from .errors import MegaNzError, RequestError, ValidationError
28
28
 
@@ -207,7 +207,7 @@ class MegaNzClient(MegaCore):
207
207
  base_path = Path(output_dir or ".")
208
208
  folder_url = f"{_DOMAIN}/folder/{public_handle}#{public_key}"
209
209
 
210
- async def worker(file: Node) -> tuple[NodeID, Path | Exception]:
210
+ async def download(file: Node) -> tuple[NodeID, Path | Exception]:
211
211
  web_url = folder_url + f"/file/{file.id}"
212
212
  output_path = base_path / fs.relative_path(file.id)
213
213
  try:
@@ -224,7 +224,7 @@ class MegaNzClient(MegaCore):
224
224
 
225
225
  return file.id, result
226
226
 
227
- results = await throttled_gather(worker, fs.files_from(root_id))
227
+ results = await async_map(download, fs.files_from(root_id))
228
228
  return DownloadResults.split(dict(results))
229
229
 
230
230
  async def upload(self, file_path: str | PathLike[str], dest_node_id: NodeID | None = None) -> Node:
@@ -3,14 +3,16 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import contextlib
5
5
  from contextvars import ContextVar
6
- from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeAlias
6
+ from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeAlias, TypeVar
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from collections.abc import Callable, Generator
10
10
  from types import TracebackType
11
11
 
12
- from rich.progress import Progress
12
+ from rich.progress import Progress, Task
13
+ from rich.text import Text
13
14
 
15
+ _T = TypeVar("_T")
14
16
  ProgressHook: TypeAlias = Callable[[float], None]
15
17
 
16
18
  class ProgressHookContext(Protocol):
@@ -33,12 +35,13 @@ current_hook: ContextVar[ProgressHook] = ContextVar("current_hook", default=lamb
33
35
 
34
36
 
35
37
  @contextlib.contextmanager
36
- def set_progress_factory(hook_factory: ProgressHookFactory) -> Generator[None]:
37
- token = _PROGRESS_HOOK_FACTORY.set(hook_factory)
38
+ def _enter_context(context_var: ContextVar[_T], value: _T) -> Generator[None]:
39
+ """Context manager for context vars"""
40
+ token = context_var.set(value)
38
41
  try:
39
42
  yield
40
43
  finally:
41
- _PROGRESS_HOOK_FACTORY.reset(token)
44
+ context_var.reset(token)
42
45
 
43
46
 
44
47
  @contextlib.contextmanager
@@ -48,12 +51,8 @@ def new_task(description: str, total: float, kind: Literal["UP", "DOWN"]) -> Gen
48
51
  yield
49
52
  return
50
53
 
51
- with factory(description, total, kind) as progress_hook:
52
- token = current_hook.set(progress_hook)
53
- try:
54
- yield
55
- finally:
56
- current_hook.reset(token)
54
+ with factory(description, total, kind) as new_hook, _enter_context(current_hook, new_hook):
55
+ yield
57
56
 
58
57
 
59
58
  @contextlib.contextmanager
@@ -66,45 +65,64 @@ def new_progress() -> Generator[None]:
66
65
  def hook_factory(*args, **kwargs):
67
66
  return _new_rich_task(progress, *args, **kwargs)
68
67
 
69
- with progress, set_progress_factory(hook_factory):
68
+ with (
69
+ progress,
70
+ _enter_context(_PROGRESS_HOOK_FACTORY, hook_factory),
71
+ ):
70
72
  yield
71
73
 
72
74
 
73
- def _truncate_desc(desc: str, length: int = 80, placeholder: str = "...") -> str:
74
- if len(desc) <= length:
75
- return desc
76
-
77
- return f"{desc[: length - len(placeholder)]}{placeholder}"
78
-
79
-
80
75
  def _new_rich_progress() -> Progress | None:
81
76
  try:
77
+ from rich import get_console
82
78
  from rich.progress import (
83
79
  BarColumn,
84
80
  DownloadColumn,
85
81
  Progress,
86
82
  SpinnerColumn,
83
+ TextColumn,
87
84
  TimeRemainingColumn,
88
85
  TransferSpeedColumn,
89
86
  )
87
+ from rich.table import Column
90
88
  except ImportError:
91
89
  return None
92
90
 
93
- else:
94
- return Progress(
95
- "[{task.fields[kind]}]",
96
- SpinnerColumn(),
97
- "{task.description}",
98
- BarColumn(bar_width=None),
99
- "[progress.percentage]{task.percentage:>6.2f}%",
100
- "-",
101
- DownloadColumn(),
102
- "-",
103
- TransferSpeedColumn(),
104
- "-",
105
- TimeRemainingColumn(compact=True, elapsed_when_finished=True),
106
- transient=True,
107
- )
91
+ console = get_console()
92
+
93
+ class AutoTruncatedTextColumn(TextColumn):
94
+ def render(self, task: Task) -> Text:
95
+ text = super().render(task)
96
+ width = console.width
97
+ available_witdh = min((width * 60 // 100), (width - 65))
98
+ desc_limit = max(available_witdh, 8)
99
+ text.truncate(desc_limit, overflow="ellipsis")
100
+ return text
101
+
102
+ return Progress(
103
+ "[{task.fields[kind]}]",
104
+ SpinnerColumn(),
105
+ AutoTruncatedTextColumn("{task.description}"),
106
+ BarColumn(
107
+ bar_width=None,
108
+ ),
109
+ "[progress.percentage]{task.percentage:>6.1f}%",
110
+ "•",
111
+ DownloadColumn(
112
+ table_column=Column(justify="right", no_wrap=True),
113
+ ),
114
+ "•",
115
+ TransferSpeedColumn(table_column=Column(justify="right", no_wrap=True)),
116
+ "•",
117
+ TimeRemainingColumn(
118
+ compact=True,
119
+ elapsed_when_finished=True,
120
+ table_column=Column(justify="right", no_wrap=True),
121
+ ),
122
+ transient=True,
123
+ console=console,
124
+ expand=True,
125
+ )
108
126
 
109
127
 
110
128
  @contextlib.contextmanager
@@ -114,7 +132,7 @@ def _new_rich_task(
114
132
  total: float,
115
133
  kind: Literal["UP", "DOWN"],
116
134
  ) -> Generator[ProgressHook]:
117
- task_id = progress.add_task(_truncate_desc(description), total=total, kind=kind)
135
+ task_id = progress.add_task(description, total=total, kind=kind)
118
136
 
119
137
  def progress_hook(advance: float) -> None:
120
138
  progress.advance(task_id, advance)
@@ -15,7 +15,7 @@ from mega.crypto import b64_to_a32, b64_url_decode, decrypt_attr
15
15
  from mega.data_structures import Attributes, Crypto, Node, NodeID, NodeType
16
16
  from mega.download import DownloadResults
17
17
  from mega.filesystem import FileSystem
18
- from mega.utils import Site, throttled_gather
18
+ from mega.utils import Site, async_map
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from collections.abc import Iterable
@@ -107,7 +107,7 @@ class TransferItClient(AbstractApiClient):
107
107
  base_path = Path(output_dir or ".") / f"transfer.it ({transfer_id})"
108
108
  folder_url = f"https://transfer.it/t/{transfer_id}"
109
109
 
110
- async def worker(file: Node) -> tuple[NodeID, Path | Exception]:
110
+ async def download(file: Node) -> tuple[NodeID, Path | Exception]:
111
111
  web_url = folder_url + f"#{file.id}"
112
112
  output_path = base_path / fs.relative_path(file.id)
113
113
  dl_link = self.create_download_url(transfer_id, file)
@@ -123,7 +123,7 @@ class TransferItClient(AbstractApiClient):
123
123
 
124
124
  return file.id, result
125
125
 
126
- results = await throttled_gather(worker, fs.files_from(root_id))
126
+ results = await async_map(download, fs.files_from(root_id))
127
127
  return DownloadResults.split(dict(results))
128
128
 
129
129
  async def _download_file(self, dl_link: str, output_path: str | PathLike[str]) -> Path:
@@ -77,7 +77,7 @@ def transform_v1_url(url: yarl.URL) -> yarl.URL:
77
77
 
78
78
 
79
79
  @overload
80
- async def throttled_gather(
80
+ async def async_map(
81
81
  coro_factory: Callable[[_T1], Awaitable[_T2]],
82
82
  values: Iterable[_T1],
83
83
  *,
@@ -87,7 +87,7 @@ async def throttled_gather(
87
87
 
88
88
 
89
89
  @overload
90
- async def throttled_gather(
90
+ async def async_map(
91
91
  coro_factory: Callable[[_T1], Awaitable[_T2]],
92
92
  values: Iterable[_T1],
93
93
  *,
@@ -96,7 +96,7 @@ async def throttled_gather(
96
96
  ) -> list[_T2]: ...
97
97
 
98
98
 
99
- async def throttled_gather(
99
+ async def async_map(
100
100
  coro_factory: Callable[[_T1], Awaitable[_T2]],
101
101
  values: Iterable[_T1],
102
102
  *,