stdl 0.6.7__py3-none-any.whl
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.
- stdl/__init__.py +2 -0
- stdl/color.py +1505 -0
- stdl/decorators.py +96 -0
- stdl/dt.py +355 -0
- stdl/fs.py +1711 -0
- stdl/import_lazy.py +123 -0
- stdl/log.py +142 -0
- stdl/lst.py +166 -0
- stdl/net.py +80 -0
- stdl/st.py +422 -0
- stdl-0.6.7.dist-info/METADATA +141 -0
- stdl-0.6.7.dist-info/RECORD +14 -0
- stdl-0.6.7.dist-info/WHEEL +4 -0
- stdl-0.6.7.dist-info/licenses/LICENSE +21 -0
stdl/import_lazy.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LazyImport:
|
|
8
|
+
"""A lazy import that loads the actual object on first access."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
module_name: str,
|
|
13
|
+
attr_name: str | None = None,
|
|
14
|
+
verbose: bool = False,
|
|
15
|
+
):
|
|
16
|
+
self.module_name = module_name
|
|
17
|
+
self.attr_name = attr_name
|
|
18
|
+
self.verbose = verbose
|
|
19
|
+
|
|
20
|
+
self._cached_value: Any = None
|
|
21
|
+
self._loaded = False
|
|
22
|
+
|
|
23
|
+
def _load(self) -> Any:
|
|
24
|
+
if not self._loaded:
|
|
25
|
+
if self.verbose:
|
|
26
|
+
start_time = time.time()
|
|
27
|
+
|
|
28
|
+
module_already_loaded = self.module_name in sys.modules
|
|
29
|
+
try:
|
|
30
|
+
module = importlib.import_module(self.module_name)
|
|
31
|
+
except ImportError as e:
|
|
32
|
+
if self.attr_name:
|
|
33
|
+
raise ImportError(
|
|
34
|
+
f"Cannot import '{self.attr_name}' from '{self.module_name}': {e}"
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
raise ImportError(f"Cannot import module '{self.module_name}': {e}")
|
|
38
|
+
|
|
39
|
+
if self.attr_name:
|
|
40
|
+
try:
|
|
41
|
+
self._cached_value = getattr(module, self.attr_name)
|
|
42
|
+
except AttributeError:
|
|
43
|
+
raise ImportError(
|
|
44
|
+
f"Module '{self.module_name}' has no attribute '{self.attr_name}'"
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
self._cached_value = module
|
|
48
|
+
|
|
49
|
+
if self.verbose and not module_already_loaded:
|
|
50
|
+
end_time = time.time()
|
|
51
|
+
import_time = end_time - start_time
|
|
52
|
+
import_target = (
|
|
53
|
+
f"{self.module_name}.{self.attr_name}" if self.attr_name else self.module_name
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
print(
|
|
57
|
+
f'importing "{import_target}" took {import_time:.3f}s',
|
|
58
|
+
file=sys.stderr,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self._loaded = True
|
|
62
|
+
|
|
63
|
+
return self._cached_value
|
|
64
|
+
|
|
65
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
66
|
+
return self._load()(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
def __getattr__(self, name: str) -> Any:
|
|
69
|
+
return getattr(self._load(), name)
|
|
70
|
+
|
|
71
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
72
|
+
if name in ("module_name", "attr_name", "verbose", "_cached_value", "_loaded"):
|
|
73
|
+
super().__setattr__(name, value)
|
|
74
|
+
else:
|
|
75
|
+
setattr(self._load(), name, value)
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
if self._loaded:
|
|
79
|
+
return repr(self._cached_value)
|
|
80
|
+
return f"<LazyImport: {self.module_name}{('.' + self.attr_name) if self.attr_name else ''}>"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def import_lazy(
|
|
84
|
+
module: str,
|
|
85
|
+
names: list[str] | None = None,
|
|
86
|
+
alias: str | None = None,
|
|
87
|
+
verbose: bool = False,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Lazy import a module.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
module (str): Module name to import
|
|
94
|
+
names (list[str] | None): List of specific names to import from the module. If None, imports the module itself.
|
|
95
|
+
alias (str | None): Alias to use for the imported module (only valid when names is None)
|
|
96
|
+
verbose (bool): If True, print timing information when imports actually happen
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```python
|
|
100
|
+
>>> import_lazy("module", ["name_1", "name_2"]) # from module import name_1, name_2
|
|
101
|
+
>>> import_lazy("module") # import module
|
|
102
|
+
>>> import_lazy("module", alias="mod") # import module as mod
|
|
103
|
+
>>> import_lazy("package.submodule", ["name_1"]) # from package.submodule import name_1
|
|
104
|
+
```
|
|
105
|
+
"""
|
|
106
|
+
frame = sys._getframe(1)
|
|
107
|
+
caller_globals = frame.f_globals
|
|
108
|
+
|
|
109
|
+
if names is None:
|
|
110
|
+
lazy_import = LazyImport(module, verbose=verbose)
|
|
111
|
+
if alias:
|
|
112
|
+
caller_globals[alias] = lazy_import
|
|
113
|
+
else:
|
|
114
|
+
caller_globals[module.split(".")[-1]] = lazy_import
|
|
115
|
+
else:
|
|
116
|
+
if alias:
|
|
117
|
+
raise ValueError("Cannot use alias with named imports")
|
|
118
|
+
for name in names:
|
|
119
|
+
lazy_import = LazyImport(module, name, verbose=verbose)
|
|
120
|
+
caller_globals[name] = lazy_import
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__all__ = ["import_lazy", "LazyImport"]
|
stdl/log.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from shutil import get_terminal_size
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from stdl.st import ForegroundColor, colored
|
|
6
|
+
|
|
7
|
+
IGNORE_BREAKS = False
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def color_tag(text: str, color: str) -> str:
|
|
11
|
+
return f"<{color}>{text}</{color}>"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LoguruFormatter:
|
|
15
|
+
time = color_tag("{time:YYYY-MM-DD HH:mm:ss}", "green")
|
|
16
|
+
level = color_tag("{level: <8}", "level")
|
|
17
|
+
msg = color_tag("{message:<24}", "level")
|
|
18
|
+
name = color_tag("{name}", "light-blue")
|
|
19
|
+
func = color_tag("{function}", "light-blue")
|
|
20
|
+
lineno = color_tag("{line}", "light-blue")
|
|
21
|
+
extra_key_skips: ClassVar[list[str]] = ["title"]
|
|
22
|
+
extra_key_name_color: ForegroundColor = "white"
|
|
23
|
+
|
|
24
|
+
def format(self, record: dict[str, Any]) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Example:
|
|
27
|
+
```python
|
|
28
|
+
>>> import sys
|
|
29
|
+
>>> from loguru import logger
|
|
30
|
+
>>> logger.remove()
|
|
31
|
+
>>> logger.add(sys.stdout, level="DEBUG", format=loguru_formater)
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
extras = ""
|
|
35
|
+
if len(record["extra"]):
|
|
36
|
+
for key in record["extra"].keys():
|
|
37
|
+
if key in self.extra_key_skips:
|
|
38
|
+
continue
|
|
39
|
+
extras = (
|
|
40
|
+
extras
|
|
41
|
+
+ colored(key, self.extra_key_name_color)
|
|
42
|
+
+ "="
|
|
43
|
+
+ "{extra["
|
|
44
|
+
+ key
|
|
45
|
+
+ "]}, "
|
|
46
|
+
)
|
|
47
|
+
extras = extras[:-2]
|
|
48
|
+
|
|
49
|
+
if title := record["extra"].get("title"):
|
|
50
|
+
return f"{self.time} | {self.level} | [ {title} ] {self.name}:{self.func}:{self.lineno} - {self.msg} {extras}\n"
|
|
51
|
+
return f"{self.time} | {self.level} | {self.name}:{self.func}:{self.lineno} - {self.msg} {extras}\n"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SimpleLoguruFormatter(LoguruFormatter):
|
|
55
|
+
def format(self, record: dict[str, Any]) -> str:
|
|
56
|
+
extras = ""
|
|
57
|
+
if len(record["extra"]):
|
|
58
|
+
for key in record["extra"].keys():
|
|
59
|
+
if key in self.extra_key_skips:
|
|
60
|
+
continue
|
|
61
|
+
extras = (
|
|
62
|
+
extras
|
|
63
|
+
+ colored(key, self.extra_key_name_color)
|
|
64
|
+
+ "="
|
|
65
|
+
+ "{extra["
|
|
66
|
+
+ key
|
|
67
|
+
+ "]}, "
|
|
68
|
+
)
|
|
69
|
+
extras = extras[:-2]
|
|
70
|
+
|
|
71
|
+
return f"{self.level} - {self.msg} {extras}\n"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
loguru_formater = LoguruFormatter().format
|
|
75
|
+
simple_loguru_formater = SimpleLoguruFormatter().format
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_logging_config(
|
|
79
|
+
format: str | None = None,
|
|
80
|
+
level: str = "INFO",
|
|
81
|
+
filename: str | None = None,
|
|
82
|
+
backup_count: int = 0,
|
|
83
|
+
disable_existing: bool = False,
|
|
84
|
+
console_handler: str = "logging.StreamHandler",
|
|
85
|
+
file_handler: str = "logging.handlers.TimedRotatingFileHandler",
|
|
86
|
+
format_style: str = "{",
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
format = (
|
|
89
|
+
format
|
|
90
|
+
or "{asctime} | {levelname:<8s} | [ {name} ] {module}.{funcName}:{lineno} - {message}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
config = {
|
|
94
|
+
"version": 1,
|
|
95
|
+
"disable_existing_loggers": disable_existing,
|
|
96
|
+
"formatters": {
|
|
97
|
+
"standard": {
|
|
98
|
+
"format": format,
|
|
99
|
+
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
100
|
+
"style": format_style,
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"handlers": {
|
|
104
|
+
"console": {
|
|
105
|
+
"class": console_handler,
|
|
106
|
+
"level": level,
|
|
107
|
+
"formatter": "standard",
|
|
108
|
+
"stream": "ext://sys.stdout",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
"root": {"level": level, "handlers": ["console"]},
|
|
112
|
+
}
|
|
113
|
+
if filename:
|
|
114
|
+
config["handlers"]["file"] = {
|
|
115
|
+
"class": file_handler,
|
|
116
|
+
"level": level,
|
|
117
|
+
"formatter": "standard",
|
|
118
|
+
"backupCount": backup_count,
|
|
119
|
+
"filename": filename,
|
|
120
|
+
}
|
|
121
|
+
config["root"]["handlers"].append("file")
|
|
122
|
+
|
|
123
|
+
return config
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def br(
|
|
127
|
+
c: str = "_",
|
|
128
|
+
length: int | None = None,
|
|
129
|
+
handler: Callable[[str], None] = print,
|
|
130
|
+
*,
|
|
131
|
+
newline: bool = False,
|
|
132
|
+
) -> None:
|
|
133
|
+
if IGNORE_BREAKS:
|
|
134
|
+
return
|
|
135
|
+
length = length or get_terminal_size().columns
|
|
136
|
+
line = c * length
|
|
137
|
+
if newline:
|
|
138
|
+
line += "\n"
|
|
139
|
+
handler(line)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = ["loguru_formater", "br", "get_logging_config", "LoguruFormatter"]
|
stdl/lst.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from collections import Counter
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from typing import Any, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def chunks(l: list[T], size: int) -> list[list[T]]:
|
|
9
|
+
"""
|
|
10
|
+
Splits a list into chunks of a specified size.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
l (list[T]): list to be split
|
|
14
|
+
size (int): Size of each chunk.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
list[list[T]] : a list containing the chunks.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError : if the size is less than 1 or greater than the length of the list
|
|
21
|
+
"""
|
|
22
|
+
if size < 1 or size > len(l):
|
|
23
|
+
raise ValueError(size)
|
|
24
|
+
return [l[i : i + size] for i in range(0, len(l), size)]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def split(l: list[T], parts: int) -> list[list[T]]:
|
|
28
|
+
"""
|
|
29
|
+
Splits a list into a specified number of parts
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
l (list[T]): List to be split
|
|
33
|
+
parts (int): Number of parts to split the list into.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list[list[T]] : a list containing the split parts.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError : if the parts is less than 1 or greater than the length of the list.
|
|
40
|
+
"""
|
|
41
|
+
if parts < 1 or parts > len(l):
|
|
42
|
+
raise ValueError(parts)
|
|
43
|
+
|
|
44
|
+
def inner(l: list[T], parts: int) -> list[list[T]]:
|
|
45
|
+
length = len(l)
|
|
46
|
+
si = length // parts
|
|
47
|
+
if length - si > 0:
|
|
48
|
+
return [l[:si], *inner(l[si:], parts - 1)]
|
|
49
|
+
else:
|
|
50
|
+
return [l]
|
|
51
|
+
|
|
52
|
+
return inner(l, parts)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def unique(l: Sequence[T]) -> list[T]:
|
|
56
|
+
"""
|
|
57
|
+
Filter out all non-unique values from a list.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
l (Sequence[T]): The list to filter.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
list[T]: A list of only unique values.
|
|
64
|
+
"""
|
|
65
|
+
return [i for i, count in Counter(l).items() if count == 1]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def non_unique(l: Sequence[T]) -> list[T]:
|
|
69
|
+
"""
|
|
70
|
+
Filter out all unique values from a list.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
l (Sequence[T]): The list to filter.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
list[T]: A list of only non-unique values.
|
|
77
|
+
"""
|
|
78
|
+
return [i for i, count in Counter(l).items() if count > 1]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def every_nth(l: list[T], n: int) -> list[T]:
|
|
82
|
+
"""
|
|
83
|
+
Return every nth element from a list
|
|
84
|
+
Args:
|
|
85
|
+
l (list[T]): The list to extract elements from.
|
|
86
|
+
n (int): The index to extract the element at
|
|
87
|
+
Returns:
|
|
88
|
+
list[T]: A list of every nth element
|
|
89
|
+
"""
|
|
90
|
+
return l[n - 1 :: n]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def occurrences(l: Sequence[T], val: Any) -> int:
|
|
94
|
+
"""
|
|
95
|
+
Count the number of occurrences of a value in a list
|
|
96
|
+
Args:
|
|
97
|
+
l (Sequence[T]): The list to count values in.
|
|
98
|
+
val (Any): The value to count
|
|
99
|
+
Returns:
|
|
100
|
+
int: Number of occurrences
|
|
101
|
+
"""
|
|
102
|
+
return len([i for i in l if i == val and type(i) is type(val)])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def nodups(l: Sequence[T]) -> list[T]:
|
|
106
|
+
"""Remove duplicates from a list and maintain the order"""
|
|
107
|
+
result: list[T] = []
|
|
108
|
+
for i in l:
|
|
109
|
+
if i not in result:
|
|
110
|
+
result.append(i)
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def replace_sublists(l: list[Any], sublist: list[Any], replacement: list[Any]) -> list[Any]:
|
|
115
|
+
"""
|
|
116
|
+
Replaces all occurrences of sublist `sublist` with `replacement` in `main`.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
l: The list in which to search for sublists.
|
|
120
|
+
sublist: The sublist to replace.
|
|
121
|
+
replacement: The sublist to replace `sublist` with.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> replace_all_sublist([1, 2, 3, 2, 3], [2, 3], [5])
|
|
125
|
+
[1, 5, 5]
|
|
126
|
+
"""
|
|
127
|
+
result = []
|
|
128
|
+
i = 0
|
|
129
|
+
len_old = len(sublist)
|
|
130
|
+
while i <= len(l) - len_old:
|
|
131
|
+
if l[i : i + len_old] == sublist:
|
|
132
|
+
result.extend(replacement)
|
|
133
|
+
i += len_old
|
|
134
|
+
else:
|
|
135
|
+
result.append(l[i])
|
|
136
|
+
i += 1
|
|
137
|
+
result.extend(l[i:])
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def contains_sublist(l: list[Any], sublist: list[Any]) -> bool:
|
|
142
|
+
"""
|
|
143
|
+
Checks if list contains a sublist.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
l: The list to search.
|
|
147
|
+
sublist: The sublist to search for.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
len_b = len(sublist)
|
|
151
|
+
for i in range(len(l) - len_b + 1):
|
|
152
|
+
if l[i : i + len_b] == sublist:
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = [
|
|
158
|
+
"unique",
|
|
159
|
+
"non_unique",
|
|
160
|
+
"every_nth",
|
|
161
|
+
"occurrences",
|
|
162
|
+
"chunks",
|
|
163
|
+
"split",
|
|
164
|
+
"replace_sublists",
|
|
165
|
+
"contains_sublist",
|
|
166
|
+
]
|
stdl/net.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import warnings
|
|
3
|
+
from http.client import HTTPMessage
|
|
4
|
+
from urllib.request import urlopen, urlretrieve
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
except ImportError: # pragma: no cover - optional dependency
|
|
9
|
+
tqdm = None
|
|
10
|
+
|
|
11
|
+
from stdl.fs import bytes_readable, readable_size_to_bytes
|
|
12
|
+
|
|
13
|
+
if tqdm is not None:
|
|
14
|
+
|
|
15
|
+
class ProgressBarTQDM(tqdm):
|
|
16
|
+
def update_to(self, b: int = 1, bsize: int = 1, tsize: int | None = None) -> None:
|
|
17
|
+
if tsize is not None:
|
|
18
|
+
self.total = tsize
|
|
19
|
+
self.update(b * bsize - self.n)
|
|
20
|
+
else:
|
|
21
|
+
ProgressBarTQDM = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DownloadSizeExceededError(Exception):
|
|
25
|
+
def __init__(self, filesize: int, maxsize: int):
|
|
26
|
+
self.filesize = filesize
|
|
27
|
+
self.maxsize = maxsize
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
return f"File size {self.filesize} ({bytes_readable(self.filesize)}) exceeds maximum size limit of {self.maxsize} bytes ({bytes_readable(self.maxsize)})"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def download(
|
|
34
|
+
url: str,
|
|
35
|
+
path: str,
|
|
36
|
+
*,
|
|
37
|
+
maxsize: int | str | None = None,
|
|
38
|
+
progressbar: bool = False,
|
|
39
|
+
overwrite: bool = False,
|
|
40
|
+
) -> tuple[str, HTTPMessage]:
|
|
41
|
+
"""
|
|
42
|
+
Download a file
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
url (str): File URL
|
|
46
|
+
path (str): Save path
|
|
47
|
+
maxsize (int | str | None, optional): Maximum file size in bytes or human readable format.
|
|
48
|
+
progressbar (bool, optional): Display progress bar in console.
|
|
49
|
+
overwrite (bool, optional): Overwrite destination path if it already exists.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
FileExistsError: if path already exists and overwrite is set to False
|
|
53
|
+
DownloadSizeExceededError: if file size exceeds maxsize
|
|
54
|
+
"""
|
|
55
|
+
if maxsize is not None:
|
|
56
|
+
if isinstance(maxsize, str):
|
|
57
|
+
maxsize = readable_size_to_bytes(maxsize)
|
|
58
|
+
filesize = int(urlopen(url).headers.get("Content-Length", 0))
|
|
59
|
+
if filesize > maxsize:
|
|
60
|
+
raise DownloadSizeExceededError(filesize, maxsize)
|
|
61
|
+
|
|
62
|
+
if os.path.exists(path) and not overwrite:
|
|
63
|
+
raise FileExistsError(path)
|
|
64
|
+
|
|
65
|
+
if progressbar and ProgressBarTQDM is not None:
|
|
66
|
+
with ProgressBarTQDM(unit="B", unit_scale=True, unit_divisor=1024) as t:
|
|
67
|
+
r = urlretrieve(url, path, reporthook=t.update_to, data=None)
|
|
68
|
+
t.total = t.n
|
|
69
|
+
else:
|
|
70
|
+
if progressbar and ProgressBarTQDM is None:
|
|
71
|
+
warnings.warn(
|
|
72
|
+
"progressbar=True requested but tqdm is not installed; downloading without progress bar.",
|
|
73
|
+
RuntimeWarning,
|
|
74
|
+
stacklevel=2,
|
|
75
|
+
)
|
|
76
|
+
r = urlretrieve(url, path)
|
|
77
|
+
return r
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = ["download"]
|