Skip to content

Utils

utils #

Utility functions and classes used throughout package.

ArgumentError #

Bases: Exception

Exception for mismatched or mistyped CLI arguments.

Memo(func) #

Memoize cache.

PARAMETER DESCRIPTION
func

The results of this callable will be cached.

TYPE: Callable

Construct cache.

Source code in torrentfile\utils.py
45
46
47
48
49
50
51
def __init__(self, func):
    """
    Construct cache.
    """
    self.func = func
    self.counter = 0
    self.cache = {}

__call__(path: str) #

Invoke each time memo function is executed.

PARAMETER DESCRIPTION
path

The relative or absolute path being used as key in cache dict.

TYPE: str

RETURNS DESCRIPTION
Any

The results of calling the function with path.

Source code in torrentfile\utils.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def __call__(self, path: str):
    """
    Invoke each time memo function is executed.

    Parameters
    ----------
    path : str
        The relative or absolute path being used as key in cache dict.

    Returns
    -------
    Any :
        The results of calling the function with path.
    """
    if path in self.cache and os.path.exists(path):
        self.counter += 1
        return self.cache[path]
    result = self.func(path)
    self.cache[path] = result
    return result

MissingPathError(message: str = None) #

Bases: Exception

Path parameter is required to specify target content.

Creating a .torrent file with no contents seems rather silly.

PARAMETER DESCRIPTION
message

Message for user (optional).

TYPE: str DEFAULT: None

Raise when creating a meta file without specifying target content.

The message argument is a message to pass to Exception base class.

Source code in torrentfile\utils.py
87
88
89
90
91
92
93
94
def __init__(self, message: str = None):
    """
    Raise when creating a meta file without specifying target content.

    The `message` argument is a message to pass to Exception base class.
    """
    self.message = f"Path arguement is missing and required {str(message)}"
    super().__init__(message)

PieceLengthValueError(message: str = None) #

Bases: Exception

Piece Length parameter must equal a perfect power of 2.

PARAMETER DESCRIPTION
message

Message for user (optional).

TYPE: str DEFAULT: None

Raise when creating a meta file with incorrect piece length value.

The message argument is a message to pass to Exception base class.

Source code in torrentfile\utils.py
107
108
109
110
111
112
113
114
def __init__(self, message: str = None):
    """
    Raise when creating a meta file with incorrect piece length value.

    The `message` argument is a message to pass to Exception base class.
    """
    self.message = f"Incorrect value for piece length: {str(message)}"
    super().__init__(message)

check_path_writable(path: str) -> bool #

Test if output path is writable.

PARAMETER DESCRIPTION
path

file system path string

TYPE: str

RETURNS DESCRIPTION
bool

True if writeable, otherwise raises PermissionError

Source code in torrentfile\utils.py
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def check_path_writable(path: str) -> bool:
    """
    Test if output path is writable.

    Parameters
    ----------
    path : str
        file system path string

    Returns
    -------
    bool
        True if writeable, otherwise raises PermissionError
    """
    try:
        if path.endswith("\\") or path.endswith("/"):
            path = os.path.join(path, ".torrent")
        with open(path, "ab") as _:
            pass
        os.remove(path)
    except PermissionError as err:  # pragma: nocover
        directory = os.path.dirname(path)
        message = f"Target directory is not writeable {directory}"
        raise PermissionError(message) from err
    return True

colored(string: str, key: int) -> str #

Output terminal content with formatting.

Source code in torrentfile\utils.py
458
459
460
461
462
def colored(string: str, key: int) -> str:
    """
    Output terminal content with formatting.
    """
    return f"\033[{key}m{string}\033[0m"

copypath(source: str, dest: str) -> None #

Copy the file located at source to dest.

If one or more directory paths don’t exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten.

PARAMETER DESCRIPTION
source

path to source file

TYPE: str

dest

path to target destination

TYPE: str

Source code in torrentfile\utils.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def copypath(source: str, dest: str) -> None:
    """
    Copy the file located at source to dest.

    If one or more directory paths don't exist in dest, they will be created.
    If dest already exists and dest and source are the same size, it will be
    ignored, however if dest is smaller than source, dest will be overwritten.

    Parameters
    ----------
    source : str
        path to source file
    dest : str
        path to target destination
    """
    if not os.path.exists(source) or (os.path.exists(dest)
                                      and os.path.getsize(source)
                                      <= os.path.getsize(dest)):
        return
    path_parts = Path(dest).parts
    if len(path_parts) > 1:
        root = path_parts[0]
        path_parts = path_parts[1:-1]
        if not os.path.exists(root):
            os.mkdir(root)  # pragma: nocover
        for part in path_parts:
            path = os.path.join(root, part)
            if not os.path.exists(path):
                os.mkdir(path)
            root = path
        shutil.copy(source, dest)

