import logging
from typing import Any, Union, Collection, Dict
logger = logging.getLogger(__name__)
[docs]
def pull_variables(
child: "cosapp.systems.System",
pulling: Union[str, Collection[str], Dict[str, str]],
):
"""Pull variables from child to the parent.
Parameters
----------
child: System
`System` asking to pull variables to its parent
pulling: str or List[str] or Dict[str, str]
Map of child ports to pulled ports at the parent system level
selection: str or List[str] or Dict[str, str]
"""
from cosapp.systems import System
from cosapp.ports.port import BasePort, Port
from cosapp.utils.naming import CommonPorts
parent: System = child.parent
if parent is None:
raise AttributeError(
f"Can't pull variables from orphan System {child.name!r}"
)
method_map = {
CommonPorts.INWARDS.value: 'add_inward',
CommonPorts.OUTWARDS.value: 'add_outward',
CommonPorts.MODEVARS_IN.value: 'add_inward_modevar',
CommonPorts.MODEVARS_OUT.value: 'add_outward_modevar',
}
def log_debug(parent_attr, child_attr) -> str:
logger.debug(
f"{parent_attr} has been duplicated from {child_attr}"
f" - including validation range and scope."
)
def copy_variable(
child: System,
port_name: str,
child_var: str,
parent_var: str,
value: Any,
) -> None:
"""Copy variable `child_var` from `child` into `parent`, as `parent_var`."""
child_port: BasePort = child[port_name]
parent_port: BasePort = parent[port_name]
lock_status = parent._locked
parent._locked = False
# Call `add_inward`, `add_outward`, etc., depending on context
add_to_parent = getattr(parent, method_map[port_name])
add_to_parent(parent_var, value)
parent._locked = lock_status
# Copy full variable detail in parent port
parent_port.copy_variable_from(child_port, child_var, parent_var)
log_debug(
f"{parent.name}.{parent_var}",
f"{parent.name}.{child.name}.{child_var}",
)
# Transform pulling into a name mapping (dict)
if isinstance(pulling, str):
pulling = {pulling: pulling}
elif isinstance(pulling, dict):
pass
elif isinstance(pulling, Collection):
pulling = dict(zip(pulling, pulling))
for child_attr_name, parent_attr_name in pulling.items():
child_attr = child[child_attr_name]
if isinstance(child_attr, Port):
if parent_attr_name not in parent:
pulled_port = child_attr.copy(parent_attr_name)
parent._add_port(pulled_port)
log_debug(
f"Port {pulled_port.contextual_name}",
f"{parent.name}.{child_attr.contextual_name}",
)
else:
pulled_port = parent[parent_attr_name]
parent.connect(child_attr, pulled_port)
else: # ExtensiblePort (inwards or outwards)
if isinstance(child_attr, BasePort): # Pulling all inwards or outwards
port_name = child_attr.name
parent_port = parent[port_name]
for varname, value in child_attr.items():
if varname not in parent_port:
copy_variable(child, port_name, varname, varname, value)
parent.connect(child_attr, parent_port, list(child_attr))
else: # Pulling individual variable
var = child.name2variable[child_attr_name]
port_name = var.mapping.name
if parent_attr_name not in parent[port_name]:
copy_variable(child, port_name, child_attr_name, parent_attr_name, child_attr)
parent.connect(child[port_name], parent[port_name], {child_attr_name: parent_attr_name})