python-minifier 2.9.0__tar.gz → 2.10.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.
Files changed (83) hide show
  1. {python_minifier-2.9.0 → python_minifier-2.10.0}/PKG-INFO +5 -4
  2. {python_minifier-2.9.0 → python_minifier-2.10.0}/README.md +2 -2
  3. {python_minifier-2.9.0 → python_minifier-2.10.0}/setup.py +4 -4
  4. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/__init__.py +43 -5
  5. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/__init__.pyi +6 -3
  6. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/__main__.py +86 -16
  7. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/ast_compare.py +1 -1
  8. python_minifier-2.10.0/src/python_minifier/ast_compat.py +40 -0
  9. python_minifier-2.10.0/src/python_minifier/ast_printer.py +130 -0
  10. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/expression_printer.py +11 -6
  11. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/f_string.py +41 -21
  12. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/module_printer.py +63 -7
  13. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/bind_names.py +24 -6
  14. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/binding.py +48 -3
  15. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/mapper.py +16 -1
  16. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/rename_literals.py +17 -2
  17. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/renamer.py +1 -1
  18. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/resolve_names.py +28 -10
  19. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/util.py +10 -1
  20. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/token_printer.py +4 -2
  21. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/combine_imports.py +8 -5
  22. python_minifier-2.10.0/src/python_minifier/transforms/constant_folding.py +114 -0
  23. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_annotations.py +25 -5
  24. python_minifier-2.10.0/src/python_minifier/transforms/remove_annotations_options.py +35 -0
  25. python_minifier-2.10.0/src/python_minifier/transforms/remove_annotations_options.pyi +17 -0
  26. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_asserts.py +1 -1
  27. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_debug.py +1 -1
  28. python_minifier-2.10.0/src/python_minifier/transforms/remove_exception_brackets.py +124 -0
  29. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_explicit_return_none.py +1 -1
  30. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_literal_statements.py +1 -1
  31. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_object_base.py +4 -1
  32. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_pass.py +1 -1
  33. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_posargs.py +1 -1
  34. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/suite_transformer.py +4 -1
  35. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/util.py +7 -7
  36. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier.egg-info/PKG-INFO +6 -5
  37. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier.egg-info/SOURCES.txt +14 -1
  38. python_minifier-2.10.0/test/test_bind_names.py +1354 -0
  39. python_minifier-2.10.0/test/test_bind_names_python2.py +605 -0
  40. python_minifier-2.10.0/test/test_bind_names_python33.py +620 -0
  41. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_combine_imports.py +80 -0
  42. python_minifier-2.10.0/test/test_folding.py +136 -0
  43. python_minifier-2.10.0/test/test_fstring.py +103 -0
  44. python_minifier-2.10.0/test/test_generic_types.py +278 -0
  45. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_hoist_literals.py +26 -0
  46. python_minifier-2.10.0/test/test_is_ast_node.py +40 -0
  47. python_minifier-2.10.0/test/test_remove_annotations.py +325 -0
  48. python_minifier-2.10.0/test/test_remove_exception_brackets.py +165 -0
  49. python_minifier-2.9.0/test/test_empty_fstring.py +0 -19
  50. python_minifier-2.9.0/test/test_remove_annotations.py +0 -218
  51. {python_minifier-2.9.0 → python_minifier-2.10.0}/LICENSE +0 -0
  52. {python_minifier-2.9.0 → python_minifier-2.10.0}/setup.cfg +0 -0
  53. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/ministring.py +0 -0
  54. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/py.typed +0 -0
  55. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/__init__.py +0 -0
  56. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/rename/name_generator.py +0 -0
  57. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier/transforms/__init__.py +0 -0
  58. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier.egg-info/dependency_links.txt +0 -0
  59. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier.egg-info/entry_points.txt +0 -0
  60. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier.egg-info/top_level.txt +0 -0
  61. {python_minifier-2.9.0 → python_minifier-2.10.0}/src/python_minifier.egg-info/zip-safe +0 -0
  62. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_assignment_expressions.py +0 -0
  63. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_await_fstring.py +0 -0
  64. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_comprehension_rename.py +0 -0
  65. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_decorator_expressions.py +0 -0
  66. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_dict_expansion.py +0 -0
  67. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_import.py +0 -0
  68. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_iterable_unpacking.py +0 -0
  69. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_match.py +0 -0
  70. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_match_rename.py +0 -0
  71. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_name_generator.py +0 -0
  72. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_nonlocal.py +0 -0
  73. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_posargs.py +0 -0
  74. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_preserve_shebang.py +0 -0
  75. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_remove_assert.py +0 -0
  76. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_remove_debug.py +0 -0
  77. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_remove_explicit_return_none.py +0 -0
  78. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_remove_literal_statements.py +0 -0
  79. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_remove_object.py +0 -0
  80. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_remove_pass.py +0 -0
  81. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_rename_builtins.py +0 -0
  82. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_rename_locals.py +0 -0
  83. {python_minifier-2.9.0 → python_minifier-2.10.0}/test/test_slice.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python_minifier
