python-minifier 3.2.0__tar.gz → 3.4.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 (107) hide show
  1. {python_minifier-3.2.0/src/python_minifier.egg-info → python_minifier-3.4.0}/PKG-INFO +5 -4
  2. {python_minifier-3.2.0 → python_minifier-3.4.0}/README.md +2 -2
  3. {python_minifier-3.2.0 → python_minifier-3.4.0}/setup.py +3 -2
  4. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/__init__.py +9 -3
  5. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/__init__.pyi +2 -1
  6. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/__main__.py +10 -1
  7. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/expression_printer.py +10 -6
  8. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/module_printer.py +6 -1
  9. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/mapper.py +3 -1
  10. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/rename_literals.py +3 -1
  11. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/token_printer.py +2 -2
  12. python_minifier-3.4.0/src/python_minifier/transforms/combine_imports.py +119 -0
  13. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/constant_folding.py +33 -2
  14. python_minifier-3.4.0/src/python_minifier/transforms/remove_dead_branches.py +273 -0
  15. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_debug.py +14 -12
  16. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/suite_transformer.py +45 -0
  17. {python_minifier-3.2.0 → python_minifier-3.4.0/src/python_minifier.egg-info}/PKG-INFO +5 -4
  18. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/SOURCES.txt +4 -0
  19. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/subprocess_compat.py +1 -1
  20. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_combine_imports.py +19 -0
  21. python_minifier-3.4.0/test/test_comprehension_unpacking.py +188 -0
  22. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_folding.py +132 -2
  23. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_is_constant_node.py +2 -2
  24. python_minifier-3.4.0/test/test_lazy_imports.py +338 -0
  25. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_match.py +28 -0
  26. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_assert.py +56 -0
  27. python_minifier-3.4.0/test/test_remove_dead_branches.py +695 -0
  28. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_debug.py +157 -5
  29. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_pass.py +79 -0
  30. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_unicode_cli.py +6 -9
  31. python_minifier-3.2.0/src/python_minifier/transforms/combine_imports.py +0 -75
  32. {python_minifier-3.2.0 → python_minifier-3.4.0}/LICENSE +0 -0
  33. {python_minifier-3.2.0 → python_minifier-3.4.0}/MANIFEST.in +0 -0
  34. {python_minifier-3.2.0 → python_minifier-3.4.0}/setup.cfg +0 -0
  35. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/ast_annotation/__init__.py +0 -0
  36. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/ast_compare.py +0 -0
  37. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/ast_compat.py +0 -0
  38. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/ast_printer.py +0 -0
  39. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/f_string.py +0 -0
  40. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/ministring.py +0 -0
  41. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/py.typed +0 -0
  42. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/README.md +0 -0
  43. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/__init__.py +0 -0
  44. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/bind_names.py +0 -0
  45. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/binding.py +0 -0
  46. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/name_generator.py +0 -0
  47. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/renamer.py +0 -0
  48. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/resolve_names.py +0 -0
  49. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/rename/util.py +0 -0
  50. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/t_string.py +0 -0
  51. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/__init__.py +0 -0
  52. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_annotations.py +0 -0
  53. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_annotations_options.py +0 -0
  54. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_annotations_options.pyi +0 -0
  55. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_asserts.py +0 -0
  56. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_exception_brackets.py +0 -0
  57. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_explicit_return_none.py +0 -0
  58. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_literal_statements.py +0 -0
  59. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_object_base.py +0 -0
  60. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_pass.py +0 -0
  61. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_posargs.py +0 -0
  62. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier/util.py +0 -0
  63. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/dependency_links.txt +0 -0
  64. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/entry_points.txt +0 -0
  65. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/top_level.txt +0 -0
  66. {python_minifier-3.2.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/zip-safe +0 -0
  67. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/ast_annotation/test_add_parent.py +0 -0
  68. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/conftest.py +0 -0
  69. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/helpers.py +0 -0
  70. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/requirements.txt +0 -0
  71. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_assignment_expressions.py +0 -0
  72. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_await_fstring.py +0 -0
  73. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_best_effort_cli.py +0 -0
  74. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_bind_names.py +0 -0
  75. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_bind_names_namedexpr.py +0 -0
  76. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_bind_names_python2.py +0 -0
  77. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_bind_names_python312.py +0 -0
  78. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_bind_names_python313.py +0 -0
  79. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_bind_names_python33.py +0 -0
  80. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_comprehension_rename.py +0 -0
  81. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_decorator_expressions.py +0 -0
  82. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_dict_expansion.py +0 -0
  83. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_fstring.py +0 -0
  84. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_generic_types.py +0 -0
  85. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_hoist_literals.py +0 -0
  86. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_import.py +0 -0
  87. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_iterable_unpacking.py +0 -0
  88. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_match_rename.py +0 -0
  89. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_name_generator.py +0 -0
  90. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_nonlocal.py +0 -0
  91. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_posargs.py +0 -0
  92. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_prefer_single_line.py +0 -0
  93. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_preserve_shebang.py +0 -0
  94. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_raw_fstring_backslash.py +0 -0
  95. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_raw_tstring_backslash.py +0 -0
  96. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_annotations.py +0 -0
  97. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_exception_brackets.py +0 -0
  98. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_explicit_return_none.py +0 -0
  99. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_literal_statements.py +0 -0
  100. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_remove_object.py +0 -0
  101. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_rename_builtins.py +0 -0
  102. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_rename_locals.py +0 -0
  103. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_slice.py +0 -0
  104. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_template_strings.py +0 -0
  105. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_tuple_with_bug.py +0 -0
  106. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_type_param_defaults.py +0 -0
  107. {python_minifier-3.2.0 → python_minifier-3.4.0}/test/test_utf8_encoding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_minifier
3
- Version: 3.2.0
3
+ Version: 3.4.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
@@ -26,13 +26,14 @@ Classifier: Programming Language :: Python :: 3.11
26
26
  Classifier: Programming Language :: Python :: 3.12
27
27
  Classifier: Programming Language :: Python :: 3.13
28
28
  Classifier: Programming Language :: Python :: 3.14
29
+ Classifier: Programming Language :: Python :: 3.15
29
30
  Classifier: Programming Language :: Python :: 2
30
31
  Classifier: Programming Language :: Python :: 2.7
31
32
  Classifier: Programming Language :: Python :: Implementation :: CPython
32
33
  Classifier: Programming Language :: Python :: Implementation :: PyPy
33
34
  Classifier: Intended Audience :: Developers
34
35
  Classifier: Topic :: Software Development
35
- Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.15
36
+ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.16
36
37
  Description-Content-Type: text/markdown
37
38
  License-File: LICENSE
38
39
  Dynamic: author
@@ -54,7 +55,7 @@ Transforms Python source code into its most compact representation.
54
55
 
55
56
  [Try it out!](https://python-minifier.com)
56
57
 
57
- python-minifier currently supports Python 2.7 and Python 3.3 to 3.14. Previous releases supported Python 2.6.
58
+ python-minifier currently supports Python 2.7 and Python 3.3 to 3.15. Previous releases supported Python 2.6.
58
59
 
59
60
  * [PyPI](https://pypi.org/project/python-minifier/)
60
61
  * [Documentation](https://dflook.github.io/python-minifier/)
@@ -160,7 +161,7 @@ and outputs source code compatible with the version of the interpreter it is run
160
161
  This means that if you minify code written for Python 3.11 using python-minifier running with Python 3.12,
161
162
  the minified code may only run with Python 3.12.
162
163
 
163
- python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.14.
164
+ python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.15.
164
165
 
165
166
  ## Usage
166
167
 
@@ -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.14. Previous releases supported Python 2.6.
7
+ python-minifier currently supports Python 2.7 and Python 3.3 to 3.15. 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/)
@@ -110,7 +110,7 @@ and outputs source code compatible with the version of the interpreter it is run
110
110
  This means that if you minify code written for Python 3.11 using python-minifier running with Python 3.12,
111
111
  the minified code may only run with Python 3.12.
112
112
 
113
- python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.14.
113
+ python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.15.
114
114
 
115
115
  ## Usage
116
116
 
@@ -26,8 +26,8 @@ setup(
26
26
  long_description=long_desc,
27
27
  long_description_content_type='text/markdown',
28
28
 
29
- python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.15',
30
- version='3.2.0',
29
+ python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.16',
30
+ version='3.4.0',
31
31
 
32
32
  classifiers=[
33
33
  'Development Status :: 5 - Production/Stable',
@@ -46,6 +46,7 @@ setup(
46
46
  'Programming Language :: Python :: 3.12',
47
47
  'Programming Language :: Python :: 3.13',
48
48
  'Programming Language :: Python :: 3.14',
49
+ 'Programming Language :: Python :: 3.15',
49
50
  'Programming Language :: Python :: 2',
50
51
  'Programming Language :: Python :: 2.7',
51
52
  'Programming Language :: Python :: Implementation :: CPython',
@@ -25,6 +25,7 @@ from python_minifier.transforms.constant_folding import FoldConstants
25
25
  from python_minifier.transforms.remove_annotations import RemoveAnnotations
26
26
  from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
27
27
  from python_minifier.transforms.remove_asserts import RemoveAsserts
28
+ from python_minifier.transforms.remove_dead_branches import RemoveDeadBranches
28
29
  from python_minifier.transforms.remove_debug import RemoveDebug
29
30
  from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call
30
31
  from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone
@@ -74,6 +75,7 @@ def minify(
74
75
  remove_builtin_exception_brackets=True,
75
76
  constant_folding=True,
76
77
  prefer_single_line=False,
78
+ remove_dead_branches=True
77
79
  ):
78
80
  """
79
81
  Minify a python module
@@ -109,6 +111,7 @@ def minify(
109
111
  :param bool remove_builtin_exception_brackets: If brackets should be removed when raising exceptions with no arguments
110
112
  :param bool constant_folding: If literal expressions should be evaluated
111
113
  :param bool prefer_single_line: If semi-colons should be preferred over newlines where there is no difference in output size
114
+ :param bool remove_dead_branches: If if-statements with a constant False test should be removed
112
115
 
113
116
  :rtype: str
114
117
 
@@ -155,12 +158,15 @@ def minify(
155
158
  if remove_debug:
156
159
  module = RemoveDebug()(module)
157
160
 
158
- if remove_explicit_return_none:
159
- module = RemoveExplicitReturnNone()(module)
160
-
161
161
  if constant_folding:
162
162
  module = FoldConstants()(module)
163
163
 
164
+ if remove_dead_branches:
165
+ module = RemoveDeadBranches()(module)
166
+
167
+ if remove_explicit_return_none:
168
+ module = RemoveExplicitReturnNone()(module)
169
+
164
170
  bind_names(module)
165
171
  resolve_names(module)
166
172
 
@@ -29,7 +29,8 @@ def minify(
29
29
  remove_explicit_return_none: bool = ...,
30
30
  remove_builtin_exception_brackets: bool = ...,
31
31
  constant_folding: bool = ...,
32
- prefer_single_line: bool = ...
32
+ prefer_single_line: bool = ...,
33
+ remove_dead_branches: bool = ...
33
34
  ) -> Text: ...
34
35
 
35
36
 
@@ -10,7 +10,6 @@ from python_minifier.transforms.remove_annotations_options import RemoveAnnotati
10
10
 
11
11
  class MinificationNotBeneficialError(Exception):
12
12
  """Raised when minification results in larger output than the original."""
13
- pass
14
13
 
15
14
  def stdout_write_bytes(data):
16
15
  """Write bytes to stdout with proper Python 2.7/3.x compatibility."""
@@ -250,6 +249,12 @@ def parse_args():
250
249
  help='Disable evaluating literal expressions',
251
250
  dest='constant_folding',
252
251
  )
252
+ minification_options.add_argument(
253
+ '--no-remove-dead-branches',
254
+ action='store_false',
255
+ help='Disable removing branches that are never executed',
256
+ dest='remove_dead_branches',
257
+ )
253
258
 
254
259
  annotation_options = parser.add_argument_group('remove annotations options', 'Options that affect how annotations are removed')
255
260
  annotation_options.add_argument(
@@ -305,6 +310,10 @@ def parse_args():
305
310
  sys.stderr.write('error: --remove-class-attribute-annotations would do nothing when used with --no-remove-annotations\n')
306
311
  sys.exit(1)
307
312
 
313
+ if args.remove_debug and not args.remove_dead_branches:
314
+ sys.stderr.write('error: --remove-debug would do nothing when used with --no-remove-dead-branches\n')
315
+ sys.exit(1)
316
+
308
317
  return args
309
318
 
310
319
 
@@ -221,9 +221,7 @@ class ExpressionPrinter(object):
221
221
  right_precedence = self.precedence(node.operand)
222
222
  op_precedence = self.precedence(node)
223
223
 
224
- if right_precedence != 0 and (
225
- (op_precedence > right_precedence)
226
- ):
224
+ if right_precedence != 0 and op_precedence > right_precedence:
227
225
  self.printer.delimiter('(')
228
226
  self._expression(node.operand)
229
227
  self.printer.delimiter(')')
@@ -526,9 +524,15 @@ class ExpressionPrinter(object):
526
524
 
527
525
  def visit_DictComp(self, node):
528
526
  self.printer.delimiter('{')
529
- self._expression(node.key)
530
- self.printer.delimiter(':')
531
- self._expression(node.value)
527
+
528
+ if node.value is None:
529
+ self.printer.operator('**')
530
+ self._expression(node.key)
531
+ else:
532
+ self._expression(node.key)
533
+ self.printer.delimiter(':')
534
+ self._expression(node.value)
535
+
532
536
  [self.visit_comprehension(x) for x in node.generators]
533
537
  self.printer.delimiter('}')
534
538
 
@@ -234,6 +234,8 @@ class ModulePrinter(ExpressionPrinter):
234
234
  def visit_Import(self, node):
235
235
  assert isinstance(node, ast.Import)
236
236
 
237
+ if getattr(node, 'is_lazy', 0) == 1:
238
+ self.printer.keyword('lazy')
237
239
  self.printer.keyword('import')
238
240
 
239
241
  delimiter = Delimiter(self.printer)
@@ -246,6 +248,9 @@ class ModulePrinter(ExpressionPrinter):
246
248
  def visit_ImportFrom(self, node):
247
249
  assert isinstance(node, ast.ImportFrom)
248
250
 
251
+ if getattr(node, 'is_lazy', 0) == 1:
252
+ self.printer.keyword('lazy')
253
+
249
254
  self.printer.keyword('from')
250
255
  for _i in range(node.level):
251
256
  self.printer.delimiter('.')
@@ -602,7 +607,7 @@ class ModulePrinter(ExpressionPrinter):
602
607
 
603
608
  if node.guard is not None:
604
609
  self.printer.keyword('if')
605
- self.visit(node.guard)
610
+ self._expression(node.guard)
606
611
 
607
612
  self.printer.delimiter(':')
608
613
  self._suite(node.body)
@@ -104,7 +104,9 @@ def add_parent_to_comprehension(node, namespace):
104
104
  add_parent(node.elt, namespace=node)
105
105
  elif hasattr(node, 'key'):
106
106
  add_parent(node.key, namespace=node)
107
- add_parent(node.value, namespace=node)
107
+
108
+ if node.value is not None:
109
+ add_parent(node.value, namespace=node)
108
110
 
109
111
  iter_namespace = namespace
110
112
  for generator in node.generators:
@@ -1,3 +1,5 @@
1
+ from collections import OrderedDict
2
+
1
3
  import python_minifier.ast_compat as ast
2
4
  from python_minifier.ast_annotation import get_parent, set_parent
3
5
 
@@ -118,7 +120,7 @@ class HoistLiterals(NodeVisitor):
118
120
  def __call__(self, module, ignore_slots=True):
119
121
  self.module = module
120
122
  self._ignore_slots = ignore_slots
121
- self._hoisted = {}
123
+ self._hoisted = OrderedDict()
122
124
  self.visit(module)
123
125
  self.place_bindings()
124
126
 
@@ -130,7 +130,7 @@ class TokenPrinter(object):
130
130
  'or', 'pass', 'raise', 'return',
131
131
  'try', 'while', 'with', 'yield', '_',
132
132
  'case', 'match', 'print', 'exec',
133
- 'type'
133
+ 'type', 'lazy'
134
134
  ]
135
135
 
136
136
  if self.previous_token in [TokenTypes.Identifier, TokenTypes.Keyword, TokenTypes.SoftKeyword, TokenTypes.NumberLiteral]:
@@ -138,7 +138,7 @@ class TokenPrinter(object):
138
138
 
139
139
  self._code += kw
140
140
 
141
- if kw in ['_', 'case', 'match', 'type']:
141
+ if kw in ['_', 'case', 'match', 'type', 'lazy']:
142
142
  self.previous_token = TokenTypes.SoftKeyword
143
143
  else:
144
144
  self.previous_token = TokenTypes.Keyword
@@ -0,0 +1,119 @@
1
+ import python_minifier.ast_compat as ast
2
+
3
+ from python_minifier.transforms.suite_transformer import SuiteTransformer
4
+
5
+
6
+ def import_kwargs(import_node):
7
+ lazy = getattr(import_node, 'is_lazy', None)
8
+ if lazy is None:
9
+ return {}
10
+ return {'is_lazy': lazy}
11
+
12
+
13
+ class CombineImports(SuiteTransformer):
14
+ """
15
+ Combine multiple import statements where possible
16
+
17
+ This doesn't change the order of imports
18
+
19
+ """
20
+
21
+ def _combine_import(self, node_list, parent):
22
+
23
+ prev_import = None
24
+ alias = []
25
+
26
+ def can_merge_with_previous_group(statement):
27
+ if not isinstance(statement, ast.Import):
28
+ return False
29
+
30
+ if prev_import is None:
31
+ return True
32
+
33
+ if getattr(statement, 'is_lazy', None) != getattr(prev_import, 'is_lazy', None):
34
+ return False
35
+
36
+ return True
37
+
38
+ def can_create_group(statement):
39
+ return isinstance(statement, ast.Import)
40
+
41
+ for statement in node_list:
42
+ if can_merge_with_previous_group(statement):
43
+ prev_import = statement
44
+ alias += statement.names
45
+ else:
46
+ if alias:
47
+ yield self.add_child(ast.Import(names=alias, **import_kwargs(prev_import)), parent=parent, namespace=prev_import.namespace)
48
+ prev_import = None
49
+ alias = []
50
+
51
+ if can_create_group(statement):
52
+ prev_import = statement
53
+ alias += statement.names
54
+ else:
55
+ yield statement
56
+
57
+ if alias:
58
+ yield self.add_child(ast.Import(names=alias, **import_kwargs(prev_import)), parent=parent, namespace=prev_import.namespace)
59
+
60
+ def _combine_import_from(self, node_list, parent):
61
+
62
+ prev_import = None
63
+ alias = []
64
+
65
+ def can_merge_with_previous_group(statement):
66
+ if not isinstance(statement, ast.ImportFrom):
67
+ return False
68
+
69
+ if len(statement.names) == 1 and statement.names[0].name == '*':
70
+ return False
71
+
72
+ if prev_import is None:
73
+ return True
74
+
75
+ if getattr(statement, 'is_lazy', None) != getattr(prev_import, 'is_lazy', None):
76
+ return False
77
+
78
+ if statement.module == prev_import.module and statement.level == prev_import.level:
79
+ return True
80
+
81
+ return False
82
+
83
+ def can_create_group(statement):
84
+ if not isinstance(statement, ast.ImportFrom):
85
+ return False
86
+
87
+ if len(statement.names) == 1 and statement.names[0].name == '*':
88
+ return False
89
+
90
+ return True
91
+
92
+ for statement in node_list:
93
+ if can_merge_with_previous_group(statement):
94
+ prev_import = statement
95
+ alias += statement.names
96
+ else:
97
+ if alias:
98
+ yield self.add_child(
99
+ ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level, **import_kwargs(prev_import)), parent=parent, namespace=prev_import.namespace
100
+ )
101
+ prev_import = None
102
+ alias = []
103
+
104
+ if can_create_group(statement):
105
+ prev_import = statement
106
+ alias += statement.names
107
+ else:
108
+ yield statement
109
+
110
+ if alias:
111
+ yield self.add_child(
112
+ ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level, **import_kwargs(prev_import)), parent=parent, namespace=prev_import.namespace
113
+ )
114
+
115
+ def suite(self, node_list, parent):
116
+ a = list(self._combine_import(node_list, parent))
117
+ b = list(self._combine_import_from(a, parent))
118
+
119
+ return [self.visit(n) for n in b]
@@ -14,7 +14,7 @@ def is_foldable_constant(node):
14
14
  """
15
15
  Check if a node is a constant expression that can participate in folding.
16
16
 
17
- We can asume that children have already been folded, so foldable constants are either:
17
+ We can assume that children have already been folded, so foldable constants are either:
18
18
  - Simple literals (Num, NameConstant)
19
19
  - UnaryOp(USub/Invert) on a Num - these don't fold to shorter forms,
20
20
  so they remain after child visiting. UAdd and Not would have been
@@ -42,7 +42,14 @@ class FoldConstants(SuiteTransformer):
42
42
  # Evaluate the expression
43
43
  try:
44
44
  original_expression = unparse_expression(node)
45
- original_value = safe_eval(original_expression)
45
+
46
+ if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Invert) and is_constant_node(node.operand, ast.NameConstant) and isinstance(node.operand.value, bool):
47
+ # ~ on a bool is deprecated from python 3.12, to be removed in 3.16.
48
+ if sys.version_info >= (3, 16):
49
+ return node
50
+ original_value = ~int(node.operand.value)
51
+ else:
52
+ original_value = safe_eval(original_expression)
46
53
  except Exception:
47
54
  return node
48
55
 
@@ -119,6 +126,30 @@ class FoldConstants(SuiteTransformer):
119
126
 
120
127
  return self.fold(node)
121
128
 
129
+ def visit_BoolOp(self, node):
130
+ node.values = [self.visit(v) for v in node.values]
131
+
132
+ # Check this is a constant expression that could be folded
133
+ if not all(is_foldable_constant(v) for v in node.values):
134
+ return node
135
+
136
+ return self.fold(node)
137
+
138
+ def visit_Compare(self, node):
139
+ node.left = self.visit(node.left)
140
+ node.comparators = [self.visit(c) for c in node.comparators]
141
+
142
+ operands = [node.left] + node.comparators
143
+ if not all(is_foldable_constant(n) for n in operands):
144
+ return node
145
+
146
+ if any(isinstance(op, (ast.Is, ast.IsNot)) for op in node.ops):
147
+ # Identity is only language-guaranteed for the singletons
148
+ if not all(is_constant_node(n, ast.NameConstant) for n in operands):
149
+ return node
150
+
151
+ return self.fold(node)
152
+
122
153
  def visit_UnaryOp(self, node):
123
154
 
124
155
  node.operand = self.visit(node.operand)