Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: recipe_engine/loader.py

Issue 1421843006: Add simple depends_on API. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/recipes-py@master
Patch Set: Fix small broken tests, add invoke tests, respond to nits. Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2013-2015 The Chromium Authors. All rights reserved. 1 # Copyright 2013-2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import collections 5 import collections
6 import contextlib 6 import contextlib
7 import imp 7 import imp
8 import inspect 8 import inspect
9 import os 9 import os
10 import sys 10 import sys
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
124 # TODO(luqui): Forbid depending on a module from a (locally) undeclared 124 # TODO(luqui): Forbid depending on a module from a (locally) undeclared
125 # dependency. 125 # dependency.
126 def __init__(self, package, module, local_name, universe): 126 def __init__(self, package, module, local_name, universe):
127 mod_path = ( 127 mod_path = (
128 universe.package_deps.get_package(package).module_path(module)) 128 universe.package_deps.get_package(package).module_path(module))
129 super(PackageDependency, self).__init__( 129 super(PackageDependency, self).__init__(
130 mod_path, local_name, universe=universe) 130 mod_path, local_name, universe=universe)
131 131
132 132
133 class RecipeUniverse(object): 133 class RecipeUniverse(object):
134 def __init__(self, package_deps): 134 def __init__(self, package_deps, config_file):
135 self._loaded = {} 135 self._loaded = {}
136 self._package_deps = package_deps 136 self._package_deps = package_deps
137 self._config_file = config_file
138
139 @property
140 def config_file(self):
141 return self._config_file
137 142
138 @property 143 @property
139 def module_dirs(self): 144 def module_dirs(self):
140 return self._package_deps.all_module_dirs 145 return self._package_deps.all_module_dirs
141 146
142 @property 147 @property
143 def recipe_dirs(self): 148 def recipe_dirs(self):
144 return self._package_deps.all_recipe_dirs 149 return self._package_deps.all_recipe_dirs
145 150
146 @property 151 @property
(...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after
399 self._instances = {} 404 self._instances = {}
400 405
401 def instantiate(self, mod): 406 def instantiate(self, mod):
402 if mod in self._instances: 407 if mod in self._instances:
403 return self._instances[mod] 408 return self._instances[mod]
404 deps_dict = { name: self.instantiate(dep) 409 deps_dict = { name: self.instantiate(dep)
405 for name, dep in mod.LOADED_DEPS.iteritems() } 410 for name, dep in mod.LOADED_DEPS.iteritems() }
406 self._instances[mod] = self._instantiator(mod, deps_dict) 411 self._instances[mod] = self._instantiator(mod, deps_dict)
407 return self._instances[mod] 412 return self._instances[mod]
408 413
414 def _invoke_with_properties(callable_obj, all_props, prop_defs, arg_names,
415 **additional_args):
416 """Internal version of invoke_with_properties.
417
418 The main difference is it gets passed the argument names as `arg_names`.
419 This allows us to reuse this logic elsewhere, without defining a fake function
420 which has arbitrary argument names.
421 """
422 for name, prop in prop_defs.items():
423 if not isinstance(prop, BoundProperty):
424 raise ValueError(
425 "You tried to invoke {} with an unbound Property {} named {}".format(
426 callable, prop, name))
427
428 props = []
429 for arg in arg_names:
430 if arg in additional_args:
431 props.append(additional_args.pop(arg))
432 continue
433
434 if arg not in prop_defs:
435 raise UndefinedPropertyException(
436 "Missing property definition for '{}'.".format(arg))
437
438 prop = prop_defs[arg]
439 props.append(prop.interpret(all_props.get(
440 prop.param_name, PROPERTY_SENTINEL)))
441
442 return callable_obj(*props, **additional_args)
443
409 def invoke_with_properties(callable_obj, all_props, prop_defs, 444 def invoke_with_properties(callable_obj, all_props, prop_defs,
410 **additional_args): 445 **additional_args):
411 """ 446 """
412 Invokes callable with filtered, type-checked properties. 447 Invokes callable with filtered, type-checked properties.
413 448
414 Args: 449 Args:
415 callable_obj: The function to call, or class to instantiate. 450 callable_obj: The function to call, or class to instantiate.
416 This supports passing in either RunSteps, or a recipe module, 451 This supports passing in either RunSteps, or a recipe module,
417 which is a class. 452 which is a class.
418 all_props: A dictionary containing all the properties (instances of BoundPro perty) 453 all_props: A dictionary containing all the properties (instances of BoundPro perty)
419 currently defined in the system. 454 currently defined in the system.
420 prop_defs: A dictionary of name to property definitions for this callable. 455 prop_defs: A dictionary of name to property definitions for this callable.
421 additional_args: kwargs to pass through to the callable. 456 additional_args: kwargs to pass through to the callable.
422 Note that the names of the arguments can correspond to 457 Note that the names of the arguments can correspond to
423 positional arguments as well. 458 positional arguments as well.
424 459
425 Returns: 460 Returns:
426 The result of calling callable with the filtered properties 461 The result of calling callable with the filtered properties
427 and additional arguments. 462 and additional arguments.
428 """ 463 """
429 # Check that we got passed BoundProperties, and not Properties 464 # Check that we got passed BoundProperties, and not Properties
430 465
431 for name, prop in prop_defs.items():
432 if not isinstance(prop, BoundProperty):
433 raise ValueError(
434 "You tried to invoke {} with an unbound Property {} named {}".format(
435 callable, prop, name))
436 466
437 # To detect when they didn't specify a property that they have as a 467 # To detect when they didn't specify a property that they have as a
438 # function argument, list the arguments, through inspection, 468 # function argument, list the arguments, through inspection,
439 # and then comparing this list to the provided properties. We use a list 469 # and then comparing this list to the provided properties. We use a list
440 # instead of a dict because getargspec returns a list which we would have to 470 # instead of a dict because getargspec returns a list which we would have to
441 # convert to a dictionary, and the benefit of the dictionary is pretty small. 471 # convert to a dictionary, and the benefit of the dictionary is pretty small.
442 props = []
443 if inspect.isclass(callable_obj): 472 if inspect.isclass(callable_obj):
444 arg_names = inspect.getargspec(callable_obj.__init__).args 473 arg_names = inspect.getargspec(callable_obj.__init__).args
445 474
446 arg_names.pop(0) 475 arg_names.pop(0)
447 else: 476 else:
448 arg_names = inspect.getargspec(callable_obj).args 477 arg_names = inspect.getargspec(callable_obj).args
478 return _invoke_with_properties(callable_obj, all_props, prop_defs, arg_names,
479 **additional_args)
449 480
450 for arg in arg_names:
451 if arg in additional_args:
452 props.append(additional_args.pop(arg))
453 continue
454
455 if arg not in prop_defs:
456 raise UndefinedPropertyException(
457 "Missing property definition for '{}'.".format(arg))
458
459 prop = prop_defs[arg]
460 props.append(prop.interpret(all_props.get(
461 prop.param_name, PROPERTY_SENTINEL)))
462
463 return callable_obj(*props, **additional_args)
464 481
465 def create_recipe_api(toplevel_deps, engine, test_data=DisabledTestData()): 482 def create_recipe_api(toplevel_deps, engine, test_data=DisabledTestData()):
466 def instantiator(mod, deps): 483 def instantiator(mod, deps):
467 kwargs = { 484 kwargs = {
468 'module': mod, 485 'module': mod,
469 'engine': engine, 486 'engine': engine,
470 # TODO(luqui): test_data will need to use canonical unique names. 487 # TODO(luqui): test_data will need to use canonical unique names.
471 'test_data': test_data.get_module_test_data(mod.NAME) 488 'test_data': test_data.get_module_test_data(mod.NAME)
472 } 489 }
473 prop_defs = mod.PROPERTIES 490 prop_defs = mod.PROPERTIES
(...skipping 22 matching lines...) Expand all
496 return modapi 513 return modapi
497 514
498 mapper = DependencyMapper(instantiator) 515 mapper = DependencyMapper(instantiator)
499 api = RecipeTestApi(module=None) 516 api = RecipeTestApi(module=None)
500 for k,v in toplevel_deps.iteritems(): 517 for k,v in toplevel_deps.iteritems():
501 setattr(api, k, mapper.instantiate(v)) 518 setattr(api, k, mapper.instantiate(v))
502 return api 519 return api
503 520
504 521
505 522
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698