3
- Version: 2.9.0
3
+ Version: 2.10.0
4
4
  Summary: Transform Python source code into it's most compact representation
5
5
  Home-page: https://github.com/dflook/python-minifier
6
6
  Author: Daniel Flook
@@ -22,13 +22,14 @@ Classifier: Programming Language :: Python :: 3.8
22
22
  Classifier: Programming Language :: Python :: 3.9
23
23
  Classifier: Programming Language :: Python :: 3.10
24
24
  Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
25
26
  Classifier: Programming Language :: Python :: 2
26
27
  Classifier: Programming Language :: Python :: 2.7
27
28
  Classifier: Programming Language :: Python :: Implementation :: CPython
28
29
  Classifier: Programming Language :: Python :: Implementation :: PyPy
29
30
  Classifier: Intended Audience :: Developers
30
31
  Classifier: Topic :: Software Development
31
- Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12
32
+ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13
32
33
  Description-Content-Type: text/markdown
33
34
  License-File: LICENSE
34
35
 
@@ -38,7 +39,7 @@ Transforms Python source code into its most compact representation.
38
39
 
39
40
  [Try it out!](https://python-minifier.com)
40
41
 
41
- python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6.
42
+ python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.
42
43
 
43
44
  * [PyPI](https://pypi.org/project/python-minifier/)
44
45
  * [Documentation](https://dflook.github.io/python-minifier/)
@@ -141,7 +142,7 @@ $ pip install python-minifier
141
142
  Note that python-minifier depends on the python interpreter for parsing source code,
142
143
  so install using a version of python appropriate for your source.
143
144
 
144
- python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11.
145
+ python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.
145
146
 
146
147
  ## Usage
147
148
 
@@ -4,7 +4,7 @@ Transforms Python source code into its most compact representation.
4
4
 
5
5
  [Try it out!](https://python-minifier.com)
6
6
 
7
- python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6.
7
+ python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.
8
8
 
9
9
  * [PyPI](https://pypi.org/project/python-minifier/)
10
10
  * [Documentation](https://dflook.github.io/python-minifier/)
@@ -107,7 +107,7 @@ $ pip install python-minifier
107
107
  Note that python-minifier depends on the python interpreter for parsing source code,
108
108
  so install using a version of python appropriate for your source.
109
109
 
110
- python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11.
110
+ python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.
111
111
 
112
112
  ## Usage
113
113
 
@@ -1,5 +1,4 @@
1
1
  import os.path
2
- import sys
3
2
 
4
3
  from setuptools import setup, find_packages
5
4
 
@@ -23,12 +22,12 @@ setup(
23
22
 
24
23
  package_dir={'': 'src'},
25
24
  packages=find_packages('src'),
26
- package_data={"python_minifier": ["py.typed", "__init__.pyi"]},
25
+ package_data={"python_minifier": ["py.typed", "*.pyi", "rename/*.pyi", "transforms/*.pyi"]},
27
26
  long_description=long_desc,
28
27
  long_description_content_type='text/markdown',
29
28
 
30
- python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12',
31
- version='2.9.0',
29
+ python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13',
30
+ version='2.10.0',
32
31
 
33
32
  classifiers=[
34
33
  'Development Status :: 5 - Production/Stable',
@@ -44,6 +43,7 @@ setup(
44
43
  'Programming Language :: Python :: 3.9',
45
44
  'Programming Language :: Python :: 3.10',
46
45
  'Programming Language :: Python :: 3.11',
46
+ 'Programming Language :: Python :: 3.12',
47
47
  'Programming Language :: Python :: 2',
48
48
  'Programming Language :: Python :: 2.7',
49
49
  'Programming Language :: Python :: Implementation :: CPython',
@@ -4,7 +4,7 @@ a 'minified' representation of the same source code.
4
4
 
5
5
  """
6
6
 
7
- import ast
7
+ import python_minifier.ast_compat as ast
8
8
  import re
9
9
 
10
10
  from python_minifier.ast_compare import CompareError, compare_ast
@@ -20,10 +20,13 @@ from python_minifier.rename import (
20
20
  )
21
21
 
22
22
  from python_minifier.transforms.combine_imports import CombineImports
23
+ from python_minifier.transforms.constant_folding import FoldConstants
23
24
  from python_minifier.transforms.remove_annotations import RemoveAnnotations
25
+ from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
24
26
  from python_minifier.transforms.remove_asserts import RemoveAsserts
25
27
  from python_minifier.transforms.remove_debug import RemoveDebug
26
28
  from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone
29
+ from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call
27
30
  from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements
28
31
  from python_minifier.transforms.remove_object_base import RemoveObject
29
32
  from python_minifier.transforms.remove_pass import RemovePass
@@ -52,7 +55,7 @@ class UnstableMinification(RuntimeError):
52
55
  def minify(
53
56
  source,
54
57
  filename=None,
55
- remove_annotations=True,
58
+ remove_annotations=RemoveAnnotationsOptions(),
56
59
  remove_pass=True,
57
60
  remove_literal_statements=False,
58
61
  combine_imports=True,
@@ -67,6 +70,8 @@ def minify(
67
70
  remove_asserts=False,
68
71
  remove_debug=False,
69
72
  remove_explicit_return_none=True,
73
+ remove_builtin_exception_brackets=True,
74
+ constant_folding=True
70
75
  ):
71
76
  """
72
77
  Minify a python module
@@ -80,7 +85,8 @@ def minify(
80
85
  :param str source: The python module source code
81
86
  :param str filename: The original source filename if known
82
87
 
83
- :param bool remove_annotations: If type annotations should be removed where possible
88
+ :param remove_annotations: Configures the removal of type annotations. True removes all annotations, False removes none. RemoveAnnotationsOptions can be used to configure the removal of specific annotations.
89
+ :type remove_annotations: bool or RemoveAnnotationsOptions
84
90
  :param bool remove_pass: If Pass statements should be removed where possible
85
91
  :param bool remove_literal_statements: If statements consisting of a single literal should be removed, including docstrings
86
92
  :param bool combine_imports: Combine adjacent import statements where possible
@@ -97,6 +103,8 @@ def minify(
97
103
  :param bool remove_asserts: If assert statements should be removed
98
104
  :param bool remove_debug: If conditional statements that test '__debug__ is True' should be removed
99
105
  :param bool remove_explicit_return_none: If explicit return None statements should be replaced with a bare return
106
+ :param bool remove_builtin_exception_brackets: If brackets should be removed when raising exceptions with no arguments
107
+ :param bool constant_folding: If literal expressions should be evaluated
100
108
 
101
109
  :rtype: str
102
110
 
@@ -115,8 +123,20 @@ def minify(
115
123
  if combine_imports:
116
124
  module = CombineImports()(module)
117
125
 
118
- if remove_annotations:
119
- module = RemoveAnnotations()(module)
126
+ if isinstance(remove_annotations, bool):
127
+ remove_annotations_options = RemoveAnnotationsOptions(
128
+ remove_variable_annotations=remove_annotations,
129
+ remove_return_annotations=remove_annotations,
130
+ remove_argument_annotations=remove_annotations,
131
+ remove_class_attribute_annotations=remove_annotations,
132
+ )
133
+ elif isinstance(remove_annotations, RemoveAnnotationsOptions):
134
+ remove_annotations_options = remove_annotations
135
+ else:
136
+ raise TypeError('remove_annotations must be a bool or RemoveAnnotationsOptions')
137
+
138
+ if remove_annotations_options:
139
+ module = RemoveAnnotations(remove_annotations_options)(module)
120
140
 
121
141
  if remove_pass:
122
142
  module = RemovePass()(module)
@@ -133,13 +153,31 @@ def minify(
133
153
  if remove_explicit_return_none:
134
154
  module = RemoveExplicitReturnNone()(module)
135
155
 
156
+ if constant_folding:
157
+ module = FoldConstants()(module)
158
+
136
159
  bind_names(module)
137
160
  resolve_names(module)
138
161
 
162
+ if remove_builtin_exception_brackets and not module.tainted:
163
+ remove_no_arg_exception_call(module)
164
+
139
165
  if module.tainted:
140
166
  rename_globals = False
141
167
  rename_locals = False
142
168
 
169
+ if preserve_locals is None:
170
+ preserve_locals = []
171
+ elif isinstance(preserve_locals, str):
172
+ preserve_locals = [preserve_locals]
173
+ if preserve_globals is None:
174
+ preserve_globals = []
175
+ elif isinstance(preserve_globals, str):
176
+ preserve_globals = [preserve_globals]
177
+
178
+ preserve_locals.extend(module.preserved)
179
+ preserve_globals.extend(module.preserved)
180
+
143
181
  allow_rename_locals(module, rename_locals, preserve_locals)
144
182
  allow_rename_globals(module, rename_globals, preserve_globals)
145
183
 
@@ -1,6 +1,7 @@
1
1
  import ast
2
- from typing import List, Text, AnyStr, Optional, Any
2
+ from typing import List, Text, AnyStr, Optional, Any, Union
3
3
 
4
+ from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions
4
5
 
5
6
  class UnstableMinification(RuntimeError):
6
7
  def __init__(self, exception: Any, source: Any, minified: Any): ...
@@ -8,7 +9,7 @@ class UnstableMinification(RuntimeError):
8
9
  def minify(
9
10
  source: AnyStr,
10
11
  filename: Optional[str] = ...,
11
- remove_annotations: bool = ...,
12
+ remove_annotations: Union[bool, RemoveAnnotationsOptions] = ...,
12
13
  remove_pass: bool = ...,
13
14
  remove_literal_statements: bool = ...,
14
15
  combine_imports: bool = ...,
@@ -22,7 +23,9 @@ def minify(
22
23
  preserve_shebang: bool = ...,
23
24
  remove_asserts: bool = ...,
24
25
  remove_debug: bool = ...,
25
- remove_explicit_return_none: bool = ...
26
+ remove_explicit_return_none: bool = ...,
27
+ remove_builtin_exception_brackets: bool = ...,
28
+ constant_folding: bool = ...
26
29
  ) -> Text: ...
27
30
 
28
31
  def unparse(module: ast.Module) -> Text: ...
@@ -1,17 +1,24 @@
1
1
  from __future__ import print_function
2
2
 
3
- import sys
4
- import os
5
-
6
3
  import argparse
7
- from pkg_resources import get_distribution, DistributionNotFound
4
+ import os
5
+ import sys
8
6
 
9
7
  from python_minifier import minify
10
-
11
- try:
12
- version = get_distribution('python_minifier').version
13
- except DistributionNotFound:
14
- version = '0.0.0'
8
+ from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
9
+
10
+ if sys.version_info >= (3, 8):
11
+ from importlib import metadata
12
+ try:
13
+ version = metadata.version('python-minifier')
14
+ except metadata.PackageNotFoundError:
15
+ version = '0.0.0'
16
+ else:
17
+ from pkg_resources import get_distribution, DistributionNotFound
18
+ try:
19
+ version = get_distribution('python_minifier').version
20
+ except DistributionNotFound:
21
+ version = '0.0.0'
15
22
 
16
23
 
17
24
  def main():
@@ -68,6 +75,7 @@ examples:
68
75
  else:
69
76
  sys.stdout.write(minified)
70
77
 
78
+
71
79
  def parse_args():
72
80
  parser = argparse.ArgumentParser(prog='pyminify', description='Minify Python source code', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=main.__doc__)
73
81
 
@@ -113,12 +121,6 @@ def parse_args():
113
121
  help='Enable removing statements that are just a literal (including docstrings)',
114
122
  dest='remove_literal_statements',
115
123
  )
116
- minification_options.add_argument(
117
- '--no-remove-annotations',
118
- action='store_false',
119
- help='Disable removing function and variable annotations',
120
- dest='remove_annotations',
121
- )
122
124
  minification_options.add_argument(
123
125
  '--no-hoist-literals',
124
126
  action='store_false',
@@ -189,6 +191,51 @@ def parse_args():
189
191
  help='Replace explicit return None with a bare return',
190
192
  dest='remove_explicit_return_none',
191
193
  )
194
+ minification_options.add_argument(
195
+ '--no-remove-builtin-exception-brackets',
196
+ action='store_false',
197
+ help='Disable removing brackets when raising builtin exceptions with no arguments',
198
+ dest='remove_exception_brackets',
199
+ )
200
+ minification_options.add_argument(
201
+ '--no-constant-folding',
202
+ action='store_false',
203
+ help='Disable evaluating literal expressions',
204
+ dest='constant_folding',
205
+ )
206
+
207
+ annotation_options = parser.add_argument_group('remove annotations options', 'Options that affect how annotations are removed')
208
+ annotation_options.add_argument(
209
+ '--no-remove-annotations',
210
+ action='store_false',
211
+ help='Disable removing all annotations',
212
+ dest='remove_annotations',
213
+ )
214
+ annotation_options.add_argument(
215
+ '--no-remove-variable-annotations',
216
+ action='store_false',
217
+ help='Disable removing variable annotations',
218
+ dest='remove_variable_annotations',
219
+ )
220
+ annotation_options.add_argument(
221
+ '--no-remove-return-annotations',
222
+ action='store_false',
223
+ help='Disable removing function return annotations',
224
+ dest='remove_return_annotations',
225
+ )
226
+ annotation_options.add_argument(
227
+ '--no-remove-argument-annotations',
228
+ action='store_false',
229
+ help='Disable removing function argument annotations',
230
+ dest='remove_argument_annotations',
231
+ )
232
+ annotation_options.add_argument(
233
+ '--remove-class-attribute-annotations',
234
+ action='store_true',
235
+ help='Enable removing class attribute annotations',
236
+ dest='remove_class_attribute_annotations',
237
+ )
238
+
192
239
  parser.add_argument('--version', '-v', action='version', version=version)
193
240
 
194
241
  args = parser.parse_args()
@@ -207,8 +254,13 @@ def parse_args():
207
254
  sys.stderr.write('error: path ' + args.path[0] + ' is a directory, --in-place required\n')
208
255
  sys.exit(1)
209
256
 
257
+ if args.remove_class_attribute_annotations and not args.remove_annotations:
258
+ sys.stderr.write('error: --remove-class-attribute-annotations would do nothing when used with --no-remove-annotations\n')
259
+ sys.exit(1)
260
+
210
261
  return args
211
262
 
263
+
212
264
  def source_modules(args):
213
265
 
214
266
  def error(os_error):
@@ -223,6 +275,7 @@ def source_modules(args):
223
275
  else:
224
276
  yield path_arg
225
277
 
278
+
226
279
  def do_minify(source, filename, minification_args):
227
280
 
228
281
  preserve_globals = []
@@ -237,12 +290,27 @@ def do_minify(source, filename, minification_args):
237
290
  names = [name.strip() for name in arg.split(',') if name]
238
291
  preserve_locals.extend(names)
239
292
 
293
+ if minification_args.remove_annotations is False:
294
+ remove_annotations = RemoveAnnotationsOptions(
295
+ remove_variable_annotations=False,
296
+ remove_return_annotations=False,
297
+ remove_argument_annotations=False,
298
+ remove_class_attribute_annotations=False,
299
+ )
300
+ else:
301
+ remove_annotations = RemoveAnnotationsOptions(
302
+ remove_variable_annotations=minification_args.remove_variable_annotations,
303
+ remove_return_annotations=minification_args.remove_return_annotations,
304
+ remove_argument_annotations=minification_args.remove_argument_annotations,
305
+ remove_class_attribute_annotations=minification_args.remove_class_attribute_annotations,
306
+ )
307
+
240
308
  return minify(
241
309
  source,
242
310
  filename=filename,
243
311
  combine_imports=minification_args.combine_imports,
244
312
  remove_pass=minification_args.remove_pass,
245
- remove_annotations=minification_args.remove_annotations,
313
+ remove_annotations=remove_annotations,
246
314
  remove_literal_statements=minification_args.remove_literal_statements,
247
315
  hoist_literals=minification_args.hoist_literals,
248
316
  rename_locals=minification_args.rename_locals,
@@ -255,6 +323,8 @@ def do_minify(source, filename, minification_args):
255
323
  remove_asserts=minification_args.remove_asserts,
256
324
  remove_debug=minification_args.remove_debug,
257
325
  remove_explicit_return_none=minification_args.remove_explicit_return_none,
326
+ remove_builtin_exception_brackets=minification_args.remove_exception_brackets,
327
+ constant_folding=minification_args.constant_folding
258
328
  )
259
329
 
260
330
 
@@ -1,4 +1,4 @@
1
- import ast
1
+ import python_minifier.ast_compat as ast
2
2
 
3
3
  from python_minifier.util import is_ast_node
4
4
 
@@ -0,0 +1,40 @@
1
+ """
2
+ The is a backwards compatible shim for the ast module.
3
+
4
+ This is the best way to make the ast module work the same in both python 2 and 3.
5
+ This is essentially what the ast module was doing until 3.12, when it started throwing
6
+ deprecation warnings.
7
+ """
8
+
9
+ from ast import *
10
+
11
+ # Ideally we don't import anything else
12
+
13
+ if 'TypeAlias' in globals():
14
+
15
+ # Add n and s properties to Constant so it can stand in for Num, Str and Bytes
16
+ Constant.n = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
17
+ Constant.s = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
18
+
19
+ # These classes are redefined from the ones in ast that complain about deprecation
20
+ # They will continue to work once they are removed from ast
21
+
22
+ class Str(Constant): # type: ignore[no-redef]
23
+ def __new__(cls, s, *args, **kwargs):
24
+ return Constant(value=s, *args, **kwargs)
25
+
26
+ class Bytes(Constant): # type: ignore[no-redef]
27
+ def __new__(cls, s, *args, **kwargs):
28
+ return Constant(value=s, *args, **kwargs)
29
+
30
+ class Num(Constant): # type: ignore[no-redef]
31
+ def __new__(cls, n, *args, **kwargs):
32
+ return Constant(value=n, *args, **kwargs)
33
+
34
+ class NameConstant(Constant): # type: ignore[no-redef]
35
+ def __new__(cls, *args, **kwargs):
36
+ return Constant(*args, **kwargs)
37
+
38
+ class Ellipsis(Constant): # type: ignore[no-redef]
39
+ def __new__(cls, *args, **kwargs):
40
+ return Constant(value=literal_eval('...'), *args, **kwargs)
@@ -0,0 +1,130 @@
1
+ """
2
+ Print a representation of an AST
3
+
4
+ This prints a human readable representation of the nodes in the AST.
5
+ The goal is to make it easy to see what the AST looks like, and to
6
+ make it easy to compare two ASTs.
7
+
8
+ This is not intended to be a complete representation of the AST, some
9
+ fields or field names may be omitted for clarity. It should still be precise and unambiguous.
10
+
11
+ """
12
+
13
+ import python_minifier.ast_compat as ast
14
+
15
+ from python_minifier.util import is_ast_node
16
+
17
+ INDENT = ' '
18
+
19
+ # The field name that can be omitted for each node
20
+ # Either it's the only field or would otherwise be obvious
21
+ default_fields = {
22
+ 'Constant': 'value',
23
+ 'Num': 'n',
24
+ 'Str': 's',
25
+ 'Bytes': 's',
26
+ 'NameConstant': 'value',
27
+ 'FormattedValue': 'value',
28
+ 'JoinedStr': 'values',
29
+ 'List': 'elts',
30
+ 'Tuple': 'elts',
31
+ 'Set': 'elts',
32
+ 'Name': 'id',
33
+ 'Expr': 'value',
34
+ 'UnaryOp': 'op',
35
+ 'BinOp': 'op',
36
+ 'BoolOp': 'op',
37
+ 'Call': 'func',
38
+ 'Index': 'value',
39
+ 'ExtSlice': 'dims',
40
+ 'Assert': 'test',
41
+ 'Delete': 'targets',
42
+ 'Import': 'names',
43
+ 'If': 'test',
44
+ 'While': 'test',
45
+ 'Try': 'handlers',
46
+ 'TryExcept': 'handlers',
47
+ 'With': 'items',
48
+ 'withitem': 'context_expr',
49
+ 'FunctionDef': 'name',
50
+ 'arg': 'arg',
51
+ 'Return': 'value',
52
+ 'Yield': 'value',
53
+ 'YieldFrom': 'value',
54
+ 'Global': 'names',
55
+ 'Nonlocal': 'names',
56
+ 'ClassDef': 'name',
57
+ 'AsyncFunctionDef': 'name',
58
+ 'Await': 'value',
59
+ 'AsyncWith': 'items',
60
+ 'Raise': 'exc',
61
+ 'Subscript': 'value',
62
+ 'Attribute': 'value',
63
+ 'AugAssign': 'op',
64
+ }
65
+
66
+ def is_literal(node, field):
67
+ if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value':
68
+ return True
69
+
70
+ if is_ast_node(node, ast.Num) and field == 'n':
71
+ return True
72
+
73
+ if is_ast_node(node, ast.Str) and field == 's':
74
+ return True
75
+
76
+ if is_ast_node(node, 'Bytes') and field == 's':
77
+ return True
78
+
79
+ if is_ast_node(node, 'NameConstant') and field == 'value':
80
+ return True
81
+
82
+ return False
83
+
84
+ def print_ast(node):
85
+ if not isinstance(node, ast.AST):
86
+ return repr(node)
87
+
88
+ s = ''
89
+
90
+ node_name = node.__class__.__name__
91
+ s += node_name
92
+ s += '('
93
+
94
+ first = True
95
+ for field, value in ast.iter_fields(node):
96
+ if not value and not is_literal(node, field):
97
+ # Don't bother printing fields that are empty, except for literals
98
+ continue
99
+
100
+ if field == 'ctx':
101
+ # Don't print the ctx, it's always apparent from context
102
+ continue
103
+
104
+ if first:
105
+ first = False
106
+ else:
107
+ s += ', '
108
+
109
+ if default_fields.get(node_name) != field:
110
+ s += field + '='
111
+
112
+ if isinstance(value, ast.AST):
113
+ s += print_ast(value)
114
+ elif isinstance(value, list):
115
+ s += '['
116
+ first_list = True
117
+ for item in value:
118
+ if first_list:
119
+ first_list = False
120
+ else:
121
+ s += ','
122
+
123
+ for line in print_ast(item).splitlines():
124
+ s += '\n' + INDENT + line
125
+ s += '\n]'
126
+ else:
127
+ s += repr(value)
128
+
129
+ s += ')'
130
+ return s
@@ -1,4 +1,4 @@
1
- import ast
1
+ import python_minifier.ast_compat as ast
2
2
  import sys
3
3
 
4
4
  from python_minifier.util import is_ast_node
@@ -69,7 +69,7 @@ class ExpressionPrinter(object):
69
69
 
70
70
  # Python2 parses negative ints as an ast.Num with a negative value.
71
71
  # Make sure the Num get the precedence of the USub operator in this case.
72
- if sys.version_info < (3, 0) and isinstance(node, ast.Num):
72
+ if sys.version_info < (3, 0) and is_ast_node(node, ast.Num):
73
73
  if str(node.n)[0] == '-':
74
74
  return self.precedences['USub']
75
75
 
@@ -208,7 +208,7 @@ class ExpressionPrinter(object):
208
208
  def visit_UnaryOp(self, node):
209
209
  self.visit(node.op)
210
210
 
211
- if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Num):
211
+ if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and is_ast_node(node.operand, ast.Num):
212
212
  # For: -(1), which is parsed as a UnaryOp(USub, Num(1)).
213
213
  # Without this special case it would be printed as -1
214
214
  # This is fine, but python 2 will then parse it at Num(-1) so the AST wouldn't round-trip.
@@ -428,7 +428,7 @@ class ExpressionPrinter(object):
428
428
  value_precedence = self.precedence(node.value)
429
429
  attr_precedence = self.precedence(node)
430
430
 
431
- if (value_precedence != 0 and (attr_precedence > value_precedence)) or isinstance(node.value, ast.Num):
431
+ if (value_precedence != 0 and (attr_precedence > value_precedence)) or is_ast_node(node.value, ast.Num):
432
432
  self.printer.delimiter('(')
433
433
  self._expression(node.value)
434
434
  self.printer.delimiter(')')
@@ -462,7 +462,7 @@ class ExpressionPrinter(object):
462
462
  self.visit_Slice(node.slice)
463
463
  elif isinstance(node.slice, ast.ExtSlice):
464
464
  self.visit_ExtSlice(node.slice)
465
- elif isinstance(node.slice, ast.Ellipsis):
465
+ elif is_ast_node(node.slice, ast.Ellipsis):
466
466
  self.visit_Ellipsis(node)
467
467
  elif sys.version_info >= (3, 9) and isinstance(node.slice, ast.Tuple):
468
468
  self.visit_Tuple(node.slice)
@@ -735,7 +735,12 @@ class ExpressionPrinter(object):
735
735
 
736
736
  import python_minifier.f_string
737
737
 
738
- self.printer.fstring(str(python_minifier.f_string.OuterFString(node)))
738
+ if sys.version_info < (3, 12):
739
+ pep701 = False
740
+ else:
741
+ pep701 = True
742
+
743
+ self.printer.fstring(str(python_minifier.f_string.OuterFString(node, pep701=pep701)))
739
744
 
740
745
  def visit_NamedExpr(self, node):
741
746
  self._expression(node.target)