log21 3.0.1__tar.gz → 3.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: log21
3
- Version: 3.0.1
3
+ Version: 3.1.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,85 +94,139 @@ pip install git+https://github.com/MPCodeWriter21/log21
94
94
  Changelog
95
95
  ---------
96
96
 
97
- ### v3.0.1
97
+ ### v3.1.0
98
98
 
99
- Fix the issue with `argumentify` which would result in falsy default values to be
100
- replaced with None.
99
+ Change the way `argumentify` handles function parameters to argument-parser arguments
100
+ conversion.
101
101
 
102
- + Example:
102
+ + `POSITIONAL_ONLY` and `VAR_POSITIONAL` parameters will be positional arguments.
103
+ + `POSITIONAL_OR_KEYWORD` and `KEYWORD_ONLY` parameters have flags assigned to them.
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.
107
+
108
+ #### Example 1
109
+
110
+ ```python
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
+ ```
144
+
145
+ _Note that `path` and `output` are required._
146
+
147
+ #### Example 2
148
+
149
+ ```python
150
+ def main(output: Path, /, *inputs: Path):
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
+ ```
170
+
171
+ The help looks like this:
172
+
173
+ ```help
174
+ usage: test.py [-h] output [inputs ...]
175
+
176
+ Process multiple files into one.
177
+
178
+ positional arguments:
179
+ output The output file
180
+ inputs The path to the input files
181
+
182
+ options:
183
+ -h, --help
184
+ show this help message and exit
185
+
186
+ ```
187
+
188
+ #### Example 3
103
189
 
104
190
  ```python
105
- def main(offset: int = 0) -> None:
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
+ """
106
199
  ...
107
200
 
108
- argumentify(main)
201
+
202
+ if __name__ == "__main__":
203
+ argumentify(main)
204
+ ```
205
+
206
+ The help looks like this:
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
225
+
109
226
  ```
110
227
 
111
- if no value is provided for `--offset`, the default will be `None` instead of `0` which
112
- is unexpected and can lead to issues.
113
-
114
- #### Breaking Changes
115
-
116
- + **Internal module renaming and normalization**
117
- + All internal modules were renamed to lowercase and, in some cases, split or
118
- reorganized.
119
- + Imports such as `log21.Colors`, `log21.Logger`, `log21.ProgressBar`, etc. are no
120
- longer valid.
121
- + Users importing from internal modules must update their imports to the new module
122
- names.
123
- + Public imports from `log21` remain supported.
124
-
125
- + **Argumentify exception renames**
126
- + Several exceptions were renamed to follow a consistent `*Error` naming convention:
127
- + `TooFewArguments` → `TooFewArgumentsError`
128
- + `RequiredArgument` → `RequiredArgumentError`
129
- + `IncompatibleArguments` → `IncompatibleArgumentsError`
130
- + Code that explicitly raises or catches these exceptions must be updated.
131
-
132
- #### Changes
133
-
134
- + **Crash reporter behavior improvement**
135
- + Prevented the default file crash reporter from creating `.crash_report` files when it
136
- is not actually used.
137
- + Implemented using an internal `FakeModule` helper.
138
-
139
- + **Argparse compatibility update**
140
- + Bundled and used the Python 3.13 `argparse` implementation to ensure consistent
141
- behavior across supported Python versions.
142
-
143
- + **Progress bar module rename**
144
- + Renamed the internal progress bar module to `progress_bar` for consistency with the
145
- new naming scheme.
146
- + This will not break the usages of `log21.progress_bar(...)` since the call
147
- functionality was added to the module using the `FakeModule` helper.
148
-
149
- + **Examples added and updated**
150
- + Added new example code files.
151
- + Updated existing examples to match the v3 API and conventions.
152
-
153
- #### Fixes
154
-
155
- + Resolved various linting and static-analysis issues across the codebase.
156
- + Addressed minor compatibility issues uncovered by running linters and pre-commit hooks.
157
- + Resolved errors occurring in environments with newer versions of argparse.
158
-
159
- #### Internal and Maintenance Changes
160
-
161
- + Migrated the build system configuration to `uv`.
162
- + Updated Python version classifiers and set the supported Python version to 3.9+.
163
- + Added `vermin` to the pre-commit configuration.
164
- + Updated `.gitignore`, license metadata, and tool configurations.
165
- + Silenced and resolved a large number of linter warnings.
166
- + General internal refactoring with no intended user-visible behavioral changes.
167
-
168
- #### Notes
169
-
170
- + There are **no intentional behavioral changes** in logging output, argument parsing
171
- logic, or UI components.
172
- + Most projects will require **minimal or no changes** unless they depend on internal
173
- modules or renamed exceptions.
174
- + See [MIGRATION-V2-V3.md](https://github.com/MPCodeWriter21/log21/blob/master/MIGRATION-V2-V3.md)
175
- for detailed upgrade instructions.
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._
176
230
 
177
231
  [Full CHANGELOG](https://github.com/MPCodeWriter21/log21/blob/master/CHANGELOG.md)
178
232
 
@@ -69,85 +69,139 @@ pip install git+https://github.com/MPCodeWriter21/log21
69
69
  Changelog
70
70
  ---------
71
71
 
72
- ### v3.0.1
72
+ ### v3.1.0
73
73
 
74
- Fix the issue with `argumentify` which would result in falsy default values to be
75
- replaced with None.
74
+ Change the way `argumentify` handles function parameters to argument-parser arguments
75
+ conversion.
76
76
 
77
- + Example:
77
+ + `POSITIONAL_ONLY` and `VAR_POSITIONAL` parameters will be positional arguments.
78
+ + `POSITIONAL_OR_KEYWORD` and `KEYWORD_ONLY` parameters have flags assigned to them.
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.
82
+
83
+ #### Example 1
84
+
85
+ ```python
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
+ ```
119
+
120
+ _Note that `path` and `output` are required._
121
+
122
+ #### Example 2
123
+
124
+ ```python
125
+ def main(output: Path, /, *inputs: Path):
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
+ ```
145
+
146
+ The help looks like this:
147
+
148
+ ```help
149
+ usage: test.py [-h] output [inputs ...]
150
+
151
+ Process multiple files into one.
152
+
153
+ positional arguments:
154
+ output The output file
155
+ inputs The path to the input files
156
+
157
+ options:
158
+ -h, --help
159
+ show this help message and exit
160
+
161
+ ```
162
+
163
+ #### Example 3
78
164
 
79
165
  ```python