debug_is_on() -> bool #

Return True if debug mode is on in environment variables.

RETURNS DESCRIPTION
bool

is debug mode on

Source code in torrentfile\utils.py
412
413
414
415
416
417
418
419
420
421
def debug_is_on() -> bool:
    """
    Return True if debug mode is on in environment variables.

    Returns
    -------
    bool
        is debug mode on
    """
    return os.environ["TORRENTFILE_DEBUG"] == "ON"

filelist_total(pathstring: str) -> os.PathLike #

Perform error checking and format conversion to os.PathLike.

PARAMETER DESCRIPTION
pathstring

An existing filesystem path.

TYPE: str

RETURNS DESCRIPTION
os.PathLike

Input path converted to bytes format.

RAISES DESCRIPTION
MissingPathError

File could not be found.

Source code in torrentfile\utils.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
@Memo
def filelist_total(pathstring: str) -> os.PathLike:
    """
    Perform error checking and format conversion to os.PathLike.

    Parameters
    ----------
    pathstring : str
        An existing filesystem path.

    Returns
    -------
    os.PathLike
        Input path converted to bytes format.

    Raises
    ------
    MissingPathError
        File could not be found.
    """
    if os.path.exists(pathstring):
        path = Path(pathstring)
        return _filelist_total(path)
    raise MissingPathError

get_file_list(path: str) -> list #

Return a sorted list of file paths contained in directory.

PARAMETER DESCRIPTION
path

target file or directory.

TYPE: str

RETURNS DESCRIPTION
list

sorted list of file paths.

TYPE: list

Source code in torrentfile\utils.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def get_file_list(path: str) -> list:
    """
    Return a sorted list of file paths contained in directory.

    Parameters
    ----------
    path : str
        target file or directory.

    Returns
    -------
    list :
        sorted list of file paths.
    """
    _, filelist = filelist_total(path)
    return filelist

get_piece_length(size: int) -> int #

Calculate the ideal piece length for bittorrent data.

PARAMETER DESCRIPTION
size

Total bits of all files incluided in .torrent file.

TYPE: int

RETURNS DESCRIPTION
int

Ideal piece length.

Source code in torrentfile\utils.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
def get_piece_length(size: int) -> int:
    """
    Calculate the ideal piece length for bittorrent data.

    Parameters
    ----------
    size : int
        Total bits of all files incluided in .torrent file.

    Returns
    -------
    int
        Ideal piece length.
    """
    exp = 14
    while size / (2**exp) > 1000 and exp < 24:
        exp += 1
    return 2**exp

green(string: str) -> str #

Output terminal content in green color.

Source code in torrentfile\utils.py
451
452
453
454
455
def green(string: str) -> str:
    """
    Output terminal content in green color.
    """
    return colored(string, 92)

humanize_bytes(amount: int) -> str #

Convert integer into human readable memory sized denomination.

PARAMETER DESCRIPTION
amount

total number of bytes.

TYPE: int

RETURNS DESCRIPTION
str

human readable representation of the given amount of bytes.

Source code in torrentfile\utils.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def humanize_bytes(amount: int) -> str:
    """
    Convert integer into human readable memory sized denomination.

    Parameters
    ----------
    amount : int
        total number of bytes.

    Returns
    -------
    str
        human readable representation of the given amount of bytes.
    """
    base = 1024
    amount = float(amount)
    value = abs(amount)
    if value == 1:
        return f"{amount} Byte"
    if value < base:
        return f"{amount} Bytes"
    for i, s in enumerate(SUFFIXES):
        unit = base**(i + 2)
        if value < unit:
            break
    value = base * amount / unit
    return f"{value:.1f} {s}"

next_power_2(value: int) -> int #

Calculate the next perfect power of 2 equal to or greater than value.

PARAMETER DESCRIPTION
value

integer value that is less than some perfect power of 2.

TYPE: int

RETURNS DESCRIPTION
int

