python-minifier
Advanced tools
+2
-2
| Metadata-Version: 2.4 | ||
| Name: python_minifier | ||
| Version: 3.1.0 | ||
| Version: 3.1.1 | ||
| Summary: Transform Python source code into it's most compact representation | ||
@@ -163,3 +163,3 @@ Home-page: https://github.com/dflook/python-minifier | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.13. | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.14. | ||
@@ -166,0 +166,0 @@ ## Usage |
+1
-1
@@ -113,3 +113,3 @@ # Python Minifier | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.13. | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.14. | ||
@@ -116,0 +116,0 @@ ## Usage |
+1
-1
@@ -30,3 +30,3 @@ import os.path | ||
| python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.15', | ||
| version='3.1.0', | ||
| version='3.1.1', | ||
@@ -33,0 +33,0 @@ classifiers=[ |
| Metadata-Version: 2.4 | ||
| Name: python_minifier | ||
| Version: 3.1.0 | ||
| Version: 3.1.1 | ||
| Summary: Transform Python source code into it's most compact representation | ||
@@ -163,3 +163,3 @@ Home-page: https://github.com/dflook/python-minifier | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.13. | ||
| python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.14. | ||
@@ -166,0 +166,0 @@ ## Usage |
@@ -66,3 +66,3 @@ import python_minifier.ast_compat as ast | ||
| continue | ||
| if field == 'str' and hasattr(ast, 'Interpolation') and isinstance(l_ast, ast.Interpolation): | ||
@@ -69,0 +69,0 @@ continue |
@@ -80,2 +80,2 @@ """ | ||
| if _node_type not in globals(): | ||
| globals()[_node_type] = type(_node_type, (AST,), {}) | ||
| globals()[_node_type] = type(_node_type, (AST,), {}) |
@@ -304,4 +304,4 @@ import sys | ||
| (op_precedence > value_precedence) | ||
| or op_precedence == value_precedence | ||
| and self._is_left_associative(node.op) | ||
| or (op_precedence == value_precedence | ||
| and self._is_left_associative(node.op)) | ||
| ): | ||
@@ -308,0 +308,0 @@ self.printer.delimiter('(') |
@@ -599,3 +599,3 @@ import sys | ||
| if isinstance(node.pattern, ast.MatchSequence): | ||
| self.visit_MatchSequence(node.pattern, open=True) | ||
| self.visit_MatchSequence(node.pattern, omit_brackets=True) | ||
| else: | ||
@@ -630,6 +630,6 @@ self.pattern(node.pattern) | ||
| def visit_MatchSequence(self, node, open=False): | ||
| def visit_MatchSequence(self, node, omit_brackets=False): | ||
| assert isinstance(node, ast.MatchSequence) | ||
| if len(node.patterns) < 2 or not open: | ||
| if len(node.patterns) < 2 or not omit_brackets: | ||
| self.printer.delimiter('[') | ||
@@ -642,3 +642,3 @@ | ||
| if len(node.patterns) < 2 or not open: | ||
| if len(node.patterns) < 2 or not omit_brackets: | ||
| self.printer.delimiter(']') | ||
@@ -645,0 +645,0 @@ |
@@ -38,3 +38,3 @@ import python_minifier.ast_compat as ast | ||
| def comp(tup): | ||
| namespace, binding = tup | ||
| _namespace, binding = tup | ||
| return binding.new_mention_count() | ||
@@ -41,0 +41,0 @@ |
@@ -106,3 +106,3 @@ """Tools for assembling python code from tokens.""" | ||
| return self._code | ||
| def __unicode__(self): | ||
@@ -109,0 +109,0 @@ """Return the output code as unicode (for Python 2.7 compatibility).""" |
@@ -30,14 +30,34 @@ import sys | ||
| if isinstance(node.test, ast.Name) and node.test.id == '__debug__': | ||
| return True | ||
| def is_simple_debug_check(): | ||
| # Simple case: if __debug__: | ||
| if isinstance(node.test, ast.Name) and node.test.id == '__debug__': | ||
| return True | ||
| return False | ||
| if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.Is) and self.constant_value(node.test.comparators[0]) is True: | ||
| return True | ||
| def is_truthy_debug_comparison(): | ||
| # Comparison case: if __debug__ is True / False / etc. | ||
| if not isinstance(node.test, ast.Compare): | ||
| return False | ||
| if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.IsNot) and self.constant_value(node.test.comparators[0]) is False: | ||
| return True | ||
| if not isinstance(node.test.left, ast.Name): | ||
| return False | ||
| if isinstance(node.test, ast.Compare) and len(node.test.ops) == 1 and isinstance(node.test.ops[0], ast.Eq) and self.constant_value(node.test.comparators[0]) is True: | ||
| if node.test.left.id != '__debug__': | ||
| return False | ||
| if len(node.test.ops) == 1: | ||
| op = node.test.ops[0] | ||
| comparator_value = self.constant_value(node.test.comparators[0]) | ||
| if isinstance(op, ast.Is) and comparator_value is True: | ||
| return True | ||
| if isinstance(op, ast.IsNot) and comparator_value is False: | ||
| return True | ||
| if isinstance(op, ast.Eq) and comparator_value is True: | ||
| return True | ||
| return False | ||
| if is_simple_debug_check() or is_truthy_debug_comparison(): | ||
| return True | ||
| return False | ||
@@ -44,0 +64,0 @@ |
@@ -33,3 +33,3 @@ import pytest | ||
| add_parent(tree) | ||
| with pytest.raises(ValueError): | ||
| with pytest.raises(ValueError, match="Node has no parent"): | ||
| get_parent(tree) | ||
@@ -40,3 +40,3 @@ | ||
| tree = ast.parse('a = 1') | ||
| with pytest.raises(ValueError): | ||
| with pytest.raises(ValueError, match="Node has no parent"): | ||
| get_parent(tree.body[0]) | ||
@@ -43,0 +43,0 @@ |
+1
-1
@@ -6,2 +6,2 @@ """Pytest configuration and fixtures for python-minifier tests.""" | ||
| # Tests can explicitly unset this if they need to test size-based behavior | ||
| os.environ.setdefault('PYMINIFY_FORCE_BEST_EFFORT', '1') | ||
| os.environ.setdefault('PYMINIFY_FORCE_BEST_EFFORT', '1') |
@@ -11,20 +11,19 @@ """Subprocess compatibility utilities for Python 2.7/3.x.""" | ||
| input_bytes = input_data.encode('utf-8') if isinstance(input_data, str) else input_data | ||
| return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||
| return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||
| input=input_bytes, timeout=timeout, env=env) | ||
| else: | ||
| # Python 2.7, 3.3, 3.4 - no subprocess.run, no timeout support | ||
| popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||
| stdin=subprocess.PIPE if input_data else None, env=env) | ||
| # For Python 3.3/3.4, communicate() doesn't support timeout | ||
| # Also, Python 3.x needs bytes for stdin, Python 2.x needs str | ||
| if input_data and sys.version_info[0] >= 3 and isinstance(input_data, str): | ||
| input_data = input_data.encode('utf-8') | ||
| stdout, stderr = popen.communicate(input_data) | ||
| # Create a simple result object similar to subprocess.CompletedProcess | ||
| class Result: | ||
| def __init__(self, returncode, stdout, stderr): | ||
| self.returncode = returncode | ||
| self.stdout = stdout | ||
| self.stderr = stderr | ||
| return Result(popen.returncode, stdout, stderr) | ||
| # Python 2.7, 3.3, 3.4 - no subprocess.run, no timeout support | ||
| popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | ||
| stdin=subprocess.PIPE if input_data else None, env=env) | ||
| # For Python 3.3/3.4, communicate() doesn't support timeout | ||
| # Also, Python 3.x needs bytes for stdin, Python 2.x needs str | ||
| if input_data and sys.version_info[0] >= 3 and isinstance(input_data, str): | ||
| input_data = input_data.encode('utf-8') | ||
| stdout, stderr = popen.communicate(input_data) | ||
| # Create a simple result object similar to subprocess.CompletedProcess | ||
| class Result: | ||
| def __init__(self, returncode, stdout, stderr): | ||
| self.returncode = returncode | ||
| self.stdout = stdout | ||
| self.stderr = stderr | ||
| return Result(popen.returncode, stdout, stderr) | ||
@@ -39,2 +38,2 @@ | ||
| return data.decode(encoding, 'replace') | ||
| return data | ||
| return data |
@@ -20,20 +20,20 @@ """Tests for CLI size-based output selection (best effort) functionality.""" | ||
| ''' | ||
| with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: | ||
| f.write(code) | ||
| temp_file = f.name | ||
| try: | ||
| env = os.environ.copy() | ||
| env.pop('PYMINIFY_FORCE_BEST_EFFORT', None) | ||
| result = run_subprocess([ | ||
| sys.executable, '-m', 'python_minifier', temp_file | ||
| ], timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
| stdout_text = safe_decode(result.stdout) | ||
| assert len(stdout_text) < len(code) | ||
| finally: | ||
@@ -46,20 +46,20 @@ os.unlink(temp_file) | ||
| code = 'True if 0in x else False' | ||
| with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: | ||
| f.write(code) | ||
| temp_file = f.name | ||
| try: | ||
| env = os.environ.copy() | ||
| env.pop('PYMINIFY_FORCE_BEST_EFFORT', None) | ||
| result = run_subprocess([ | ||
| sys.executable, '-m', 'python_minifier', temp_file | ||
| ], timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
| stdout_text = safe_decode(result.stdout) | ||
| assert stdout_text == code | ||
| finally: | ||
@@ -73,20 +73,20 @@ os.unlink(temp_file) | ||
| expected_output = 'True if 0 in x else False' | ||
| with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: | ||
| f.write(code) | ||
| temp_file = f.name | ||
| try: | ||
| env = os.environ.copy() | ||
| env['PYMINIFY_FORCE_BEST_EFFORT'] = '1' | ||
| result = run_subprocess([ | ||
| sys.executable, '-m', 'python_minifier', temp_file | ||
| ], timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
| stdout_text = safe_decode(result.stdout) | ||
| assert stdout_text == expected_output | ||
| finally: | ||
@@ -100,22 +100,22 @@ os.unlink(temp_file) | ||
| expected_output = 'True if 0 in x else False' | ||
| # Without env var - should return original | ||
| env = os.environ.copy() | ||
| env.pop('PYMINIFY_FORCE_BEST_EFFORT', None) | ||
| result = run_subprocess([ | ||
| sys.executable, '-m', 'python_minifier', '-' | ||
| ], input_data=code, timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
| stdout_text = safe_decode(result.stdout) | ||
| assert stdout_text == code | ||
| # With env var - should return minified | ||
| env['PYMINIFY_FORCE_BEST_EFFORT'] = '1' | ||
| result = run_subprocess([ | ||
| sys.executable, '-m', 'python_minifier', '-' | ||
| ], input_data=code, timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
@@ -129,14 +129,14 @@ stdout_text = safe_decode(result.stdout) | ||
| code = 'True if 0in x else False' | ||
| with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as input_file: | ||
| input_file.write(code) | ||
| input_filename = input_file.name | ||
| with tempfile.NamedTemporaryFile(delete=False) as output_file: | ||
| output_filename = output_file.name | ||
| try: | ||
| env = os.environ.copy() | ||
| env.pop('PYMINIFY_FORCE_BEST_EFFORT', None) | ||
| result = run_subprocess([ | ||
@@ -146,10 +146,10 @@ sys.executable, '-m', 'python_minifier', | ||
| ], timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
| with open(output_filename, 'r') as f: | ||
| output_content = f.read() | ||
| assert output_content == code | ||
| finally: | ||
@@ -163,11 +163,11 @@ os.unlink(input_filename) | ||
| code = 'True if 0in x else False' | ||
| with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: | ||
| f.write(code) | ||
| temp_file = f.name | ||
| try: | ||
| env = os.environ.copy() | ||
| env.pop('PYMINIFY_FORCE_BEST_EFFORT', None) | ||
| result = run_subprocess([ | ||
@@ -177,11 +177,11 @@ sys.executable, '-m', 'python_minifier', | ||
| ], timeout=30, env=env) | ||
| assert result.returncode == 0 | ||
| with open(temp_file, 'r') as f: | ||
| modified_content = f.read() | ||
| assert modified_content == code | ||
| finally: | ||
| os.unlink(temp_file) | ||
| os.unlink(temp_file) |
@@ -9,3 +9,3 @@ import sys | ||
| def test_module_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -28,3 +28,3 @@ | ||
| def test_lambda_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -52,3 +52,3 @@ | ||
| def test_function_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -90,3 +90,3 @@ | ||
| def test_generator_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -110,3 +110,3 @@ | ||
| def test_multi_generator_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -133,3 +133,3 @@ | ||
| def test_multi_generator_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -159,3 +159,3 @@ | ||
| def test_nested_generator(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -186,3 +186,3 @@ | ||
| def test_nested_generator_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -214,3 +214,3 @@ | ||
| def test_setcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -234,3 +234,3 @@ | ||
| def test_multi_setcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -257,3 +257,3 @@ | ||
| def test_multi_setcomp_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -283,3 +283,3 @@ | ||
| def test_nested_setcomp(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -310,3 +310,3 @@ | ||
| def test_nested_setcomp_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -337,3 +337,3 @@ | ||
| def test_listcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -357,3 +357,3 @@ | ||
| def test_multi_listcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -380,3 +380,3 @@ | ||
| def test_multi_listcomp_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -406,3 +406,3 @@ | ||
| def test_nested_listcomp(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -433,3 +433,3 @@ | ||
| def test_nested_listcomp_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -460,3 +460,3 @@ | ||
| def test_dictcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -480,3 +480,3 @@ | ||
| def test_multi_dictcomp_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -503,3 +503,3 @@ | ||
| def test_multi_dictcomp_namespace_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -529,3 +529,3 @@ | ||
| def test_nested_dictcomp(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -556,3 +556,3 @@ | ||
| def test_nested_dictcomp_2(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -581,3 +581,3 @@ | ||
| def test_class_namespace(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -616,3 +616,3 @@ | ||
| def test_class_name_rebinding(): | ||
| if sys.version_info < (3, 3) or sys.version_info > (3, 4): | ||
| if sys.version_info < (3, 3) or sys.version_info >= (3, 4): | ||
| pytest.skip('Test is for python3.3 only') | ||
@@ -619,0 +619,0 @@ |
@@ -50,4 +50,4 @@ import ast | ||
| # statement = '''print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")''' | ||
| # assert unparse(ast.parse(statement)) == statement | ||
| statement = '''print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")''' | ||
| assert unparse(ast.parse(statement)) == statement | ||
@@ -82,5 +82,5 @@ statement = '''f"Magic wand: {bag["wand"]}"''' | ||
| # statement = '''f"{"":*^{1:{1:{1}}}}"''' | ||
| # assert unparse(ast.parse(statement)) == statement | ||
| # SyntaxError: f-string: expressions nested too deeply | ||
| # Verify that Python raises SyntaxError for f-strings nested too deeply | ||
| with pytest.raises(SyntaxError, match="expressions nested too deeply"): | ||
| ast.parse('''f"{"":*^{1:{1:{1}}}}"''') | ||
@@ -87,0 +87,0 @@ statement = '''f"___{ |
@@ -11,3 +11,3 @@ import sys | ||
| @pytest.mark.filterwarnings("ignore:ast.Str is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Bytes is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Bytes is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Num is deprecated:DeprecationWarning") | ||
@@ -40,3 +40,3 @@ @pytest.mark.filterwarnings("ignore:ast.NameConstant is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Str is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Bytes is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Bytes is deprecated:DeprecationWarning") | ||
| @pytest.mark.filterwarnings("ignore:ast.Num is deprecated:DeprecationWarning") | ||
@@ -66,5 +66,5 @@ @pytest.mark.filterwarnings("ignore:ast.NameConstant is deprecated:DeprecationWarning") | ||
| pytest.skip('ast_compat types test only for Python 3.14+') | ||
| import python_minifier.ast_compat as ast_compat | ||
| # Test that ast_compat provides the removed types | ||
@@ -85,5 +85,5 @@ assert is_constant_node(ast_compat.Str('a'), ast_compat.Str) | ||
| pytest.skip('ast_compat constant test only for Python 3.14+') | ||
| import python_minifier.ast_compat as ast_compat | ||
| # Test that Constant nodes work with ast_compat types | ||
@@ -90,0 +90,0 @@ assert is_constant_node(ast.Constant('a'), ast_compat.Str) |
@@ -14,3 +14,3 @@ """ | ||
| @pytest.mark.parametrize('source,description', [ | ||
| @pytest.mark.parametrize(('source', 'description'), [ | ||
| # Raw f-string backslash tests - core regression fix | ||
@@ -56,3 +56,3 @@ pytest.param(r'rf"{x:\\xFF}"', 'Single backslash in format spec (minimal failing case)', id='raw-fstring-backslash-format-spec'), | ||
| @pytest.mark.parametrize('source,description', [ | ||
| @pytest.mark.parametrize(('source', 'description'), [ | ||
| pytest.param(r'f"{f"\\n{x}\\t"}"', 'Nested f-strings with backslashes in inner string parts', id='nested-fstring-backslashes'), | ||
@@ -59,0 +59,0 @@ pytest.param(r'f"{rf"\\xFF{y}\\n"}"', 'Nested raw f-strings with backslashes', id='nested-raw-fstring-backslashes'), |
@@ -19,3 +19,3 @@ """ | ||
| @pytest.mark.parametrize('source,description', [ | ||
| @pytest.mark.parametrize(('source', 'description'), [ | ||
| # Raw t-string backslash tests - core regression testing | ||
@@ -75,3 +75,3 @@ pytest.param(r'rt"{x:\\xFF}"', 'Single backslash in format spec (minimal case)', id='raw-tstring-backslash-format-spec'), | ||
| @pytest.mark.parametrize('source,description', [ | ||
| @pytest.mark.parametrize(('source', 'description'), [ | ||
| pytest.param(r't"{t"\\n{x}\\t"}"', 'Nested t-strings with backslashes in inner string parts', id='nested-tstring-backslashes'), | ||
@@ -100,2 +100,2 @@ pytest.param(r't"{rt"\\xFF{y}\\n"}"', 'Nested raw t-strings with backslashes', id='nested-raw-tstring-backslashes'), | ||
| actual_code = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(actual_code)) | ||
| compare_ast(expected_ast, ast.parse(actual_code)) |
@@ -170,1 +170,59 @@ import ast | ||
| compare_ast(expected_ast, actual_ast) | ||
| @pytest.mark.parametrize( | ||
| 'condition', [ | ||
| '__sandwich__', | ||
| '__sandwich__ is True', | ||
| '__sandwich__ is False', | ||
| '__sandwich__ is not False', | ||
| '__sandwich__ == True', | ||
| '__sandwich__ == __debug__', | ||
| '__sandwich() == True', | ||
| 'func() is True', | ||
| 'some_call(a, b) is True', | ||
| 'obj.method() is True', | ||
| 'obj.attr is True', | ||
| 'True is something', | ||
| 'True == something', | ||
| ] | ||
| ) | ||
| def test_no_remove_not_debug(condition): | ||
| source = ''' | ||
| value = 10 | ||
| # Not a __debug__ test | ||
| if ''' + condition + ''': | ||
| value += 1 | ||
| print(value) | ||
| ''' | ||
| expected = source | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_debug(source) | ||
| compare_ast(expected_ast, actual_ast) | ||
| def test_no_remove_is_true_in_elif_chain(): | ||
| """Regression test for issue #142 - if/elif/else with 'is True' comparisons""" | ||
| source = ''' | ||
| def check_is_internet_working(c): | ||
| url, url_hostname = get_url_and_url_hostname(c) | ||
| if is_internet_working_socket_test(c, url_hostname) is True: | ||
| c.is_internet_connected = True | ||
| elif is_internet_working_urllib_open(c, url) is True: | ||
| c.is_internet_connected = True | ||
| else: | ||
| c.is_internet_connected = False | ||
| return c.is_internet_connected | ||
| ''' | ||
| expected = source | ||
| expected_ast = ast.parse(expected) | ||
| actual_ast = remove_debug(source) | ||
| compare_ast(expected_ast, actual_ast) |
@@ -70,3 +70,3 @@ import ast | ||
| ] | ||
| for statement in statements: | ||
@@ -105,3 +105,3 @@ # Just test that it parses and round-trips correctly, don't care about exact quote style | ||
| ] | ||
| for input_statement, expected_output in test_cases: | ||
@@ -124,3 +124,3 @@ assert unparse(ast.parse(input_statement)) == expected_output | ||
| ] | ||
| for input_statement, expected_output in test_cases: | ||
@@ -155,3 +155,3 @@ assert unparse(ast.parse(input_statement)) == expected_output | ||
| ] | ||
| for statement in statements: | ||
@@ -180,10 +180,10 @@ # Test that it parses and round-trips correctly | ||
| fstring = 'f"Hello {name}"' | ||
| t_ast = ast.parse(tstring) | ||
| f_ast = ast.parse(fstring) | ||
| # Should be different node types in the expression | ||
| assert type(t_ast.body[0].value).__name__ == 'TemplateStr' | ||
| assert type(f_ast.body[0].value).__name__ == 'JoinedStr' | ||
| # But should unparse correctly | ||
@@ -203,18 +203,18 @@ assert unparse(t_ast) == tstring | ||
| ] | ||
| for statement in raw_statements: | ||
| # Raw t-strings should parse successfully | ||
| ast.parse(statement) | ||
| # Test that raw behavior is preserved in the AST even if prefix is lost | ||
| raw_backslash = 'rt"backslash \\\\n and {name}"' | ||
| regular_backslash = 't"backslash \\n and {name}"' # Only two backslashes for regular | ||
| raw_ast = ast.parse(raw_backslash) | ||
| regular_ast = ast.parse(regular_backslash) | ||
| # The AST should show different string content | ||
| raw_content = raw_ast.body[0].value.values[0].value | ||
| regular_content = regular_ast.body[0].value.values[0].value | ||
| # Raw should have literal backslash-n, regular should have actual newline | ||
@@ -232,3 +232,3 @@ assert '\\\\n' in raw_content # literal backslash-n (two chars: \ and n) | ||
| assert unparse(ast.parse('t"{value=:.2f}"')) == 't"{value=:.2f}"' | ||
| # But are lost when there's a preceding literal (same limitation as f-strings) | ||
@@ -238,3 +238,3 @@ assert unparse(ast.parse('t"Hello {name=}"')) == 't"Hello name={name!r}"' | ||
| assert unparse(ast.parse('t"Hello {name=:.2f}"')) == 't"Hello name={name:.2f}"' | ||
| # This matches f-string behavior exactly | ||
@@ -253,3 +253,3 @@ assert unparse(ast.parse('f"Hello {name=}"')) == 'f"Hello name={name!r}"' | ||
| ] | ||
| for case in complex_cases: | ||
@@ -263,2 +263,2 @@ try: | ||
| except Exception as e: | ||
| pytest.fail("Failed to handle complex case {}: {}".format(case, e)) | ||
| pytest.fail("Failed to handle complex case {}: {}".format(case, e)) |
@@ -34,2 +34,2 @@ import ast | ||
| minified = unparse(expected_ast) | ||
| compare_ast(expected_ast, ast.parse(minified)) | ||
| compare_ast(expected_ast, ast.parse(minified)) |
@@ -32,3 +32,3 @@ # -*- coding: utf-8 -*- | ||
| u"left": u"←", | ||
| u"right": u"→", | ||
| u"right": u"→", | ||
| u"up": u"↑", | ||
@@ -97,3 +97,3 @@ u"down": u"↓" | ||
| self.message = u"Héllö Wörld with àccénts!" | ||
| def get_symbols(self): | ||
@@ -100,0 +100,0 @@ return u"Symbols: ™ © ® ° ± × ÷ ≠ ≤ ≥" |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
487646
0.34%12202
0.57%