-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #547 from pfebrer/nodes_bug
mnt: nodes context settings are no longer a regular input
- Loading branch information
Showing
9 changed files
with
620 additions
and
545 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
from .node import Node | ||
from .workflow import Workflow | ||
from .context import lazy_context, set_lazy_computation | ||
from .context import NodeContext, SISL_NODES_CONTEXT, temporal_context |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,103 @@ | ||
import contextlib | ||
from collections import ChainMap | ||
from typing import Any, Union | ||
|
||
def set_lazy_computation(nodes: bool = True, workflows: bool = True): | ||
"""Set the lazy computation mode for nodes and workflows. | ||
# The main sisl nodes context that all nodes will use by default as their base. | ||
SISL_NODES_CONTEXT = dict( | ||
# Whether the nodes should compute lazily or immediately when inputs are updated. | ||
lazy=True, | ||
# On initialization, should the node compute? If None, defaults to `lazy`. | ||
lazy_init=None, | ||
# Debugging options | ||
debug=False, | ||
debug_show_inputs=False | ||
) | ||
|
||
# Temporal contexts stack. It should not be used directly by users, the aim of this | ||
# stack is to populate it when context managers are used. This is a chainmap and | ||
# not a simple dict because we might have nested context managers. | ||
_TEMPORAL_CONTEXTS = ChainMap() | ||
|
||
class NodeContext(ChainMap): | ||
"""Extension of Chainmap that always checks on the temporal context first. | ||
Parameters | ||
---------- | ||
nodes: bool, optional | ||
Whether lazy computation is turned on for nodes. | ||
workflows: bool, optional | ||
Whether lazy computation is turned on for workflows. | ||
Using this class is equivalent to forcing users to have the temporal context | ||
always in the first position of the chainmap. Since this is not a very nice | ||
thing to force on users, we use this class instead. | ||
Keys: | ||
lazy: bool | ||
If `False`, nodes will automatically recompute if any of their inputs | ||
have changed, even if no other node needs their output yet. | ||
lazy_init: bool or None | ||
Whether the node should compute on initialization. If None, defaults to | ||
`lazy`. | ||
debug: bool | ||
Whether to print debugging information. | ||
debug_show_inputs: | ||
Whether to print the inputs of the node when debugging. | ||
""" | ||
from .node import Node | ||
from .workflow import Workflow | ||
|
||
Node._lazy_computation = nodes | ||
Workflow._lazy_computation = workflows | ||
def __getitem__(self, key: str): | ||
if key in _TEMPORAL_CONTEXTS: | ||
return _TEMPORAL_CONTEXTS[key] | ||
else: | ||
return super().__getitem__(key) | ||
|
||
@contextlib.contextmanager | ||
def lazy_context(nodes: bool = True, workflows: bool = True): | ||
from .node import Node | ||
from .workflow import Workflow | ||
def temporal_context(context: Union[dict, ChainMap, None] = None, **context_keys: Any): | ||
"""Sets a context temporarily (until the context manager is exited). | ||
Parameters | ||
---------- | ||
context: dict or ChainMap, optional | ||
The context that should be updated temporarily. This could for example be | ||
sisl's main context or the context of a specific node class. | ||
If None, the keys and values are forced on all nodes. | ||
**context_keys: Any | ||
The keys and values that should be used for the nodes context. | ||
old_lazy = { | ||
"nodes": Node._lazy_computation, | ||
"workflows": Workflow._lazy_computation, | ||
} | ||
Examples | ||
------- | ||
Forcing a certain context on all nodes: | ||
set_lazy_computation(nodes, workflows) | ||
>>> from sisl.nodes import temporal_context | ||
>>> with temporal_context(lazy=False): | ||
>>> # If a node class is called here, the computation will be performed | ||
>>> # immediately and the result returned. | ||
Switching off lazy behavior for workflows: | ||
>>> from sisl.nodes import Workflow, temporal_context | ||
>>> with temporal_context(context=Workflow.context, lazy=False): | ||
>>> # If a workflow is called here, the computation will be performed | ||
>>> # immediately and the result returned, unless that specific workflow | ||
>>> # class overwrites the lazy behavior. | ||
""" | ||
if context is not None: | ||
# We have to temporally update a context dictionary. We keep a copy of the | ||
# original context so that we can restore it later. | ||
old_context = {k: context[k] for k in context_keys} | ||
context.update(context_keys) | ||
|
||
def _restore(): | ||
# Restore the original context. | ||
context.update(old_context) | ||
else: | ||
# Add this temporal context on top of the temporal contexts stack. | ||
_TEMPORAL_CONTEXTS.maps.insert(0, context_keys) | ||
|
||
def _restore(): | ||
# Remove the temporal context from the stack. | ||
del _TEMPORAL_CONTEXTS.maps[0] | ||
|
||
# We have entered the context, execute whatever code is inside the "with" block. | ||
try: | ||
yield | ||
_restore() | ||
except Exception as e: | ||
set_lazy_computation(**old_lazy) | ||
raise e | ||
|
||
set_lazy_computation(**old_lazy) | ||
# The block has raised an exception, restore the context and re-raise. | ||
_restore() | ||
raise e |
Oops, something went wrong.