The next power of 2 greater than value, or value if already power of 2.

Source code in torrentfile\utils.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
def next_power_2(value: int) -> int:
    """
    Calculate the next perfect power of 2 equal to or greater than value.

    Parameters
    ----------
    value : int
        integer value that is less than some perfect power of 2.

    Returns
    -------
    int
        The next power of 2 greater than value, or value if already power of 2.
    """
    if not value & (value - 1) and value:
        return value
    start = 1
    while start < value:
        start <<= 1
    return start

normalize_piece_length(piece_length: int) -> int #

Verify input piece_length is valid and convert accordingly.

PARAMETER DESCRIPTION
piece_length

The piece length provided by user.

TYPE: int | str

RETURNS DESCRIPTION
int

normalized piece length.

RAISES DESCRIPTION
PieceLengthValueError :

Piece length is improper value.

Source code in torrentfile\utils.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def normalize_piece_length(piece_length: int) -> int:
    """
    Verify input piece_length is valid and convert accordingly.

    Parameters
    ----------
    piece_length : int | str
        The piece length provided by user.

    Returns
    -------
    int
        normalized piece length.

    Raises
    ------
    PieceLengthValueError :
        Piece length is improper value.
    """
    if isinstance(piece_length, str):
        if piece_length.isnumeric():
            piece_length = int(piece_length)
        else:
            raise PieceLengthValueError(piece_length)

    if piece_length > (1 << 14):
        if 2**math.log2(piece_length) == piece_length:
            return piece_length
        raise PieceLengthValueError(piece_length)

    if 13 < piece_length < 26:
        return 2**piece_length
    if piece_length <= 13:
        raise PieceLengthValueError(piece_length)

    log = int(math.log2(piece_length))
    if 2**log == piece_length:
        return piece_length
    raise PieceLengthValueError

path_piece_length(path: str) -> int #

Calculate piece length for input path and contents.

PARAMETER DESCRIPTION
path

The absolute path to directory and contents.

TYPE: str

RETURNS DESCRIPTION
int

The size of pieces of torrent content.

Source code in torrentfile\utils.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def path_piece_length(path: str) -> int:
    """
    Calculate piece length for input path and contents.

    Parameters
    ----------
    path : str
        The absolute path to directory and contents.

    Returns
    -------
    int
        The size of pieces of torrent content.
    """
    psize = path_size(path)
    return get_piece_length(psize)

path_size(path: str) -> int #

Return the total size of all files in path recursively.

PARAMETER DESCRIPTION
path

path to target file or directory.

TYPE: str

RETURNS DESCRIPTION
int

total size of files.

Source code in torrentfile\utils.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def path_size(path: str) -> int:
    """
    Return the total size of all files in path recursively.

    Parameters
    ----------
    path : str
        path to target file or directory.

    Returns
    -------
    int
        total size of files.
    """
    total_size, _ = filelist_total(path)
    return total_size

path_stat(path: str) -> tuple #

Calculate directory statistics.

PARAMETER DESCRIPTION
path

The path to start calculating from.

TYPE: str

RETURNS DESCRIPTION
Tuple[list, int, int]

list - List of all files contained in Directory int - Total sum of bytes from all contents of dir int - The size of pieces of the torrent contents.

Source code in torrentfile\utils.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def path_stat(path: str) -> tuple:
    """
    Calculate directory statistics.

    Parameters
    ----------
    path : str
        The path to start calculating from.

    Returns
    -------
    Tuple[list, int, int] :
        list - List of all files contained in Directory
        int - Total sum of bytes from all contents of dir
        int - The size of pieces of the torrent contents.
    """
    total_size, filelist = filelist_total(path)
    piece_length = get_piece_length(total_size)
    return (filelist, total_size, piece_length)

toggle_debug_mode(switch_on: bool) #

Switch the environment variable debug indicator on or off.

PARAMETER DESCRIPTION
switch_on

if true turn debug mode on otherwise off

TYPE: bool

Source code in torrentfile\utils.py
400
401
402
403
404
405
406
407
408
409
def toggle_debug_mode(switch_on: bool):
    """
    Switch the environment variable debug indicator on or off.

    Parameters
    ----------
    switch_on : bool
        if true turn debug mode on otherwise off
    """
    os.environ["TORRENTFILE_DEBUG"] = "ON" if switch_on else "OFF"