from __future__ import annotations
import logging
from typing import Any, Collection, TYPE_CHECKING
if TYPE_CHECKING:
from cosapp.systems import System
logger = logging.getLogger(__name__)
[docs]
def pull_variables(
child: System,
pulling: str | Collection[str | dict[str, str]] | dict[str, str],
) -> None:
"""Pull variables from child to the parent.
Parameters
----------
child: System
`System` exposing variables to its parent API.
pulling [str | collection[str | dict[str, str]] | dict[str, str]]:
Map of child ports to pulled ports at the parent system level.
"""
from cosapp.ports.port import BasePort, Port
from cosapp.ports.connectors import BaseConnector
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_varname: str,
parent_varname: str,
value: Any,
) -> None:
"""Copy variable `child_varname` from `child` into `parent`, as `parent_varname`."""
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_varname, value)
parent._locked = lock_status
# Copy full variable detail in parent port
parent_port.copy_variable_from(child_port, child_varname, parent_varname)
log_debug(
f"{parent.name}.{parent_varname}",
f"{parent.name}.{child.name}.{child_varname}",
)
name_mapping = BaseConnector.format_mapping(pulling)
for child_attr_name, parent_attr_name in name_mapping.items():
child_attr = getattr(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, desc=child_attr.description)
log_debug(
f"Port {pulled_port.contextual_name}",
f"{parent.name}.{child_attr.contextual_name}",
)
else:
pulled_port = getattr(parent, parent_attr_name)
parent.connect(child_attr, pulled_port)
else: # inwards, outwards, or mode variables
if isinstance(child_attr, BasePort): # Pulling all variables
port_name = child_attr.name
parent_port = getattr(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})