def test_pep():
    if sys.version_info < (3, 8):
        pytest.skip('No Assignment expressions in python < 3.8')

    source = '''
def name(p1, p2, /, p_or_kw, *, kw): pass
def name(p1, p2=None, /, p_or_kw=None, *, kw): pass
def name(p1, p2=None, /, *, kw): pass
def name(p1, p2=None, /): pass
def name(p1, p2, /, p_or_kw): pass
def name(p1, p2, /): pass
def name(p_or_kw, *, kw): pass
def name(*, kw): pass

def standard_arg(arg):
    print(arg)
def pos_only_arg(arg, /):
    print(arg)
def kwd_only_arg(*, arg):
    print(arg)
def combined_example(pos_only, /, standard, *, kwd_only):
    print(pos_only, standard, kwd_only)
'''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
Exemple #2
0
def test_match_unparse():
    if sys.version_info < (3, 10):
        pytest.skip('Match statement not in python < 3.10')

    source = '''
match a:
    case (0 as a) as b: pass

match a:
    case _:pass
    
match a:
    case 0|(0|0): pass
    case (0|0)|0: pass    
    case 0|0|0: pass

match (lambda: a)():
    case [action, obj]:pass

match a:= h:
    case [action, obj]:pass
    case {**rest}: pass   
    '''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
Exemple #3
0
def assert_code(expected_ast, actual_ast):
    try:
        compare_ast(expected_ast, actual_ast)
    except CompareError as e:
        print(e)
        print(unparse(actual_ast))
        raise
def test_file(path):

    try:
        with open(path, 'rb') as f:
            source = f.read()
    except IOError:
        pytest.skip('IOError opening file')

    try:
        original_ast = ast.parse(source, path)
    except SyntaxError:
        pytest.skip('Invalid syntax in file')

    # Test unparsing
    unparse(original_ast)

    # Test transforms
    minify(source, filename=path)
def test_pep():
    if sys.version_info < (3, 9):
        pytest.skip('Decorator expression not allowed in python <3.9')

    source = """
buttons = [QPushButton(f'Button {i}') for i in range(10)]

# Do stuff with the list of buttons...

@buttons[0].clicked.connect
def spam():
    ...

@buttons[1].clicked.connect
def eggs():
    ...

# Do stuff with the list of buttons...
@(f, g)
def a(): pass

@(f, g)
class A:pass

@lambda func: (lambda *p: func(*p).u())
def g(n): pass

@s := lambda func: (lambda *p: func(*p).u())
def g(name): pass

@s
def r(n, t):
    pass

@lambda f: lambda *p: f or f(*p).u()
def g(name): pass

@lambda f: lambda *p: \
        [_ for _ in [ \
            f(*p),
            ] if _][0]
def c(): pass

@lambda f: lambda *p: \
            list(filter(lambda _: _,[
                (a := t()) and False,
                f(*p),
                (b := t()) and False,
            ]))[0]
def c(): pass

"""

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
Exemple #6
0
def hoist(source):
    module = ast.parse(source)
    add_namespace(module)
    bind_names(module)
    resolve_names(module)
    allow_rename_locals(module, False)
    allow_rename_globals(module, False)
    rename_literals(module)
    rename(module)
    print(unparse(module))
    return module
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))
Exemple #8
0
def test_await_fstring():
    if sys.version_info < (3, 7):
        pytest.skip(
            'Await in f-string expressions not allowed in python < 3.7')

    source = '''
async def a(): return 'hello'
async def b(): return f'{await b()}'
'''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
def test_dict_expanson():
    if sys.version_info < (3, 5):
        pytest.skip('dict expansion not allowed in python < 3.5')

    source = [
        r'{**a>>9}', r'{**(a or b)}', r'{**(a and b)}', r'{**a+b}',
        r'{**(lambda a:a)}', r'{**(a<b)}', r'{**(yield a())}',
        r'{**(a if a else a)}'
    ]

    for expression in source:
        expected_ast = ast.parse(expression)
        minified = unparse(expected_ast)
        compare_ast(expected_ast, ast.parse(minified))
        assert expression == minified
