New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

python-minifier

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

python-minifier - pypi Package Compare versions

Comparing version
2.9.0
to
2.10.0
+40
src/python_minifier/ast_compat.py
"""
The is a backwards compatible shim for the ast module.
This is the best way to make the ast module work the same in both python 2 and 3.
This is essentially what the ast module was doing until 3.12, when it started throwing
deprecation warnings.
"""
from ast import *
# Ideally we don't import anything else
if 'TypeAlias' in globals():
# Add n and s properties to Constant so it can stand in for Num, Str and Bytes
Constant.n = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
Constant.s = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
# These classes are redefined from the ones in ast that complain about deprecation
# They will continue to work once they are removed from ast
class Str(Constant): # type: ignore[no-redef]
def __new__(cls, s, *args, **kwargs):
return Constant(value=s, *args, **kwargs)
class Bytes(Constant): # type: ignore[no-redef]
def __new__(cls, s, *args, **kwargs):
return Constant(value=s, *args, **kwargs)
class Num(Constant): # type: ignore[no-redef]
def __new__(cls, n, *args, **kwargs):
return Constant(value=n, *args, **kwargs)
class NameConstant(Constant): # type: ignore[no-redef]
def __new__(cls, *args, **kwargs):
return Constant(*args, **kwargs)
class Ellipsis(Constant): # type: ignore[no-redef]
def __new__(cls, *args, **kwargs):
return Constant(value=literal_eval('...'), *args, **kwargs)
"""
Print a representation of an AST
This prints a human readable representation of the nodes in the AST.
The goal is to make it easy to see what the AST looks like, and to
make it easy to compare two ASTs.
This is not intended to be a complete representation of the AST, some
fields or field names may be omitted for clarity. It should still be precise and unambiguous.
"""
import python_minifier.ast_compat as ast
from python_minifier.util import is_ast_node
INDENT = ' '
# The field name that can be omitted for each node
# Either it's the only field or would otherwise be obvious
default_fields = {
'Constant': 'value',
'Num': 'n',
'Str': 's',
'Bytes': 's',
'NameConstant': 'value',
'FormattedValue': 'value',
'JoinedStr': 'values',
'List': 'elts',
'Tuple': 'elts',
'Set': 'elts',
'Name': 'id',
'Expr': 'value',
'UnaryOp': 'op',
'BinOp': 'op',
'BoolOp': 'op',
'Call': 'func',
'Index': 'value',
'ExtSlice': 'dims',
'Assert': 'test',
'Delete': 'targets',
'Import': 'names',
'If': 'test',
'While': 'test',
'Try': 'handlers',
'TryExcept': 'handlers',
'With': 'items',
'withitem': 'context_expr',
'FunctionDef': 'name',
'arg': 'arg',
'Return': 'value',
'Yield': 'value',
'YieldFrom': 'value',
'Global': 'names',
'Nonlocal': 'names',
'ClassDef': 'name',
'AsyncFunctionDef': 'name',
'Await': 'value',
'AsyncWith': 'items',
'Raise': 'exc',
'Subscript': 'value',
'Attribute': 'value',
'AugAssign': 'op',
}
def is_literal(node, field):
if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value':
return True
if is_ast_node(node, ast.Num) and field == 'n':
return True
if is_ast_node(node, ast.Str) and field == 's':
return True
if is_ast_node(node, 'Bytes') and field == 's':
return True
if is_ast_node(node, 'NameConstant') and field == 'value':
return True
return False
def print_ast(node):
if not isinstance(node, ast.AST):
return repr(node)
s = ''
node_name = node.__class__.__name__
s += node_name
s += '('
first = True
for field, value in ast.iter_fields(node):
if not value and not is_literal(node, field):
# Don't bother printing fields that are empty, except for literals
continue
if field == 'ctx':
# Don't print the ctx, it's always apparent from context
continue
if first:
first = False
else:
s += ', '
if default_fields.get(node_name) != field:
s += field + '='
if isinstance(value, ast.AST):
s += print_ast(value)
elif isinstance(value, list):
s += '['
first_list = True
for item in value:
if first_list:
first_list = False
else:
s += ','
for line in print_ast(item).splitlines():
s += '\n' + INDENT + line
s += '\n]'
else:
s += repr(value)
s += ')'
return s
import python_minifier.ast_compat as ast
import math
import sys
from python_minifier.ast_compare import compare_ast
from python_minifier.expression_printer import ExpressionPrinter
from python_minifier.transforms.suite_transformer import SuiteTransformer
from python_minifier.util import is_ast_node
class FoldConstants(SuiteTransformer):
"""
Fold Constants if it would reduce the size of the source
"""
def __init__(self):
super(FoldConstants, self).__init__()
def visit_BinOp(self, node):
node.left = self.visit(node.left)
node.right = self.visit(node.right)
# Check this is a constant expression that could be folded
# We don't try to fold strings or bytes, since they have probably been arranged this way to make the source shorter and we are unlikely to beat that
if not is_ast_node(node.left, (ast.Num, 'NameConstant')):
return node
if not is_ast_node(node.right, (ast.Num, 'NameConstant')):
return node
if isinstance(node.op, ast.Div):
# Folding div is subtle, since it can have different results in Python 2 and Python 3
# Do this once target version options have been implemented
return node
if isinstance(node.op, ast.Pow):
# This can be folded, but it is unlikely to reduce the size of the source
# It can also be slow to evaluate
return node
# Evaluate the expression
try:
original_expression = unparse_expression(node)
original_value = safe_eval(original_expression)
except Exception:
return node
# Choose the best representation of the value
if isinstance(original_value, float) and math.isnan(original_value):
# There is no nan literal.
# we could use float('nan'), but that complicates folding as it's not a Constant
return node
elif isinstance(original_value, bool):
new_node = ast.NameConstant(value=original_value)
elif isinstance(original_value, (int, float, complex)):
try:
if repr(original_value).startswith('-') and not sys.version_info < (3, 0):
# Represent negative numbers as a USub UnaryOp, so that the ast roundtrip is correct
new_node = ast.UnaryOp(op=ast.USub(), operand=ast.Num(n=-original_value))
else:
new_node = ast.Num(n=original_value)
except Exception:
# repr(value) failed, most likely due to some limit
return node
else:
return node
# Evaluate the new value representation
try:
folded_expression = unparse_expression(new_node)
folded_value = safe_eval(folded_expression)
except Exception as e:
# This can happen if the value is too large to be represented as a literal
# or if the value is unparsed as nan, inf or -inf - which are not valid python literals
return node
if len(folded_expression) >= len(original_expression):
# Result is not shorter than original expression
return node
# Check the folded expression parses back to the same AST
try:
folded_ast = ast.parse(folded_expression, 'folded expression', mode='eval')
compare_ast(new_node, folded_ast.body)
except Exception:
# This can happen if the printed value doesn't parse back to the same AST
# e.g. complex numbers can be parsed as BinOp
return node
# Check the folded value is the same as the original value
if not equal_value_and_type(folded_value, original_value):
return node
# New representation is shorter and has the same value, so use it
return self.add_child(new_node, node.parent, node.namespace)
def equal_value_and_type(a, b):
if type(a) != type(b):
return False
if isinstance(a, float) and math.isnan(a) and not math.isnan(b):
return False
return a == b
def safe_eval(expression):
globals = {}
locals = {}
# This will return the value, or could raise an exception
return eval(expression, globals, locals)
def unparse_expression(node):
expression_printer = ExpressionPrinter()
return expression_printer(node)
class RemoveAnnotationsOptions(object):
"""
Options for the RemoveAnnotations transform
This can be passed to the minify function as the remove_annotations argument
:param remove_variable_annotations: Remove variable annotations
:type remove_variable_annotations: bool
:param remove_return_annotations: Remove return annotations
:type remove_return_annotations: bool
:param remove_argument_annotations: Remove argument annotations
:type remove_argument_annotations: bool
:param remove_class_attribute_annotations: Remove class attribute annotations
:type remove_class_attribute_annotations: bool
"""
remove_variable_annotations = True
remove_return_annotations = True
remove_argument_annotations = True
remove_class_attribute_annotations = False
def __init__(self, remove_variable_annotations=True, remove_return_annotations=True, remove_argument_annotations=True, remove_class_attribute_annotations=False):
self.remove_variable_annotations = remove_variable_annotations
self.remove_return_annotations = remove_return_annotations
self.remove_argument_annotations = remove_argument_annotations
self.remove_class_attribute_annotations = remove_class_attribute_annotations
def __repr__(self):
return 'RemoveAnnotationsOptions(remove_variable_annotations=%r, remove_return_annotations=%r, remove_argument_annotations=%r, remove_class_attribute_annotations=%r)' % (self.remove_variable_annotations, self.remove_return_annotations, self.remove_argument_annotations, self.remove_class_attribute_annotations)
def __nonzero__(self):
return any((self.remove_variable_annotations, self.remove_return_annotations, self.remove_argument_annotations, self.remove_class_attribute_annotations))
def __bool__(self):
return self.__nonzero__()
class RemoveAnnotationsOptions:
remove_variable_annotations: bool
remove_return_annotations: bool
remove_argument_annotations: bool
remove_class_attribute_annotations: bool
def __init__(self,
remove_variable_annotations: bool = ...,
remove_return_annotations: bool = ...,
remove_argument_annotations: bool = ...,
remove_class_attribute_annotations: bool = ...):
...
def __repr__(self) -> str: ...
def __nonzero__(self) -> bool: ...
def __bool__(self) -> bool: ...
"""
Remove Call nodes that are only used to raise exceptions with no arguments
If a Raise statement is used on a Name and the name refers to an exception, it is automatically instantiated with no arguments
We can remove any Call nodes that are only used to raise exceptions with no arguments and let the Raise statement do the instantiation.
When printed, this essentially removes the brackets from the exception name.
We can't generally know if a name refers to an exception, so we only do this for builtin exceptions
"""
import python_minifier.ast_compat as ast
import sys
from python_minifier.rename.binding import BuiltinBinding
# These are always exceptions, in every version of python
builtin_exceptions = [
'SyntaxError', 'Exception', 'ValueError', 'BaseException', 'MemoryError', 'RuntimeError', 'DeprecationWarning', 'UnicodeEncodeError', 'KeyError', 'LookupError', 'TypeError', 'BufferError',
'ImportError', 'OSError', 'StopIteration', 'ArithmeticError', 'UserWarning', 'PendingDeprecationWarning', 'RuntimeWarning', 'IndentationError', 'UnicodeTranslateError', 'UnboundLocalError',
'AttributeError', 'EOFError', 'UnicodeWarning', 'BytesWarning', 'NameError', 'IndexError', 'TabError', 'SystemError', 'OverflowError', 'FutureWarning', 'SystemExit', 'Warning',
'FloatingPointError', 'ReferenceError', 'UnicodeError', 'AssertionError', 'SyntaxWarning', 'UnicodeDecodeError', 'GeneratorExit', 'ImportWarning', 'KeyboardInterrupt', 'ZeroDivisionError',
'NotImplementedError'
]
# These are exceptions only in python 2.7
builtin_exceptions_2_7 = [
'IOError',
'StandardError',
'EnvironmentError',
'VMSError',
'WindowsError'
]
# These are exceptions in 3.3+
builtin_exceptions_3_3 = [
'ChildProcessError',
'ConnectionError',
'BrokenPipeError',
'ConnectionAbortedError',
'ConnectionRefusedError',
'ConnectionResetError',
'FileExistsError',
'FileNotFoundError',
'InterruptedError',
'IsADirectoryError',
'NotADirectoryError',
'PermissionError',
'ProcessLookupError',
'TimeoutError',
'ResourceWarning',
]
# These are exceptions in 3.5+
builtin_exceptions_3_5 = [
'StopAsyncIteration',
'RecursionError',
]
# These are exceptions in 3.6+
builtin_exceptions_3_6 = [
'ModuleNotFoundError'
]
# These are exceptions in 3.10+
builtin_exceptions_3_10 = [
'EncodingWarning'
]
# These are exceptions in 3.11+
builtin_exceptions_3_11 = [
'BaseExceptionGroup',
'ExceptionGroup',
'BaseExceptionGroup',
]
def _remove_empty_call(binding):
assert isinstance(binding, BuiltinBinding)
for name_node in binding.references:
# For this to be a builtin, all references must be name nodes as it is not defined anywhere
assert isinstance(name_node, ast.Name) and isinstance(name_node.ctx, ast.Load)
if not isinstance(name_node.parent, ast.Call):
# This is not a call
continue
call_node = name_node.parent
if not isinstance(call_node.parent, ast.Raise):
# This is not a raise statement
continue
raise_node = call_node.parent
if len(call_node.args) > 0 or len(call_node.keywords) > 0:
# This is a call with arguments
continue
# This is an instance of the exception being called with no arguments
# let's replace it with just the name, cutting out the Call node
if raise_node.exc is call_node:
raise_node.exc = name_node
elif raise_node.cause is call_node:
raise_node.cause = name_node
name_node.parent = raise_node
def remove_no_arg_exception_call(module):
assert isinstance(module, ast.Module)
if sys.version_info < (3, 0):
return module
for binding in module.bindings:
if not isinstance(binding, BuiltinBinding):
continue
if binding.is_redefined():
continue
if binding.name in builtin_exceptions:
# We can remove any calls to builtin exceptions
_remove_empty_call(binding)
return module
import sys
import pytest
from helpers import assert_namespace_tree
def test_module_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
name_in_module = True
name_in_module = True
'''
expected_namespaces = '''
+ Module
- BuiltinBinding(name='True', allow_rename=True) <references=2>
- NameBinding(name='name_in_module', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_lambda_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
name_in_module = 0
b = lambda arg, *args, **kwargs: arg + 1
'''
expected_namespaces = '''
+ Module
- NameBinding(name='b', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=1>
+ Lambda
- NameBinding(name='arg', allow_rename=False) <references=2>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_function_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
name_in_module = True
def func(arg, *args, **kwargs):
name_in_func = True
print(name_in_module)
def inner_func():
print(name_in_module)
name_in_inner_func = True
'''
expected_namespaces = '''
+ Module
- BuiltinBinding(name='True', allow_rename=True) <references=3>
- NameBinding(name='func', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=3>
+ Function func
- NameBinding(name='arg', allow_rename=True) <references=1>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='inner_func', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
- NameBinding(name='name_in_func', allow_rename=True) <references=1>
+ Function inner_func
- NameBinding(name='name_in_inner_func', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
# region generator namespace
def test_generator_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
a = (x for x in range(10))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_generator_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = []
f = []
a = (x for x in f for x in x)
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_generator_namespace_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = (c for line in file for c in line)
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_generator():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = (c for c in (line for line in file))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='c', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_generator_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = ''
a = (x for x in (x for x in x))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region setcomp
def test_setcomp_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
a = {x for x in range(10)}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_setcomp_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = []
f = []
a = {x for x in f for x in x}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_setcomp_namespace_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = {c for line in file for c in line}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_setcomp():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = {c for c in {line for line in file}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='c', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_setcomp_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = ''
a = {x for x in {x for x in x}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region listcomp
def test_listcomp_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
a = [x for x in range(10)]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_listcomp_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = []
f = []
a = [x for x in f for x in x]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=5>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_listcomp_namespace_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = [c for line in file for c in line]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=3>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_listcomp():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a =[c for c in [line for line in file]]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=3>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_listcomp_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = ''
a =[x for x in [x for x in x]]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=6>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region dictcomp
def test_dictcomp_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
a = {x: x for x in range(10)}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_dictcomp_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = []
f = []
a = {x: x for x, x in f for x, x in x}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=7>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_dictcomp_namespace_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = {c: c for line, line in file for c, c in line}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='c', allow_rename=True) <references=4>
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_dictcomp():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
c = ''
line = ''
file = []
a = {c: c for c, c in {line: line for line in file}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=False) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='c', allow_rename=True) <references=4>
+ DictComp
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_dictcomp_2():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
x = {}
a = {x:x for x, x in {x: x for x in x}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=4>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
def test_class_namespace():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
OhALongName = 'Hello'
OhALongName = 'Hello'
MyOtherName = 'World'
def func():
class C:
OhALongName = ' World'
MyOtherName = OhALongName
ClassName = 0
func()
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyOtherName', allow_rename=True) <references=1>
- NameBinding(name='OhALongName', allow_rename=False) <references=4>
- NameBinding(name='func', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='C', allow_rename=True) <references=1>
+ Class C
- nonlocal OhALongName
- NameBinding(name='ClassName', allow_rename=False) <references=1>
- NameBinding(name='MyOtherName', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_name_rebinding():
if sys.version_info >= (3, 0):
pytest.skip('Test is for python 2 only')
source = '''
OhALongName = 'Hello'
OhALongName = 'Hello'
MyOtherName = 'World'
def func():
class C:
OhALongName = OhALongName + ' World'
MyOtherName = OhALongName
func()
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyOtherName', allow_rename=True) <references=1>
- NameBinding(name='OhALongName', allow_rename=False) <references=5>
- NameBinding(name='func', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='C', allow_rename=True) <references=1>
+ Class C
- nonlocal OhALongName
- NameBinding(name='MyOtherName', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
import sys
import pytest
from helpers import assert_namespace_tree
def test_module_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
name_in_module = True
name_in_module = True
'''
expected_namespaces = '''
+ Module
- BuiltinBinding(name='True', allow_rename=True) <references=2>
- NameBinding(name='name_in_module', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_lambda_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
name_in_module = 0
b = lambda arg, *args, **kwargs: arg + 1
'''
expected_namespaces = '''
+ Module
- NameBinding(name='b', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=1>
+ Lambda
- NameBinding(name='arg', allow_rename=False) <references=2>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_function_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
name_in_module = True
def func(arg, *args, **kwargs):
name_in_func = True
print(name_in_module)
def inner_func():
print(name_in_module)
name_in_inner_func = True
'''
expected_namespaces = '''
+ Module
- BuiltinBinding(name='True', allow_rename=True) <references=3>
- NameBinding(name='func', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=3>
- BuiltinBinding(name='print', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='arg', allow_rename=True) <references=1>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='inner_func', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
- NameBinding(name='name_in_func', allow_rename=True) <references=1>
+ Function inner_func
- NameBinding(name='name_in_inner_func', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
# region generator namespace
def test_generator_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
a = (x for x in range(10))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_generator_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = []
f = []
a = (x for x in f for x in x)
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_generator_namespace_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = (c for line in file for c in line)
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_generator():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = (c for c in (line for line in file))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='c', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_generator_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = ''
a = (x for x in (x for x in x))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region setcomp
def test_setcomp_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
a = {x for x in range(10)}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_setcomp_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = []
f = []
a = {x for x in f for x in x}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_setcomp_namespace_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = {c for line in file for c in line}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_setcomp():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = {c for c in {line for line in file}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='c', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_setcomp_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = ''
a = {x for x in {x for x in x}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region listcomp
def test_listcomp_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
a = [x for x in range(10)]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_listcomp_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = []
f = []
a = [x for x in f for x in x]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_listcomp_namespace_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = [c for line in file for c in line]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_listcomp():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a =[c for c in [line for line in file]]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='c', allow_rename=True) <references=2>
+ ListComp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_listcomp_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = ''
a =[x for x in [x for x in x]]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=2>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region dictcomp
def test_dictcomp_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
a = {x: x for x in range(10)}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_dictcomp_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = []
f = []
a = {x: x for x, x in f for x, x in x}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=7>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_dictcomp_namespace_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = {c: c for line, line in file for c, c in line}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='c', allow_rename=True) <references=4>
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_dictcomp():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
c = ''
line = ''
file = []
a = {c: c for c, c in {line: line for line in file}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='c', allow_rename=True) <references=4>
+ DictComp
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_dictcomp_2():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
x = {}
a = {x:x for x, x in {x: x for x in x}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=4>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
def test_class_namespace():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
OhALongName = 'Hello'
OhALongName = 'Hello'
MyOtherName = 'World'
def func():
class C:
OhALongName = ' World'
MyOtherName = OhALongName
ClassName = 0
func()
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyOtherName', allow_rename=True) <references=1>
- NameBinding(name='OhALongName', allow_rename=False) <references=4>
- NameBinding(name='func', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='C', allow_rename=True) <references=1>
+ Class C
- nonlocal OhALongName
- NameBinding(name='ClassName', allow_rename=False) <references=1>
- NameBinding(name='MyOtherName', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_name_rebinding():
if sys.version_info < (3, 3) or sys.version_info > (3, 4):
pytest.skip('Test is for python3.3 only')
source = '''
OhALongName = 'Hello'
OhALongName = 'Hello'
MyOtherName = 'World'
def func():
class C:
OhALongName = OhALongName + ' World'
MyOtherName = OhALongName
func()
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyOtherName', allow_rename=True) <references=1>
- NameBinding(name='OhALongName', allow_rename=False) <references=5>
- NameBinding(name='func', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='C', allow_rename=True) <references=1>
+ Class C
- nonlocal OhALongName
- NameBinding(name='MyOtherName', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
import sys
import pytest
from helpers import assert_namespace_tree
def test_module_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
name_in_module = True
name_in_module = True
'''
expected_namespaces = '''
+ Module
- NameBinding(name='name_in_module', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_lambda_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
name_in_module = True
b = lambda arg, *args, **kwargs: arg + 1
'''
expected_namespaces = '''
+ Module
- NameBinding(name='b', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=1>
+ Lambda
- NameBinding(name='arg', allow_rename=False) <references=2>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_function_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
name_in_module = True
def func(arg, *args, **kwargs):
name_in_func = True
print(name_in_module)
def inner_func():
print(name_in_module)
name_in_inner_func = True
'''
expected_namespaces = '''
+ Module
- NameBinding(name='func', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=3>
- BuiltinBinding(name='print', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='arg', allow_rename=True) <references=1>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='inner_func', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
- NameBinding(name='name_in_func', allow_rename=True) <references=1>
+ Function inner_func
- NameBinding(name='name_in_inner_func', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_async_function_namespace():
if sys.version_info < (3, 5):
pytest.skip('No async functions in python < 3.5')
source = '''
name_in_module = True
async def func(arg, *args, **kwargs):
name_in_func = True
print(name_in_module)
async def inner_func():
print(name_in_module)
name_in_inner_func = True
'''
expected_namespaces = '''
+ Module
- NameBinding(name='func', allow_rename=True) <references=1>
- NameBinding(name='name_in_module', allow_rename=True) <references=3>
- BuiltinBinding(name='print', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='arg', allow_rename=True) <references=1>
- NameBinding(name='args', allow_rename=True) <references=1>
- NameBinding(name='inner_func', allow_rename=True) <references=1>
- NameBinding(name='kwargs', allow_rename=True) <references=1>
- NameBinding(name='name_in_func', allow_rename=True) <references=1>
+ Function inner_func
- NameBinding(name='name_in_inner_func', allow_rename=True) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
# region generator namespace
def test_generator_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
a = (x for x in range(10))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_generator_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = []
f = []
a = (x for x in f for x in x)
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_generator_namespace_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = (c for line in file for c in line)
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_generator():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = (c for c in (line for line in file))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ GeneratorExp
- NameBinding(name='c', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_generator_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = ''
a = (x for x in (x for x in x))
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
+ GeneratorExp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region setcomp
def test_setcomp_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
a = {x for x in range(10)}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_setcomp_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = []
f = []
a = {x for x in f for x in x}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_setcomp_namespace_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = {c for line in file for c in line}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_setcomp():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = {c for c in {line for line in file}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ SetComp
- NameBinding(name='c', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_setcomp_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = ''
a = {x for x in {x for x in x}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
+ SetComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region listcomp
def test_listcomp_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
a = [x for x in range(10)]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_listcomp_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = []
f = []
a = [x for x in f for x in x]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=4>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_listcomp_namespace_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = [c for line in file for c in line]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='c', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_listcomp():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a =[c for c in [line for line in file]]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ ListComp
- NameBinding(name='c', allow_rename=True) <references=2>
+ ListComp
- NameBinding(name='line', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_listcomp_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = ''
a =[x for x in [x for x in x]]
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=2>
+ ListComp
- NameBinding(name='x', allow_rename=True) <references=2>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
# region dictcomp
def test_dictcomp_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
a = {x: x for x in range(10)}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- BuiltinBinding(name='range', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_dictcomp_namespace():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = []
f = []
a = {x: x for x, x in f for x, x in x}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='f', allow_rename=True) <references=2>
- NameBinding(name='x', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=7>
'''
assert_namespace_tree(source, expected_namespaces)
def test_multi_dictcomp_namespace_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = {c: c for line, line in file for c, c in line}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='c', allow_rename=True) <references=4>
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_dictcomp():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
c = ''
line = ''
file = []
a = {c: c for c, c in {line: line for line in file}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='c', allow_rename=True) <references=1>
- NameBinding(name='file', allow_rename=True) <references=2>
- NameBinding(name='line', allow_rename=True) <references=1>
+ DictComp
- NameBinding(name='c', allow_rename=True) <references=4>
+ DictComp
- NameBinding(name='line', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
def test_nested_dictcomp_2():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
x = {}
a = {x:x for x, x in {x: x for x in x}}
'''
expected_namespaces = '''
+ Module
- NameBinding(name='a', allow_rename=True) <references=1>
- NameBinding(name='x', allow_rename=True) <references=2>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=4>
+ DictComp
- NameBinding(name='x', allow_rename=True) <references=3>
'''
assert_namespace_tree(source, expected_namespaces)
# endregion
def test_class_namespace():
if sys.version_info < (3, 6):
pytest.skip('Annotations are not available in python < 3.6')
source = '''
OhALongName = 'Hello'
OhALongName = 'Hello'
MyOtherName = 'World'
def func():
class C:
OhALongName = ' World'
MyOtherName = OhALongName
ClassName: int
func()
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyOtherName', allow_rename=True) <references=1>
- NameBinding(name='OhALongName', allow_rename=False) <references=4>
- NameBinding(name='func', allow_rename=True) <references=2>
- BuiltinBinding(name='int', allow_rename=True) <references=1>
+ Function func
- NameBinding(name='C', allow_rename=True) <references=1>
+ Class C
- nonlocal OhALongName
- nonlocal int
- NameBinding(name='ClassName', allow_rename=False) <references=1>
- NameBinding(name='MyOtherName', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_name_rebinding():
if sys.version_info < (3, 4):
pytest.skip('Test requires python 3.4 or later')
source = '''
OhALongName = 'Hello'
OhALongName = 'Hello'
MyOtherName = 'World'
def func():
class C:
OhALongName = OhALongName + ' World'
MyOtherName = OhALongName
func()
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyOtherName', allow_rename=True) <references=1>
- NameBinding(name='OhALongName', allow_rename=False) <references=5>
- NameBinding(name='func', allow_rename=True) <references=2>
+ Function func
- NameBinding(name='C', allow_rename=True) <references=1>
+ Class C
- nonlocal OhALongName
- NameBinding(name='MyOtherName', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_name_allow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=True) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_name_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=1>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_builtin_name_allow_rename():
source = '''
class LazyList:
MyAttribute = int
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- BuiltinBinding(name='int', allow_rename=True) <references=1>
+ Class LazyList
- nonlocal int
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_builtin_name_disallow_rename():
source = '''
class LazyList:
MyAttribute = int
int = 1
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- BuiltinBinding(name='int', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal int
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_non_builtin_name_disallow_rename():
if sys.version_info < (3, 4):
pytest.skip('NameConstants are different in python < 3.4')
source = '''
int = None
class LazyList:
MyAttribute = int
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='int', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal int
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_name_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
MyNonLocal = 2
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_name_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
MyNonLocal = 2
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_import_as_disallow_rename():
"""import os as MyNonLocal"""
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
import os as MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_import_as_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
import os as MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_import_from_as_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
from os import Hello as MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_import_from_as_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
from os import Hello as MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_import_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
import MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_import_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
import MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_import_from_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
from Hello import MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_import_from_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
from Hello import MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_dotted_import_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
import MyNonLocal.Hello
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_dotted_import_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
import MyNonLocal.Hello
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_func_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
def MyNonLocal(): pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
+ Function MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_func_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
def MyNonLocal(): pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
+ Function MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_async_func_disallow_rename():
if sys.version_info < (3, 5):
pytest.skip('No async functions in python < 3.5')
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
async def MyNonLocal(): pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
+ Function MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_async_func_disallow_rename():
if sys.version_info < (3, 5):
pytest.skip('No async functions in python < 3.5')
source = '''
class LazyList:
MyAttribute = MyNonLocal
async def MyNonLocal(): pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
+ Function MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_classdef_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
class MyNonLocal(): pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
+ Class MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_classdef_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
class MyNonLocal(): pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
+ Class MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_except_disallow_rename():
source = '''
MyNonLocal = 1
class LazyList:
MyAttribute = MyNonLocal
try:
pass
except Exception as MyNonLocal:
pass
'''
expected_namespaces = '''
+ Module
- BuiltinBinding(name='Exception', allow_rename=True) <references=1>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal Exception
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_except_disallow_rename():
source = '''
class LazyList:
MyAttribute = MyNonLocal
try:
pass
except Exception as MyNonLocal:
pass
'''
expected_namespaces = '''
+ Module
- BuiltinBinding(name='Exception', allow_rename=True) <references=1>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal Exception
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_match_disallow_rename():
if sys.version_info < (3, 10):
pytest.skip('Match statement not in python < 3.10')
source = '''
MyNonLocal = 1
Blah = "Hello"
class LazyList:
MyAttribute = MyNonLocal
match Blah:
case MyNonLocal:
pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='Blah', allow_rename=True) <references=2>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal Blah
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_match_disallow_rename():
if sys.version_info < (3, 10):
pytest.skip('Match statement not in python < 3.10')
source = '''
Blah = "Hello"
class LazyList:
MyAttribute = MyNonLocal
match Blah:
case MyNonLocal:
pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='Blah', allow_rename=True) <references=2>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal Blah
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_match_star_disallow_rename():
if sys.version_info < (3, 10):
pytest.skip('Match statement not in python < 3.10')
source = '''
MyNonLocal = 1
Blah = "Hello"
class LazyList:
MyAttribute = MyNonLocal
match Blah:
case [*MyNonLocal]:
pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='Blah', allow_rename=True) <references=2>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal Blah
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_match_star_disallow_rename():
if sys.version_info < (3, 10):
pytest.skip('Match statement not in python < 3.10')
source = '''
Blah = "Hello"
class LazyList:
MyAttribute = MyNonLocal
match Blah:
case [*MyNonLocal]:
pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='Blah', allow_rename=True) <references=2>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal Blah
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_match_mapping_disallow_rename():
if sys.version_info < (3, 10):
pytest.skip('Match statement not in python < 3.10')
source = '''
MyNonLocal = 1
Blah = "Hello"
class LazyList:
MyAttribute = MyNonLocal
match Blah:
case {**MyNonLocal}:
pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='Blah', allow_rename=True) <references=2>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=3>
+ Class LazyList
- nonlocal Blah
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_match_mapping_disallow_rename():
if sys.version_info < (3, 10):
pytest.skip('Match statement not in python < 3.10')
source = '''
Blah = "Hello"
class LazyList:
MyAttribute = MyNonLocal
match Blah:
case {**MyNonLocal}:
pass
'''
expected_namespaces = '''
+ Module
- NameBinding(name='Blah', allow_rename=True) <references=2>
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal Blah
- nonlocal MyNonLocal
- NameBinding(name='MyAttribute', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_nonlocal_disallow_rename():
if sys.version_info < (3, 0):
pytest.skip('nonlocal in class is invalid in Python 2')
source = '''
MyNonLocal = 1
class LazyList:
nonlocal MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=2>
+ Class LazyList
- nonlocal MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
def test_class_namespace_undefined_nonlocal_disallow_rename():
if sys.version_info < (3, 0):
pytest.skip('nonlocal in class is invalid in Python 2')
source = '''
class LazyList:
nonlocal MyNonLocal
'''
expected_namespaces = '''
+ Module
- NameBinding(name='LazyList', allow_rename=True) <references=1>
- NameBinding(name='MyNonLocal', allow_rename=False) <references=1>
+ Class LazyList
- nonlocal MyNonLocal
'''
assert_namespace_tree(source, expected_namespaces)
import ast
import sys
import pytest
from python_minifier import add_namespace
from python_minifier.transforms.constant_folding import FoldConstants
from python_minifier.ast_compare import compare_ast
def fold_constants(source):
module = ast.parse(source)
add_namespace(module)
FoldConstants()(module)
return module
def run_test(source, expected):
try:
expected_ast = ast.parse(expected)
except SyntaxError:
pytest.skip('Syntax not supported in this version of python')
actual_ast = fold_constants(source)
compare_ast(expected_ast, actual_ast)
@pytest.mark.parametrize('source,expected', [
('True | True', 'True'),
('True | False', 'True'),
('False | True', 'True'),
('False | False', 'False'),
('True & True', 'True'),
('True & False', 'False'),
('False & True', 'False'),
('False & False', 'False'),
('True ^ True', 'False'),
('True ^ False', 'True'),
('False ^ True', 'True'),
('False ^ False', 'False'),
('(True | True) | True', 'True'),
('(True | True) | False', 'True'),
('(True | False) | True', 'True'),
('(True | False) | False', 'True'),
('(False | True) | True', 'True'),
('(False | True) | False', 'True'),
('(False | False) | True', 'True'),
('(False | False) | False', 'False'),
('(True | True) & True', 'True'),
('(True | True) & False', 'False'),
('(True | False) & True', 'True'),
('(True | False) & False', 'False'),
('(False | True) & True', 'True'),
('(False | True) & False', 'False'),
('(False | False) & True', 'False'),
('(False | False) & False', 'False'),
('(True | True) ^ True', 'False'),
('(True | True) ^ False', 'True'),
('(True | False) ^ True', 'False'),
('(True | False) ^ False', 'True'),
('(False | True) ^ True', 'False'),
('(False | True) ^ False', 'True'),
('(False | False) ^ True', 'True'),
('(False | False) ^ False', 'False'),
('True | (True | True)', 'True'),
('True | (True | False)', 'True'),
('True | (False | True)', 'True'),
('True | (False | False)', 'True'),
('False | (True | True)', 'True'),
('False | (True | False)', 'True'),
('False | (False | True)', 'True'),
('False | (False | False)', 'False'),
])
def test_bool(source, expected):
"""
Test BinOp with bool operands
This is mainly testing we fold the constants correctly
"""
if sys.version_info < (3, 4):
pytest.skip('NameConstant not in python < 3.4')
run_test(source, expected)
@pytest.mark.parametrize('source,expected', [
('10 + 10', '20'),
('10 + 0', '10'),
('0 + 10', '10'),
('10 + 10 + 5', '25'),
('10 - 5 + 5', '10'),
('10 * 10', '100'),
('10 * 10 * 10', '1000'),
('(10 * 10) // 10', '10'),
('(2 * 10) // (2+2)', '5'),
('8>>2', '2'),
('8<<2', '32'),
('0xff^0x0f', '0xf0'),
('0xf0&0xff', '0xf0'),
('0xf0|0x0f', '0xff'),
('10%2', '0'),
('10%3', '1'),
('10-100', '-90')
])
def test_int(source, expected):
"""
Test BinOp with integer operands we can fold
"""
run_test(source, expected)
@pytest.mark.parametrize('source,expected', [
('10/10', '10/10'),
('5+5/10', '5+5/10'),
('2*5/10', '10/10'),
('2/5*10', '2/5*10'),
('2**5', '2**5'),
('5@6', '5@6'),
])
def test_int_not_eval(source, expected):
"""
Test BinOp with operations we don't want to fold
"""
run_test(source, expected)
@pytest.mark.parametrize('source,expected', [
('"Hello" + "World"', '"Hello" + "World"'),
('"Hello" * 5', '"Hello" * 5'),
('b"Hello" + b"World"', 'b"Hello" + b"World"'),
('b"Hello" * 5', 'b"Hello" * 5'),
])
def test_not_eval(source, expected):
"""
Test BinOps we don't want to fold
"""
run_test(source, expected)
import ast
import sys
import pytest
from python_minifier import unparse
from python_minifier.ast_compare import compare_ast
@pytest.mark.parametrize('statement', [
'f"{1=!r:.4}"',
'f"{1=:.4}"',
'f"{1=!s:.4}"',
'f"{1=:.4}"',
'f"{1}"',
'f"{1=}"',
'f"{1=!s}"',
'f"{1=!a}"'
])
def test_fstring_statement(statement):
if sys.version_info < (3, 8):
pytest.skip('f-string debug specifier added in python 3.8')
assert unparse(ast.parse(statement)) == statement
def test_pep0701():
if sys.version_info < (3, 12):
pytest.skip('f-string syntax is bonkers before python 3.12')
statement = 'f"{f"{f"{f"{"hello"}"}"}"}"'
assert unparse(ast.parse(statement)) == statement
statement = 'f"This is the playlist: {", ".join([])}"'
assert unparse(ast.parse(statement)) == statement
statement = 'f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"'
assert unparse(ast.parse(statement)) == statement
statement = """
f"This is the playlist: {", ".join([
'Take me back to Eden', # My, my, those eyes like fire
'Alkaline', # Not acid nor alkaline
'Ascensionism' # Take to the broken skies at last
])}"
"""
assert unparse(ast.parse(statement)) == 'f"This is the playlist: {", ".join(["Take me back to Eden","Alkaline","Ascensionism"])}"'
#statement = '''print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")'''
#assert unparse(ast.parse(statement)) == statement
statement = '''f"Magic wand: {bag["wand"]}"'''
assert unparse(ast.parse(statement)) == statement
statement = """
f'''A complex trick: {
bag['bag'] # recursive bags!
}'''
"""
assert unparse(ast.parse(statement)) == 'f"A complex trick: {bag["bag"]}"'
statement = '''f"These are the things: {", ".join(things)}"'''
assert unparse(ast.parse(statement)) == statement
statement = '''f"{source.removesuffix(".py")}.c: $(srcdir)/{source}"'''
assert unparse(ast.parse(statement)) == statement
statement = '''f"{f"{f"infinite"}"}"+' '+f"{f"nesting!!!"}"'''
assert unparse(ast.parse(statement)) == statement
statement = '''f"{"\\n".join(a)}"'''
assert unparse(ast.parse(statement)) == statement
statement = '''f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"'''
assert unparse(ast.parse(statement)) == statement
statement = '''f"{"":*^{1:{1}}}"'''
assert unparse(ast.parse(statement)) == statement
#statement = '''f"{"":*^{1:{1:{1}}}}"'''
#assert unparse(ast.parse(statement)) == statement
# SyntaxError: f-string: expressions nested too deeply
statement = '''f"___{
x
}___"'''
assert unparse(ast.parse(statement)) == '''f"___{x}___"'''
statement = '''f"Useless use of lambdas: {(lambda x:x*2)}"'''
assert unparse(ast.parse(statement)) == statement
def test_fstring_empty_str():
if sys.version_info < (3, 6):
pytest.skip('f-string expressions not allowed in python < 3.6')
source = r'''
f"""\
{fg_br}"""
'''
print(source)
expected_ast = ast.parse(source)
actual_ast = unparse(expected_ast)
compare_ast(expected_ast, ast.parse(actual_ast))
import ast
import sys
import pytest
from python_minifier import unparse
from python_minifier.ast_compare import compare_ast
def test_type_statement():
if sys.version_info < (3, 12):
pytest.skip('Improved generic syntax python < 3.12')
source = '''
type Point = tuple[float, float]
type ListOrSet[T] = list[T] | set[T]
'''
expected_ast = ast.parse(source)
actual_ast = unparse(expected_ast)
compare_ast(expected_ast, ast.parse(actual_ast))
def test_function_generic():
if sys.version_info < (3, 12):
pytest.skip('Improved generic syntax python < 3.12')
source = '''
def a(): ...
def func[T](a: T, b: T) -> T:
...
'''
expected_ast = ast.parse(source)
actual_ast = unparse(expected_ast)
compare_ast(expected_ast, ast.parse(actual_ast))
def test_class_generic():
if sys.version_info < (3, 12):
pytest.skip('Improved generic syntax python < 3.12')
source = '''
class A:
...
class B[S]:
...
class C[T: str]:
...
'''
expected_ast = ast.parse(source)
actual_ast = unparse(expected_ast)
compare_ast(expected_ast, ast.parse(actual_ast))
def test_pep695_unparse():
if sys.version_info < (3, 12):
pytest.skip('Improved generic syntax python < 3.12')
source = '''
class ClassA[T: str]:
def method1(self) -> T:
...
def func[T](a: T, b: T) -> T:
...
type ListOrSet[T] = list[T] | set[T]
# This generic class is parameterized by a TypeVar T, a
# TypeVarTuple Ts, and a ParamSpec P.
class ChildClass[T, *Ts, **P]: ...
class ClassA[S, T](Protocol): ... # OK
class ClassB[S, T](Protocol[S, T]): ... # Recommended type checker error
class ClassA[T: str]: ...
class ClassA[T: dict[str, int]]: ... # OK
class ClassB[T: "ForwardReference"]: ... # OK
class ClassC[V]:
class ClassD[T: dict[str, V]]: ... # Type checker error: generic type
class ClassE[T: [str, int]]: ... # Type checker error: illegal expression form
class ClassA[AnyStr: (str, bytes)]: ... # OK
class ClassB[T: ("ForwardReference", bytes)]: ... # OK
class ClassC[T: ()]: ... # Type checker error: two or more types required
class ClassD[T: (str, )]: ... # Type checker error: two or more types required
t1 = (bytes, str)
class ClassE[T: t1]: ... # Type checker error: literal tuple expression required
class ClassF[T: (3, bytes)]: ... # Type checker error: invalid expression form
class ClassG[T: (list[S], str)]: ... # Type checker error: generic type
# A non-generic type alias
type IntOrStr = int | str
# A generic type alias
type ListOrSet[T] = list[T] | set[T]
# A type alias that includes a forward reference
type AnimalOrVegetable = Animal | "Vegetable"
# A generic self-referential type alias
type RecursiveList[T] = T | list[RecursiveList[T]]
T = TypeVar("T")
type MyList = list[T] # Type checker error: traditional type variable usage
# The following generates no compiler error, but a type checker
# should generate an error because an upper bound type must be concrete,
# and ``Sequence[S]`` is generic. Future extensions to the type system may
# eliminate this limitation.
class ClassA[S, T: Sequence[S]]: ...
# The following generates no compiler error, because the bound for ``S``
# is lazily evaluated. However, type checkers should generate an error.
class ClassB[S: Sequence[T], T]: ...
class ClassA[T](BaseClass[T], param = Foo[T]): ... # OK
print(T) # Runtime error: 'T' is not defined
@dec(Foo[T]) # Runtime error: 'T' is not defined
class ClassA[T]: ...
def func1[T](a: T) -> T: ... # OK
print(T) # Runtime error: 'T' is not defined
def func2[T](a = list[T]): ... # Runtime error: 'T' is not defined
@dec(list[T]) # Runtime error: 'T' is not defined
def func3[T](): ...
type Alias1[K, V] = Mapping[K, V] | Sequence[K]
S = 0
def outer1[S]():
S = 1
T = 1
def outer2[T]():
def inner1():
nonlocal S # OK because it binds variable S from outer1
#nonlocal T # Syntax error: nonlocal binding not allowed for type parameter
def inner2():
global S # OK because it binds variable S from global scope
class Outer:
class Private:
pass
# If the type parameter scope was like a traditional scope,
# the base class 'Private' would not be accessible here.
class Inner[T](Private, Sequence[T]):
pass
# Likewise, 'Inner' would not be available in these type annotations.
def method1[T](self, a: Inner[T]) -> Inner[T]:
return a
T = 0
@decorator(T) # Argument expression `T` evaluates to 0
class ClassA[T](Sequence[T]):
T = 1
# All methods below should result in a type checker error
# "type parameter 'T' already in use" because they are using the
# type parameter 'T', which is already in use by the outer scope
# 'ClassA'.
def method1[T](self):
...
def method2[T](self, x = T): # Parameter 'x' gets default value of 1
...
def method3[T](self, x: T): # Parameter 'x' has type T (scoped to method3)
...
T = 0
# T refers to the global variable
print(T) # Prints 0
class Outer[T]:
T = 1
# T refers to the local variable scoped to class 'Outer'
print(T) # Prints 1
class Inner1:
T = 2
# T refers to the local type variable within 'Inner1'
print(T) # Prints 2
def inner_method(self):
# T refers to the type parameter scoped to class 'Outer';
# If 'Outer' did not use the new type parameter syntax,
# this would instead refer to the global variable 'T'
print(T) # Prints 'T'
def outer_method(self):
T = 3
# T refers to the local variable within 'outer_method'
print(T) # Prints 3
def inner_func():
# T refers to the variable captured from 'outer_method'
print(T) # Prints 3
class ClassA[T1, T2, T3](list[T1]):
def method1(self, a: T2) -> None:
...
def method2(self) -> T3:
...
upper = ClassA[object, Dummy, Dummy]
lower = ClassA[T1, Dummy, Dummy]
upper = ClassA[Dummy, object, Dummy]
lower = ClassA[Dummy, T2, Dummy]
upper = ClassA[Dummy, Dummy, object]
lower = ClassA[Dummy, Dummy, T3]
T1 = TypeVar("T1", infer_variance=True) # Inferred variance
T2 = TypeVar("T2") # Invariant
T3 = TypeVar("T3", covariant=True) # Covariant
# A type checker should infer the variance for T1 but use the
# specified variance for T2 and T3.
class ClassA(Generic[T1, T2, T3]): ...
K = TypeVar("K")
class ClassA[V](dict[K, V]): ... # Type checker error
class ClassB[K, V](dict[K, V]): ... # OK
class ClassC[V]:
# The use of K and V for "method1" is OK because it uses the
# "traditional" generic function mechanism where type parameters
# are implicit. In this case V comes from an outer scope (ClassC)
# and K is introduced implicitly as a type parameter for "method1".
def method1(self, a: V, b: K) -> V | K: ...
# The use of M and K are not allowed for "method2". A type checker
# should generate an error in this case because this method uses the
# new syntax for type parameters, and all type parameters associated
# with the method must be explicitly declared. In this case, ``K``
# is not declared by "method2", nor is it supplied by a new-style
# type parameter defined in an outer scope.
def method2[M](self, a: M, b: K) -> M | K: ...
'''
expected_ast = ast.parse(source)
unparse(expected_ast)
import sys
import pytest
import python_minifier.ast_compat as ast
from python_minifier.util import is_ast_node
def test_type_nodes():
assert is_ast_node(ast.Str('a'), ast.Str)
if hasattr(ast, 'Bytes'):
assert is_ast_node(ast.Bytes(b'a'), ast.Bytes)
assert is_ast_node(ast.Num(1), ast.Num)
assert is_ast_node(ast.Num(0), ast.Num)
if hasattr(ast, 'NameConstant'):
assert is_ast_node(ast.NameConstant(True), ast.NameConstant)
assert is_ast_node(ast.NameConstant(False), ast.NameConstant)
assert is_ast_node(ast.NameConstant(None), ast.NameConstant)
else:
assert is_ast_node(ast.Name(id='True', ctx=ast.Load()), ast.Name)
assert is_ast_node(ast.Name(id='False', ctx=ast.Load()), ast.Name)
assert is_ast_node(ast.Name(id='None', ctx=ast.Load()), ast.Name)
assert is_ast_node(ast.Ellipsis(), ast.Ellipsis)
def test_constant_nodes():
# only test on python 3.8+
if sys.version_info < (3, 8):
pytest.skip('Constant not available')
assert is_ast_node(ast.Constant('a'), ast.Str)
assert is_ast_node(ast.Constant(b'a'), ast.Bytes)
assert is_ast_node(ast.Constant(1), ast.Num)
assert is_ast_node(ast.Constant(0), ast.Num)
assert is_ast_node(ast.Constant(True), ast.NameConstant)
assert is_ast_node(ast.Constant(False), ast.NameConstant)
assert is_ast_node(ast.Constant(None), ast.NameConstant)
assert is_ast_node(ast.Constant(ast.literal_eval('...')), ast.Ellipsis)
import ast
import sys
import pytest
from python_minifier.rename import add_namespace, bind_names, resolve_names
from python_minifier.ast_compare import compare_ast
from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call
def remove_brackets(source):
module = ast.parse(source, 'remove_brackets')
add_namespace(module)
bind_names(module)
resolve_names(module)
return remove_no_arg_exception_call(module)
def test_exception_brackets():
"""This is a buitin so remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = 'def a(): raise Exception()'
expected = 'def a(): raise Exception'
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_zero_division_error_brackets():
"""This is a buitin so remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = 'def a(): raise ZeroDivisionError()'
expected = 'def a(): raise ZeroDivisionError'
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_builtin_with_arg():
"""This has an arg so dont' remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = 'def a(): raise Exception(1)'
expected = 'def a(): raise Exception(1)'
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_one_division_error_brackets():
"""This is not a builtin so don't remove the brackets even though it's not defined in the module"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = 'def a(): raise OneDivisionError()'
expected = source
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_redefined():
"""This is usually a builtin, but don't remove brackets if it's been redefined"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = '''
def a():
raise ZeroDivisionError()
def b():
ZeroDivisionError = blag
raise ZeroDivisionError()
'''
expected = '''
def a():
raise ZeroDivisionError
def b():
ZeroDivisionError = blag
raise ZeroDivisionError()
'''
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_raise_from():
"""This is a builtin so remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('raise from not supported in this version of python')
source = 'def a(): raise Exception() from Exception()'
expected = 'def a(): raise Exception from Exception'
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_raise_from_only():
"""This is a builtin so remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('raise from not supported in this version of python')
source = 'def a(): raise Hello() from Exception()'
expected = 'def a(): raise Hello() from Exception'
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_raise_from_arg():
"""This is a builtin so remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('raise from not supported in this version of python')
source = 'def a(): raise Hello() from Exception(1)'
expected = 'def a(): raise Hello() from Exception(1)'
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_raise_builtin_in_class():
"""This is a builtin so remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = '''
class A:
raise IndexError()
'''
expected = '''
class A:
raise IndexError
'''
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
def test_raise_redefined_builtin_in_class():
"""This was a builtin at some point, but it was redefined so don't remove the brackets"""
if sys.version_info < (3, 0):
pytest.skip('transform does not work in this version of python')
source = '''
class A:
if random.choice([True, False]):
IndexError = IndexError
raise IndexError()
'''
expected = '''
class A:
if random.choice([True, False]):
IndexError = IndexError
raise IndexError()
'''
expected_ast = ast.parse(expected)
actual_ast = remove_brackets(source)
compare_ast(expected_ast, actual_ast)
+5
-4
Metadata-Version: 2.1
Name: python_minifier
Version: 2.9.0
Version: 2.10.0
Summary: Transform Python source code into it's most compact representation

@@ -25,2 +25,3 @@ Home-page: https://github.com/dflook/python-minifier

Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 2

@@ -32,3 +33,3 @@ Classifier: Programming Language :: Python :: 2.7

Classifier: Topic :: Software Development
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13
Description-Content-Type: text/markdown

@@ -43,3 +44,3 @@ License-File: LICENSE

python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6.
python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.

@@ -147,3 +148,3 @@ * [PyPI](https://pypi.org/project/python-minifier/)

python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11.
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.

@@ -150,0 +151,0 @@ ## Usage

@@ -7,3 +7,3 @@ # Python Minifier

python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6.
python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.

@@ -111,3 +111,3 @@ * [PyPI](https://pypi.org/project/python-minifier/)

python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11.
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.

@@ -114,0 +114,0 @@ ## Usage

import os.path
import sys

@@ -26,8 +25,8 @@ from setuptools import setup, find_packages

packages=find_packages('src'),
package_data={"python_minifier": ["py.typed", "__init__.pyi"]},
package_data={"python_minifier": ["py.typed", "*.pyi", "rename/*.pyi", "transforms/*.pyi"]},
long_description=long_desc,
long_description_content_type='text/markdown',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12',
version='2.9.0',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13',
version='2.10.0',

@@ -48,2 +47,3 @@ classifiers=[

'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 2',

@@ -50,0 +50,0 @@ 'Programming Language :: Python :: 2.7',

Metadata-Version: 2.1
Name: python-minifier
Version: 2.9.0
Name: python_minifier
Version: 2.10.0
Summary: Transform Python source code into it's most compact representation

@@ -25,2 +25,3 @@ Home-page: https://github.com/dflook/python-minifier

Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 2

@@ -32,3 +33,3 @@ Classifier: Programming Language :: Python :: 2.7

Classifier: Topic :: Software Development
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.12
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13
Description-Content-Type: text/markdown

@@ -43,3 +44,3 @@ License-File: LICENSE

python-minifier currently supports Python 2.7 and Python 3.3 to 3.11. Previous releases supported Python 2.6.
python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.

@@ -147,3 +148,3 @@ * [PyPI](https://pypi.org/project/python-minifier/)

python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.11.
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.

@@ -150,0 +151,0 @@ ## Usage

@@ -8,2 +8,4 @@ LICENSE

src/python_minifier/ast_compare.py
src/python_minifier/ast_compat.py
src/python_minifier/ast_printer.py
src/python_minifier/expression_printer.py

@@ -33,5 +35,9 @@ src/python_minifier/f_string.py

src/python_minifier/transforms/combine_imports.py
src/python_minifier/transforms/constant_folding.py
src/python_minifier/transforms/remove_annotations.py
src/python_minifier/transforms/remove_annotations_options.py
src/python_minifier/transforms/remove_annotations_options.pyi
src/python_minifier/transforms/remove_asserts.py
src/python_minifier/transforms/remove_debug.py
src/python_minifier/transforms/remove_exception_brackets.py
src/python_minifier/transforms/remove_explicit_return_none.py

@@ -45,2 +51,5 @@ src/python_minifier/transforms/remove_literal_statements.py

test/test_await_fstring.py
test/test_bind_names.py
test/test_bind_names_python2.py
test/test_bind_names_python33.py
test/test_combine_imports.py

@@ -50,5 +59,8 @@ test/test_comprehension_rename.py

test/test_dict_expansion.py
test/test_empty_fstring.py
test/test_folding.py
test/test_fstring.py
test/test_generic_types.py
test/test_hoist_literals.py
test/test_import.py
test/test_is_ast_node.py
test/test_iterable_unpacking.py

@@ -64,2 +76,3 @@ test/test_match.py

test/test_remove_debug.py
test/test_remove_exception_brackets.py
test/test_remove_explicit_return_none.py

@@ -66,0 +79,0 @@ test/test_remove_literal_statements.py

@@ -7,3 +7,3 @@ """

import ast
import python_minifier.ast_compat as ast
import re

@@ -24,6 +24,9 @@

from python_minifier.transforms.combine_imports import CombineImports
from python_minifier.transforms.constant_folding import FoldConstants
from python_minifier.transforms.remove_annotations import RemoveAnnotations
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
from python_minifier.transforms.remove_asserts import RemoveAsserts
from python_minifier.transforms.remove_debug import RemoveDebug
from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone
from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call
from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements

@@ -57,3 +60,3 @@ from python_minifier.transforms.remove_object_base import RemoveObject

filename=None,
remove_annotations=True,
remove_annotations=RemoveAnnotationsOptions(),
remove_pass=True,

@@ -73,2 +76,4 @@ remove_literal_statements=False,

remove_explicit_return_none=True,
remove_builtin_exception_brackets=True,
constant_folding=True
):

@@ -87,3 +92,4 @@ """

:param bool remove_annotations: If type annotations should be removed where possible
: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.
:type remove_annotations: bool or RemoveAnnotationsOptions
:param bool remove_pass: If Pass statements should be removed where possible

@@ -105,2 +111,4 @@ :param bool remove_literal_statements: If statements consisting of a single literal should be removed, including docstrings

:param bool remove_explicit_return_none: If explicit return None statements should be replaced with a bare return
:param bool remove_builtin_exception_brackets: If brackets should be removed when raising exceptions with no arguments
:param bool constant_folding: If literal expressions should be evaluated

@@ -124,5 +132,17 @@ :rtype: str

if remove_annotations:
module = RemoveAnnotations()(module)
if isinstance(remove_annotations, bool):
remove_annotations_options = RemoveAnnotationsOptions(
remove_variable_annotations=remove_annotations,
remove_return_annotations=remove_annotations,
remove_argument_annotations=remove_annotations,
remove_class_attribute_annotations=remove_annotations,
)
elif isinstance(remove_annotations, RemoveAnnotationsOptions):
remove_annotations_options = remove_annotations
else:
raise TypeError('remove_annotations must be a bool or RemoveAnnotationsOptions')
if remove_annotations_options:
module = RemoveAnnotations(remove_annotations_options)(module)
if remove_pass:

@@ -143,5 +163,11 @@ module = RemovePass()(module)

if constant_folding:
module = FoldConstants()(module)
bind_names(module)
resolve_names(module)
if remove_builtin_exception_brackets and not module.tainted:
remove_no_arg_exception_call(module)
if module.tainted:

@@ -151,2 +177,14 @@ rename_globals = False

if preserve_locals is None:
preserve_locals = []
elif isinstance(preserve_locals, str):
preserve_locals = [preserve_locals]
if preserve_globals is None:
preserve_globals = []
elif isinstance(preserve_globals, str):
preserve_globals = [preserve_globals]
preserve_locals.extend(module.preserved)
preserve_globals.extend(module.preserved)
allow_rename_locals(module, rename_locals, preserve_locals)

@@ -153,0 +191,0 @@ allow_rename_globals(module, rename_globals, preserve_globals)

import ast
from typing import List, Text, AnyStr, Optional, Any
from typing import List, Text, AnyStr, Optional, Any, Union
from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions

@@ -11,3 +12,3 @@ class UnstableMinification(RuntimeError):

filename: Optional[str] = ...,
remove_annotations: bool = ...,
remove_annotations: Union[bool, RemoveAnnotationsOptions] = ...,
remove_pass: bool = ...,

@@ -26,3 +27,5 @@ remove_literal_statements: bool = ...,

remove_debug: bool = ...,
remove_explicit_return_none: bool = ...
remove_explicit_return_none: bool = ...,
remove_builtin_exception_brackets: bool = ...,
constant_folding: bool = ...
) -> Text: ...

@@ -29,0 +32,0 @@

from __future__ import print_function
import argparse
import os
import sys
import os
import argparse
from pkg_resources import get_distribution, DistributionNotFound
from python_minifier import minify
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
try:
version = get_distribution('python_minifier').version
except DistributionNotFound:
version = '0.0.0'
if sys.version_info >= (3, 8):
from importlib import metadata
try:
version = metadata.version('python-minifier')
except metadata.PackageNotFoundError:
version = '0.0.0'
else:
from pkg_resources import get_distribution, DistributionNotFound
try:
version = get_distribution('python_minifier').version
except DistributionNotFound:
version = '0.0.0'

@@ -71,2 +78,3 @@

def parse_args():

@@ -118,8 +126,2 @@ parser = argparse.ArgumentParser(prog='pyminify', description='Minify Python source code', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=main.__doc__)

minification_options.add_argument(
'--no-remove-annotations',
action='store_false',
help='Disable removing function and variable annotations',
dest='remove_annotations',
)
minification_options.add_argument(
'--no-hoist-literals',

@@ -194,2 +196,47 @@ action='store_false',

)
minification_options.add_argument(
'--no-remove-builtin-exception-brackets',
action='store_false',
help='Disable removing brackets when raising builtin exceptions with no arguments',
dest='remove_exception_brackets',
)
minification_options.add_argument(
'--no-constant-folding',
action='store_false',
help='Disable evaluating literal expressions',
dest='constant_folding',
)
annotation_options = parser.add_argument_group('remove annotations options', 'Options that affect how annotations are removed')
annotation_options.add_argument(
'--no-remove-annotations',
action='store_false',
help='Disable removing all annotations',
dest='remove_annotations',
)
annotation_options.add_argument(
'--no-remove-variable-annotations',
action='store_false',
help='Disable removing variable annotations',
dest='remove_variable_annotations',
)
annotation_options.add_argument(
'--no-remove-return-annotations',
action='store_false',
help='Disable removing function return annotations',
dest='remove_return_annotations',
)
annotation_options.add_argument(
'--no-remove-argument-annotations',
action='store_false',
help='Disable removing function argument annotations',
dest='remove_argument_annotations',
)
annotation_options.add_argument(
'--remove-class-attribute-annotations',
action='store_true',
help='Enable removing class attribute annotations',
dest='remove_class_attribute_annotations',
)
parser.add_argument('--version', '-v', action='version', version=version)

@@ -213,4 +260,9 @@

if args.remove_class_attribute_annotations and not args.remove_annotations:
sys.stderr.write('error: --remove-class-attribute-annotations would do nothing when used with --no-remove-annotations\n')
sys.exit(1)
return args
def source_modules(args):

@@ -230,2 +282,3 @@

def do_minify(source, filename, minification_args):

@@ -245,2 +298,17 @@

if minification_args.remove_annotations is False:
remove_annotations = RemoveAnnotationsOptions(
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=False,
)
else:
remove_annotations = RemoveAnnotationsOptions(
remove_variable_annotations=minification_args.remove_variable_annotations,
remove_return_annotations=minification_args.remove_return_annotations,
remove_argument_annotations=minification_args.remove_argument_annotations,
remove_class_attribute_annotations=minification_args.remove_class_attribute_annotations,
)
return minify(

@@ -251,3 +319,3 @@ source,

remove_pass=minification_args.remove_pass,
remove_annotations=minification_args.remove_annotations,
remove_annotations=remove_annotations,
remove_literal_statements=minification_args.remove_literal_statements,

@@ -265,2 +333,4 @@ hoist_literals=minification_args.hoist_literals,

remove_explicit_return_none=minification_args.remove_explicit_return_none,
remove_builtin_exception_brackets=minification_args.remove_exception_brackets,
constant_folding=minification_args.constant_folding
)

@@ -267,0 +337,0 @@

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -3,0 +3,0 @@ from python_minifier.util import is_ast_node

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast
import sys

@@ -72,3 +72,3 @@

# Make sure the Num get the precedence of the USub operator in this case.
if sys.version_info < (3, 0) and isinstance(node, ast.Num):
if sys.version_info < (3, 0) and is_ast_node(node, ast.Num):
if str(node.n)[0] == '-':

@@ -212,3 +212,3 @@ return self.precedences['USub']

if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Num):
if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and is_ast_node(node.operand, ast.Num):
# For: -(1), which is parsed as a UnaryOp(USub, Num(1)).

@@ -433,3 +433,3 @@ # Without this special case it would be printed as -1

if (value_precedence != 0 and (attr_precedence > value_precedence)) or isinstance(node.value, ast.Num):
if (value_precedence != 0 and (attr_precedence > value_precedence)) or is_ast_node(node.value, ast.Num):
self.printer.delimiter('(')

@@ -468,3 +468,3 @@ self._expression(node.value)

self.visit_ExtSlice(node.slice)
elif isinstance(node.slice, ast.Ellipsis):
elif is_ast_node(node.slice, ast.Ellipsis):
self.visit_Ellipsis(node)

@@ -742,4 +742,9 @@ elif sys.version_info >= (3, 9) and isinstance(node.slice, ast.Tuple):

self.printer.fstring(str(python_minifier.f_string.OuterFString(node)))
if sys.version_info < (3, 12):
pep701 = False
else:
pep701 = True
self.printer.fstring(str(python_minifier.f_string.OuterFString(node, pep701=pep701)))
def visit_NamedExpr(self, node):

@@ -746,0 +751,0 @@ self._expression(node.target)

@@ -9,3 +9,3 @@ """

import ast
import python_minifier.ast_compat as ast
import copy

@@ -28,3 +28,3 @@ import re

def __init__(self, node, allowed_quotes):
def __init__(self, node, allowed_quotes, pep701):
assert isinstance(node, ast.JoinedStr)

@@ -34,2 +34,3 @@

self.allowed_quotes = allowed_quotes
self.pep701 = pep701

@@ -59,3 +60,3 @@ def is_correct_ast(self, code):

if value_node.format_spec is not None:
conversion_candidates = [c + ':' + fs for c in conversion_candidates for fs in FormatSpec(value_node.format_spec, self.allowed_quotes).candidates()]
conversion_candidates = [c + ':' + fs for c in conversion_candidates for fs in FormatSpec(value_node.format_spec, self.allowed_quotes, self.pep701).candidates()]

@@ -71,3 +72,6 @@ return [x + '}' for x in conversion_candidates]

nested_allowed = copy.copy(self.allowed_quotes)
nested_allowed.remove(quote)
if not self.pep701:
nested_allowed.remove(quote)
for v in self.node.values:

@@ -94,3 +98,3 @@ if is_ast_node(v, ast.Str):

candidates = [
x + y for x in candidates for y in FormattedValue(v, nested_allowed).get_candidates()
x + y for x in candidates for y in FormattedValue(v, nested_allowed, self.pep701).get_candidates()
] + completed

@@ -120,5 +124,5 @@ debug_specifier_candidates = []

def __init__(self, node):
def __init__(self, node, pep701=False):
assert isinstance(node, ast.JoinedStr)
super(OuterFString, self).__init__(node, ['"', "'", '"""', "'''"])
super(OuterFString, self).__init__(node, ['"', "'", '"""', "'''"], pep701=pep701)

@@ -161,3 +165,3 @@ def __str__(self):

def __init__(self, node, allowed_quotes):
def __init__(self, node, allowed_quotes, pep701):
super(FormattedValue, self).__init__()

@@ -168,2 +172,3 @@

self.allowed_quotes = allowed_quotes
self.pep701 = pep701
self.candidates = ['']

@@ -189,3 +194,3 @@

self.printer.delimiter(':')
self._append(FormatSpec(self.node.format_spec, self.allowed_quotes).candidates())
self._append(FormatSpec(self.node.format_spec, self.allowed_quotes, pep701=self.pep701).candidates())

@@ -219,3 +224,3 @@ self.printer.delimiter('}')

def visit_Str(self, node):
self.printer.append(str(Str(node.s, self.allowed_quotes)), TokenTypes.NonNumberLiteral)
self.printer.append(str(Str(node.s, self.allowed_quotes, self.pep701)), TokenTypes.NonNumberLiteral)

@@ -229,4 +234,9 @@ def visit_Bytes(self, node):

self.printer.delimiter(' ')
self._append(FString(node, allowed_quotes=self.allowed_quotes).candidates())
self._append(FString(node, allowed_quotes=self.allowed_quotes, pep701=self.pep701).candidates())
def visit_Lambda(self, node):
self.printer.delimiter('(')
super().visit_Lambda(node)
self.printer.delimiter(')')
def _finalize(self):

@@ -245,10 +255,11 @@ self.candidates = [x + str(self.printer) for x in self.candidates]

May use any of the allowed quotes, no backslashes!
May use any of the allowed quotes. In Python <3.12, backslashes are not allowed.
"""
def __init__(self, s, allowed_quotes):
def __init__(self, s, allowed_quotes, pep701=False):
self._s = s
self.allowed_quotes = allowed_quotes
self.current_quote = None
self.pep701 = pep701

@@ -259,3 +270,3 @@ def _can_quote(self, c):

if (c == '\n' or c == '\r') and len(self.current_quote) == 1:
if (c == '\n' or c == '\r') and len(self.current_quote) == 1 and not self.pep701:
return False

@@ -270,3 +281,3 @@

for quote in self.allowed_quotes:
if c == '\n' or c == '\r':
if not self.pep701 and (c == '\n' or c == '\r'):
if len(quote) == 3:

@@ -292,4 +303,12 @@ return quote

l += self.current_quote
l += c
if c == '\n':
l += '\\n'
elif c == '\r':
l += '\\r'
elif c == '\\':
l += '\\\\'
else:
l += c
if l:

@@ -303,6 +322,6 @@ l += self.current_quote

if '\0' in self._s or '\\' in self._s:
raise ValueError('Impossible to represent a %r character in f-string expression part')
if '\0' in self._s or ('\\' in self._s and not self.pep701):
raise ValueError('Impossible to represent a character in f-string expression part')
if '\n' in self._s or '\r' in self._s:
if not self.pep701 and ('\n' in self._s or '\r' in self._s):
if '"""' not in self.allowed_quotes and "'''" not in self.allowed_quotes:

@@ -339,3 +358,3 @@ raise ValueError(

def __init__(self, node, allowed_quotes):
def __init__(self, node, allowed_quotes, pep701):
assert isinstance(node, ast.JoinedStr)

@@ -345,2 +364,3 @@

self.allowed_quotes = allowed_quotes
self.pep701 = pep701

@@ -355,3 +375,3 @@ def candidates(self):

candidates = [
x + y for x in candidates for y in FormattedValue(v, self.allowed_quotes).get_candidates()
x + y for x in candidates for y in FormattedValue(v, self.allowed_quotes, self.pep701).get_candidates()
]

@@ -358,0 +378,0 @@ else:

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast
import sys

@@ -443,3 +443,3 @@

def visit_With(self, node, is_async=False):
assert isinstance(node, ast.With) or (hasattr(ast, 'AsyncWith') and isinstance(node, ast.AsyncWith))
assert is_ast_node(node, (ast.With, 'AsyncWith'))

@@ -473,3 +473,3 @@ self.printer.newline()

def visit_withitem(self, node):
assert (hasattr(ast, 'withitem') and isinstance(node, ast.withitem)) or isinstance(node, ast.With)
assert is_ast_node(node, ('withitem', ast.With))

@@ -483,5 +483,3 @@ self._expression(node.context_expr)

def visit_FunctionDef(self, node, is_async=False):
assert isinstance(node, ast.FunctionDef) or (
hasattr(ast, 'AsyncFunctionDef') and isinstance(node, ast.AsyncFunctionDef)
)
assert is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef'))

@@ -500,2 +498,11 @@ self.printer.newline()

self.printer.identifier(node.name)
if hasattr(node, 'type_params') and node.type_params:
self.printer.delimiter('[')
delimiter = Delimiter(self.printer)
for type_param in node.type_params:
delimiter.new_item()
self.visit(type_param)
self.printer.delimiter(']')
self.printer.delimiter('(')

@@ -530,2 +537,10 @@ self.visit_arguments(node.args)

if hasattr(node, 'type_params') and node.type_params:
self.printer.delimiter('[')
delimiter = Delimiter(self.printer)
for type_param in node.type_params:
delimiter.new_item()
self.visit(type_param)
self.printer.delimiter(']')
with Delimiter(self.printer, add_parens=True) as delimiter:

@@ -720,2 +735,42 @@

# region Generic Types
def visit_TypeAlias(self, node):
assert isinstance(node, ast.TypeAlias)
self.printer.keyword('type')
self.visit_Name(node.name)
if hasattr(node, 'type_params') and node.type_params:
self.printer.delimiter('[')
delimiter = Delimiter(self.printer)
for param in node.type_params:
delimiter.new_item()
self.visit(param)
self.printer.delimiter(']')
self.printer.delimiter('=')
self._expression(node.value)
self.printer.end_statement()
def visit_TypeVar(self, node):
assert isinstance(node, ast.TypeVar)
assert isinstance(node.name, str)
self.printer.identifier(node.name)
if node.bound:
self.printer.delimiter(':')
self._expression(node.bound)
def visit_TypeVarTuple(self, node):
assert isinstance(node, ast.TypeVarTuple)
self.printer.operator('*')
self.printer.identifier(node.name)
def visit_ParamSpec(self, node):
assert isinstance(node, ast.ParamSpec)
self.printer.operator('*')
self.printer.operator('*')
self.printer.identifier(node.name)
# endregion
def visit_Module(self, node):

@@ -795,3 +850,4 @@ if hasattr(node, 'docstring') and node.docstring is not None:

'Match': self.visit_Match,
'match_case': self.visit_match_case
'match_case': self.visit_match_case,
'TypeAlias': self.visit_TypeAlias
}

@@ -798,0 +854,0 @@

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -18,2 +18,3 @@ from python_minifier.rename.binding import NameBinding

module.tainted = False
module.preserved = set()
return self.visit(module)

@@ -28,7 +29,2 @@

if isinstance(namespace, ast.ClassDef):
binding = self.get_binding(name, get_nonlocal_namespace(namespace))
binding.disallow_rename()
return binding
for binding in namespace.bindings:

@@ -48,2 +44,6 @@ if binding.name == name:

if isinstance(namespace, ast.ClassDef):
# This name will become an attribute of the class, so it can't be renamed
binding.disallow_rename()
return binding

@@ -168,2 +168,20 @@

def visit_TypeVar(self, node):
if node.name not in node.namespace.nonlocal_names:
self.get_binding(node.name, node.namespace).add_reference(node)
get_global_namespace(node.namespace).preserved.add(node.name)
def visit_TypeVarTuple(self, node):
if node.name not in node.namespace.nonlocal_names:
self.get_binding(node.name, node.namespace).add_reference(node)
get_global_namespace(node.namespace).preserved.add(node.name)
def visit_ParamSpec(self, node):
if node.name not in node.namespace.nonlocal_names:
self.get_binding(node.name, node.namespace).add_reference(node)
get_global_namespace(node.namespace).preserved.add(node.name)
def bind_names(module):

@@ -170,0 +188,0 @@ """

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -14,3 +14,2 @@ from python_minifier.rename.util import arg_rename_in_place, insert

:param bool allow_rename: If this binding may be renamed
:param int rename_cost: The cost of renaming this binding in bytes, NOT including the difference in name lengths

@@ -135,2 +134,8 @@ """

pass
elif is_ast_node(node, 'TypeVar'):
pass
elif is_ast_node(node, 'TypeVarTuple'):
pass
elif is_ast_node(node, 'ParamSpec'):
pass

@@ -183,2 +188,8 @@ else:

pass
elif is_ast_node(node, 'TypeVar'):
pass
elif is_ast_node(node, 'TypeVarTuple'):
pass
elif is_ast_node(node, 'ParamSpec'):
pass

@@ -227,2 +238,8 @@ else:

mentions += 1
elif is_ast_node(node, 'TypeVar'):
mentions += 1
elif is_ast_node(node, 'TypeVarTuple'):
mentions += 1
elif is_ast_node(node, 'ParamSpec'):
mentions += 1

@@ -296,3 +313,3 @@ else:

def __repr__(self):
return self.__class__.__name__ + '(name=%r) <references=%r>' % (self._name, len(self._references))
return self.__class__.__name__ + '(name=%r, allow_rename=%r) <references=%r>' % (self._name, self._allow_rename, len(self._references))

@@ -395,2 +412,8 @@ def should_rename(self, new_name):

node.rest = new_name
elif is_ast_node(node, 'TypeVar'):
node.name = new_name
elif is_ast_node(node, 'TypeVarTuple'):
node.name = new_name
elif is_ast_node(node, 'ParamSpec'):
node.name = new_name

@@ -457,1 +480,23 @@ if func_namespace_binding is not None:

)
def is_redefined(self):
"""
Do one of the references to this builtin name redefine it?
Could some references actually not be references to the builtin?
This can happen with code like:
class MyClass:
IndexError = IndexError
"""
for node in self.references:
if not isinstance(node, ast.Name):
return True
if not isinstance(node.ctx, ast.Load):
return True
return False

@@ -5,3 +5,3 @@ """

import ast
import python_minifier.ast_compat as ast

@@ -65,2 +65,6 @@ from python_minifier.rename.util import is_namespace

if hasattr(functiondef, 'type_params') and functiondef.type_params is not None:
for node in functiondef.type_params:
add_parent(node, parent=functiondef, namespace=functiondef.namespace)
if hasattr(functiondef, 'returns') and functiondef.returns is not None:

@@ -94,2 +98,6 @@ add_parent(functiondef.returns, parent=functiondef, namespace=functiondef.namespace)

if hasattr(classdef, 'type_params') and classdef.type_params is not None:
for node in classdef.type_params:
add_parent(node, parent=classdef, namespace=classdef.namespace)
def add_parent_to_comprehension(node, namespace):

@@ -158,2 +166,9 @@ assert is_ast_node(node, (ast.GeneratorExp, 'SetComp', 'DictComp', 'ListComp'))

if isinstance(node, ast.Name):
if isinstance(namespace, ast.ClassDef):
if isinstance(node.ctx, ast.Load):
namespace.nonlocal_names.add(node.id)
elif isinstance(node.ctx, ast.Store) and isinstance(node.parent, ast.AugAssign):
namespace.nonlocal_names.add(node.id)
for child in ast.iter_child_nodes(node):

@@ -160,0 +175,0 @@ add_parent(child, parent=node, namespace=namespace)

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -116,4 +116,5 @@ from python_minifier.rename.binding import Binding

def __call__(self, module):
def __call__(self, module, ignore_slots=True):
self.module = module
self._ignore_slots = ignore_slots
self._hoisted = {}

@@ -234,3 +235,17 @@ self.visit(module)

def visit_Assign(self, node):
if not self._ignore_slots:
return self.generic_visit(node)
if not is_ast_node(node.namespace, ast.ClassDef):
return self.generic_visit(node)
for target in node.targets:
if is_ast_node(target, ast.Name) and target.id == '__slots__':
# This is a __slots__ assignment, don't hoist the literals
return
return self.generic_visit(node)
def rename_literals(module):
HoistLiterals()(module)

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -3,0 +3,0 @@ from python_minifier.rename.binding import NameBinding

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -37,3 +37,11 @@ from python_minifier.rename.binding import BuiltinBinding, NameBinding

def get_binding_disallow_class_namespace_rename(name, namespace):
binding = get_binding(name, namespace)
if isinstance(namespace, ast.ClassDef):
# This name will become an attribute of a class, so it can't be renamed
binding.disallow_rename()
return binding
def resolve_names(node):

@@ -51,8 +59,16 @@ """

elif isinstance(node, ast.Name) and node.id in node.namespace.nonlocal_names:
get_binding(node.id, node.namespace).add_reference(node)
binding = get_binding(node.id, node.namespace)
binding.add_reference(node)
if isinstance(node.ctx, ast.Store) and isinstance(node.namespace, ast.ClassDef):
binding.disallow_rename()
elif isinstance(node, ast.ClassDef) and node.name in node.namespace.nonlocal_names:
get_binding(node.name, node.namespace).add_reference(node)
binding = get_binding_disallow_class_namespace_rename(node.name, node.namespace)
binding.add_reference(node)
elif is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef')) and node.name in node.namespace.nonlocal_names:
get_binding(node.name, node.namespace).add_reference(node)
binding = get_binding_disallow_class_namespace_rename(node.name, node.namespace)
binding.add_reference(node)
elif isinstance(node, ast.alias):

@@ -62,3 +78,5 @@

if node.asname in node.namespace.nonlocal_names:
get_binding(node.asname, node.namespace).add_reference(node)
binding = get_binding_disallow_class_namespace_rename(node.asname, node.namespace)
binding.add_reference(node)
else:

@@ -69,3 +87,3 @@ # This binds the root module only for a dotted import

if root_module in node.namespace.nonlocal_names:
binding = get_binding(root_module, node.namespace)
binding = get_binding_disallow_class_namespace_rename(root_module, node.namespace)
binding.add_reference(node)

@@ -78,11 +96,11 @@

if isinstance(node.name, str) and node.name in node.namespace.nonlocal_names:
get_binding(node.name, node.namespace).add_reference(node)
get_binding_disallow_class_namespace_rename(node.name, node.namespace).add_reference(node)
elif is_ast_node(node, 'Nonlocal'):
for name in node.names:
get_binding(name, node.namespace).add_reference(node)
get_binding_disallow_class_namespace_rename(name, node.namespace).add_reference(node)
elif is_ast_node(node, ('MatchAs', 'MatchStar')) and node.name in node.namespace.nonlocal_names:
get_binding(node.name, node.namespace).add_reference(node)
get_binding_disallow_class_namespace_rename(node.name, node.namespace).add_reference(node)
elif is_ast_node(node, 'MatchMapping') and node.rest in node.namespace.nonlocal_names:
get_binding(node.rest, node.namespace).add_reference(node)
get_binding_disallow_class_namespace_rename(node.rest, node.namespace).add_reference(node)

@@ -89,0 +107,0 @@ elif is_ast_node(node, 'Exec'):

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast
import sys

@@ -26,2 +26,11 @@

def iter_child_namespaces(node):
for child in ast.iter_child_nodes(node):
if is_namespace(child):
yield child
else:
for c in iter_child_namespaces(child):
yield c
def get_global_namespace(node):

@@ -28,0 +37,0 @@ """

@@ -120,3 +120,5 @@ """Tools for assembling python code from tokens."""

'or', 'pass', 'raise', 'return',
'try', 'while', 'with', 'yield', '_', 'case', 'match', 'print', 'exec'
'try', 'while', 'with', 'yield', '_',
'case', 'match', 'print', 'exec',
'type'
]

@@ -129,3 +131,3 @@

if kw in ['_', 'case', 'match']:
if kw in ['_', 'case', 'match', 'type']:
self.previous_token = TokenTypes.SoftKeyword

@@ -132,0 +134,0 @@ else:

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -17,4 +17,6 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer

alias = []
namespace = None
for statement in node_list:
namespace = statement.namespace
if isinstance(statement, ast.Import):

@@ -24,3 +26,3 @@ alias += statement.names

if alias:
yield self.add_child(ast.Import(names=alias), parent=parent)
yield self.add_child(ast.Import(names=alias), parent=parent, namespace=namespace)
alias = []

@@ -31,4 +33,5 @@

if alias:
yield self.add_child(ast.Import(names=alias), parent=parent)
yield self.add_child(ast.Import(names=alias), parent=parent, namespace=namespace)
def _combine_import_from(self, node_list, parent):

@@ -61,3 +64,3 @@

yield self.add_child(
ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent
ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent, namespace=prev_import.namespace
)

@@ -70,3 +73,3 @@ alias = []

yield self.add_child(
ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent
ast.ImportFrom(module=prev_import.module, names=alias, level=prev_import.level), parent=parent, namespace=prev_import.namespace
)

@@ -73,0 +76,0 @@

@@ -1,4 +0,5 @@

import ast
import python_minifier.ast_compat as ast
import sys
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
from python_minifier.transforms.suite_transformer import SuiteTransformer

@@ -12,2 +13,7 @@

def __init__(self, options):
assert isinstance(options, RemoveAnnotationsOptions)
self._options = options
super(RemoveAnnotations, self).__init__()
def __call__(self, node):

@@ -23,3 +29,6 @@ if sys.version_info < (3, 0):

if hasattr(node, 'returns'):
if hasattr(node, 'type_params') and node.type_params is not None:
node.type_params = [self.visit(t) for t in node.type_params]
if hasattr(node, 'returns') and self._options.remove_return_annotations:
node.returns = None

@@ -42,3 +51,4 @@

if hasattr(node, 'varargannotation'):
node.varargannotation = None
if self._options.remove_argument_annotations:
node.varargannotation = None
else:

@@ -49,3 +59,4 @@ if node.vararg:

if hasattr(node, 'kwargannotation'):
node.kwargannotation = None
if self._options.remove_argument_annotations:
node.kwargannotation = None
else:

@@ -58,3 +69,4 @@ if node.kwarg:

def visit_arg(self, node):
node.annotation = None
if self._options.remove_argument_annotations:
node.annotation = None
return node

@@ -105,2 +117,10 @@

# is this a class attribute or a variable?
if isinstance(node.parent, ast.ClassDef):
if not self._options.remove_class_attribute_annotations:
return node
else:
if not self._options.remove_variable_annotations:
return node
if is_dataclass_field(node) or is_typing_sensitive(node):

@@ -107,0 +127,0 @@ return node

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -3,0 +3,0 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast
import sys

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast
import sys

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -3,0 +3,0 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast
import sys

@@ -19,4 +19,7 @@

if hasattr(node, 'type_params') and node.type_params is not None:
node.type_params = [self.visit(t) for t in node.type_params]
node.body = [self.visit(n) for n in node.body]
return node

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -3,0 +3,0 @@ from python_minifier.transforms.suite_transformer import SuiteTransformer

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -53,2 +53,5 @@ from python_minifier.rename.mapper import add_parent

if hasattr(node, 'type_params') and node.type_params is not None:
node.type_params = [self.visit(t) for t in node.type_params]
node.body = self.suite(node.body, parent=node)

@@ -55,0 +58,0 @@ node.decorator_list = [self.visit(d) for d in node.decorator_list]

@@ -1,2 +0,2 @@

import ast
import python_minifier.ast_compat as ast

@@ -35,12 +35,12 @@ def is_ast_node(node, types):

if hasattr(ast, 'Constant') and isinstance(node, ast.Constant):
if node.value in [None, True, False]:
return ast.NameConstant in types
if type(node.value) in [type(None), type(True), type(False)]:
return ast.NameConstant in actual_types
elif isinstance(node.value, (int, float, complex)):
return ast.Num in types
return ast.Num in actual_types
elif isinstance(node.value, str):
return ast.Str in types
return ast.Str in actual_types
elif isinstance(node.value, bytes):
return ast.Bytes in types
return ast.Bytes in actual_types
elif node.value == Ellipsis:
return ast.Ellipsis in types
return ast.Ellipsis in actual_types
else:

@@ -47,0 +47,0 @@ raise RuntimeError('Unknown Constant value %r' % type(node.value))

import ast
from helpers import print_namespace
from python_minifier import add_namespace
from python_minifier.rename import bind_names, resolve_names
from python_minifier.transforms.combine_imports import CombineImports

@@ -12,2 +14,15 @@ from python_minifier.ast_compare import compare_ast

def assert_namespace_tree(source, expected_tree):
tree = ast.parse(source)
add_namespace(tree)
CombineImports()(tree)
bind_names(tree)
resolve_names(tree)
actual = print_namespace(tree)
print(actual)
assert actual.strip() == expected_tree.strip()
def test_import():

@@ -85,1 +100,66 @@ source = '''import builtins

compare_ast(expected_ast, actual_ast)
def test_import_as_class_namespace():
source = '''
class MyClass:
import os as Hello
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyClass', allow_rename=True) <references=1>
+ Class MyClass
- NameBinding(name='Hello', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_import_class_namespace():
source = '''
class MyClass:
import Hello
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyClass', allow_rename=True) <references=1>
+ Class MyClass
- NameBinding(name='Hello', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_from_import_class_namespace():
source = '''
class MyClass:
from hello import Hello
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyClass', allow_rename=True) <references=1>
+ Class MyClass
- NameBinding(name='Hello', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)
def test_from_import_as_class_namespace():
source = '''
class MyClass:
from hello import babadook as Hello
'''
expected_namespaces = '''
+ Module
- NameBinding(name='MyClass', allow_rename=True) <references=1>
+ Class MyClass
- NameBinding(name='Hello', allow_rename=False) <references=1>
'''
assert_namespace_tree(source, expected_namespaces)

@@ -8,2 +8,3 @@ import ast

from python_minifier.ast_compare import compare_ast
from python_minifier.ast_printer import print_ast
from python_minifier.rename import add_namespace, bind_names, resolve_names, rename, rename_literals, allow_rename_locals, allow_rename_globals

@@ -495,1 +496,26 @@

compare_ast(expected_ast, actual_ast)
def test_no_hoist_slots():
source = '''
class SlotsA(object):
__slots__ = ['aaaaa', 'bbbbb']
notslots = ['aaaaa']
class SlotsB(object):
__slots__ = 'aaaaa', 'bbbbb'
notslots = ['aaaaa']
'''
expected = '''
A = 'aaaaa'
class SlotsA(object):
__slots__ = ['aaaaa', 'bbbbb']
notslots = [A]
class SlotsB(object):
__slots__ = 'aaaaa', 'bbbbb'
notslots = [A]
'''
expected_ast = ast.parse(expected)
actual_ast = hoist(source)
print(print_ast(expected_ast))
print(print_ast(actual_ast))
compare_ast(expected_ast, actual_ast)

@@ -5,10 +5,10 @@ import ast

from python_minifier import add_namespace
from python_minifier import add_namespace, RemoveAnnotationsOptions
from python_minifier.transforms.remove_annotations import RemoveAnnotations
from python_minifier.ast_compare import compare_ast
def remove_annotations(source):
def remove_annotations(source, **kwargs):
module = ast.parse(source)
add_namespace(module)
RemoveAnnotations()(module)
RemoveAnnotations(RemoveAnnotationsOptions(**kwargs))(module)
return module

@@ -23,9 +23,20 @@

b = 2
c : int = 3'''
c : int = 3
def a(z: str) -> int: pass
class A:
a: int
'''
expected = '''a = 1
b = 2
c = 3'''
c = 3
def a(z: str) -> int: pass
class A:
a: int'''
expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=True,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=False)
compare_ast(expected_ast, actual_ast)

@@ -37,2 +48,3 @@

# args and return are removed
source = '''def test(a: str, b: int=1, *c: hello, **aws: crap) -> None:

@@ -44,5 +56,39 @@ pass'''

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=True,
remove_argument_annotations=True,
remove_class_attribute_annotations=False)
compare_ast(expected_ast, actual_ast)
# args only are removed
source = '''def test(a: str, b: int=1, *c: hello, **aws: crap) -> None:
pass'''
expected = '''def test(a, b=1, *c, **aws) -> None:
pass'''
expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=True,
remove_class_attribute_annotations=False)
compare_ast(expected_ast, actual_ast)
# return only are removed
source = '''def test(a: str, b: int=1, *c: hello, **aws: crap) -> None:
pass'''
expected = '''def test(a: str, b: int=1, *c: hello, **aws: crap):
pass'''
expected_ast = ast.parse(expected)
actual_ast = remove_annotations(
source,
remove_variable_annotations=False,
remove_return_annotations=True,
remove_argument_annotations=False,
remove_class_attribute_annotations=False
)
compare_ast(expected_ast, actual_ast)
def test_AsyncFunctionDef():

@@ -58,3 +104,7 @@ if sys.version_info < (3, 6):

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=True,
remove_return_annotations=True,
remove_argument_annotations=True,
remove_class_attribute_annotations=False)
compare_ast(expected_ast, actual_ast)

@@ -67,9 +117,46 @@

source = '''a :str
class A:
a: int
def c(self, a: int) -> None: pass
'''
expected = '''a:0'''
expected = '''a:0
class A:
a: int
def c(self, a: int) -> None: pass
'''
expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=True,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=False)
compare_ast(expected_ast, actual_ast)
def test_class_attributes():
if sys.version_info < (3, 6):
pytest.skip('Variable annotation unsupported in python < 3.6')
source = '''a :str
class A:
a: int
b: int=2
def c(self, a: int) -> None: pass
'''
expected = '''a:str
class A:
a:0
b=2
def c(self, a: int) -> None: pass
'''
expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=True)
compare_ast(expected_ast, actual_ast)
def test_no_remove_dataclass():

@@ -106,3 +193,7 @@ if sys.version_info < (3, 6):

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=True)
compare_ast(expected_ast, actual_ast)

@@ -130,3 +221,7 @@

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=True)
compare_ast(expected_ast, actual_ast)

@@ -155,3 +250,7 @@

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=True)
compare_ast(expected_ast, actual_ast)

@@ -182,3 +281,7 @@

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=True)
compare_ast(expected_ast, actual_ast)

@@ -227,3 +330,7 @@

expected_ast = ast.parse(expected)
actual_ast = remove_annotations(source)
actual_ast = remove_annotations(source,
remove_variable_annotations=False,
remove_return_annotations=False,
remove_argument_annotations=False,
remove_class_attribute_annotations=True)
compare_ast(expected_ast, actual_ast)
import ast
import sys
import pytest
from python_minifier import unparse
from python_minifier.ast_compare import compare_ast
def test_fstring_empty_str():
if sys.version_info < (3, 6):
pytest.skip('f-string expressions not allowed in python < 3.6')
source = r'''
f"""\
{fg_br}"""
'''
print(source)
expected_ast = ast.parse(source)
actual_ast = unparse(expected_ast)
compare_ast(expected_ast, ast.parse(actual_ast))