log21 3.2.0__tar.gz → 3.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: log21
3
- Version: 3.2.0
3
+ Version: 3.3.1
4
4
  Summary: A simple logging package
5
5
  Keywords: python,log,colorize,color,logging,Python3,CodeWriter21
6
6
  Author: CodeWriter21(Mehrad Pooryoussof)
@@ -94,24 +94,45 @@ pip install git+https://github.com/MPCodeWriter21/log21
94
94
  Changelog
95
95
  ---------
96
96
 
97
- ### v3.2.0
97
+ ### v3.3.1
98
98
 
99
- Add `file_mode` and `file_encoding` parameters to `get_logger` for finer control over
100
- the way a simple logger handles files.
99
+ Get rid of `invalid NoneType value` error message.
101
100
 
102
- For even more control you can still define Logger, Handlers, and Formatters manually.
101
+ In the previous versions, if you used an optional union type such as `int | float | None`
102
+ which ended with `None`, you'd get an error saying `invalid NoneType value` that didn't
103
+ make any sense to the user. The code has been updated to use the name of the last
104
+ non-None type for the error message in these situations.
103
105
 
104
106
  #### Example
105
107
 
106
108
  ```python
107
109
  import log21
110
+ from log21.helper_types import FileSize
108
111
 
109
- logger = log21.get_logger(
110
- "My File Logger", show_level=False, show_time=True, file="myapp.log", file_mode="a",
111
- file_encoding="utf-8"
112
- )
113
112
 
114
- logger.info("Hello World!")
113
+ def main(min_size: FileSize | None = None, max_size: FileSize | None = None) -> None:
114
+ log21.info("Min Size: %s, Max Size: %s", args=(min_size, max_size))
115
+
116
+ if __name__ == "__main__":
117
+ log21.argumentify(main)
118
+ ```
119
+
120
+ Before v3.3.1:
121
+
122
+ ```shell
123
+ $ python test.py -m Hello
124
+ usage: test.py [-h] [--min-size MIN_SIZE] [--max-size MAX_SIZE]
125
+
126
+ test.py: error: argument --min-size/-m: invalid NoneType value: 'Hello'
127
+ ```
128
+
129
+ With v3.3.1 update:
130
+
131
+ ```shell
132
+ $ python test.py -m Hello
133
+ usage: test.py [-h] [--min-size MIN_SIZE] [--max-size MAX_SIZE]
134
+
135
+ test.py: error: argument --min-size/-m: invalid FileSize value: 'Hello'
115
136
  ```
116
137
 
117
138
  [Full CHANGELOG](https://github.com/MPCodeWriter21/log21/blob/master/CHANGELOG.md)
@@ -69,24 +69,45 @@ pip install git+https://github.com/MPCodeWriter21/log21
69
69
  Changelog
70
70
  ---------
71
71
 
72
- ### v3.2.0
72
+ ### v3.3.1
73
73
 
74
- Add `file_mode` and `file_encoding` parameters to `get_logger` for finer control over
75
- the way a simple logger handles files.
74
+ Get rid of `invalid NoneType value` error message.
76
75
 
77
- For even more control you can still define Logger, Handlers, and Formatters manually.
76
+ In the previous versions, if you used an optional union type such as `int | float | None`
77
+ which ended with `None`, you'd get an error saying `invalid NoneType value` that didn't
78
+ make any sense to the user. The code has been updated to use the name of the last
79
+ non-None type for the error message in these situations.
78
80
 
79
81
  #### Example
80
82
 
81
83
  ```python
82
84
  import log21
85
+ from log21.helper_types import FileSize
83
86
 
84
- logger = log21.get_logger(
85
- "My File Logger", show_level=False, show_time=True, file="myapp.log", file_mode="a",
86
- file_encoding="utf-8"
87
- )
88
87
 
89
- logger.info("Hello World!")
88
+ def main(min_size: FileSize | None = None, max_size: FileSize | None = None) -> None:
89
+ log21.info("Min Size: %s, Max Size: %s", args=(min_size, max_size))
90
+
91
+ if __name__ == "__main__":
92
+ log21.argumentify(main)
93
+ ```
94
+
95
+ Before v3.3.1:
96
+
97
+ ```shell
98
+ $ python test.py -m Hello
99
+ usage: test.py [-h] [--min-size MIN_SIZE] [--max-size MAX_SIZE]
100
+
101
+ test.py: error: argument --min-size/-m: invalid NoneType value: 'Hello'
102
+ ```
103
+
104
+ With v3.3.1 update:
105
+
106
+ ```shell
107
+ $ python test.py -m Hello
108
+ usage: test.py [-h] [--min-size MIN_SIZE] [--max-size MAX_SIZE]
109
+
110
+ test.py: error: argument --min-size/-m: invalid FileSize value: 'Hello'
90
111
  ```
91
112
 
92
113
  [Full CHANGELOG](https://github.com/MPCodeWriter21/log21/blob/master/CHANGELOG.md)
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "webcolors",
24
24
  "docstring-parser"
25
25
  ]
26
- version = "3.2.0"
26
+ version = "3.3.1"
27
27
 
28
28
  [build-system]
29
29
  requires = ["uv_build>=0.8.15,<0.9.0"]
@@ -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.2.0'
36
+ __version__ = '3.3.1'
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()
@@ -784,7 +784,8 @@ class ColorizingArgumentParser(_argparse.ArgumentParser, _ActionsContainer):
784
784
  else:
785
785
  exception = ValueError()
786
786
  for type_ in func_type:
787
- name = getattr(type_, '__name__', repr(type_))
787
+ if type_ is not type(None):
788
+ name = getattr(type_, '__name__', repr(type_))
788
789
  try:
789
790
  result = type_(arg_string)
790
791
  break
@@ -451,7 +451,7 @@ def _argumentify(functions: _Dict[str, Callable]) -> None:
451
451
  args = []
452
452
  kwargs = {}
453
453
  info = None
454
- for name, (function, info) in functions_info.items(): # noqa: B007
454
+ for _name, (function, info) in functions_info.items(): # noqa: B007
455
455
  if function == cli_args.func:
456
456
  break
457
457
  else:
@@ -475,7 +475,7 @@ def _argumentify(functions: _Dict[str, Callable]) -> None:
475
475
 
476
476
  def argumentify(
477
477
  entry_point: _Union[Callable, _List[Callable], _Dict[str, Callable]]
478
- ) -> _Callable:
478
+ ) -> _Union[Callable, _List[Callable], _Dict[str, Callable]]:
479
479
  """This function argumentifies one or more functions as the entry point of the
480
480
  script.
481
481
 
@@ -493,7 +493,7 @@ def argumentify(
493
493
  12 if __name__ == '__main__':
494
494
  13 argumentify(main)
495
495
 
496
- $ python argumentified.py Ahmad Ahmadi --age 20
496
+ $ python argumentified.py Ahmad Mohammadi --age 20
497
497
  Ahmad Ahmadi is 20 years old.
498
498
  $ python argumentified.py Mehrad Pooryoussof
499
499
  Mehrad Pooryoussof is not yet born.
@@ -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