80
- def main(offset: int = 0) -> None:
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
+ """
81
174
  ...
82
175
 
83
- argumentify(main)
176
+
177
+ if __name__ == "__main__":
178
+ argumentify(main)
179
+ ```
180
+
181
+ The help looks like this:
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
200
+
84
201
  ```
85
202
 
86
- if no value is provided for `--offset`, the default will be `None` instead of `0` which
87
- is unexpected and can lead to issues.
88
-
89
- #### Breaking Changes
90
-
91
- + **Internal module renaming and normalization**
92
- + All internal modules were renamed to lowercase and, in some cases, split or
93
- reorganized.
94
- + Imports such as `log21.Colors`, `log21.Logger`, `log21.ProgressBar`, etc. are no
95
- longer valid.
96
- + Users importing from internal modules must update their imports to the new module
97
- names.
98
- + Public imports from `log21` remain supported.
99
-
100
- + **Argumentify exception renames**
101
- + Several exceptions were renamed to follow a consistent `*Error` naming convention:
102
- + `TooFewArguments` → `TooFewArgumentsError`
103
- + `RequiredArgument` → `RequiredArgumentError`
104
- + `IncompatibleArguments` → `IncompatibleArgumentsError`
105
- + Code that explicitly raises or catches these exceptions must be updated.
106
-
107
- #### Changes
108
-
109
- + **Crash reporter behavior improvement**
110
- + Prevented the default file crash reporter from creating `.crash_report` files when it
111
- is not actually used.
112
- + Implemented using an internal `FakeModule` helper.
113
-
114
- + **Argparse compatibility update**
115
- + Bundled and used the Python 3.13 `argparse` implementation to ensure consistent
116
- behavior across supported Python versions.
117
-
118
- + **Progress bar module rename**
119
- + Renamed the internal progress bar module to `progress_bar` for consistency with the
120
- new naming scheme.
121
- + This will not break the usages of `log21.progress_bar(...)` since the call
122
- functionality was added to the module using the `FakeModule` helper.
123
-
124
- + **Examples added and updated**
125
- + Added new example code files.
126
- + Updated existing examples to match the v3 API and conventions.
127
-
128
- #### Fixes
129
-
130
- + Resolved various linting and static-analysis issues across the codebase.
131
- + Addressed minor compatibility issues uncovered by running linters and pre-commit hooks.
132
- + Resolved errors occurring in environments with newer versions of argparse.
133
-
134
- #### Internal and Maintenance Changes
135
-
136
- + Migrated the build system configuration to `uv`.
137
- + Updated Python version classifiers and set the supported Python version to 3.9+.
138
- + Added `vermin` to the pre-commit configuration.
139
- + Updated `.gitignore`, license metadata, and tool configurations.
140
- + Silenced and resolved a large number of linter warnings.
141
- + General internal refactoring with no intended user-visible behavioral changes.
142
-
143
- #### Notes
144
-
145
- + There are **no intentional behavioral changes** in logging output, argument parsing
146
- logic, or UI components.
147
- + Most projects will require **minimal or no changes** unless they depend on internal
148
- modules or renamed exceptions.
149
- + See [MIGRATION-V2-V3.md](https://github.com/MPCodeWriter21/log21/blob/master/MIGRATION-V2-V3.md)
150
- for detailed upgrade instructions.
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._
151
205
 
152
206
  [Full CHANGELOG](https://github.com/MPCodeWriter21/log21/blob/master/CHANGELOG.md)
153
207
 
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "webcolors",
24
24
  "docstring-parser"
25
25
  ]
26
- version = "3.0.1"
26
+ version = "3.1.0"
27
27
 
28
28
  [build-system]
29
29
  requires = ["uv_build>=0.8.15,<0.9.0"]
@@ -31,7 +31,7 @@ from .stream_handler import StreamHandler, ColorizingStreamHandler
31
31
  # yapf: enable
32
32
 
33
33
  __author__ = 'CodeWriter21 (Mehrad Pooryoussof)'
34
- __version__ = '3.0.1'
34
+ __version__ = '3.1.0'
35
35
  __github__ = 'https://GitHub.com/MPCodeWriter21/log21'
36
36
  __all__ = [
37
37
  'ColorizingStreamHandler', 'DecolorizingFileHandler', 'ColorizingFormatter',
@@ -344,6 +344,28 @@ def _add_arguments(
344
344
  """
