log21 3.1.0__tar.gz → 3.3.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.
- {log21-3.1.0 → log21-3.3.0}/PKG-INFO +33 -116
- {log21-3.1.0 → log21-3.3.0}/README.md +32 -115
- {log21-3.1.0 → log21-3.3.0}/pyproject.toml +1 -1
- {log21-3.1.0 → log21-3.3.0}/src/log21/__init__.py +20 -8
- log21-3.3.0/src/log21/helper_types.py +126 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/_argparse.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/_module_helper.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/argparse.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/argumentify.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/colors.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/crash_reporter/__init__.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/crash_reporter/formatters.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/crash_reporter/reporters.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/file_handler.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/formatters.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/levels.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/logger.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/logging_window.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/manager.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/pprint.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/progress_bar.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/stream_handler.py +0 -0
- {log21-3.1.0 → log21-3.3.0}/src/log21/tree_print.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: log21
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: A simple logging package
|
|
5
5
|
Keywords: python,log,colorize,color,logging,Python3,CodeWriter21
|
|
6
6
|
Author: CodeWriter21(Mehrad Pooryoussof)
|
|
@@ -94,140 +94,57 @@ pip install git+https://github.com/MPCodeWriter21/log21
|
|
|
94
94
|
Changelog
|
|
95
95
|
---------
|
|
96
96
|
|
|
97
|
-
### v3.
|
|
97
|
+
### v3.3.0
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
conversion.
|
|
99
|
+
Add `log21.helper_types` module.
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
+ `POSITIONAL_OR_KEYWORD` parameters will be required if at least one `KEYWORD_ONLY`
|
|
105
|
-
parameter is there, otherwise they are optional.
|
|
106
|
-
+ `VAR_KEYWORD` parameters are still not supported.
|
|
101
|
+
This module contains a collection of useful types meant for using with argument parser
|
|
102
|
+
to parse CLI arguments to more usable formats.
|
|
107
103
|
|
|
108
|
-
|
|
104
|
+
+ `FileSize`: Can take `str` and `int` values. Will convert human inputs such as "121 KB",
|
|
105
|
+
"21MiB", or "4.56 GB" to bytes. Can also be used to represent bytes value in more
|
|
106
|
+
human-readable formats.
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
def main(path: Path, /, output: Path, *, verbose: bool = False):
|
|
112
|
-
"""Process a file.
|
|
113
|
-
|
|
114
|
-
:param path: The input file path
|
|
115
|
-
:param output: The output file
|
|
116
|
-
:param verbose: Write more logs to the standard output.
|
|
117
|
-
"""
|
|
118
|
-
...
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if __name__ == "__main__":
|
|
122
|
-
argumentify(main)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
The help looks like this:
|
|
126
|
-
|
|
127
|
-
```help
|
|
128
|
-
usage: test.py [-h] --output OUTPUT [--verbose] path
|
|
129
|
-
|
|
130
|
-
Process a file.
|
|
131
|
-
|
|
132
|
-
positional arguments:
|
|
133
|
-
path The input file path
|
|
134
|
-
|
|
135
|
-
options:
|
|
136
|
-
-h, --help
|
|
137
|
-
show this help message and exit
|
|
138
|
-
--output OUTPUT, -o OUTPUT
|
|
139
|
-
The output file
|
|
140
|
-
--verbose, -v
|
|
141
|
-
Write more logs to the standard output.
|
|
142
|
-
|
|
143
|
-
```
|
|
108
|
+
For even more control you can still define Logger, Handlers, and Formatters manually.
|
|
144
109
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
#### Example 2
|
|
110
|
+
#### Example
|
|
148
111
|
|
|
149
112
|
```python
|
|
150
|
-
|
|
151
|
-
"""Process multiple files into one.
|
|
152
|
-
|
|
153
|
-
:param output: The output file
|
|
154
|
-
:param inputs: The path to the input files
|
|
155
|
-
"""
|
|
156
|
-
# Since `inputs` is a VAR_POSITIONAL, while being a positional argument, it can have
|
|
157
|
-
# zero length which is in many cases not intended.
|
|
158
|
-
# You might want to add a check for its length and raise an ArgumentError if it does
|
|
159
|
-
# not match your needs
|
|
160
|
-
|
|
161
|
-
# Check if at least one input has been passed and mark the argument as required
|
|
162
|
-
# if len(inputs) < 1:
|
|
163
|
-
# raise RequiredArgumentError("inputs")
|
|
164
|
-
|
|
165
|
-
# Raise an error unless at least two inputs are present
|
|
166
|
-
if len(inputs) < 2:
|
|
167
|
-
raise ArgumentError(message="You need to pass at least two files as input.")
|
|
168
|
-
...
|
|
169
|
-
```
|
|
113
|
+
from pathlib import Path
|
|
170
114
|
|
|
171
|
-
|
|
115
|
+
import log21
|
|
116
|
+
from log21.helper_types import FileSize
|
|
172
117
|
|
|
173
|
-
```help
|
|
174
|
-
usage: test.py [-h] output [inputs ...]
|
|
175
118
|
|
|
176
|
-
|
|
119
|
+
def main(path: Path, min_size: FileSize, max_size: FileSize, /):
|
|
120
|
+
log21.info(
|
|
121
|
+
"Files that are smaller than %s or bigger than %s will be ignored.",
|
|
122
|
+
args=(min_size, max_size),
|
|
123
|
+
)
|
|
177
124
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
#### Example 3
|
|
189
|
-
|
|
190
|
-
```python
|
|
191
|
-
def main(first_name: str, last_name: str, output: Path, verbose: bool = False):
|
|
192
|
-
"""Write a greeting message.
|
|
193
|
-
|
|
194
|
-
:param first_name: The first name of the user to greet (optional)
|
|
195
|
-
:param last_name: The last name of the user to greet (optional)
|
|
196
|
-
:param output: The output file (stdout if none is provided)
|
|
197
|
-
:param verbose: If provided, will write the debug logs to stdout
|
|
198
|
-
"""
|
|
199
|
-
...
|
|
125
|
+
for file in path.iterdir():
|
|
126
|
+
if not file.is_file():
|
|
127
|
+
continue
|
|
128
|
+
if min_size <= (size := file.stat().st_size) <= max_size:
|
|
129
|
+
log21.print(
|
|
130
|
+
"`%s` is %s.",
|
|
131
|
+
args=(file, FileSize(size).humanize(binary=False, fmt="%.4f")),
|
|
132
|
+
)
|
|
200
133
|
|
|
201
134
|
|
|
202
135
|
if __name__ == "__main__":
|
|
203
|
-
argumentify(main)
|
|
136
|
+
log21.argumentify(main)
|
|
204
137
|
```
|
|
205
138
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```help
|
|
209
|
-
usage: test.py [-h] [--first-name FIRST_NAME] [--last-name LAST_NAME] [--output OUTPUT]
|
|
210
|
-
[--verbose]
|
|
211
|
-
|
|
212
|
-
Write a greeting message.
|
|
213
|
-
|
|
214
|
-
options:
|
|
215
|
-
-h, --help
|
|
216
|
-
show this help message and exit
|
|
217
|
-
--first-name FIRST_NAME, -f FIRST_NAME
|
|
218
|
-
The first name of the user to greet (optional)
|
|
219
|
-
--last-name LAST_NAME, -l LAST_NAME
|
|
220
|
-
The last name of the user to greet (optional)
|
|
221
|
-
--output OUTPUT, -o OUTPUT
|
|
222
|
-
The output file (stdout if none is provided)
|
|
223
|
-
--verbose, -v
|
|
224
|
-
If provided, will write the debug logs to stdout
|
|
139
|
+
Example usage and output:
|
|
225
140
|
|
|
141
|
+
```shell
|
|
142
|
+
$ uv run test.py . "1.23MiB" "0.5 GB"
|
|
143
|
+
[21:21:21] [INFO] Files that are smaller than 1.23 MiB or bigger than 476.84 MiB will be
|
|
144
|
+
ignored.
|
|
145
|
+
`myfile21.zip` is 35.1856 MB.
|
|
226
146
|
```
|
|
227
147
|
|
|
228
|
-
_Note that all the options are optional and default to None. `verbose` is False by
|
|
229
|
-
default since a default value is provided for it in function definition._
|
|
230
|
-
|
|
231
148
|
[Full CHANGELOG](https://github.com/MPCodeWriter21/log21/blob/master/CHANGELOG.md)
|
|
232
149
|
|
|
233
150
|
Usage Examples
|
|
@@ -69,140 +69,57 @@ pip install git+https://github.com/MPCodeWriter21/log21
|
|
|
69
69
|
Changelog
|
|
70
70
|
---------
|
|
71
71
|
|
|
72
|
-
### v3.
|
|
72
|
+
### v3.3.0
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
conversion.
|
|
74
|
+
Add `log21.helper_types` module.
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
+ `POSITIONAL_OR_KEYWORD` parameters will be required if at least one `KEYWORD_ONLY`
|
|
80
|
-
parameter is there, otherwise they are optional.
|
|
81
|
-
+ `VAR_KEYWORD` parameters are still not supported.
|
|
76
|
+
This module contains a collection of useful types meant for using with argument parser
|
|
77
|
+
to parse CLI arguments to more usable formats.
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
+ `FileSize`: Can take `str` and `int` values. Will convert human inputs such as "121 KB",
|
|
80
|
+
"21MiB", or "4.56 GB" to bytes. Can also be used to represent bytes value in more
|
|
81
|
+
human-readable formats.
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
def main(path: Path, /, output: Path, *, verbose: bool = False):
|
|
87
|
-
"""Process a file.
|
|
88
|
-
|
|
89
|
-
:param path: The input file path
|
|
90
|
-
:param output: The output file
|
|
91
|
-
:param verbose: Write more logs to the standard output.
|
|
92
|
-
"""
|
|
93
|
-
...
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if __name__ == "__main__":
|
|
97
|
-
argumentify(main)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
The help looks like this:
|
|
101
|
-
|
|
102
|
-
```help
|
|
103
|
-
usage: test.py [-h] --output OUTPUT [--verbose] path
|
|
104
|
-
|
|
105
|
-
Process a file.
|
|
106
|
-
|
|
107
|
-
positional arguments:
|
|
108
|
-
path The input file path
|
|
109
|
-
|
|
110
|
-
options:
|
|
111
|
-
-h, --help
|
|
112
|
-
show this help message and exit
|
|
113
|
-
--output OUTPUT, -o OUTPUT
|
|
114
|
-
The output file
|
|
115
|
-
--verbose, -v
|
|
116
|
-
Write more logs to the standard output.
|
|
117
|
-
|
|
118
|
-
```
|
|
83
|
+
For even more control you can still define Logger, Handlers, and Formatters manually.
|
|
119
84
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
#### Example 2
|
|
85
|
+
#### Example
|
|
123
86
|
|
|
124
87
|
```python
|
|
125
|
-
|
|
126
|
-
"""Process multiple files into one.
|
|
127
|
-
|
|
128
|
-
:param output: The output file
|
|
129
|
-
:param inputs: The path to the input files
|
|
130
|
-
"""
|
|
131
|
-
# Since `inputs` is a VAR_POSITIONAL, while being a positional argument, it can have
|
|
132
|
-
# zero length which is in many cases not intended.
|
|
133
|
-
# You might want to add a check for its length and raise an ArgumentError if it does
|
|
134
|
-
# not match your needs
|
|
135
|
-
|
|
136
|
-
# Check if at least one input has been passed and mark the argument as required
|
|
137
|
-
# if len(inputs) < 1:
|
|
138
|
-
# raise RequiredArgumentError("inputs")
|
|
139
|
-
|
|
140
|
-
# Raise an error unless at least two inputs are present
|
|
141
|
-
if len(inputs) < 2:
|
|
142
|
-
raise ArgumentError(message="You need to pass at least two files as input.")
|
|
143
|
-
...
|
|
144
|
-
```
|
|
88
|
+
from pathlib import Path
|
|
145
89
|
|
|
146
|
-
|
|
90
|
+
import log21
|
|
91
|
+
from log21.helper_types import FileSize
|
|
147
92
|
|
|
148
|
-
```help
|
|
149
|
-
usage: test.py [-h] output [inputs ...]
|
|
150
93
|
|
|
151
|
-
|
|
94
|
+
def main(path: Path, min_size: FileSize, max_size: FileSize, /):
|
|
95
|
+
log21.info(
|
|
96
|
+
"Files that are smaller than %s or bigger than %s will be ignored.",
|
|
97
|
+
args=(min_size, max_size),
|
|
98
|
+
)
|
|
152
99
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
#### Example 3
|
|
164
|
-
|
|
165
|
-
```python
|
|
166
|
-
def main(first_name: str, last_name: str, output: Path, verbose: bool = False):
|
|
167
|
-
"""Write a greeting message.
|
|
168
|
-
|
|
169
|
-
:param first_name: The first name of the user to greet (optional)
|
|
170
|
-
:param last_name: The last name of the user to greet (optional)
|
|
171
|
-
:param output: The output file (stdout if none is provided)
|
|
172
|
-
:param verbose: If provided, will write the debug logs to stdout
|
|
173
|
-
"""
|
|
174
|
-
...
|
|
100
|
+
for file in path.iterdir():
|
|
101
|
+
if not file.is_file():
|
|
102
|
+
continue
|
|
103
|
+
if min_size <= (size := file.stat().st_size) <= max_size:
|
|
104
|
+
log21.print(
|
|
105
|
+
"`%s` is %s.",
|
|
106
|
+
args=(file, FileSize(size).humanize(binary=False, fmt="%.4f")),
|
|
107
|
+
)
|
|
175
108
|
|
|
176
109
|
|
|
177
110
|
if __name__ == "__main__":
|
|
178
|
-
argumentify(main)
|
|
111
|
+
log21.argumentify(main)
|
|
179
112
|
```
|
|
180
113
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```help
|
|
184
|
-
usage: test.py [-h] [--first-name FIRST_NAME] [--last-name LAST_NAME] [--output OUTPUT]
|
|
185
|
-
[--verbose]
|
|
186
|
-
|
|
187
|
-
Write a greeting message.
|
|
188
|
-
|
|
189
|
-
options:
|
|
190
|
-
-h, --help
|
|
191
|
-
show this help message and exit
|
|
192
|
-
--first-name FIRST_NAME, -f FIRST_NAME
|
|
193
|
-
The first name of the user to greet (optional)
|
|
194
|
-
--last-name LAST_NAME, -l LAST_NAME
|
|
195
|
-
The last name of the user to greet (optional)
|
|
196
|
-
--output OUTPUT, -o OUTPUT
|
|
197
|
-
The output file (stdout if none is provided)
|
|
198
|
-
--verbose, -v
|
|
199
|
-
If provided, will write the debug logs to stdout
|
|
114
|
+
Example usage and output:
|
|
200
115
|
|
|
116
|
+
```shell
|
|
117
|
+
$ uv run test.py . "1.23MiB" "0.5 GB"
|
|
118
|
+
[21:21:21] [INFO] Files that are smaller than 1.23 MiB or bigger than 476.84 MiB will be
|
|
119
|
+
ignored.
|
|
120
|
+
`myfile21.zip` is 35.1856 MB.
|
|
201
121
|
```
|
|
202
122
|
|
|
203
|
-
_Note that all the options are optional and default to None. `verbose` is False by
|
|
204
|
-
default since a default value is provided for it in function definition._
|
|
205
|
-
|
|
206
123
|
[Full CHANGELOG](https://github.com/MPCodeWriter21/log21/blob/master/CHANGELOG.md)
|
|
207
124
|
|
|
208
125
|
Usage Examples
|
|
@@ -10,6 +10,8 @@ from types import ModuleType as _ModuleType
|
|
|
10
10
|
from typing import (Type as _Type, Union as _Union, Literal as _Literal,
|
|
11
11
|
Mapping as _Mapping, Iterable as _Iterable, Optional as _Optional)
|
|
12
12
|
|
|
13
|
+
import log21.helper_types
|
|
14
|
+
|
|
13
15
|
from . import crash_reporter
|
|
14
16
|
from .colors import (Colors, get_color, get_colors, ansi_escape, closest_color,
|
|
15
17
|
get_color_name)
|
|
@@ -31,7 +33,7 @@ from .stream_handler import StreamHandler, ColorizingStreamHandler
|
|
|
31
33
|
# yapf: enable
|
|
32
34
|
|
|
33
35
|
__author__ = 'CodeWriter21 (Mehrad Pooryoussof)'
|
|
34
|
-
__version__ = '3.
|
|
36
|
+
__version__ = '3.3.0'
|
|
35
37
|
__github__ = 'https://GitHub.com/MPCodeWriter21/log21'
|
|
36
38
|
__all__ = [
|
|
37
39
|
'ColorizingStreamHandler', 'DecolorizingFileHandler', 'ColorizingFormatter',
|
|
@@ -44,7 +46,7 @@ __all__ = [
|
|
|
44
46
|
'log', 'basic_config', 'basicConfig', 'ProgressBar', 'LoggingWindow',
|
|
45
47
|
'LoggingWindowHandler', 'get_logging_window', 'crash_reporter', 'console_reporter',
|
|
46
48
|
'file_reporter', 'argumentify', 'ArgumentError', 'IncompatibleArgumentsError',
|
|
47
|
-
'RequiredArgumentError', 'TooFewArgumentsError'
|
|
49
|
+
'RequiredArgumentError', 'TooFewArgumentsError', 'FileHandler'
|
|
48
50
|
]
|
|
49
51
|
|
|
50
52
|
_manager = Manager()
|
|
@@ -60,7 +62,8 @@ def _prepare_formatter(
|
|
|
60
62
|
colorize_time_and_level: bool = True,
|
|
61
63
|
level_names: _Optional[_Mapping[int, str]] = None,
|
|
62
64
|
level_colors: _Optional[_Mapping[int, tuple[str, ...]]] = None,
|
|
63
|
-
formatter_class: _Type[_logging.Formatter] = ColorizingFormatter
|
|
65
|
+
formatter_class: _Type[_logging.Formatter] = ColorizingFormatter,
|
|
66
|
+
prefix_carriage_return: bool = True,
|
|
64
67
|
) -> _logging.Formatter:
|
|
65
68
|
# Prepares a formatting if the fmt was None
|
|
66
69
|
if not fmt:
|
|
@@ -70,7 +73,8 @@ def _prepare_formatter(
|
|
|
70
73
|
fmt = '[%(levelname)s] ' + fmt
|
|
71
74
|
if show_time:
|
|
72
75
|
fmt = '[%(asctime)s] ' + fmt
|
|
73
|
-
|
|
76
|
+
if prefix_carriage_return:
|
|
77
|
+
fmt = '\r' + fmt
|
|
74
78
|
|
|
75
79
|
if level_colors and not issubclass(formatter_class, ColorizingFormatter):
|
|
76
80
|
warning(
|
|
@@ -116,7 +120,10 @@ def get_logger(
|
|
|
116
120
|
override: bool = False,
|
|
117
121
|
level_names: _Optional[_Mapping[int, str]] = None,
|
|
118
122
|
level_colors: _Optional[_Mapping[int, tuple[str, ...]]] = None,
|
|
119
|
-
|
|
123
|
+
# TODO: Rename file to file_path in one future update
|
|
124
|
+
file: _Optional[_Union[_os.PathLike, str]] = None,
|
|
125
|
+
file_mode: _Optional[str] = None,
|
|
126
|
+
file_encoding: _Optional[str] = None,
|
|
120
127
|
) -> Logger:
|
|
121
128
|
"""Returns a logging.Logger with colorizing support.
|
|
122
129
|
|
|
@@ -182,7 +189,9 @@ def get_logger(
|
|
|
182
189
|
:param level_names: Mapping[int, str] = None: You can specify custom level names.
|
|
183
190
|
:param level_colors: Mapping[int, Tuple[str, ...]] = None: You can specify custom
|
|
184
191
|
level colors.
|
|
185
|
-
:param file: Union[os.PathLike, str] = None: The file to log to
|
|
192
|
+
:param file: Union[os.PathLike, str] = None: The file path to log to
|
|
193
|
+
:param file_mode: str = None: The mode to open file at (Defaults to 'a')
|
|
194
|
+
:param file_encoding: str = None: The file encoding
|
|
186
195
|
:return: log21.Logger
|
|
187
196
|
"""
|
|
188
197
|
if not isinstance(name, str):
|
|
@@ -209,7 +218,9 @@ def get_logger(
|
|
|
209
218
|
_manager.addLogger(name, logger)
|
|
210
219
|
|
|
211
220
|
if file:
|
|
212
|
-
file_handler =
|
|
221
|
+
file_handler = DecolorizingFileHandler(
|
|
222
|
+
file, mode=file_mode or 'a', encoding=file_encoding
|
|
223
|
+
)
|
|
213
224
|
file_formatter = _prepare_formatter(
|
|
214
225
|
fmt,
|
|
215
226
|
style,
|
|
@@ -218,7 +229,8 @@ def get_logger(
|
|
|
218
229
|
show_time,
|
|
219
230
|
False,
|
|
220
231
|
level_names,
|
|
221
|
-
formatter_class=DecolorizingFormatter
|
|
232
|
+
formatter_class=DecolorizingFormatter,
|
|
233
|
+
prefix_carriage_return=False,
|
|
222
234
|
)
|
|
223
235
|
file_handler.setFormatter(file_formatter)
|
|
224
236
|
logger.addHandler(file_handler)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# log21.helper_types.py
|
|
2
|
+
# CodeWriter21
|
|
3
|
+
"""A collection of useful types meant for using with argument parser to parse CLI
|
|
4
|
+
arguments to more usable formats.
|
|
5
|
+
|
|
6
|
+
+ FileSize: Can take `str` and `int` values. Will convert human inputs such as "121 KB",
|
|
7
|
+
"21MiB", or "4.56 GB" to bytes. Can also be used to represent bytes value in more
|
|
8
|
+
human-readable formats.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# yapf: disable
|
|
12
|
+
|
|
13
|
+
import re as _re
|
|
14
|
+
from math import log as _log
|
|
15
|
+
from typing import Union as _Union, SupportsInt as _SupportsInt
|
|
16
|
+
|
|
17
|
+
# yapf: enable
|
|
18
|
+
|
|
19
|
+
__all__ = ["FileSize"]
|
|
20
|
+
|
|
21
|
+
POWERS = "KMGTPEZYRQ"
|
|
22
|
+
FILE_SIZE_PATTERN = _re.compile(rf"^([+-]?[0-9]+(?:\.[0-9]+)?)\s*(|[{POWERS}])(i?)B$")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FileSize:
|
|
26
|
+
|
|
27
|
+
def __init__(self, value: _Union[int, str]) -> None:
|
|
28
|
+
"""An interface for converting different inputs to file-size (bytes).
|
|
29
|
+
|
|
30
|
+
:param value: int value in bytes or a string such as "100 KB", "20MiB", or "1.23
|
|
31
|
+
GB"
|
|
32
|
+
:raises TypeError: If the value is not of type int or str
|
|
33
|
+
:raises ValueError: If the str value does not match the file-size pattern:
|
|
34
|
+
^([+-]?[0-9]+(?:\\.[0-9]+)?)\\s*(|[KMGTPEZYRQ])(i?)B$
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(value, int):
|
|
37
|
+
self.bytes = value
|
|
38
|
+
elif isinstance(value, str):
|
|
39
|
+
match = FILE_SIZE_PATTERN.match(value)
|
|
40
|
+
if not match:
|
|
41
|
+
raise ValueError(f"Input does not match the file-size pattern: {value}")
|
|
42
|
+
val, prefix, binary = match.groups()
|
|
43
|
+
power = POWERS.index(prefix) + 1
|
|
44
|
+
assert power is not None
|
|
45
|
+
self.bytes = int(float(val) * (1024 if binary else 1000)**power)
|
|
46
|
+
else:
|
|
47
|
+
raise TypeError(f"Input to FileSize() can be int or str, not {type(value)}")
|
|
48
|
+
|
|
49
|
+
def humanize(
|
|
50
|
+
self,
|
|
51
|
+
binary: bool = False,
|
|
52
|
+
gnu: bool = False,
|
|
53
|
+
fmt: str = "%.2f",
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Returns the size in a human readable way."""
|
|
56
|
+
base = 1024 if (gnu or binary) else 1000
|
|
57
|
+
abs_bytes = abs(self.bytes)
|
|
58
|
+
|
|
59
|
+
if abs_bytes == 1 and not gnu:
|
|
60
|
+
return f"{self.bytes} Byte"
|
|
61
|
+
|
|
62
|
+
if abs_bytes < base:
|
|
63
|
+
return f"{self.bytes}B" if gnu else f"{self.bytes} Bytes"
|
|
64
|
+
|
|
65
|
+
power = int(min(_log(abs_bytes, base), len(POWERS)))
|
|
66
|
+
result: str = fmt % (self.bytes / (base**power))
|
|
67
|
+
if gnu:
|
|
68
|
+
return result + POWERS[power - 1]
|
|
69
|
+
result += " " + POWERS[power - 1]
|
|
70
|
+
if binary:
|
|
71
|
+
result += "i"
|
|
72
|
+
result += "B"
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def KB(self) -> float:
|
|
77
|
+
return self.bytes / 1000
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def MB(self) -> float:
|
|
81
|
+
return self.bytes / 1000_000
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def GB(self) -> float:
|
|
85
|
+
return self.bytes / 1000_000_000
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def KiB(self) -> float:
|
|
89
|
+
return self.bytes / 1024
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def MiB(self) -> float:
|
|
93
|
+
return self.bytes / 1048576
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def GiB(self) -> float:
|
|
97
|
+
return self.bytes / 1073741824
|
|
98
|
+
|
|
99
|
+
def __int__(self) -> int:
|
|
100
|
+
return self.bytes
|
|
101
|
+
|
|
102
|
+
def __eq__(self, value: object) -> bool:
|
|
103
|
+
if not isinstance(value, _SupportsInt):
|
|
104
|
+
return False
|
|
105
|
+
return self.bytes == int(value)
|
|
106
|
+
|
|
107
|
+
def __lt__(self, value: _SupportsInt) -> bool:
|
|
108
|
+
return self.bytes < int(value)
|
|
109
|
+
|
|
110
|
+
def __le__(self, value: _SupportsInt) -> bool:
|
|
111
|
+
return self.bytes <= int(value)
|
|
112
|
+
|
|
113
|
+
def __gt__(self, value: _SupportsInt) -> bool:
|
|
114
|
+
return int(value) < self.bytes
|
|
115
|
+
|
|
116
|
+
def __ge__(self, value: _SupportsInt) -> bool:
|
|
117
|
+
return int(value) <= self.bytes
|
|
118
|
+
|
|
119
|
+
def __add__(self, value: _SupportsInt) -> "FileSize":
|
|
120
|
+
return FileSize(self.bytes + int(value))
|
|
121
|
+
|
|
122
|
+
def __str__(self) -> str:
|
|
123
|
+
return self.humanize(binary=True)
|
|
124
|
+
|
|
125
|
+
def __repr__(self) -> str:
|
|
126
|
+
return f"<{self.__class__.__name__}: '{self!s}'>"
|
|
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
|