Source code for tespy.tools.schema

# -*- coding: utf-8

"""Utility for generating a JSON schema description of TESPy components
and connections, listing each parameter with its data-container type,
physical quantity and description.
"""

import json
import warnings

from tespy.tools.data_containers import ComponentCharacteristicMaps
from tespy.tools.data_containers import ComponentCharacteristics
from tespy.tools.data_containers import ComponentMandatoryConstraints
from tespy.tools.data_containers import ComponentProperties
from tespy.tools.data_containers import FluidComposition
from tespy.tools.data_containers import FluidProperties
from tespy.tools.data_containers import GroupedComponentCharacteristics
from tespy.tools.data_containers import GroupedComponentProperties
from tespy.tools.data_containers import ReferencedFluidProperties
from tespy.tools.data_containers import SimpleDataContainer

# Human-readable names for each data-container class
_DC_TYPE_NAMES = {
    ComponentProperties: "ComponentProperty",
    ComponentCharacteristics: "ComponentCharacteristic",
    ComponentCharacteristicMaps: "ComponentCharacteristicMap",
    GroupedComponentProperties: "GroupedComponentProperties",
    GroupedComponentCharacteristics: "GroupedComponentCharacteristics",
    ComponentMandatoryConstraints: "ComponentMandatoryConstraint",
    FluidProperties: "FluidProperties",
    FluidComposition: "FluidComposition",
    ReferencedFluidProperties: "ReferencedFluidProperties",
    SimpleDataContainer: "SimpleDataContainer",
}


def _dc_type_name(dc_instance):
    """Return the human-readable data-container type name."""
    for cls, name in _DC_TYPE_NAMES.items():
        if type(dc_instance) is cls:
            return name
    # Fallback: use the class name directly
    return type(dc_instance).__name__


def _serialize_parameter(name, dc_instance):
    """Build a dict describing one parameter from its data-container."""
    entry = {
        "name": name,
        "container_type": _dc_type_name(dc_instance),
        "description": getattr(dc_instance, "description", None),
    }

    # Physical quantity (present on ComponentProperties / FluidProperties
    # and subclasses, absent on SimpleDataContainer etc.)
    quantity = getattr(dc_instance, "quantity", None)
    if quantity is not None:
        entry["quantity"] = quantity

    # For grouped containers, list the member parameter names
    if isinstance(dc_instance, (GroupedComponentProperties, GroupedComponentCharacteristics)):
        entry["elements"] = list(getattr(dc_instance, "elements", []))

    return entry


def _instantiate_component(cls):
    """
    Return a minimally initialised instance of a component class.

    Node-like classes declare get_parameters() as a @staticmethod and do not
    need an instance.  For all other classes we run the base Component.__init__
    with a harmless dummy label, which is the safest path because some
    get_parameters() overrides inspect self.inl / self.outl counts.
    """
    import inspect
    if isinstance(inspect.getattr_static(cls, "get_parameters"), staticmethod):
        return None  # no instance needed

    from tespy.components.component import Component
    instance = cls.__new__(cls)
    Component.__init__(instance, "_schema_probe_")
    return instance


def _get_parameters_for_class(cls, instance=None):
    """Return the get_parameters() dict for *cls*, reusing *instance* if given."""
    import inspect
    if isinstance(inspect.getattr_static(cls, "get_parameters"), staticmethod):
        return cls.get_parameters()
    if instance is None:
        instance = _instantiate_component(cls)
    return instance.get_parameters()



[docs] def generate_component_schema( exclude_base_classes=("Component",), as_json=True, indent=2, ): """ Return a serialized description of all registered TESPy component classes. For every class in the component registry (except those listed in *exclude_base_classes*) the function collects every parameter returned by ``get_parameters()`` and records: * the parameter name * the data-container type (e.g. ``ComponentProperty``, ``ComponentCharacteristic``) * the physical quantity (e.g. ``"pressure"``, ``"efficiency"``) when available * the human-readable description Parameters ---------- exclude_base_classes : tuple[str], optional Names of registry entries to skip. Defaults to ``("Component",)``. as_json : bool, optional When *True* (default) return a JSON string; when *False* return the raw nested dict. indent : int, optional JSON indentation level (only used when *as_json* is True). Returns ------- str | dict JSON string or dict depending on *as_json*. """ warnings.warn( "tespy.tools.schema is not yet stable and may change without notice in future releases.", FutureWarning, stacklevel=2, ) from tespy.components.component import component_registry schema = {} for class_name, cls in component_registry.items.items(): if class_name in exclude_base_classes: continue try: instance = _instantiate_component(cls) parameters = _get_parameters_for_class(cls, instance) except Exception as exc: schema[class_name] = {"error": str(exc)} continue params_list = [ _serialize_parameter(param_name, dc) for param_name, dc in parameters.items() ] # e.g. "tespy.components.turbomachinery.turbine" → "turbomachinery" module_parts = cls.__module__.split(".") submodule = module_parts[2] if len(module_parts) > 2 else cls.__module__ ports = cls.port_schema() schema[class_name] = { "module": cls.__module__, "submodule": submodule, "inlets": ports["inlets"], "outlets": ports["outlets"], "powerinlets": ports["powerinlets"], "poweroutlets": ports["poweroutlets"], "heatinlets": ports["heatinlets"], "heatoutlets": ports["heatoutlets"], "parameters": params_list, } if as_json: return json.dumps(schema, indent=indent) return schema
[docs] def generate_connection_schema( exclude_base_classes=(), as_json=True, indent=2, ): """ Return a serialized description of all registered TESPy connection classes. Parameters ---------- exclude_base_classes : tuple[str], optional Names of registry entries to skip. Defaults to ``("Connection", "PowerConnection")``. as_json : bool, optional When *True* (default) return a JSON string; when *False* return the raw nested dict. indent : int, optional JSON indentation level (only used when *as_json* is True). Returns ------- str | dict JSON string or dict depending on *as_json*. """ warnings.warn( "tespy.tools.schema is not yet stable and may change without notice in future releases.", FutureWarning, stacklevel=2, ) from tespy.connections.connection import connection_registry schema = {} for class_name, cls in connection_registry.items.items(): if class_name in exclude_base_classes: continue # Connection classes need two component arguments; use a lightweight # approach: call get_parameters() on a bare (uninitialized) instance # via a staticmethod check, or fall back to calling the unbound method # with a sentinel self. try: import inspect as _inspect static_gp = _inspect.getattr_static(cls, "get_parameters", None) if isinstance(static_gp, staticmethod): parameters = cls.get_parameters() else: # Connection.get_parameters() only accesses self for method # lookup; create a hollow proxy to avoid full __init__ instance = cls.__new__(cls) parameters = cls.get_parameters(instance) except Exception as exc: schema[class_name] = {"error": str(exc)} continue params_list = [ _serialize_parameter(param_name, dc) for param_name, dc in parameters.items() ] schema[class_name] = { "module": cls.__module__, "parameters": params_list, } if as_json: return json.dumps(schema, indent=indent) return schema