Add specific scoping documentation
Adds information into the arguments and result docs about how scoping lookup works and what it implies. Change-Id: I810874dce042ec43fe9e704d6689215e19d67c9c
This commit is contained in:
		| @@ -346,6 +346,47 @@ failures have occurred then the engine will have finished and if so desired the | |||||||
| :doc:`persistence <persistence>` can be used to cleanup any details that were | :doc:`persistence <persistence>` can be used to cleanup any details that were | ||||||
| saved for this execution. | saved for this execution. | ||||||
|  |  | ||||||
|  | Scoping | ||||||
|  | ======= | ||||||
|  |  | ||||||
|  | During creation of flows it is also important to understand the lookup | ||||||
|  | strategy (also typically known as `scope`_ resolution) that the engine you | ||||||
|  | are using will internally use. For example when a task ``A`` provides | ||||||
|  | result 'a' and a task ``B`` after ``A`` provides a different result 'a' and a | ||||||
|  | task ``C`` after ``A`` and after ``B`` requires 'a' to run, which one will | ||||||
|  | be selected? | ||||||
|  |  | ||||||
|  | Default strategy | ||||||
|  | ---------------- | ||||||
|  |  | ||||||
|  | When a engine is executing it internally interacts with the | ||||||
|  | :py:class:`~taskflow.storage.Storage` class | ||||||
|  | and that class interacts with the a | ||||||
|  | :py:class:`~taskflow.engines.action_engine.scopes.ScopeWalker` instance | ||||||
|  | and the :py:class:`~taskflow.storage.Storage` class uses the following | ||||||
|  | lookup order to find (or fail) a atoms requirement lookup/request: | ||||||
|  |  | ||||||
|  | #. Injected atom specific arguments. | ||||||
|  | #. Transient injected arguments. | ||||||
|  | #. Non-transient injected arguments. | ||||||
|  | #. First scope visited provider that produces the named result; note that | ||||||
|  |    if multiple providers are found in the same scope the *first* (the scope | ||||||
|  |    walkers yielded ordering defines what *first* means) that produced that | ||||||
|  |    result *and* can be extracted without raising an error is selected as the | ||||||
|  |    provider of the requested requirement. | ||||||
|  | #. Fails with :py:class:`~taskflow.exceptions.NotFound` if unresolved at this | ||||||
|  |    point (the ``cause`` attribute of this exception may have more details on | ||||||
|  |    why the lookup failed). | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     To examine this this information when debugging it is recommended to | ||||||
|  |     enable the ``BLATHER`` logging level (level 5). At this level the storage | ||||||
|  |     and scope code/layers will log what is being searched for and what is | ||||||
|  |     being found. | ||||||
|  |  | ||||||
|  | .. _scope: http://en.wikipedia.org/wiki/Scope_%28computer_science%29 | ||||||
|  |  | ||||||
| Interfaces | Interfaces | ||||||
| ========== | ========== | ||||||
|  |  | ||||||
| @@ -362,7 +403,8 @@ Implementations | |||||||
| .. automodule:: taskflow.engines.action_engine.runner | .. automodule:: taskflow.engines.action_engine.runner | ||||||
| .. automodule:: taskflow.engines.action_engine.runtime | .. automodule:: taskflow.engines.action_engine.runtime | ||||||
| .. automodule:: taskflow.engines.action_engine.scheduler | .. automodule:: taskflow.engines.action_engine.scheduler | ||||||
| .. automodule:: taskflow.engines.action_engine.scopes | .. autoclass:: taskflow.engines.action_engine.scopes.ScopeWalker | ||||||
|  |     :special-members: __iter__ | ||||||
|  |  | ||||||
| Hierarchy | Hierarchy | ||||||
| ========= | ========= | ||||||
|   | |||||||
| @@ -44,6 +44,8 @@ def _extract_atoms(node, idx=-1): | |||||||
| class ScopeWalker(object): | class ScopeWalker(object): | ||||||
|     """Walks through the scopes of a atom using a engines compilation. |     """Walks through the scopes of a atom using a engines compilation. | ||||||
|  |  | ||||||
|  |     NOTE(harlowja): for internal usage only. | ||||||
|  |  | ||||||
|     This will walk the visible scopes that are accessible for the given |     This will walk the visible scopes that are accessible for the given | ||||||
|     atom, which can be used by some external entity in some meaningful way, |     atom, which can be used by some external entity in some meaningful way, | ||||||
|     for example to find dependent values... |     for example to find dependent values... | ||||||
| @@ -63,29 +65,35 @@ class ScopeWalker(object): | |||||||
|  |  | ||||||
|         How this works is the following: |         How this works is the following: | ||||||
|  |  | ||||||
|         We find all the possible predecessors of the given atom, this is useful |         We first grab all the predecessors of the given atom (lets call it | ||||||
|         since we know they occurred before this atom but it doesn't tell us |         ``Y``) by using the :py:class:`~.compiler.Compilation` execution | ||||||
|         the corresponding scope *level* that each predecessor was created in, |         graph (and doing a reverse breadth-first expansion to gather its | ||||||
|         so we need to find this information. |         predecessors), this is useful since we know they *always* will | ||||||
|  |         exist (and execute) before this atom but it does not tell us the | ||||||
|  |         corresponding scope *level* (flow, nested flow...) that each | ||||||
|  |         predecessor was created in, so we need to find this information. | ||||||
|  |  | ||||||
|         For that information we consult the location of the atom ``Y`` in the |         For that information we consult the location of the atom ``Y`` in the | ||||||
|         node hierarchy. We lookup in a reverse order the parent ``X`` of ``Y`` |         :py:class:`~.compiler.Compilation` hierarchy/tree. We lookup in a | ||||||
|         and traverse backwards from the index in the parent where ``Y`` |         reverse order the parent ``X`` of ``Y`` and traverse backwards from | ||||||
|         occurred, all children in ``X`` that we encounter in this backwards |         the index in the parent where ``Y`` exists to all siblings (and | ||||||
|         search (if a child is a flow itself, its atom contents will be |         children of those siblings) in ``X`` that we encounter in this | ||||||
|         expanded) will be assumed to be at the same scope. This is then a |         backwards search (if a sibling is a flow itself, its atom(s) | ||||||
|         *potential* single scope, to make an *actual* scope we remove the items |         will be recursively expanded and included). This collection will | ||||||
|         from the *potential* scope that are not predecessors of ``Y`` to form |         then be assumed to be at the same scope. This is what is called | ||||||
|         the *actual* scope. |         a *potential* single scope, to make an *actual* scope we remove the | ||||||
|  |         items from the *potential* scope that are **not** predecessors | ||||||
|  |         of ``Y`` to form the *actual* scope which we then yield back. | ||||||
|  |  | ||||||
|         Then for additional scopes we continue up the tree, by finding the |         Then for additional scopes we continue up the tree, by finding the | ||||||
|         parent of ``X`` (lets call it ``Z``) and perform the same operation, |         parent of ``X`` (lets call it ``Z``) and perform the same operation, | ||||||
|         going through the children in a reverse manner from the index in |         going through the children in a reverse manner from the index in | ||||||
|         parent ``Z`` where ``X`` was located. This forms another *potential* |         parent ``Z`` where ``X`` was located. This forms another *potential* | ||||||
|         scope which we provide back as an *actual* scope after reducing the |         scope which we provide back as an *actual* scope after reducing the | ||||||
|         potential set by the predecessors of ``Y``. We then repeat this process |         potential set to only include predecessors previously gathered. We | ||||||
|         until we no longer have any parent nodes (aka have reached the top of |         then repeat this process until we no longer have any parent | ||||||
|         the tree) or we run out of predecessors. |         nodes (aka we have reached the top of the tree) or we run out of | ||||||
|  |         predecessors. | ||||||
|         """ |         """ | ||||||
|         predecessors = set(self._graph.bfs_predecessors_iter(self._atom)) |         predecessors = set(self._graph.bfs_predecessors_iter(self._atom)) | ||||||
|         last = self._node |         last = self._node | ||||||
|   | |||||||
| @@ -673,24 +673,10 @@ class Storage(object): | |||||||
|         with self._lock.read_lock(): |         with self._lock.read_lock(): | ||||||
|             if optional_args is None: |             if optional_args is None: | ||||||
|                 optional_args = [] |                 optional_args = [] | ||||||
|  |  | ||||||
|             if atom_name and atom_name not in self._atom_name_to_uuid: |             if atom_name and atom_name not in self._atom_name_to_uuid: | ||||||
|                 raise exceptions.NotFound("Unknown atom name: %s" % atom_name) |                 raise exceptions.NotFound("Unknown atom name: %s" % atom_name) | ||||||
|             if not args_mapping: |             if not args_mapping: | ||||||
|                 return {} |                 return {} | ||||||
|  |  | ||||||
|             # The order of lookup is the following: |  | ||||||
|             # |  | ||||||
|             # 1. Injected atom specific arguments. |  | ||||||
|             # 2. Transient injected arguments. |  | ||||||
|             # 3. Non-transient injected arguments. |  | ||||||
|             # 4. First scope visited group that produces the named result. |  | ||||||
|             #    a). The first of that group that actually provided the name |  | ||||||
|             #        result is selected (if group size is greater than one). |  | ||||||
|             # |  | ||||||
|             # Otherwise: blowup! (this will also happen if reading or |  | ||||||
|             # extracting an expected result fails, since it is better to fail |  | ||||||
|             # on lookup then provide invalid data from the wrong provider) |  | ||||||
|             if atom_name: |             if atom_name: | ||||||
|                 injected_args = self._injected_args.get(atom_name, {}) |                 injected_args = self._injected_args.get(atom_name, {}) | ||||||
|             else: |             else: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Joshua Harlow
					Joshua Harlow