def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and isinstance(node.func, ast.Name) and node.func.id == 'open' and not has_starargs(node)): if len(node.args) >= 2 and isinstance(node.args[1], ast.Str): if (node.args[1].s in U_MODE_REPLACE or (len(node.args) == 2 and node.args[1].s in U_MODE_REMOVE)): func = functools.partial( _fix_open_mode, arg_idx=1, ) yield ast_to_offset(node), func elif node.keywords and (len(node.keywords) + len(node.args) > 1): mode = next( (FunctionArg(n, keyword.value) for n, keyword in enumerate(node.keywords) if keyword.arg == 'mode'), None, ) if (mode is not None and isinstance(mode.value, ast.Str) and (mode.value.s in U_MODE_REMOVE or mode.value.s in U_MODE_REPLACE)): func = functools.partial( _fix_open_mode, arg_idx=len(node.args) + mode.arg_idx, ) yield ast_to_offset(node), func
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, 8) and not node.args and not node.keywords and is_name_attr( node.func, state.from_imports, 'functools', ('lru_cache', ), )): yield ast_to_offset(node), _remove_call elif (state.settings.min_version >= (3, 9) and isinstance(node.func, ast.Attribute) and node.func.attr == 'lru_cache' and isinstance(node.func.value, ast.Name) and node.func.value.id == 'functools' and not node.args and len(node.keywords) == 1 and node.keywords[0].arg == 'maxsize' and isinstance(node.keywords[0].value, ast.NameConstant) and node.keywords[0].value.value is None): func = functools.partial( find_and_replace_call, template='functools.cache', ) yield ast_to_offset(node), func
def visit_Subscript( state: State, node: ast.Subscript, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if not _supported_version(state): return if is_name_attr(node.value, state.from_imports, 'typing', ('Optional', )): yield ast_to_offset(node), _fix_optional elif is_name_attr(node.value, state.from_imports, 'typing', ('Union', )): if sys.version_info >= (3, 9): # pragma: no cover (py39+) node_slice: ast.expr = node.slice elif isinstance(node.slice, ast.Index): # pragma: no cover (<py39) node_slice = node.slice.value else: # pragma: no cover (<py39) return # unexpected slice type if isinstance(node_slice, ast.Tuple): if node_slice.elts: arg_count = len(node_slice.elts) else: return # empty Union else: arg_count = 1 func = functools.partial(_fix_union, arg_count=arg_count) yield ast_to_offset(node), func
def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if state.settings.min_version < (3,): return for decorator in node.decorator_list: if ( isinstance(decorator, ast.Call) and is_name_attr( decorator.func, state.from_imports, 'six', ('add_metaclass',), ) and not has_starargs(decorator) ): yield ast_to_offset(decorator), _fix_add_metaclass if ( len(node.bases) == 1 and isinstance(node.bases[0], ast.Call) and is_name_attr( node.bases[0].func, state.from_imports, 'six', ('with_metaclass',), ) and not has_starargs(node.bases[0]) ): yield ast_to_offset(node.bases[0]), _fix_with_metaclass
def visit_Assign(self, node: ast.Assign) -> None: if ( # NT = ...("NT", ...) len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and isinstance(node.value, ast.Call) and len(node.value.args) >= 1 and isinstance(node.value.args[0], ast.Str) and node.targets[0].id == node.value.args[0].s and not has_starargs(node.value) ): if ( self._is_attr( node.value.func, {'typing'}, 'NamedTuple', ) and len(node.value.args) == 2 and not node.value.keywords and isinstance(node.value.args[1], (ast.List, ast.Tuple)) and len(node.value.args[1].elts) > 0 and all( isinstance(tup, ast.Tuple) and len(tup.elts) == 2 and isinstance(tup.elts[0], ast.Str) and tup.elts[0].s.isidentifier() and tup.elts[0].s not in KEYWORDS for tup in node.value.args[1].elts ) ): self.named_tuples[ast_to_offset(node)] = node.value elif ( self._is_attr( node.value.func, {'typing', 'typing_extensions'}, 'TypedDict', ) and len(node.value.args) == 1 and len(node.value.keywords) > 0 ): self.kw_typed_dicts[ast_to_offset(node)] = node.value elif ( self._is_attr( node.value.func, {'typing', 'typing_extensions'}, 'TypedDict', ) and len(node.value.args) == 2 and not node.value.keywords and isinstance(node.value.args[1], ast.Dict) and node.value.args[1].keys and all( isinstance(k, ast.Str) and k.s.isidentifier() and k.s not in KEYWORDS for k in node.value.args[1].keys ) ): self.dict_typed_dicts[ast_to_offset(node)] = node.value self.generic_visit(node)
def visit_BinOp( state: State, node: ast.BinOp, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (not state.settings.keep_percent_format and isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Str)): try: parsed = _parse_percent_format(node.left.s) except ValueError: pass else: for _, fmt in parsed: if not fmt: continue key, conversion_flag, width, precision, conversion = fmt # timid: these require out-of-order parameter consumption if width == '*' or precision == '.*': break # these conversions require modification of parameters if conversion in {'d', 'i', 'u', 'c'}: break # timid: py2: %#o formats different from {:#o} (--py3?) if '#' in (conversion_flag or '') and conversion == 'o': break # no equivalent in format if key == '': break # timid: py2: conversion is subject to modifiers (--py3?) nontrivial_fmt = any((conversion_flag, width, precision)) if conversion == '%' and nontrivial_fmt: break # no equivalent in format if conversion in {'a', 'r'} and nontrivial_fmt: break # all dict substitutions must be named if isinstance(node.right, ast.Dict) and not key: break else: if isinstance(node.right, ast.Tuple): func = functools.partial( _fix_percent_format_tuple, node_right=node.right, ) yield ast_to_offset(node), func elif isinstance(node.right, ast.Dict): func = functools.partial( _fix_percent_format_dict, node_right=node.right, ) yield ast_to_offset(node), func
def _is_oserror_alias( node: ast.AST, from_imports: Dict[str, Set[str]], ) -> Optional[Tuple[Offset, str]]: if isinstance(node, ast.Name) and node.id in ERROR_NAMES: return ast_to_offset(node), node.id elif (isinstance(node, ast.Name) and node.id == 'error' and any(node.id in from_imports[mod] for mod in ERROR_MODULES)): return ast_to_offset(node), node.id elif (isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and node.value.id in ERROR_MODULES and node.attr == 'error'): return ast_to_offset(node), node.attr else: return None
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (isinstance(node.func, ast.Name) and node.func.id == 'set' and len(node.args) == 1 and not node.keywords and isinstance(node.args[0], SET_TRANSFORM)): arg, = node.args if isinstance(arg, (ast.List, ast.Tuple)) and not arg.elts: yield ast_to_offset(node.func), _fix_set_empty_literal else: func = functools.partial(_fix_set_literal, arg=arg) yield ast_to_offset(node.func), func
def visit_If( state: State, node: ast.If, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and ( # if six.PY2: is_name_attr(node.test, state.from_imports, 'six', ('PY2', )) or # if not six.PY3: (isinstance(node.test, ast.UnaryOp) and isinstance(node.test.op, ast.Not) and is_name_attr( node.test.operand, state.from_imports, 'six', ('PY3', ), )) or # sys.version_info == 2 or < (3,) (isinstance(node.test, ast.Compare) and is_name_attr( node.test.left, state.from_imports, 'sys', ('version_info', ), ) and len(node.test.ops) == 1 and (_eq(node.test, 2) or _compare_to_3(node.test, ast.Lt))))): if node.orelse and not isinstance(node.orelse[0], ast.If): yield ast_to_offset(node), _fix_py2_block elif (state.settings.min_version >= (3, ) and ( # if six.PY3: is_name_attr(node.test, state.from_imports, 'six', ('PY3', )) or # if not six.PY2: (isinstance(node.test, ast.UnaryOp) and isinstance(node.test.op, ast.Not) and is_name_attr( node.test.operand, state.from_imports, 'six', ('PY2', ), )) or # sys.version_info == 3 or >= (3,) or > (3,) (isinstance(node.test, ast.Compare) and is_name_attr( node.test.left, state.from_imports, 'sys', ('version_info', ), ) and len(node.test.ops) == 1 and (_eq(node.test, 3) or _compare_to_3(node.test, (ast.Gt, ast.GtE)))))): if node.orelse and not isinstance(node.orelse[0], ast.If): yield ast_to_offset(node), _fix_py3_block_else elif not node.orelse: yield ast_to_offset(node), _fix_py3_block
def visit_Dict( state: State, node: ast.Dict, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if state.settings.min_version < (3, 9): return if all(key is None for key in node.keys) and len(node.values) > 1: yield ast_to_offset(node), _replace_dict_brackets arg_count = len(node.values) for idx, arg in enumerate(node.values): yield ast_to_offset(arg), _remove_double_star if idx < arg_count - 1: yield ast_to_offset(arg), _replace_comma_with_pipe
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if ( isinstance(node.func, ast.Name) and node.func.id in ALLOWED_FUNCS and node.args and isinstance(node.args[0], ast.ListComp) ): if len(node.args) == 1 and not node.keywords: yield ast_to_offset(node.args[0]), _delete_list_comp_brackets else: yield ast_to_offset(node.args[0]), replace_list_comp_brackets
def visit_Call(self, node: ast.Call) -> None: parsed = self._parse(node) if parsed is not None: params = _format_params(node) seen: Set[str] = set() i = 0 for _, name, spec, _ in parsed: # timid: difficult to rewrite correctly if spec is not None and '{' in spec: break if name is not None: candidate, _, _ = name.partition('.') # timid: could make the f-string longer if candidate and candidate in seen: break # timid: bracketed elif '[' in name: break seen.add(candidate) key = candidate or str(i) # their .format() call is broken currently if key not in params: break if not candidate: i += 1 else: self.fstrings[ast_to_offset(node)] = node self.generic_visit(node)
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and is_a_native_literal_call(node, state.from_imports)): yield ast_to_offset(node), _fix_native_str
def visit_Starred( state: State, node: ast.Starred, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and isinstance(node.value, ast.ListComp)): yield ast_to_offset(node.value), replace_list_comp_brackets
def _fix_percent_format_dict( i: int, tokens: List[Token], *, node_right: ast.Dict, ) -> None: # TODO: handle \N escape sequences if r'\N' in tokens[i].src: return seen_keys: Set[str] = set() keys = {} for k in node_right.keys: # not a string key if not isinstance(k, ast.Str): return # duplicate key elif k.s in seen_keys: return # not an identifier elif not k.s.isidentifier(): return # a keyword elif k.s in KEYWORDS: return seen_keys.add(k.s) keys[ast_to_offset(k)] = k # TODO: this is overly timid brace = i + 4 if tokens_to_src(tokens[i + 1:brace + 1]) != ' % {': return fmt_victims = victims(tokens, brace, node_right, gen=False) brace_end = fmt_victims.ends[-1] key_indices = [] for j, token in enumerate(tokens[brace:brace_end], brace): key = keys.pop(token.offset, None) if key is None: continue # we found the key, but the string didn't match (implicit join?) elif ast.literal_eval(token.src) != key.s: return # the map uses some strange syntax that's not `'key': value` elif tokens[j + 1].src != ':' or tokens[j + 2].src != ' ': return else: key_indices.append((j, key.s)) assert not keys, keys tokens[brace_end] = tokens[brace_end]._replace(src=')') for key_index, s in reversed(key_indices): tokens[key_index:key_index + 3] = [Token('CODE', f'{s}=')] newsrc = _percent_to_format(tokens[i].src) tokens[i] = tokens[i]._replace(src=newsrc) tokens[i + 1:brace + 1] = [Token('CODE', '.format'), Token('OP', '(')]
def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3, ): for base in node.bases: if is_name_attr(base, state.from_imports, 'six', ('Iterator', )): yield ast_to_offset(base), remove_base_class
def visit_ClassDef( state: State, node: ast.ClassDef, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if state.settings.min_version >= (3, ): for base in node.bases: if isinstance(base, ast.Name) and base.id == 'object': yield ast_to_offset(base), remove_base_class
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, 7) and is_name_attr( node.func, state.from_imports, 'subprocess', ('run', ), )): stdout_idx = None stderr_idx = None universal_newlines_idx = None for n, keyword in enumerate(node.keywords): if keyword.arg == 'stdout' and is_name_attr( keyword.value, state.from_imports, 'subprocess', ('PIPE', ), ): stdout_idx = n elif keyword.arg == 'stderr' and is_name_attr( keyword.value, state.from_imports, 'subprocess', ('PIPE', ), ): stderr_idx = n elif keyword.arg == 'universal_newlines': universal_newlines_idx = n if universal_newlines_idx is not None: func = functools.partial( _replace_universal_newlines_with_text, arg_idx=len(node.args) + universal_newlines_idx, ) yield ast_to_offset(node), func if stdout_idx is not None and stderr_idx is not None: func = functools.partial( _use_capture_output, stdout_arg_idx=len(node.args) + stdout_idx, stderr_arg_idx=len(node.args) + stderr_idx, ) yield ast_to_offset(node), func
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'io' and node.func.attr == 'open'): yield ast_to_offset(node.func), _replace_io_open
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and isinstance(node.func, ast.Name) and node.func.id == 'open' and not has_starargs(node) and len(node.args) >= 2 and isinstance(node.args[1], ast.Str) and (node.args[1].s in U_MODE_REPLACE or (len(node.args) == 2 and node.args[1].s in U_MODE_REMOVE))): yield ast_to_offset(node), _fix_open_mode
def visit_ImportFrom( state: State, node: ast.ImportFrom, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and not state.settings.keep_mock and not node.level and node.module in MOCK_MODULES ): yield ast_to_offset(node), _fix_import_from_mock
def visit_Assign( state: State, node: ast.Assign, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Tuple) and isinstance(node.value, ast.ListComp) ): yield ast_to_offset(node.value), _replace_list_comprehension
def visit_Import( state: State, node: ast.Import, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and not state.settings.keep_mock and len(node.names) == 1 and node.names[0].name in MOCK_MODULES ): yield ast_to_offset(node), _fix_import_mock
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (isinstance(node.func, ast.Name) and node.func.id == 'dict' and len(node.args) == 1 and not node.keywords and isinstance(node.args[0], (ast.ListComp, ast.GeneratorExp)) and isinstance(node.args[0].elt, (ast.Tuple, ast.List)) and len(node.args[0].elt.elts) == 2): func = functools.partial(_fix_dict_comp, arg=node.args[0]) yield ast_to_offset(node.func), func
def visit_Call( state: State, node: ast.Call, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Str) and node.func.attr == 'encode' and not has_starargs(node) and len(node.args) == 1 and isinstance(node.args[0], ast.Str) and is_codec(node.args[0].s, 'utf-8')): yield ast_to_offset(node), _fix_default_encoding
def visit_Name( state: State, node: ast.Name, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (_should_rewrite(state) and node.id in state.from_imports['typing'] and node.id in PEP585_BUILTINS): func = functools.partial( replace_name, name=node.id, new=node.id.lower(), ) yield ast_to_offset(node), func
def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (_should_rewrite(state) and isinstance(node.value, ast.Name) and node.value.id == 'typing' and node.attr in PEP585_BUILTINS): func = functools.partial( replace_name, name=node.attr, new=node.attr.lower(), ) yield ast_to_offset(node), func
def visit_Attribute( state: State, node: ast.Attribute, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if ( state.settings.min_version >= (3,) and not state.settings.keep_mock and isinstance(node.value, ast.Name) and node.value.id == 'mock' and node.attr == 'mock' ): yield ast_to_offset(node), _fix_mock_mock
def visit_Name( state: State, node: ast.Name, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: if (state.settings.min_version >= (3, ) and node.id in state.from_imports['typing'] and node.id == 'Text'): func = functools.partial( replace_name, name=node.id, new='str', ) yield ast_to_offset(node), func
def visit_Compare( state: State, node: ast.Compare, parent: ast.AST, ) -> Iterable[Tuple[Offset, TokenFunc]]: left = node.left for op, right in zip(node.ops, node.comparators): if (isinstance(op, (ast.Is, ast.IsNot)) and (isinstance(left, LITERAL_TYPES) or isinstance(right, LITERAL_TYPES))): func = functools.partial(_fix_is_literal, op=op) yield ast_to_offset(right), func left = right