Exemple #10
0
def test_return():
    if sys.version_info < (3, 0):
        pytest.skip('Iterable unpacking in return not allowed in python < 3.0')

    elif sys.version_info < (3, 8):
        # Parenthesis are required
        source = 'def a():return(True,*[False])'

    else:
        # Parenthesis not required
        source = 'def a():return True,*[False]'

    expected_ast = ast.parse(source)
    minified = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(minified))
    assert source == minified
Exemple #11
0
def test_pep():
    if sys.version_info < (3, 8):
        pytest.skip('No Assignment expressions in python < 3.8')

    source = '''
if a := True:
    print(a)
if self._is_special and (ans := self._check_nans(context=context)):
    return ans    
results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]
stuff = [[y := f(x), x/y] for x in range(5)]    
'''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
Exemple #12
0
def test_slice():
    """AST for slices was changed in 3.9"""

    source = '''
x[name]
x[1:2]
x[1:2, 3]
x[()]
x[1:2, 2:2]
x[a, ..., b:c]
x[a, ..., b]
x[(a, b)]
x[a:b,]
'''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
Exemple #13
0
def test_pep635_unparse():
    if sys.version_info < (3, 10):
        pytest.skip('Match statement not in python < 3.10')

    source = '''
match x:
    case host, port:
        mode = "http"
    case host, port, mode:
        pass
        
match node:
    case BinOp("+", a, BinOp("*", b, c)):
        pass   
        
match json_pet:
    case {"type": "cat", "name": name, "pattern": pattern}:
        return Cat(name, pattern)
    case {"type": "dog", "name": name, "breed": breed}:
        return Dog(name, breed)
    case _:
        raise ValueError("Not a suitable pet")
        
def sort(seq):
    match seq:
        case [] | [_]:
            return seq
        case [x, y] if x <= y:
            return seq
        case [x, y]:
            return [y, x]
        case [x, y, z] if x <= y <= z:
            return seq
        case [x, y, z] if x >= y >= z:
            return [z, y, x]
        case [p, *rest]:
            a = sort([x for x in rest if x <= p])
            b = sort([x for x in rest if p < x])
            return a + [p] + b

def simplify_expr(tokens):
    match tokens:
        case [('('|'[') as l, *expr, (')'|']') as r] if (l+r) in ('()', '[]'):
            return simplify_expr(expr)
        case [0, ('+'|'-') as op, right]:
            return UnaryOp(op, right)
        case [(int() | float() as left) | Num(left), '+', (int() | float() as right) | Num(right)]:
            return Num(left + right)
        case [(int() | float()) as value]:
            return Num(value)
   
def simplify(expr):
    match expr:
        case ('/', 0, 0):
            return expr
        case ('*'|'/', 0, _):
            return 0
        case ('+'|'-', x, 0) | ('+', 0, x) | ('*', 1, x) | ('*'|'/', x, 1):
            return x
    return expr

def simplify(expr):
    match expr:
        case ('+', 0, x):
            return x
        case ('+' | '-', x, 0):
            return x
        case ('and', True, x):
            return x
        case ('and', False, x):
            return False
        case ('or', False, x):
            return x
        case ('or', True, x):
            return True
        case ('not', ('not', x)):
            return x
    return expr

def average(*args):
    match args:
        case [x, y]:           # captures the two elements of a sequence
            return (x + y) / 2
        case [x]:              # captures the only element of a sequence
            return x
        case []:
            return 0
        case a:                # captures the entire sequence
            return sum(a) / len(a)
 
def is_closed(sequence):
   match sequence:
       case [_]:               # any sequence with a single element
           return True
       case [start, *_, end]:  # a sequence with at least two elements
           return start == end
       case _:                 # anything
           return False
 
def handle_reply(reply):
   match reply:
       case (HttpStatus.OK, MimeType.TEXT, body):
           process_text(body)
       case (HttpStatus.OK, MimeType.APPL_ZIP, body):
           text = deflate(body)
           process_text(text)
       case (HttpStatus.MOVED_PERMANENTLY, new_URI):
           resend_request(new_URI)
       case (HttpStatus.NOT_FOUND):
           raise ResourceNotFound()

def change_red_to_blue(json_obj):
    match json_obj:
        case { 'color': ('red' | '#FF0000') }:
            json_obj['color'] = 'blue'
        case { 'children': children }:
            for child in children:
                change_red_to_blue(child)

'''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))
Exemple #14
0
def test_pep646_unparse():
    if sys.version_info < (3, 10):
        pytest.skip('Match statement not in python < 3.10')

    source = '''
match command.split():
    case [action]:
        pass
    case [action, obj]:
        pass

match command.split():
    case ["quit"]:
        print("Goodbye!")
        quit_game()
    case ["look"]:
        current_room.describe()
    case ["get", obj]:
        character.get(obj, current_room)
    case ["go", direction]:
        current_room = current_room.neighbor(direction)

match command.split():
    case ["drop", *objects]:
        for obj in objects:
            character.drop(obj, current_room)

match command.split():
    case ["quit"]: pass
    case ["go", direction]: pass
    case ["drop", *objects]: pass
    case _:
        print(f"Sorry, I couldn't understand {command!r}")

match command.split():

    case ["north"] | ["go", "north"]:
        current_room = current_room.neighbor("north")
    case ["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"]:
        pass

match command.split():
    case ["go", ("north" | "south" | "east" | "west")]:
        current_room = current_room.neighbor(...)

match command.split():
    case ["go", ("north" | "south" | "east" | "west") as direction]:
        current_room = current_room.neighbor(direction)

match command.split():
    case ["go", direction] if direction in current_room.exits:
        current_room = current_room.neighbor(direction)
    case ["go", _]:
        print("Sorry, you can't go that way")

match event.get():
    case Click(position=(x, y)):
        handle_click_at(x, y)
    case KeyPress(key_name="Q") | Quit():
        game.quit()
    case KeyPress(key_name="up arrow"):
        game.go_north()
    case KeyPress():
        pass # Ignore other keystrokes
    case other_event:
        raise ValueError(f"Unrecognized event: {other_event}")

match event.get():
    case Click((x, y)):
        handle_click_at(x, y)

match event.get():
    case Click((x, y), button=Button.LEFT):  # This is a left click
        handle_click_at(x, y)
    case Click():
        pass  # ignore other clicks

match action:
    case {"text": message, "color": c}:
        ui.set_text_color(c)
        ui.display(message)
    case {"sleep": duration}:
        ui.wait(duration)
    case {"sound": url, "format": "ogg"}:
        ui.play(url)
    case {"sound": _, "format": _}:
        warning("Unsupported audio format")

match action:
    case {"text": str(message), "color": str(c)}:
        ui.set_text_color(c)
        ui.display(message)
    case {"sleep": float(duration)}:
        ui.wait(duration)
    case {"sound": str(url), "format": "ogg"}:
        ui.play(url)
    case {"sound": _, "format": _}:
        warning("Unsupported audio format")

match status:
    case 400:
        return "Bad request"
    case 404:
        return "Not found"
    case 418:
        return "I'm a teapot"
    case 401 | 403 | 404:
        return "Not allowed"
    case _:
        return "Something's wrong with the Internet"

match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y={y}")
    case (x, 0):
        print(f"X={x}")
    case (x, y):
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")

match point:
    case Point(x=0, y=0):
        print("Origin")
    case Point(x=0, y=y):
        print(f"Y={y}")
    case Point(x=x, y=0):
        print(f"X={x}")
    case Point():
        print("Somewhere else")
    case _:
        print("Not a point")

match points:
    case []:
        print("No points")
    case [Point(0, 0)]:
        print("The origin")
    case [Point(x, y)]:
        print(f"Single point {x}, {y}")
    case [Point(0, y1), Point(0, y2)]:
        print(f"Two on the Y axis at {y1}, {y2}")
    case _:
        print("Something else")

match point:
    case Point(x, y) if x == y:
        print(f"Y=X at {x}")
    case Point(x, y):
        print(f"Not on the diagonal")

match color:
    case Color.RED:
        print("I see red!")
    case Color.GREEN:
        print("Grass is green")
    case Color.BLUE:
        print("I'm feeling the blues :(")

    '''

    expected_ast = ast.parse(source)
    actual_ast = unparse(expected_ast)
    compare_ast(expected_ast, ast.parse(actual_ast))