async-mega-py 2.3.1__tar.gz → 2.4.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.3.1 → async_mega_py-2.4.1}/PKG-INFO +2 -2
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/pyproject.toml +3 -3
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/core.py +14 -5
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/filesystem.py +47 -113
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/LICENSE +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/README.md +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/__init__.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/__main__.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/api.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/auth.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/chunker.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/client.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/crypto.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/data_structures.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/download.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/env.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/errors.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/progress/__init__.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/progress/rich_progress.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/py.typed +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/transfer_it.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/upload.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/utils.py +0 -0
- {async_mega_py-2.3.1 → async_mega_py-2.4.1}/src/mega/vault.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: async-mega.py
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
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
|
|
7
7
|
Author-email: NTFSvolume <NTFSvolume@proton.me>
|
|
8
8
|
License-Expression: Apache-2.0
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Framework :: AsyncIO
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "async-mega.py"
|
|
3
3
|
authors = [{name = "NTFSvolume", email = "NTFSvolume@proton.me"}]
|
|
4
4
|
classifiers = [
|
|
5
|
-
"Development Status ::
|
|
5
|
+
"Development Status :: 5 - Production/Stable",
|
|
6
6
|
"Environment :: Console",
|
|
7
7
|
"Framework :: AsyncIO",
|
|
8
8
|
"Intended Audience :: Developers",
|
|
@@ -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.
|
|
37
|
+
version = "2.4.1"
|
|
38
38
|
|
|
39
39
|
[project.optional-dependencies]
|
|
40
40
|
cli = [
|
|
@@ -123,7 +123,7 @@ requires = ["uv_build<=0.10.11"]
|
|
|
123
123
|
|
|
124
124
|
[dependency-groups]
|
|
125
125
|
dev = [
|
|
126
|
-
"prek>=0.
|
|
126
|
+
"prek>=0.4.0",
|
|
127
127
|
"pytest-asyncio>=0.25.0",
|
|
128
128
|
"pytest>=8.3.5",
|
|
129
129
|
"ruff==0.15.6"
|
|
@@ -106,18 +106,27 @@ class MegaCore:
|
|
|
106
106
|
return result.public_handle, result.public_key, result.selected_node
|
|
107
107
|
|
|
108
108
|
@staticmethod
|
|
109
|
-
def
|
|
110
|
-
"""
|
|
109
|
+
def ensure_v2_url(url: str | yarl.URL) -> yarl.URL:
|
|
110
|
+
"""Transform an URL in V1 format to v2"""
|
|
111
111
|
url = yarl.URL(url)
|
|
112
112
|
Site.MEGA.check_host(url)
|
|
113
113
|
new_url = transform_v1_url(url)
|
|
114
114
|
if new_url != url:
|
|
115
115
|
logger.info(f"Transformed v1 URL from {url} to {new_url}")
|
|
116
|
+
return new_url
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
@staticmethod
|
|
119
|
+
def parse_url(url: str | yarl.URL, *, check_key: bool = True) -> PublicURLInfo:
|
|
120
|
+
"""Parse a public URL"""
|
|
121
|
+
url = MegaCore.ensure_v2_url(url)
|
|
122
|
+
info = MegaCore._parse_public_v2_url(url)
|
|
123
|
+
if check_key and not info.public_key:
|
|
119
124
|
raise ValidationError(f"Public key missing from {url}")
|
|
125
|
+
return info
|
|
120
126
|
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _parse_public_v2_url(url: yarl.URL, /) -> PublicURLInfo:
|
|
129
|
+
"""Parse a public URL"""
|
|
121
130
|
match url.parts[1:]:
|
|
122
131
|
case ["file", public_handle]:
|
|
123
132
|
return PublicURLInfo(False, public_handle, url.fragment)
|
|
@@ -422,7 +431,7 @@ class MegaCore:
|
|
|
422
431
|
) -> Node:
|
|
423
432
|
"""Import the public file into user account"""
|
|
424
433
|
full_key = b64_to_a32(public_key)
|
|
425
|
-
key = Crypto.decompose(full_key).key
|
|
434
|
+
key = Crypto.decompose(full_key).key # pyright: ignore[reportArgumentType]
|
|
426
435
|
file_info = await self.request_file_info(public_handle, is_public=True)
|
|
427
436
|
name = self.decrypt_attrs(file_info._at, key, public_handle).name
|
|
428
437
|
encrypted_key = a32_to_base64(encrypt_key(full_key, self.vault.master_key))
|
|
@@ -12,7 +12,7 @@ from types import MappingProxyType
|
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Final, NamedTuple, Self
|
|
13
13
|
|
|
14
14
|
from mega.data_structures import Node, NodeID, NodeType, _DictDumper
|
|
15
|
-
from mega.errors import MultipleNodesFoundError
|
|
15
|
+
from mega.errors import MultipleNodesFoundError, ValidationError
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from collections.abc import Generator, Iterable, Iterator
|
|
@@ -48,12 +48,15 @@ def _resolve_paths(walker: _NodeWalker, *roots: Node) -> Generator[_NodeLookup]:
|
|
|
48
48
|
|
|
49
49
|
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True)
|
|
50
50
|
class _NodeWalker:
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
nodes: MappingProxyType[NodeID, Node] = dataclasses.field(repr=False)
|
|
52
|
+
"A mapping of every node"
|
|
53
|
+
|
|
54
|
+
children: MappingProxyType[NodeID, tuple[NodeID, ...]] = dataclasses.field(repr=False)
|
|
55
|
+
"A mapping of nodes to their inmediate children"
|
|
53
56
|
|
|
54
57
|
def _ls(self, node_id: NodeID, *, recursive: bool) -> Iterable[NodeID]:
|
|
55
58
|
"""Get ID of every child of this node"""
|
|
56
|
-
for child_id in self.
|
|
59
|
+
for child_id in self.children.get(node_id, ()):
|
|
57
60
|
yield child_id
|
|
58
61
|
if recursive:
|
|
59
62
|
yield from self._ls(child_id, recursive=recursive)
|
|
@@ -61,14 +64,14 @@ class _NodeWalker:
|
|
|
61
64
|
def iterdir(self, node_id: NodeID, *, recursive: bool = False) -> Iterable[Node]:
|
|
62
65
|
"""Iterate over the children in this node"""
|
|
63
66
|
for child_id in self._ls(node_id, recursive=recursive):
|
|
64
|
-
yield self.
|
|
67
|
+
yield self.nodes[child_id]
|
|
65
68
|
|
|
66
69
|
def listdir(self, node_id: NodeID) -> list[Node]:
|
|
67
70
|
"""Get a list of children of this node (non recursive)"""
|
|
68
71
|
return list(self.iterdir(node_id))
|
|
69
72
|
|
|
70
73
|
|
|
71
|
-
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True)
|
|
74
|
+
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True, repr=False)
|
|
72
75
|
class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
73
76
|
"""A simple representation of Mega.nz's file system.
|
|
74
77
|
|
|
@@ -82,6 +85,16 @@ class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
|
82
85
|
file_count: int
|
|
83
86
|
folder_count: int
|
|
84
87
|
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
def fields():
|
|
90
|
+
for name, node in [("root", self.root), ("inbox", self.inbox), ("trash_bin", self.trash_bin)]:
|
|
91
|
+
yield name, node.id if node is not None else None
|
|
92
|
+
yield "files", self.file_count
|
|
93
|
+
yield "folders", self.folder_count
|
|
94
|
+
|
|
95
|
+
all_fields = ", ".join(f"{name}={value!r}" for name, value in fields())
|
|
96
|
+
return f"<{type(self).__name__}({all_fields})>"
|
|
97
|
+
|
|
85
98
|
def __len__(self) -> int:
|
|
86
99
|
return len(self.nodes)
|
|
87
100
|
|
|
@@ -95,16 +108,6 @@ class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
|
95
108
|
"""Get the node with this ID"""
|
|
96
109
|
return self.nodes[node_id]
|
|
97
110
|
|
|
98
|
-
@property
|
|
99
|
-
def nodes(self) -> MappingProxyType[NodeID, Node]:
|
|
100
|
-
"""A mapping of every node"""
|
|
101
|
-
return self._nodes
|
|
102
|
-
|
|
103
|
-
@property
|
|
104
|
-
def children(self) -> MappingProxyType[NodeID, tuple[NodeID, ...]]:
|
|
105
|
-
"""A mapping of nodes to their inmediate children"""
|
|
106
|
-
return self._children
|
|
107
|
-
|
|
108
111
|
@property
|
|
109
112
|
def deleted(self) -> Iterable[Node]:
|
|
110
113
|
"""Files or folders currently on the trash bin (Non recursive)"""
|
|
@@ -119,6 +122,11 @@ class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
|
119
122
|
nodes_map: dict[NodeID, Node] = {}
|
|
120
123
|
children: dict[NodeID, list[NodeID]] = {}
|
|
121
124
|
|
|
125
|
+
def sanity_check(current: Node | None, found: Node) -> None:
|
|
126
|
+
if current is not None:
|
|
127
|
+
ids = current.id, found.id
|
|
128
|
+
raise ValidationError(f"Multiple nodes of type {found.type.name} found: {ids}")
|
|
129
|
+
|
|
122
130
|
for node in nodes:
|
|
123
131
|
nodes_map[node.id] = node
|
|
124
132
|
if node.parent_id:
|
|
@@ -129,10 +137,13 @@ class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
|
129
137
|
case NodeType.FOLDER:
|
|
130
138
|
folder_count += 1
|
|
131
139
|
case NodeType.ROOT_FOLDER:
|
|
140
|
+
sanity_check(root, node)
|
|
132
141
|
root = node
|
|
133
142
|
case NodeType.INBOX:
|
|
143
|
+
sanity_check(inbox, node)
|
|
134
144
|
inbox = node
|
|
135
145
|
case NodeType.TRASH:
|
|
146
|
+
sanity_check(trash_bin, node)
|
|
136
147
|
trash_bin = node
|
|
137
148
|
case _:
|
|
138
149
|
raise RuntimeError # pyright: ignore[reportUnreachable]
|
|
@@ -143,8 +154,8 @@ class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
|
143
154
|
trash_bin=trash_bin,
|
|
144
155
|
file_count=file_count,
|
|
145
156
|
folder_count=folder_count,
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
nodes=MappingProxyType(nodes_map),
|
|
158
|
+
children=MappingProxyType({node_id: tuple(nodes) for node_id, nodes in children.items()}),
|
|
148
159
|
)
|
|
149
160
|
|
|
150
161
|
def dump(self) -> dict[str, Any]:
|
|
@@ -163,7 +174,7 @@ class SimpleFileSystem(_NodeWalker, _DictDumper):
|
|
|
163
174
|
return cls.build(Node.from_dump(node) for node in dump["nodes"].values())
|
|
164
175
|
|
|
165
176
|
|
|
166
|
-
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True)
|
|
177
|
+
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True, repr=False)
|
|
167
178
|
class FileSystem(SimpleFileSystem):
|
|
168
179
|
"""Mega.nz's file system.
|
|
169
180
|
|
|
@@ -173,9 +184,13 @@ class FileSystem(SimpleFileSystem):
|
|
|
173
184
|
NOTE: Mega's filesystem is **not POSIX-compliant**: multiple nodes may have the same path
|
|
174
185
|
"""
|
|
175
186
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
paths: MappingProxyType[NodeID, PurePosixPath]
|
|
188
|
+
"""A mapping of every node to its absolute path within the filesystem"""
|
|
189
|
+
|
|
190
|
+
inv_paths: MappingProxyType[PurePosixPath, tuple[NodeID, ...]]
|
|
191
|
+
"""A mapping of paths to every node located at that path"""
|
|
192
|
+
|
|
193
|
+
_deleted: frozenset[NodeID]
|
|
179
194
|
|
|
180
195
|
@classmethod
|
|
181
196
|
def build(cls, nodes: Iterable[Node]) -> Self:
|
|
@@ -209,26 +224,13 @@ class FileSystem(SimpleFileSystem):
|
|
|
209
224
|
trash_bin=self.trash_bin,
|
|
210
225
|
file_count=self.file_count,
|
|
211
226
|
folder_count=self.folder_count,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
227
|
+
nodes=self.nodes,
|
|
228
|
+
children=self.children,
|
|
229
|
+
paths=MappingProxyType(paths),
|
|
230
|
+
inv_paths=MappingProxyType({path: tuple(nodes) for path, nodes in inv_paths.items()}),
|
|
216
231
|
_deleted=frozenset(deleted_ids),
|
|
217
232
|
)
|
|
218
233
|
|
|
219
|
-
@property
|
|
220
|
-
def paths(self) -> MappingProxyType[NodeID, PurePosixPath]:
|
|
221
|
-
"""A mapping of every node to its absolute path within the filesystem"""
|
|
222
|
-
return self._paths
|
|
223
|
-
|
|
224
|
-
@property
|
|
225
|
-
def inv_paths(self) -> MappingProxyType[PurePosixPath, tuple[NodeID, ...]]:
|
|
226
|
-
"""A mapping of paths to every node located at that path
|
|
227
|
-
|
|
228
|
-
Mega's filesystem is **not POSIX-compliant**: multiple nodes may have the same path
|
|
229
|
-
"""
|
|
230
|
-
return self._inv_paths
|
|
231
|
-
|
|
232
234
|
@property
|
|
233
235
|
def files(self) -> Iterable[Node]:
|
|
234
236
|
"""All files that are NOT deleted (recursive)"""
|
|
@@ -260,7 +262,7 @@ class FileSystem(SimpleFileSystem):
|
|
|
260
262
|
|
|
261
263
|
def absolute_path(self, node_id: NodeID) -> PurePosixPath:
|
|
262
264
|
"""Get the absolute path of this node"""
|
|
263
|
-
return self.
|
|
265
|
+
return self.paths[node_id]
|
|
264
266
|
|
|
265
267
|
def search(
|
|
266
268
|
self,
|
|
@@ -271,7 +273,7 @@ class FileSystem(SimpleFileSystem):
|
|
|
271
273
|
"""Returns nodes that have "query" as a substring on their path"""
|
|
272
274
|
query = PurePosixPath(query).as_posix()
|
|
273
275
|
|
|
274
|
-
for node_id, path in self.
|
|
276
|
+
for node_id, path in self.paths.items():
|
|
275
277
|
if query not in path.as_posix():
|
|
276
278
|
continue
|
|
277
279
|
|
|
@@ -292,7 +294,7 @@ class FileSystem(SimpleFileSystem):
|
|
|
292
294
|
"""
|
|
293
295
|
path = POSIX_ROOT / PurePosixPath(path)
|
|
294
296
|
try:
|
|
295
|
-
nodes = self.
|
|
297
|
+
nodes = self.inv_paths[path]
|
|
296
298
|
except LookupError:
|
|
297
299
|
msg = f'A node with path "{path!s}" does not exists'
|
|
298
300
|
raise FileNotFoundError(errno.ENOENT, msg) from None
|
|
@@ -332,84 +334,18 @@ class FileSystem(SimpleFileSystem):
|
|
|
332
334
|
|
|
333
335
|
return dump | dict( # noqa: C408
|
|
334
336
|
deleted=sorted(self._deleted),
|
|
335
|
-
paths={node_id: str(path) for node_id, path in self.
|
|
336
|
-
inv_paths={str(path): node_id for path, node_id in self.
|
|
337
|
+
paths={node_id: str(path) for node_id, path in self.paths.items()},
|
|
338
|
+
inv_paths={str(path): node_id for path, node_id in self.inv_paths.items()},
|
|
337
339
|
children=dict(self.children),
|
|
338
340
|
)
|
|
339
341
|
|
|
340
342
|
|
|
341
|
-
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True)
|
|
343
|
+
@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, weakref_slot=True, repr=False)
|
|
342
344
|
class UserFileSystem(FileSystem):
|
|
343
345
|
root: Node
|
|
344
346
|
inbox: Node
|
|
345
347
|
trash_bin: Node
|
|
346
348
|
|
|
347
|
-
@classmethod
|
|
348
|
-
def build_unsafe(cls, nodes: Iterable[Node]) -> Self:
|
|
349
|
-
"""Build a filesystem from a pre-ordered node stream without validation
|
|
350
|
-
|
|
351
|
-
Nodes MUST be topologically sorted:
|
|
352
|
-
- The first three nodes are ROOT, INBOX and TRASH_BIN.
|
|
353
|
-
- Every parent appears before its descendants.
|
|
354
|
-
"""
|
|
355
|
-
# Build fs using only 3 loops:
|
|
356
|
-
# - 1. Create a map to all nodes and resolve their paths
|
|
357
|
-
# - 2. Freeze children (list -> tuple)
|
|
358
|
-
# - 3. Freeze inv paths (list -> tuple)
|
|
359
|
-
root = inbox = trash_bin = None
|
|
360
|
-
file_count = folder_count = 0
|
|
361
|
-
|
|
362
|
-
paths: dict[NodeID, PurePosixPath] = {}
|
|
363
|
-
inv_paths: dict[PurePosixPath, list[NodeID]] = {}
|
|
364
|
-
deleted_ids: set[NodeID] = set()
|
|
365
|
-
nodes_map: dict[NodeID, Node] = {}
|
|
366
|
-
children: dict[NodeID, list[NodeID]] = {}
|
|
367
|
-
trash_bin_id = None
|
|
368
|
-
|
|
369
|
-
for node in nodes:
|
|
370
|
-
nodes_map[node.id] = node
|
|
371
|
-
if node.parent_id:
|
|
372
|
-
children.setdefault(node.parent_id, []).append(node.id)
|
|
373
|
-
|
|
374
|
-
path = None
|
|
375
|
-
match node.type:
|
|
376
|
-
case NodeType.FILE:
|
|
377
|
-
file_count += 1
|
|
378
|
-
case NodeType.FOLDER:
|
|
379
|
-
folder_count += 1
|
|
380
|
-
case NodeType.ROOT_FOLDER:
|
|
381
|
-
path = POSIX_ROOT
|
|
382
|
-
root = node
|
|
383
|
-
case NodeType.INBOX:
|
|
384
|
-
path = POSIX_ROOT / node.attributes.name
|
|
385
|
-
inbox = node
|
|
386
|
-
case NodeType.TRASH:
|
|
387
|
-
path = POSIX_ROOT / node.attributes.name
|
|
388
|
-
trash_bin_id = node.id
|
|
389
|
-
trash_bin = node
|
|
390
|
-
case _:
|
|
391
|
-
raise RuntimeError # pyright: ignore[reportUnreachable]
|
|
392
|
-
|
|
393
|
-
path = path or (paths[node.parent_id] / node.attributes.name)
|
|
394
|
-
paths[node.id] = path
|
|
395
|
-
inv_paths.setdefault(path, []).append(node.id)
|
|
396
|
-
if node.parent_id == trash_bin_id or node.parent_id in deleted_ids:
|
|
397
|
-
deleted_ids.add(node.id)
|
|
398
|
-
|
|
399
|
-
assert root and inbox and trash_bin
|
|
400
|
-
return cls(
|
|
401
|
-
root=root,
|
|
402
|
-
inbox=inbox,
|
|
403
|
-
trash_bin=trash_bin,
|
|
404
|
-
file_count=file_count,
|
|
405
|
-
folder_count=folder_count,
|
|
406
|
-
_nodes=MappingProxyType(nodes_map),
|
|
407
|
-
_children=MappingProxyType({node_id: tuple(nodes) for node_id, nodes in children.items()}),
|
|
408
|
-
_paths=MappingProxyType(paths),
|
|
409
|
-
_inv_paths=MappingProxyType({path: tuple(nodes) for path, nodes in inv_paths.items()}),
|
|
410
|
-
_deleted=frozenset(deleted_ids),
|
|
411
|
-
)
|
|
412
|
-
|
|
413
349
|
|
|
414
350
|
if __name__ == "__main__":
|
|
415
351
|
import json
|
|
@@ -423,7 +359,5 @@ if __name__ == "__main__":
|
|
|
423
359
|
print(f"Testing filesystem build of {len(nodes):_} nodes") # noqa: T201
|
|
424
360
|
|
|
425
361
|
iterations = 1_000
|
|
426
|
-
unsafe = timeit.timeit(lambda: UserFileSystem.build_unsafe(nodes), number=iterations)
|
|
427
|
-
print(f"{iterations} [UNSAFE] calls took {unsafe:.4f}s") # noqa: T201
|
|
428
362
|
safe = timeit.timeit(lambda: UserFileSystem.build(nodes), number=iterations)
|
|
429
363
|
print(f"{iterations} [SAFE] calls took {safe:.4f}s") # noqa: T201
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|