def _make_tests_node(data): """Create the final "tests" data that will be written to a package.py file. Important: This function technically doesn't create a "correct" parso dict. For the sake of simplicity, some corners were cut. For example, value data is converted into a raw :class:`parso.python.tree.String`. It's not technically "correct" but since this function is called just before writing the data into disk and not further modified, it's okay. Args: data (iter[str, :class:`parso.python.tree.PythonBaseNode`]): The key / value information that will be converted into a parso dict. Returns: :class:`parso.python.tree.PythonNode`: An atom PythonNode which has a full dictionary "tests" defined. This will later override an existing "tests" attribute in a package.py or be appended to the file as an assignment. """ nodes = [] for key, value in data: nodes.extend([ tree.String(_escape(key), (0, 0), prefix="\n "), tree.Operator(":", (0, 0)), ]) if isinstance(value, collections.MutableMapping): nodes.append(_make_dict_nodes(sorted(value.items()), prefix=" ")) elif hasattr(value, "get_code"): if hasattr(value, "prefix"): value.prefix = " " nodes.append(value) else: nodes.append(tree.String(_escape_all(value), (0, 0), prefix=" ")) nodes.append(tree.Operator(",", (0, 0))) base = tree.PythonNode("dictorsetmaker", nodes) return tree.PythonNode( "atom", [ tree.Operator("{", (0, 0)), base, tree.Operator("}", (0, 0), prefix="\n") ], )
def _apply_decorators(context, node): """ Returns the function, that should to be executed in the end. This is also the places where the decorators are processed. """ if node.type == 'classdef': decoratee_context = ClassContext(context.evaluator, parent_context=context, tree_node=node) else: decoratee_context = FunctionContext.from_context(context, node) initial = values = ContextSet(decoratee_context) for dec in reversed(node.get_decorators()): debug.dbg('decorator: %s %s', dec, values) dec_values = context.eval_node(dec.children[1]) trailer_nodes = dec.children[2:-1] if trailer_nodes: # Create a trailer and evaluate it. trailer = tree.PythonNode('trailer', trailer_nodes) trailer.parent = dec dec_values = eval_trailer(context, dec_values, trailer) if not len(dec_values): debug.warning('decorator not found: %s on %s', dec, node) return initial values = dec_values.execute(arguments.ValuesArguments([values])) if not len(values): debug.warning('not possible to resolve wrappers found %s', node) return initial debug.dbg('decorator end %s', values) return values
def _make_new_list(requirements): """Make a flat Python list, using parso nodes. Args: requirements (iter[:class:`parso.python.tree.String`]): The Rez requirements that'll be used to generate the new list. No nodes related to punctuation should be used here. Just requirements. Returns: :class:`parso.python.tree.PythonNode`: The generated list. """ nodes = [] for requirement in requirements: nodes.append(requirement) nodes.append(tree.Operator(",", (0, 0))) prefix = "\n" if not nodes: prefix = "" return tree.PythonNode( "atom", [tree.Operator("[", (0, 0))] + nodes + [tree.Operator("]", (0, 0), prefix=prefix)], )
def _make_dict_nodes(data, prefix=""): """Create a parso node that represents the inner key / value pairs of a Python dict. In terms of the work needed to generate a Rez "tests" attribute, this function is used mainly to create the "inner dict" that a "tests" attribute frequently defines. Args: data (iter[tuple[str, str or list or :class:`parso.python.tree.PythonBaseNode`]]): Returns: :class:`parso.python.tree.PythonNode`: A node that defines the inside of a parso dict. This node is not equivalent to an actual dict and still needs to be wrapped with another :class:`parso.python.tree.PythonNode` to be valid. """ nodes = [] for key, item in data: item = _escape_all(item) nodes.append( tree.String(_escape(key), (0, 0), prefix="\n {prefix}".format(prefix=prefix))) nodes.append(tree.Operator(":", (0, 0))) nodes.append( tree.PythonNode( "atom", [ tree.String(item, (0, 0), prefix=" "), tree.Operator(",", (0, 0)) ], ), ) return tree.PythonNode( "atom", [ tree.Operator("{", (0, 0), prefix=" "), tree.PythonNode("dictorsetmaker", nodes), tree.Operator("}", (0, 0), prefix="\n "), ], )
def _make_import(namespace_parts, prefix="", alias=None): """Make a new from-import node and insert it into an existing parso graph. Args: namespace_parts (list[str]): The import that will be converted into a parso from-import node. prefix (str, optional): The leading whitespace (indentation) placed before the new import. Returns: :class:`parso.python.tree.ImportFrom`: A newly generated import statement. """ base_names = namespace_parts[:-1] base_count = len(base_names) base_nodes = [] if base_count > 1: for is_last, name in iterbot.iter_is_last(base_names): base_nodes.append(tree.Name(name, (0, 0))) if not is_last: base_nodes.append(tree.Operator(".", (0, 0))) base_nodes[0].prefix = " " base = tree.PythonNode("dotted_name", base_nodes) else: base = tree.Name(base_names[0], (0, 0), prefix=" ") tail = tree.Name(namespace_parts[-1], (0, 0), prefix=" ") if alias: tail = tree.PythonNode( "import_as_name", [tail, tree.Keyword("as", (0, 0), prefix=" "), alias]) return tree.ImportFrom([ tree.Keyword("from", (0, 0), prefix=prefix), base, tree.Keyword("import", (0, 0), prefix=" "), tail, ])
def _make_intersphinx_dict(pairs): """Create a parso literal that defines ``intersphinx_mapping``. Args: pairs (list[:class:`parso.python.tree.Node`]): The key / value dict pairs that will be added into a new ``intersphinx_mapping`` dict literal. Returns: :class:`parso.python.tree.PythonNode`: The generated dict. """ return tree.PythonNode( "atom", [ tree.Operator("{", (0, 0), prefix=" "), tree.PythonNode("dictorsetmaker", pairs), tree.Operator("}", (len(pairs), 0), prefix="\n"), ], )
def insert_or_append_raw_node(node, graph, assignment, attribute): """Add a new `attribute` to `graph`, according to pre-existing data. Args: node (:class:`parso.python.tree.PythonNode`): The main data that will be added to `graph`. graph (:class:`parso.python.tree.PythonNode`): A Python module that may already contain an assignment for `attribute`. If it doesn't the attribute will be added, automatically. assignment (:class:`parso.python.tree.PythonNode` or NoneType): If `graph` has an existing node where `attribute` is defined, this node will represent that position. If this parameter is None, a new position for `attribute` will be automatically found and used. attribute (str): The name of the Rez-related object to use. Returns: :class:`parso.python.tree.PythonNode`: The main module with a modified `attribute`. """ if assignment: index = _find_nearest_node_index(graph.children, attribute) del graph.children[index] graph.children.insert(index, tree.PythonNode("assignment", [node])) return graph index = _find_nearest_node_index(graph.children, attribute) if index == -1: graph.children.append(tree.PythonNode("assignment", [node])) return graph prefix_node = node_seek.get_node_with_first_prefix(node) prefix_node.prefix = "\n\n" graph.children.insert(index, tree.PythonNode("assignment", [node])) return graph
def _apply_decorators(context, node): """ Returns the function, that should to be executed in the end. This is also the places where the decorators are processed. """ if node.type == 'classdef': decoratee_value = ClassValue( context.inference_state, parent_context=context, tree_node=node ) else: decoratee_value = FunctionValue.from_context(context, node) initial = values = ValueSet([decoratee_value]) if is_big_annoying_library(context): return values for dec in reversed(node.get_decorators()): debug.dbg('decorator: %s %s', dec, values, color="MAGENTA") with debug.increase_indent_cm(): dec_values = context.infer_node(dec.children[1]) trailer_nodes = dec.children[2:-1] if trailer_nodes: # Create a trailer and infer it. trailer = tree.PythonNode('trailer', trailer_nodes) trailer.parent = dec dec_values = infer_trailer(context, dec_values, trailer) if not len(dec_values): code = dec.get_code(include_prefix=False) # For the short future, we don't want to hear about the runtime # decorator in typing that was intentionally omitted. This is not # "correct", but helps with debugging. if code != '@runtime\n': debug.warning('decorator not found: %s on %s', dec, node) return initial values = dec_values.execute(arguments.ValuesArguments([values])) if not len(values): debug.warning('not possible to resolve wrappers found %s', node) return initial debug.dbg('decorator end %s', values, color="MAGENTA") if values != initial: return ValueSet([Decoratee(c, decoratee_value) for c in values]) return values
def _get_entries(assignment): """Find every help key + value entry from some "help" attribute. If `assignment` is invalid, this function may return an empty list. Args: assignment (:class:`parso.python.tree.ExprStmt`): The node that assigns the "help" attribute, which will be modified. Returns: list[:class:`parso.python.tree.PythonNode`]: The list node that is a parso "list of list of strs". """ root = _get_list_root(assignment) if root: return _get_inner_list_entries(root) string_root = _get_str_root(assignment) if not string_root: return [] if string_root.get_code().strip() in ("''", '""'): return [] node = tree.PythonNode( "testlist_comp", [ tree.String( '"{rez_configuration.DEFAULT_HELP_LABEL}"'.format( rez_configuration=rez_configuration), (0, 0), ), tree.Operator(",", (0, 0)), string_root, ], ) for child in node.children: child.parent = node return [node]
def _get_faked(grammar, module, obj, name=None): result, fake_module = _faked(grammar, module, obj, name) if result is None: # We're not interested in classes. What we want is functions. raise FakeDoesNotExist elif result.type == 'classdef': return result, fake_module else: # Set the docstr which was previously not set (faked modules don't # contain it). assert result.type == 'funcdef' doc = '"""%s"""' % obj.__doc__ # TODO need escapes. suite = result.children[-1] string = tree.String(doc, (0, 0), '') new_line = tree.Newline('\n', (0, 0)) docstr_node = tree.PythonNode('simple_stmt', [string, new_line]) suite.children.insert(1, docstr_node) return result, fake_module
def insert_or_append(node, graph, assignment, attribute): """Add a new `attribute` to `graph`, according to pre-existing data. Args: node (:class:`parso.python.tree.PythonNode`): The main data that will be added to `graph`. graph (:class:`parso.python.tree.PythonNode`): A Python module that may already contain an assignment for `attribute`. If it doesn't the attribute will be added, automatically. assignment (:class:`parso.python.tree.PythonNode` or NoneType): If `graph` has an existing node where `attribute` is defined, this node will represent that position. If this parameter is None, a new position for `attribute` will be automatically found and used. attribute (str): The name of the Rez-related object to use. Returns: :class:`parso.python.tree.PythonNode`: The main module with a modified `attribute`. """ if assignment: if hasattr(node, "children"): node.children[0].prefix = " " assignment.children[-1] = node return graph index = _find_nearest_node_index(graph.children, attribute) if index == -1: graph.children.append( tree.PythonNode( "assignment", [ tree.String( "{attribute} = ".format(attribute=attribute), (0, 0), prefix="\n\n", ) ] + [node], )) return graph _kill_suffix(graph.children[index - 1]) graph.children.insert( index, tree.PythonNode( "assignment", [ tree.String("{attribute} = ".format(attribute=attribute), (0, 0), prefix="\n\n") ] + [node], ), ) _adjust_prefix(graph.children, index) return graph
def modify_with_existing( # pylint: disable=arguments-differ cls, graph, data, append=False): """Add `data` to a parso node `graph`. Reference: https://github.com/nerdvegas/rez/wiki/Package-Definition-Guide#help Args: graph (:class:`parso.python.Tree.PythonBaseNode`): Some node that may assignment an attribute called "tests". If "tests" assignment exists, it gets overwritten. If no assignment exists then a new assignment is appended to the end of the file. data (list[list[str]] or str or :class:`parso.python.tree.PythonNode`): Any values that'd typically define a Rez "tests" attribute. Basically anything is allowed, as long the Rez package schema considers it valid. append (bool, optional): If False, anything in `data` will override the objects in `graph` if there are any conflicts between the two. If True, the conflicts are ignored and `data` is just added to `graph` is if no conflict exists. Default is False. Returns: str: The modified Python source code. It should resemble the source code of `graph` plus any serialized `data`. """ assignments = parso_utility.find_assignment_nodes( "help", graph) or parso_utility.find_definition_root_nodes( "help", graph) try: assignment = assignments[-1] except IndexError: assignment = None if isinstance(data, tree.BaseNode): graph = convention.insert_or_append_raw_node( data, graph, assignment, "help") return graph.get_code() help_data = parso.parse(json.dumps( data, cls=encoder.BuiltinEncoder)).children[0] if (assignment and not _get_list_root(assignment) and isinstance(help_data, tree.String)): help_data.prefix = " " graph = convention.insert_or_append(help_data, graph, assignment, "help") return graph.get_code() if _get_list_root(assignment) and isinstance(help_data, tree.String): raise ValueError( 'You may not add string "{help_data}" ' 'because assignment "{assignment}" is a list.'.format( help_data=help_data.get_code(), assignment=assignment.get_code())) help_data = _get_inner_list_entries(help_data) entries = [] if assignment: entries = cls._get_entries(assignment) if not append: entries = cls._resolve_entries(entries, help_data) else: entries.extend(help_data) entries = [ tree.PythonNode("atom", [ tree.Operator("[", (0, 0)), entry, tree.Operator("]", (0, 0)) ]) for entry in entries ] entries = _separate_with_commas(entries) node = tree.PythonNode( "atom", [tree.Operator("[", (0, 0))] + entries + [tree.Operator(",", (0, 0)), tree.Operator("]", (0, 0))], ) node = _apply_formatting(node) graph = convention.insert_or_append(node, graph, assignment, "help") return graph.get_code()