def wrapper(func): if not inspect.isfunction(func): raise ValueError('The @rule decorator must be applied innermost of all decorators.') owning_module = sys.modules[func.__module__] source = inspect.getsource(func) beginning_indent = _get_starting_indent(source) if beginning_indent: source = "\n".join(line[beginning_indent:] for line in source.split("\n")) module_ast = ast.parse(source) def resolve_type(name): resolved = getattr(owning_module, name, None) or owning_module.__builtins__.get(name, None) if resolved is None: raise ValueError( f'Could not resolve type `{name}` in top level of module {owning_module.__name__}' ) elif not isinstance(resolved, type): raise ValueError( f'Expected a `type` constructor for `{name}`, but got: {resolved} (type ' f'`{type(resolved).__name__}`)' ) return resolved gets = OrderedSet() rule_func_node = assert_single_element( node for node in ast.iter_child_nodes(module_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == func.__name__) parents_table = {} for parent in ast.walk(rule_func_node): for child in ast.iter_child_nodes(parent): parents_table[child] = parent rule_visitor = _RuleVisitor() rule_visitor.visit(rule_func_node) gets.update( Get.create_statically_for_rule_graph(resolve_type(p), resolve_type(s)) for p, s in rule_visitor.gets) # Register dependencies for @goal_rule/Goal. dependency_rules = (subsystem_rule(return_type.subsystem_cls),) if is_goal_cls else None # Set a default name for Goal classes if one is not explicitly provided if is_goal_cls and name is None: effective_name = return_type.name else: effective_name = name func.rule = TaskRule( return_type, tuple(parameter_types), func, input_gets=tuple(gets), dependency_rules=dependency_rules, cacheable=cacheable, name=effective_name, ) return func
def signature(cls): """Returns kwargs to construct a `TaskRule` that will construct the target Optionable. TODO: This indirection avoids a cycle between this module and the `rules` module. """ partial_construct_optionable = functools.partial( _construct_optionable, cls) # NB: We must populate several dunder methods on the partial function because partial functions # do not have these defined by default and the engine uses these values to visualize functions # in error messages and the rule graph. snake_scope = cls.options_scope.replace("-", "_") partial_construct_optionable.__name__ = f"construct_scope_{snake_scope}" partial_construct_optionable.__module__ = cls.__module__ _, class_definition_lineno = inspect.getsourcelines(cls) partial_construct_optionable.__line_number__ = class_definition_lineno return dict( output_type=cls.optionable_cls, input_selectors=tuple(), func=partial_construct_optionable, input_gets=(Get.create_statically_for_rule_graph( ScopedOptions, Scope), ), dependency_optionables=(cls.optionable_cls, ), )
def test_create(self): # Test the equivalence of the 2-arg and 3-arg versions. self.assertEqual(Get(AClass, BClass()), Get(AClass, BClass, BClass())) with self.assertRaises(TypeError) as cm: Get(AClass, BClass) self.assertEqual( """\ The two-argument form of Get does not accept a type as its second argument. args were: Get(({a!r}, {b!r})) Get.create_statically_for_rule_graph() should be used to generate a Get() for the `input_gets` field of a rule. If you are using a `await Get(...)` in a rule and a type was intended, use the 3-argument version: Get({a!r}, {t!r}, {b!r}) """.format(a=AClass, t=type(BClass), b=BClass), str(cm.exception), ) with self.assertRaises(ValueError) as cm: Get(1) self.assertEqual( "Expected either two or three arguments to Get; got (1,).", str(cm.exception))
def wrapper(func): if not inspect.isfunction(func): raise ValueError('The @rule decorator must be applied innermost of all decorators.') owning_module = sys.modules[func.__module__] source = inspect.getsource(func) beginning_indent = _get_starting_indent(source) if beginning_indent: source = "\n".join(line[beginning_indent:] for line in source.split("\n")) module_ast = ast.parse(source) def resolve_type(name): resolved = getattr(owning_module, name, None) or owning_module.__builtins__.get(name, None) if resolved is None: raise ValueError('Could not resolve type `{}` in top level of module {}' .format(name, owning_module.__name__)) elif not isinstance(resolved, type): raise ValueError('Expected a `type` constructor for `{}`, but got: {} (type `{}`)' .format(name, resolved, type(resolved).__name__)) return resolved gets = OrderedSet() rule_func_node = assert_single_element( node for node in ast.iter_child_nodes(module_ast) if isinstance(node, ast.FunctionDef) and node.name == func.__name__) parents_table = {} for parent in ast.walk(rule_func_node): for child in ast.iter_child_nodes(parent): parents_table[child] = parent rule_visitor = _RuleVisitor( func=func, func_node=rule_func_node, func_source=source, orig_indent=beginning_indent, parents_table=parents_table, ) rule_visitor.visit(rule_func_node) gets.update( Get.create_statically_for_rule_graph(resolve_type(p), resolve_type(s)) for p, s in rule_visitor.gets) # Register dependencies for @console_rule/Goal. if is_goal_cls: dependency_rules = (optionable_rule(output_type.Options),) else: dependency_rules = None func.rule = TaskRule( output_type, tuple(input_selectors), func, input_gets=tuple(gets), dependency_rules=dependency_rules, cacheable=cacheable, ) return func
def signature(cls): """Returns kwargs to construct a `TaskRule` that will construct the target Optionable. TODO: This indirection avoids a cycle between this module and the `rules` module. """ snake_scope = cls.options_scope.replace('-', '_') partial_construct_optionable = functools.partial(_construct_optionable, cls) partial_construct_optionable.__name__ = 'construct_scope_{}'.format(snake_scope) return dict( output_type=cls.optionable_cls, input_selectors=tuple(), func=partial_construct_optionable, input_gets=(Get.create_statically_for_rule_graph(ScopedOptions, Scope),), dependency_optionables=(cls.optionable_cls,), )
def signature(cls): """Returns kwargs to construct a `TaskRule` that will construct the target Optionable. TODO: This indirection avoids a cycle between this module and the `rules` module. """ snake_scope = cls.options_scope.replace('-', '_') partial_construct_optionable = functools.partial( _construct_optionable, cls) partial_construct_optionable.__name__ = f'construct_scope_{snake_scope}' return dict( output_type=cls.optionable_cls, input_selectors=tuple(), func=partial_construct_optionable, input_gets=(Get.create_statically_for_rule_graph( ScopedOptions, Scope), ), dependency_optionables=(cls.optionable_cls, ), )
def test_create(self): # Test the equivalence of the 2-arg and 3-arg versions. self.assertEqual(Get(AClass, BClass()), Get(AClass, BClass, BClass())) with self.assertRaises(TypeError) as cm: Get(AClass, BClass) self.assertEqual("""\ The two-argument form of Get does not accept a type as its second argument. args were: Get(({a!r}, {b!r})) Get.create_statically_for_rule_graph() should be used to generate a Get() for the `input_gets` field of a rule. If you are using a `yield Get(...)` in a rule and a type was intended, use the 3-argument version: Get({a!r}, {t!r}, {b!r}) """.format(a=AClass, t=type(BClass), b=BClass), str(cm.exception)) with self.assertRaises(ValueError) as cm: Get(1) self.assertEqual("Expected either two or three arguments to Get; got (1,).", str(cm.exception))
def test_create_statically_for_rule_graph(self): self.assertEqual(Get(AClass, BClass, None), Get.create_statically_for_rule_graph(AClass, BClass))
def test_create_statically_for_rule_graph(self): self.assertEqual(Get(AClass, BClass, None), Get.create_statically_for_rule_graph(AClass, BClass))
def wrapper(func): if not inspect.isfunction(func): raise ValueError( "The @rule decorator must be applied innermost of all decorators." ) owning_module = sys.modules[func.__module__] source = inspect.getsource(func) beginning_indent = _get_starting_indent(source) if beginning_indent: source = "\n".join(line[beginning_indent:] for line in source.split("\n")) module_ast = ast.parse(source) def resolve_type(name): resolved = getattr(owning_module, name, None) or owning_module.__builtins__.get( name, None) if resolved is None: raise ValueError( f"Could not resolve type `{name}` in top level of module {owning_module.__name__}" ) elif not isinstance(resolved, type): raise ValueError( f"Expected a `type` constructor for `{name}`, but got: {resolved} (type " f"`{type(resolved).__name__}`)") return resolved rule_func_node = assert_single_element( node for node in ast.iter_child_nodes(module_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == func.__name__) parents_table = {} for parent in ast.walk(rule_func_node): for child in ast.iter_child_nodes(parent): parents_table[child] = parent rule_visitor = _RuleVisitor() rule_visitor.visit(rule_func_node) gets = FrozenOrderedSet( Get.create_statically_for_rule_graph(resolve_type(p), resolve_type(s)) for p, s in rule_visitor.gets) # Register dependencies for @goal_rule/Goal. dependency_rules = (subsystem_rule( return_type.subsystem_cls), ) if is_goal_cls else None # Set a default canonical name if one is not explicitly provided. For Goal classes # this is the name of the Goal; for other named ruled this is the __name__ of the function # that implements it. effective_name = annotations.canonical_name if effective_name is None: effective_name = return_type.name if is_goal_cls else func.__name__ normalized_annotations = RuleAnnotations(canonical_name=effective_name, desc=annotations.desc) # Set our own custom `__line_number__` dunder so that the engine may visualize the line number. func.__line_number__ = func.__code__.co_firstlineno func.rule = TaskRule( return_type, tuple(parameter_types), func, input_gets=tuple(gets), dependency_rules=dependency_rules, cacheable=cacheable, annotations=normalized_annotations, ) return func
def wrapper(func): if not inspect.isfunction(func): raise ValueError( 'The @rule decorator must be applied innermost of all decorators.' ) owning_module = sys.modules[func.__module__] source = inspect.getsource(func) beginning_indent = _get_starting_indent(source) if beginning_indent: source = "\n".join(line[beginning_indent:] for line in source.split("\n")) module_ast = ast.parse(source) def resolve_type(name): resolved = getattr(owning_module, name, None) or owning_module.__builtins__.get( name, None) if resolved is None: raise ValueError( 'Could not resolve type `{}` in top level of module {}'. format(name, owning_module.__name__)) elif not isinstance(resolved, type): raise ValueError( 'Expected a `type` constructor for `{}`, but got: {} (type `{}`)' .format(name, resolved, type(resolved).__name__)) return resolved gets = OrderedSet() rule_func_node = assert_single_element( node for node in ast.iter_child_nodes(module_ast) if isinstance(node, ast.FunctionDef) and node.name == func.__name__) parents_table = {} for parent in ast.walk(rule_func_node): for child in ast.iter_child_nodes(parent): parents_table[child] = parent rule_visitor = _RuleVisitor( func=func, func_node=rule_func_node, func_source=source, orig_indent=beginning_indent, parents_table=parents_table, ) rule_visitor.visit(rule_func_node) gets.update( Get.create_statically_for_rule_graph(resolve_type(p), resolve_type(s)) for p, s in rule_visitor.gets) # For @console_rule, redefine the function to avoid needing a literal return of the output type. if for_goal: def goal_and_return(*args, **kwargs): res = func(*args, **kwargs) if isinstance(res, GeneratorType): # Return a generator with an output_type instance appended. return _terminated(res, output_type()) elif res is not None: raise Exception( 'A @console_rule should not have a return value.') return output_type() functools.update_wrapper(goal_and_return, func) wrapped_func = goal_and_return else: wrapped_func = func wrapped_func.rule = TaskRule( output_type, tuple(input_selectors), wrapped_func, input_gets=tuple(gets), goal=for_goal, cacheable=cacheable, ) return wrapped_func