def __init__(self, owner, dependency_store): self.owner = owner self.dependency_store = dependency_store self._lookup = { class_name_to_kw_arg(cls.__name__): cls for cls in dependency_store }
def create_and_update_dependencies(self, *provided_and_desired_dependencies): """in order creation of dependencies and updating of self._dependency_store to include instances, indexed by page class. If a (HasCreate, dict()) tuple is provided as a desired dependency, the dict() will be unpacked as kwargs for the `HasCreate.create(**dict())` call. *** Providing (HasCreate, dict()) tuples for dependency args to this method removes the assurance that all shared dependencies types will be the same instance and only one instance of each type is created (Tech Debt: can create orphans if default dependency isn't claimed). The provided args are only in scope of the desired page, override any previously created instance of the same class, and replace said instances in the continuing chain. *** ``` ex: self.dependencies = [awxkit.api.pages.Inventory] self.create_and_update_dependencies() inventory = self._dependency_store[awxkit.api.pages.Inventory] ex: self.dependencies = [awxkit.api.pages.Inventory] self.create_and_update_dependencies((awxkit.api.pages.Inventory, dict(attr_one=1, attr_two=2))) inventory = self._dependency_store[awxkit.api.pages.Inventory] # assume kwargs are set as attributes by Inventory.create() inventory.attr_one == 1 > True inventory.attr_two == 2 > True ex: self.dependencies = [] self.optional_dependencies = [awxkit.api.pages.Organization] self.create_and_update_dependencies(awxkit.api.pages.Organization) organization = self._dependency_store[awxkit.api.pages.Organization] ex: self.dependencies = [awxkit.api.pages.Inventory] inventory = v2.inventories.create() self.create_and_update_dependencies(inventory) inventory == self._dependency_store[awxkit.api.pages.Inventory] > True ``` """ if not any((self.dependencies, self.optional_dependencies)): return # remove falsy values provided_and_desired_dependencies = [x for x in provided_and_desired_dependencies if x] # (HasCreate(), True) tells HasCreate._update_dependencies to link provided_dependencies = [(x, True) for x in provided_and_desired_dependencies if not isinstance(x, type) and not isinstance(x, tuple)] # Since dependencies are often declared at runtime, we need to use some introspection # to determine previously created ones for proper dependency store linking. # This is done by keeping an updated dependency record by the root caller's frame caller_frame = inspect.currentframe() self.parent_frame = None for frame in inspect.stack()[1:]: if frame[3] == 'create_and_update_dependencies': self.parent_frame = frame[0] if not self.parent_frame: # a maintained dict of instantiated resources keyed by lowercase class name to be # expanded as keyword args during `create()` calls all_instantiated = all_instantiated_dependencies(*[d[0] for d in provided_dependencies]) scoped_dependencies = {class_name_to_kw_arg(d.__class__.__name__): d for d in all_instantiated} self._scoped_dependencies_by_frame[caller_frame] = [self, scoped_dependencies] else: scoped_dependencies = self._scoped_dependencies_by_frame[self.parent_frame][1] desired_dependencies = [] desired_dependency_classes = [] for item in provided_and_desired_dependencies: if isinstance(item, tuple): item_cls = item[0] elif inspect.isclass(item): item_cls = item else: item_cls = item.__class__ if item_cls not in [x[0].__class__ for x in provided_dependencies]: desired_dependency_classes.append(item_cls) desired_dependencies.append(item) if desired_dependencies: ordered_desired_dependencies = [] creation_order = [item for s in page_creation_order(*desired_dependency_classes) for item in s] for item in creation_order: for desired in desired_dependency_classes: if desired == item or is_proper_subclass(desired, item): ordered_desired_dependencies.append(desired) desired_dependency_classes.remove(desired) break # keep track of (HasCreate, kwarg_dict) provided_with_kwargs = dict() for page_cls, provided_kwargs in [x for x in desired_dependencies if isinstance(x, tuple)]: provided_with_kwargs[page_cls] = provided_kwargs for to_create in ordered_desired_dependencies: scoped_args = dict(scoped_dependencies) if to_create in provided_with_kwargs: scoped_args.pop(to_create, None) # remove any conflicts in favor of explicit kwargs scoped_args.update(provided_with_kwargs.pop(to_create)) scoped_args.pop(class_name_to_kw_arg(to_create.__name__), None) created = to_create(self.connection).create(**scoped_args) provided_dependencies.append((created, True)) for dependency, _ in provided_dependencies: if dependency not in scoped_dependencies: scoped_dependencies[class_name_to_kw_arg(dependency.__class__.__name__)] = dependency self._update_dependencies(provided_dependencies) if not self.parent_frame: del self._scoped_dependencies_by_frame[caller_frame]
def test_class_name_to_kw_arg(inp, out): assert utils.class_name_to_kw_arg(inp) == out