345
345
  if reserved_flags is None:
346
346
  reserved_flags = RESERVED_FLAGS.copy()
347
+
348
+ keyword_only_exists = False
349
+ for argument in info.arguments.values():
350
+ # Reserve the name of POSITIONAL_ONLY and VAR_POSITIONAL arguments as flags
351
+ # since their flag name and their dest need to be the same
352
+ if argument.kind in [_inspect._ParameterKind.POSITIONAL_ONLY,
353
+ _inspect._ParameterKind.VAR_POSITIONAL]:
354
+ reserved_flags.add(argument.name)
355
+ # If there is at least one KEYWORD_ONLY argument, the parameters of kind
356
+ # POSITIONAL_OR_KEYWORD will be marked as required
357
+ if argument.kind == _inspect._ParameterKind.KEYWORD_ONLY:
358
+ keyword_only_exists = True
359
+ # Check if the function has a VAR_KEYWORD argument
360
+ # Raises a ArgumentTypeError if it does
361
+ # TODO: See if we can find a use-case and a way of supporting these arguments
362
+ if argument.kind == _inspect._ParameterKind.VAR_KEYWORD:
363
+ raise ArgumentTypeError(
364
+ f"The function has a `**{argument.name}` argument, "
365
+ "which is not supported.",
366
+ unsupported_arg=argument.name
367
+ )
368
+
347
369
  # Add the arguments
348
370
  for argument in info.arguments.values():
349
371
  config: _Dict[str, _Any] = {
@@ -351,19 +373,21 @@ def _add_arguments(
351
373
  'dest': argument.name,
352
374
  'help': argument.help
353
375
  }
376
+ flags = generate_flag(argument, reserved_flags=reserved_flags)
354
377
  if argument.annotation is bool:
355
378
  config['action'] = 'store_true'
356
379
  elif argument.annotation:
357
380
  config['type'] = argument.annotation
358
381
  if argument.kind == _inspect._ParameterKind.POSITIONAL_ONLY:
359
- config['required'] = True
382
+ flags = [config.pop('dest')]
360
383
  if argument.kind == _inspect._ParameterKind.VAR_POSITIONAL:
361
384
  config['nargs'] = '*'
385
+ flags = [config.pop('dest')]
386
+ if argument.kind == _inspect._ParameterKind.POSITIONAL_OR_KEYWORD and keyword_only_exists:
387
+ config['required'] = True
362
388
  if argument.default is not None:
363
389
  config['default'] = argument.default
364
- parser.add_argument(
365
- *generate_flag(argument, reserved_flags=reserved_flags), **config
366
- )
390
+ parser.add_argument(*flags, **config)
367
391
 
368
392
 
369
393
  def _argumentify_one(func: Callable) -> None:
@@ -373,20 +397,8 @@ def _argumentify_one(func: Callable) -> None:
373
397
  """
374
398
  info = FunctionInfo(func)
375
399
 
376
- # Check if the function has a VAR_KEYWORD argument
377
- # Raises a ArgumentTypeError if it does
378
- for argument in info.arguments.values():
379
- if argument.kind == _inspect._ParameterKind.VAR_KEYWORD:
380
- raise ArgumentTypeError(
381
- f"The function has a `**{argument.name}` argument, "
382
- "which is not supported.",
383
- unsupported_arg=argument.name
384
- )
385
-
386
400
  # Create the parser
387
- parser = _argparse.ColorizingArgumentParser(
388
- description=info.docstring.short_description
389
- )
401
+ parser = _argparse.ColorizingArgumentParser(description=info.docstring.description)
390
402
  # Add the arguments
391
403
  _add_arguments(parser, info)
392
404
  cli_args = parser.parse_args()
@@ -432,7 +444,7 @@ def _argumentify(functions: _Dict[str, Callable]) -> None:
432
444
  parser = _argparse.ColorizingArgumentParser()
433
445
  subparsers = parser.add_subparsers(required=True)
434
446
  for name, (_, info) in functions_info.items():
435
- subparser = subparsers.add_parser(name, help=info.docstring.short_description)
447
+ subparser = subparsers.add_parser(name, help=info.docstring.description)
436
448
  _add_arguments(subparser, info)
437
449
  subparser.set_defaults(func=info.function)
438
450
  cli_args = parser.parse_args()
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