python-minifier 3.3.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.
- {python_minifier-3.3.0/src/python_minifier.egg-info → python_minifier-3.4.0}/PKG-INFO +5 -4
- {python_minifier-3.3.0 → python_minifier-3.4.0}/README.md +2 -2
- {python_minifier-3.3.0 → python_minifier-3.4.0}/setup.py +3 -2
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/expression_printer.py +9 -3
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/module_printer.py +5 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/mapper.py +3 -1
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/token_printer.py +2 -2
- python_minifier-3.4.0/src/python_minifier/transforms/combine_imports.py +119 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0/src/python_minifier.egg-info}/PKG-INFO +5 -4
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/SOURCES.txt +2 -0
- python_minifier-3.4.0/test/test_comprehension_unpacking.py +188 -0
- python_minifier-3.4.0/test/test_lazy_imports.py +338 -0
- python_minifier-3.3.0/src/python_minifier/transforms/combine_imports.py +0 -75
- {python_minifier-3.3.0 → python_minifier-3.4.0}/LICENSE +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/MANIFEST.in +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/setup.cfg +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/__init__.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/__init__.pyi +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/__main__.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/ast_annotation/__init__.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/ast_compare.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/ast_compat.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/ast_printer.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/f_string.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/ministring.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/py.typed +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/README.md +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/__init__.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/bind_names.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/binding.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/name_generator.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/rename_literals.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/renamer.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/resolve_names.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/rename/util.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/t_string.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/__init__.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/constant_folding.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_annotations.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_annotations_options.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_annotations_options.pyi +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_asserts.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_dead_branches.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_debug.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_exception_brackets.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_explicit_return_none.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_literal_statements.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_object_base.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_pass.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/remove_posargs.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/transforms/suite_transformer.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier/util.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/dependency_links.txt +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/entry_points.txt +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/top_level.txt +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/src/python_minifier.egg-info/zip-safe +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/ast_annotation/test_add_parent.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/conftest.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/helpers.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/requirements.txt +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/subprocess_compat.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_assignment_expressions.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_await_fstring.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_best_effort_cli.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_bind_names.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_bind_names_namedexpr.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_bind_names_python2.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_bind_names_python312.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_bind_names_python313.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_bind_names_python33.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_combine_imports.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_comprehension_rename.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_decorator_expressions.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_dict_expansion.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_folding.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_fstring.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_generic_types.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_hoist_literals.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_import.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_is_constant_node.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_iterable_unpacking.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_match.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_match_rename.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_name_generator.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_nonlocal.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_posargs.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_prefer_single_line.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_preserve_shebang.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_raw_fstring_backslash.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_raw_tstring_backslash.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_annotations.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_assert.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_dead_branches.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_debug.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_exception_brackets.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_explicit_return_none.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_literal_statements.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_object.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_remove_pass.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_rename_builtins.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_rename_locals.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_slice.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_template_strings.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_tuple_with_bug.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_type_param_defaults.py +0 -0
- {python_minifier-3.3.0 → python_minifier-3.4.0}/test/test_unicode_cli.py +0 -0
- {python_minifier-3.3.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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
30
|
-
version='3.
|
|
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',
|
|
@@ -524,9 +524,15 @@ class ExpressionPrinter(object):
|
|
|
524
524
|
|
|
525
525
|
def visit_DictComp(self, node):
|
|
526
526
|
self.printer.delimiter('{')
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
+
|
|
530
536
|
[self.visit_comprehension(x) for x in node.generators]
|
|
531
537
|
self.printer.delimiter('}')
|
|
532
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('.')
|
|
@@ -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
|
-
|
|
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:
|
|
@@ -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]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_minifier
|
|
3
|
-
Version: 3.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
|
@@ -64,6 +64,7 @@ test/test_bind_names_python313.py
|
|
|
64
64
|
test/test_bind_names_python33.py
|
|
65
65
|
test/test_combine_imports.py
|
|
66
66
|
test/test_comprehension_rename.py
|
|
67
|
+
test/test_comprehension_unpacking.py
|
|
67
68
|
test/test_decorator_expressions.py
|
|
68
69
|
test/test_dict_expansion.py
|
|
69
70
|
test/test_folding.py
|
|
@@ -73,6 +74,7 @@ test/test_hoist_literals.py
|
|
|
73
74
|
test/test_import.py
|
|
74
75
|
test/test_is_constant_node.py
|
|
75
76
|
test/test_iterable_unpacking.py
|
|
77
|
+
test/test_lazy_imports.py
|
|
76
78
|
test/test_match.py
|
|
77
79
|
test/test_match_rename.py
|
|
78
80
|
test/test_name_generator.py
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PEP 798 - unpacking in comprehensions, new in Python 3.15
|
|
3
|
+
|
|
4
|
+
[*L for L in lists] {*s for s in sets}
|
|
5
|
+
(*L for L in lists) {**d for d in dicts}
|
|
6
|
+
|
|
7
|
+
The dict form is represented as a DictComp whose `value` is None, with the
|
|
8
|
+
unpacked expression in `key`. The iterable forms reuse Starred in the existing
|
|
9
|
+
`elt` slot, so none of this changes any node's `_fields`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import ast
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from python_minifier import minify, unparse
|
|
18
|
+
from python_minifier.ast_compare import compare_ast
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(source):
|
|
22
|
+
"""
|
|
23
|
+
Execute a module and return the data it defines, for comparing behaviour
|
|
24
|
+
|
|
25
|
+
Functions and classes are dropped: minifying renames them, and two function
|
|
26
|
+
objects never compare equal anyway, so only the values they compute are
|
|
27
|
+
useful for comparison.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
namespace = {}
|
|
31
|
+
exec(compile(source, '<test>', 'exec'), namespace)
|
|
32
|
+
return {
|
|
33
|
+
k: v for k, v in namespace.items()
|
|
34
|
+
if not k.startswith('__') and not callable(v)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
39
|
+
@pytest.mark.parametrize(
|
|
40
|
+
'statement', [
|
|
41
|
+
'x=[*L for L in lists]',
|
|
42
|
+
'x=[*L for L in lists if L]',
|
|
43
|
+
'x=[*(a,b)for a in c]',
|
|
44
|
+
'x={*s for s in sets}',
|
|
45
|
+
'x=(*L for L in lists)',
|
|
46
|
+
'x=[*L for L in lists for M in L]'
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
def test_iterable_comprehension_unpacking_unparse(statement):
|
|
50
|
+
"""The * form in list/set/generator comprehensions survives a print/parse round trip"""
|
|
51
|
+
assert unparse(ast.parse(statement)) == statement
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
55
|
+
@pytest.mark.parametrize(
|
|
56
|
+
'statement', [
|
|
57
|
+
'x={**d for d in dicts}',
|
|
58
|
+
'x={**d for d in dicts if d}',
|
|
59
|
+
"x={**{'a':1}for a in c}"
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
def test_dict_comprehension_unpacking_unparse(statement):
|
|
63
|
+
"""The ** form in dict comprehensions survives a print/parse round trip"""
|
|
64
|
+
assert unparse(ast.parse(statement)) == statement
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
68
|
+
@pytest.mark.parametrize(
|
|
69
|
+
'statement', [
|
|
70
|
+
'x=[*L for L in lists]',
|
|
71
|
+
'x={*s for s in sets}',
|
|
72
|
+
'x=(*L for L in lists)',
|
|
73
|
+
'x={**d for d in dicts}'
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
def test_comprehension_unpacking_ast_preserved(statement):
|
|
77
|
+
"""Minifying does not change what the comprehension means"""
|
|
78
|
+
expected_ast = ast.parse(statement)
|
|
79
|
+
actual_ast = ast.parse(unparse(ast.parse(statement)))
|
|
80
|
+
compare_ast(expected_ast, actual_ast)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
84
|
+
def test_iterable_unpacking_behaviour_is_preserved():
|
|
85
|
+
source = '''
|
|
86
|
+
lists = [[1, 2], [3, 4], [5]]
|
|
87
|
+
sets = [{1, 2}, {2, 3}]
|
|
88
|
+
flat_list = [*L for L in lists]
|
|
89
|
+
flat_set = sorted({*s for s in sets})
|
|
90
|
+
flat_gen = list((*L for L in lists))
|
|
91
|
+
'''
|
|
92
|
+
assert run(minify(source)) == run(source)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
96
|
+
def test_dict_unpacking_behaviour_is_preserved():
|
|
97
|
+
source = '''
|
|
98
|
+
dicts = [{'a': 1}, {'b': 2}, {'a': 3}]
|
|
99
|
+
merged = {**d for d in dicts}
|
|
100
|
+
'''
|
|
101
|
+
assert run(minify(source)) == run(source)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
105
|
+
def test_dict_unpacking_in_function_is_preserved():
|
|
106
|
+
"""Renaming must reach inside the unpacked expression of a dict comprehension"""
|
|
107
|
+
source = '''
|
|
108
|
+
def merge(dictionaries):
|
|
109
|
+
return {**dictionary for dictionary in dictionaries}
|
|
110
|
+
result = merge([{'a': 1}, {'b': 2}])
|
|
111
|
+
'''
|
|
112
|
+
assert run(minify(source)) == run(source)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
116
|
+
def test_async_comprehension_unpacking():
|
|
117
|
+
source = 'async def f(g):return(*a async for a in g())'
|
|
118
|
+
assert unparse(ast.parse(source)) == source
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# PEP 798 only allows the unpacking operator at the *top level of the element
|
|
122
|
+
# expression*, not nested inside a subexpression. How much of an expression the
|
|
123
|
+
# operator swallows differs by comprehension kind, because the grammar rules
|
|
124
|
+
# differ - listcomp/setcomp take `star_named_expression`, genexp takes
|
|
125
|
+
# `starred_expression`, dictcomp takes `double_starred_kvpair`:
|
|
126
|
+
#
|
|
127
|
+
# [*x if c else z for x in y] SyntaxError - needs parentheses
|
|
128
|
+
# {*x if c else z for x in y} SyntaxError - needs parentheses
|
|
129
|
+
# (*x if c else z for x in y) legal - binds the whole conditional
|
|
130
|
+
# {**d if c else e for d in y} legal - binds the whole conditional
|
|
131
|
+
#
|
|
132
|
+
# The restriction has nothing to do with which `for` clause is involved;
|
|
133
|
+
# multiple and nested comprehensions are unrestricted.
|
|
134
|
+
|
|
135
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
136
|
+
@pytest.mark.parametrize(
|
|
137
|
+
'source', [
|
|
138
|
+
'q=[*x if c else z for x in y]',
|
|
139
|
+
'q={*x if c else z for x in y}',
|
|
140
|
+
'q=[**x for x in y]',
|
|
141
|
+
'q=(**x for x in y)',
|
|
142
|
+
'q={*k: v for k,v in y}'
|
|
143
|
+
]
|
|
144
|
+
)
|
|
145
|
+
def test_unpacking_restrictions_are_syntax_errors(source):
|
|
146
|
+
"""Forms PEP 798 does not allow, guarding the assumption the printer relies on"""
|
|
147
|
+
with pytest.raises(SyntaxError):
|
|
148
|
+
ast.parse(source)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
152
|
+
@pytest.mark.parametrize(
|
|
153
|
+
('source', 'expected'), [
|
|
154
|
+
# the iterable forms need the parentheses kept, or the result will not parse
|
|
155
|
+
('q=[*(x if c else z) for x in y]', 'q=[*(x if c else z)for x in y]'),
|
|
156
|
+
('q={*(x if c else z) for x in y}', 'q={*(x if c else z)for x in y}'),
|
|
157
|
+
# a tuple display element also needs its parentheses
|
|
158
|
+
('q=[(*x, *z) for x in y]', 'q=[(*x,*z)for x in y]'),
|
|
159
|
+
# the dict form does not - the trailing `for` ends the expression
|
|
160
|
+
('q={**(d if c else e) for d in y}', 'q={**d if c else e for d in y}'),
|
|
161
|
+
# the generator form does not require them either, but keeping them is
|
|
162
|
+
# still correct, and it is the only iterable form where they are optional
|
|
163
|
+
('q=(*(x if c else z) for x in y)', 'q=(*(x if c else z)for x in y)')
|
|
164
|
+
]
|
|
165
|
+
)
|
|
166
|
+
def test_unpacking_parenthesisation(source, expected):
|
|
167
|
+
"""Parentheses are kept exactly where the grammar requires them, and dropped where it does not"""
|
|
168
|
+
assert unparse(ast.parse(source)) == expected
|
|
169
|
+
compare_ast(ast.parse(source), ast.parse(unparse(ast.parse(source))))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.skipif(sys.version_info < (3, 15), reason='Comprehension unpacking requires Python 3.15+')
|
|
173
|
+
@pytest.mark.parametrize(
|
|
174
|
+
'source', [
|
|
175
|
+
'q=[*x for x in y for z in x]',
|
|
176
|
+
'q=[*x for x in y if x]',
|
|
177
|
+
'q=[[*x for x in y] for z in w]',
|
|
178
|
+
'q=[*[*x for x in y] for z in w]',
|
|
179
|
+
'q=[*(x := y) for r in s]',
|
|
180
|
+
'q=f(*x for x in y)',
|
|
181
|
+
'q=[*x + z for x in y]',
|
|
182
|
+
'q=[*(lambda: 1)() for x in y]',
|
|
183
|
+
'q=(*x if c else z for x in y)'
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
def test_unpacking_in_compound_expressions(source):
|
|
187
|
+
"""Unpacking composes with multiple for clauses, nesting, walrus and calls"""
|
|
188
|
+
compare_ast(ast.parse(source), ast.parse(unparse(ast.parse(source))))
|