Repository URL to install this package:
|
Version:
0.5.10.dev4+gc28d68fe2 ▾
|
# -*- coding: utf-8 -*-
import abc
import importlib
import inspect
import re
import textwrap
import uuid
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Dict,
Generic,
Iterable,
List,
Literal,
Mapping,
Type,
TypeVar,
Union,
)
import humanfriendly
import orjson
from pydantic import BaseModel, Field, PrivateAttr, field_validator
from rich import box
from rich.console import RenderableType
from rich.markdown import Markdown
from rich.panel import Panel
from rich.syntax import Syntax
from rich.table import Table
from rich.tree import Tree
from kiara.defaults import DEFAULT_NO_DESC_VALUE
from kiara.exceptions import KiaraException
from kiara.models import KiaraModel
from kiara.models.documentation import (
AuthorModel,
AuthorsMetadataModel,
ContextMetadataModel,
DocumentationMetadataModel,
)
from kiara.models.module import KiaraModuleConfig
from kiara.models.module.jobs import JobRecord
from kiara.models.module.operation import Operation
from kiara.models.module.pipeline import PipelineConfig, PipelineStep
from kiara.models.module.pipeline.structure import (
PipelineStructure,
StepInfo,
)
from kiara.models.module.pipeline.value_refs import PipelineInputRef, PipelineOutputRef
from kiara.models.python_class import PythonClass
from kiara.models.values import DataTypeCharacteristics, ValueStatus
from kiara.models.values.lineage import ValueLineage
from kiara.models.values.value import (
ORPHAN,
DataTypeInfo,
PersistedData,
Value,
ValueMap,
ValuePedigree,
)
from kiara.models.values.value_schema import ValueSchema
from kiara.modules import KiaraModule
from kiara.renderers import KiaraRenderer
from kiara.utils import log_exception, log_message
from kiara.utils.class_loading import find_all_kiara_model_classes
from kiara.utils.cli import HORIZONTALS_NO_TO_AND_BOTTOM
from kiara.utils.json import orjson_dumps
from kiara.utils.output import extract_renderable
if TYPE_CHECKING:
from kiara.context import Kiara
from kiara.data_types import DataType
from kiara.models.runtime_environment.python import PythonRuntimeEnvironment
from kiara.operations import OperationType
from kiara.registries.aliases import AliasRegistry
from kiara.registries.data import DataRegistry
INFO_BASE_INSTANCE_TYPE = TypeVar("INFO_BASE_INSTANCE_TYPE")
INFO_BASE_CLASS = TypeVar("INFO_BASE_CLASS", bound=type)
def pretty_print_value_data_terminal(value: "ValueInfo"):
try:
renderable = value._value._data_registry.pretty_print_data(
value.value_id, target_type="terminal_renderable"
)
except Exception as e:
log_exception(e)
log_message("error.pretty_print", value=value.value_id, error=e)
renderable = [str(value._value.data)]
return renderable
RENDER_FIELDS: Dict[str, Dict[str, Any]] = {
"value_id": {
"show_default": True,
"render": {"terminal": lambda v: str(v.value_id)},
},
"aliases": {
"show_default": True,
"render": {"terminal": lambda v: ", ".join(v.aliases)},
},
"type": {
"show_default": True,
"render": {"terminal": lambda x: x.value_schema.type},
},
"value_schema": {"show_default": False},
"is_persisted": {
"show_default": False,
"render": {"terminal": lambda v: "yes" if v.is_persisted else "no"},
},
"hash": {"show_default": False, "render": {"terminal": lambda v: v.value_hash}},
"data": {
"show_default": False,
"render": {"terminal": pretty_print_value_data_terminal},
},
"pedigree": {
"show_default": False,
"render": {"terminal": lambda v: "-- external data -- " if v == ORPHAN else v},
},
"lineage": {"show_default": False},
"load_config": {"show_default": False},
"data_type_config": {
"show_default": False,
"render": {
"terminal": lambda v: Syntax(
orjson_dumps(v.value_schema.type_config, option=orjson.OPT_INDENT_2),
"json",
background_color="default",
)
},
},
"serialize_details": {
"show_default": False,
"render": {"terminal": lambda v: v.serialized.create_renderable()},
},
"properties": {
"show_default": False,
"render": {
"terminal": lambda v: v.property_values.create_renderable(show_header=False)
},
},
"size": {
"show_default": True,
"render": {"terminal": lambda v: humanfriendly.format_size(v.value_size)},
},
}
class ValueTypeAndDescription(BaseModel):
description: str = Field(description="The description for the value.")
type: str = Field(description="The value type.")
value_default: Any = Field(description="Default for the value.", default=None)
required: bool = Field(description="Whether this value is required")
class ItemInfo(KiaraModel):
"""Base class that holds/manages information about an item within kiara."""
@classmethod
@abc.abstractmethod
def base_instance_class(cls) -> Type:
pass
@classmethod
@abc.abstractmethod
def create_from_instance(cls, kiara: "Kiara", instance: Any, **kwargs):
pass
@field_validator("documentation", mode="before")
@classmethod
def validate_doc(cls, value):
return DocumentationMetadataModel.create(value)
type_name: str = Field(description="The registered name for this item type.")
documentation: DocumentationMetadataModel = Field(
description="Documentation for the item."
)
authors: AuthorsMetadataModel = Field(
description="Information about authorship for the item."
)
context: ContextMetadataModel = Field(
description="Generic properties of this item (description, tags, labels, references, ...)."
)
def _retrieve_id(self) -> str:
return self.type_name
def _retrieve_data_to_hash(self) -> Any:
return self.type_name
def create_renderable(self, **config: Any) -> RenderableType:
include_doc = config.get("include_doc", True)
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 0, 0, 0))
table.add_column("property", style="i")
table.add_column("value")
if include_doc:
table.add_row(
"Documentation",
Panel(self.documentation.create_renderable(), box=box.SIMPLE),
)
table.add_row("Author(s)", self.authors.create_renderable())
table.add_row("Context", self.context.create_renderable())
if hasattr(self, "python_class"):
table.add_row("Python class", self.python_class.create_renderable()) # type: ignore
return table
class TypeInfo(ItemInfo, Generic[INFO_BASE_CLASS]):
@classmethod
def create_from_instance(cls, kiara: "Kiara", instance: INFO_BASE_CLASS, **kwargs):
return cls.create_from_type_class(type_cls=instance, kiara=kiara)
@classmethod
@abc.abstractmethod
def create_from_type_class(
self, type_cls: INFO_BASE_CLASS, kiara: "Kiara"
) -> "TypeInfo":
pass
@classmethod
def base_instance_class(self) -> INFO_BASE_CLASS:
return type # type: ignore
python_class: PythonClass = Field(
description="The python class that implements this module type."
)
INFO_ITEM_TYPE = TypeVar("INFO_ITEM_TYPE", bound=ItemInfo)
class InfoItemGroup(KiaraModel, Generic[INFO_ITEM_TYPE]):
@classmethod
@abc.abstractmethod
def base_info_class(cls) -> Type[INFO_ITEM_TYPE]:
pass
@classmethod
def create_from_instances(
cls,
kiara: "Kiara",
instances: Mapping[str, Any],
**kwargs: Any,
) -> "InfoItemGroup[INFO_ITEM_TYPE]":
info_cls = cls.base_info_class()
items = {}
for k in sorted(instances.keys()):
v = instances[k]
items[k] = info_cls.create_from_instance(kiara=kiara, instance=v, **kwargs)
group_title = kwargs.pop("group_title", None)
result = cls(group_title=group_title, item_infos=items)
result._kiara = kiara
return result
group_title: Union[str, None] = Field(description="The group alias.", default=None)
item_infos: Mapping[str, INFO_ITEM_TYPE] = Field(description="The info items.")
_kiara: Union["Kiara", None] = PrivateAttr(default=None)
def _retrieve_subcomponent_keys(self) -> Iterable[str]:
return self.item_infos.keys()
def _retrieve_data_to_hash(self) -> Any:
return {"type_name": self.__class__._kiara_model_name, "included_types": list(self.item_infos.keys())} # type: ignore
def create_renderable(self, **config: Any) -> RenderableType:
full_doc = config.get("full_doc", False)
table = Table(show_header=True, box=box.SIMPLE, show_lines=full_doc)
table.add_column("Name", style="i")
table.add_column("Description")
for type_name in sorted(self.item_infos.keys()):
t_md = self.item_infos[type_name]
if full_doc:
md = Markdown(t_md.documentation.full_doc)
else:
md = Markdown(t_md.documentation.description)
table.add_row(type_name, md)
return table
def __getitem__(self, item: str) -> INFO_ITEM_TYPE:
return self.item_infos[item]
# def __iter__(self):
# return self.item_infos.__iter__()
def __len__(self):
return len(self.item_infos)
class TypeInfoItemGroup(InfoItemGroup[TypeInfo]):
@classmethod
@abc.abstractmethod
def base_info_class(cls) -> Type[TypeInfo]:
pass
@classmethod
def create_from_type_items(
cls, kiara: "Kiara", group_title: Union[str, None] = None, **items: Type
) -> "TypeInfoItemGroup":
type_infos: Mapping[str, TypeInfo[Any]] = {
k: cls.base_info_class().create_from_type_class(type_cls=v, kiara=kiara)
for k, v in items.items()
}
data_types_info = cls(group_title=group_title, item_infos=type_infos)
return data_types_info
class KiaraModelTypeInfo(TypeInfo[Type[KiaraModel]]):
_kiara_model_id: ClassVar = "info.kiara_model"
@classmethod
def create_from_type_class(
self, type_cls: Type[KiaraModel], kiara: "Kiara"
) -> "KiaraModelTypeInfo":
authors_md = AuthorsMetadataModel.from_class(type_cls)
doc = DocumentationMetadataModel.from_class_doc(type_cls)
python_class = PythonClass.from_class(type_cls)
properties_md = ContextMetadataModel.from_class(type_cls)
type_name = type_cls._kiara_model_id # type: ignore
schema = type_cls.model_json_schema()
return KiaraModelTypeInfo(
type_name=type_name,
documentation=doc,
authors=authors_md,
context=properties_md,
python_class=python_class,
metadata_schema=schema,
)
metadata_schema: Dict[str, Any] = Field(
description="The (json) schema for this model data."
)
def create_renderable(self, **config: Any) -> RenderableType:
include_doc = config.get("include_doc", True)
include_schema = config.get("include_schema", False)
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 0, 0, 0))
table.add_column("property", style="i")
table.add_column("value")
if include_doc:
table.add_row(
"Documentation",
Panel(self.documentation.create_renderable(), box=box.SIMPLE),
)
table.add_row("Author(s)", self.authors.create_renderable())
table.add_row("Context", self.context.create_renderable())
if hasattr(self, "python_class"):
table.add_row("Python class", self.python_class.create_renderable())
if include_schema:
schema = Syntax(
orjson_dumps(self.metadata_schema, option=orjson.OPT_INDENT_2),
"json",
background_color="default",
)
table.add_row("metadata_schema", schema)
return table
class KiaraModelClassesInfo(TypeInfoItemGroup):
_kiara_model_id: ClassVar = "info.kiara_models"
@classmethod
def find_kiara_models(
cls, alias: Union[str, None] = None, only_for_package: Union[str, None] = None
) -> "KiaraModelClassesInfo":
models = find_all_kiara_model_classes()
# we don't need the kiara instance, this is just to satisfy mypy
kiara: Kiara = None # type: ignore
group: KiaraModelClassesInfo = KiaraModelClassesInfo.create_from_type_items(kiara=kiara, group_title=alias, **models) # type: ignore
if only_for_package:
temp = {}
for key, info in group.item_infos.items():
if info.context.labels.get("package") == only_for_package:
temp[key] = info
group = KiaraModelClassesInfo(
group_id=group.instance_id, group_title=group.group_alias, item_infos=temp # type: ignore
)
return group
@classmethod
def base_info_class(cls) -> Type[KiaraModelTypeInfo]:
return KiaraModelTypeInfo # type: ignore
type_name: Literal["kiara_model"] = "kiara_model"
item_infos: Mapping[str, KiaraModelTypeInfo] = Field( # type: ignore
description="The value metadata info instances for each type."
)
class ValueInfo(ItemInfo):
_kiara_model_id: ClassVar = "info.value"
@classmethod
def base_instance_class(cls) -> Type[Value]:
return Value
@classmethod
def create_from_instance(
cls, kiara: "Kiara", instance: Value, **kwargs: Any
) -> "ValueInfo":
resolve_aliases = kwargs.get("resolve_aliases", True)
resolve_destinies = kwargs.get("resolve_destinies", True)
resolve_properties = kwargs.get("resolve_properties", True)
if resolve_aliases:
aliases = sorted(
kiara.alias_registry.find_aliases_for_value_id(instance.value_id)
)
else:
aliases = None
if instance.is_stored:
persisted_details = kiara.data_registry.retrieve_persisted_value_details(
value_id=instance.value_id
)
else:
persisted_details = None
if instance.data_type_name in kiara.type_registry.data_type_profiles:
is_internal = "internal" in kiara.type_registry.get_type_lineage(
instance.data_type_name
)
else:
is_internal = False
if resolve_destinies:
destiny_links = (
kiara.data_registry.retrieve_destinies_for_value_from_archives(
value_id=instance.value_id
)
)
filtered_destinies = {}
for alias, value_id in destiny_links.items():
if (
alias in instance.property_links.keys()
and value_id == instance.property_links[alias]
):
continue
filtered_destinies[alias] = value_id
else:
filtered_destinies = None
if resolve_properties:
properties = instance.get_all_property_data()
else:
properties = None
authors = AuthorsMetadataModel()
context = ContextMetadataModel()
doc = DocumentationMetadataModel()
model = ValueInfo(
type_name=str(instance.value_id),
documentation=doc,
authors=authors,
context=context,
value_id=instance.value_id,
kiara_id=instance.kiara_id,
value_schema=instance.value_schema,
value_status=instance.value_status,
environment_hashes=instance.environment_hashes,
value_size=instance.value_size,
value_hash=instance.value_hash,
pedigree=instance.pedigree,
pedigree_output_name=instance.pedigree_output_name,
data_type_info=instance.data_type_info,
# data_type_config=instance.data_type_config,
# data_type_class=instance.data_type_class,
property_links=instance.property_links,
destiny_links=filtered_destinies,
destiny_backlinks=instance.destiny_backlinks,
aliases=aliases,
serialized=persisted_details,
properties=properties,
is_internal=is_internal,
is_persisted=instance._is_stored,
)
model._value = instance
model._alias_registry = kiara.alias_registry # type: ignore
model._data_registry = instance._data_registry
return model
value_id: uuid.UUID = Field(description="The id of the value.")
kiara_id: uuid.UUID = Field(
description="The id of the kiara context this value belongs to."
)
value_schema: ValueSchema = Field(
description="The schema that was used for this Value."
)
value_status: ValueStatus = Field(description="The set/unset status of this value.")
value_size: int = Field(description="The size of this value, in bytes.")
value_hash: str = Field(description="The hash of this value.")
pedigree: ValuePedigree = Field(
description="Information about the module and inputs that went into creating this value."
)
pedigree_output_name: str = Field(
description="The output name that produced this value (using the manifest inside the pedigree)."
)
data_type_info: DataTypeInfo = Field(
description="Information about the underlying data type and it's configuration."
)
aliases: Union[List[str], None] = Field(
description="The aliases that are registered for this value."
)
serialized: Union[PersistedData, None] = Field(
description="Details for the serialization process that was used for this value."
)
properties: Union[Mapping[str, Any], None] = Field(
description="Property data for this value.", default=None
)
destiny_links: Union[Mapping[str, uuid.UUID], None] = Field(
description="References to all the values that act as destiny for this value in this context."
)
environment_hashes: Mapping[str, Mapping[str, str]] = Field(
description="Hashes for the environments this value was created in."
)
enviroments: Union[Mapping[str, Mapping[str, Any]], None] = Field(
description="Information about the environments this value was created in.",
default=None,
)
property_links: Mapping[str, uuid.UUID] = Field(
description="Links to values that are properties of this value.",
default_factory=dict,
)
destiny_backlinks: Mapping[uuid.UUID, str] = Field(
description="Backlinks to values that this value acts as destiny/or property for.",
default_factory=dict,
)
is_internal: bool = Field(
description="Whether this value is only used internally in kiara.",
default=False,
)
is_persisted: bool = Field(
description="Whether this value is stored in at least one data store."
)
_alias_registry: "AliasRegistry" = PrivateAttr(default=None)
_data_registry: "DataRegistry" = PrivateAttr(default=None)
_value: Value = PrivateAttr(default=None)
def _retrieve_id(self) -> str:
return str(self.value_id)
def _retrieve_data_to_hash(self) -> Any:
return self.value_id.bytes
@property
def property_values(self) -> "ValueMap":
return self._value.property_values
@property
def lineage(self) -> "ValueLineage":
return self._value.lineage
def resolve_aliases(self):
if self.aliases is None:
aliases = self._alias_registry.find_aliases_for_value_id(self.value_id)
if aliases:
aliases = sorted(aliases)
self.aliases = aliases
def resolve_destinies(self):
if self.destiny_links is None:
destiny_links = (
self._value._data_registry.retrieve_destinies_for_value_from_archives(
value_id=self.value_id
)
)
filtered_destinies = {}
for alias, value_id in destiny_links.items():
if (
alias in self.property_links.keys()
and value_id == self.property_links[alias]
):
continue
filtered_destinies[alias] = value_id
self.destiny_links = filtered_destinies
def create_info_data(self, **config: Any) -> Mapping[str, Any]:
return self._value.create_info_data(**config)
def create_renderable(self, **render_config: Any) -> RenderableType:
return self._value.create_renderable(**render_config)
class ValuesInfo(InfoItemGroup[ValueInfo]):
@classmethod
def base_info_class(cls) -> Type[ValueInfo]:
return ValueInfo
def create_render_map(
self, render_type: str, default_render_func: Callable, **render_config
):
list_by_alias = render_config.get("list_by_alias", True)
show_internal = render_config.get("show_internal_values", False)
render_fields = render_config.get("render_fields", None)
if not render_fields:
render_fields = [k for k, v in RENDER_FIELDS.items() if v["show_default"]]
if list_by_alias:
render_fields[0] = "aliases"
render_fields[1] = "value_id"
render_map: Dict[uuid.UUID, Dict[str, Any]] = {}
lookup = {}
value_info: ValueInfo
for value_info in self.item_infos.values(): # type: ignore
if not show_internal and value_info.is_internal:
continue
lookup[value_info.value_id] = value_info
details = {}
for property in render_fields:
render_func = (
RENDER_FIELDS.get(property, {})
.get("render", {})
.get(render_type, None)
)
if render_func is None:
if hasattr(value_info, property):
attr = getattr(value_info, property)
rendered = default_render_func(attr)
else:
raise Exception(
f"Can't render property '{property}': no render function registered and not a property."
)
else:
rendered = render_func(value_info)
details[property] = rendered
render_map[value_info.value_id] = details
if not list_by_alias:
return {str(k): v for k, v in render_map.items()}
else:
result: Dict[str, Dict[str, Any]] = {}
for value_id, render_details in render_map.items():
value_aliases = lookup[value_id].aliases
if value_aliases:
for alias in value_aliases:
assert alias not in result.keys()
render_details = dict(render_details)
render_details["alias"] = alias
result[alias] = render_details
else:
render_details["alias"] = ""
result[f"no_aliases_{value_id}"] = render_details
return result
def create_renderable(self, render_type: str = "terminal", **render_config: Any):
render_map = self.create_render_map(
render_type=render_type,
default_render_func=extract_renderable,
**render_config,
)
list_by_alias = render_config.get("list_by_alias", True)
render_fields = render_config.get("render_fields", None)
if not render_fields:
render_fields = [k for k, v in RENDER_FIELDS.items() if v["show_default"]]
if list_by_alias:
render_fields.insert(0, "alias")
render_fields.remove("aliases")
table = Table(box=box.SIMPLE)
for property in render_fields:
if property == "aliases" and list_by_alias:
table.add_column("alias")
elif property == "size":
table.add_column("size", justify="right")
else:
table.add_column(property)
for item_id, details in render_map.items():
row = []
for field in render_fields:
value = details[field]
row.append(value)
table.add_row(*row)
return table
class KiaraModuleConfigMetadata(KiaraModel):
_kiara_model_id: ClassVar = "metadata.module_config"
@classmethod
def from_config_class(
cls,
config_cls: Type[KiaraModuleConfig],
):
schema = config_cls.model_json_schema()
fields = schema["properties"]
config_values = {}
for field_name, details in fields.items():
type_str = "unknown"
if "type" in details.keys():
type_str = details["type"]
elif "anyOf" in details.keys():
type_str = f"anyOf: {details['anyOf']}"
elif "allOf" in details.keys():
type_str = f"allOf: {details['allOf']}"
desc = details.get("description", DEFAULT_NO_DESC_VALUE)
default = config_cls.model_fields[field_name].default
if default is None:
if callable(config_cls.model_fields[field_name].default_factory):
default = config_cls.model_fields[field_name].default_factory() # type: ignore
req = config_cls.model_fields[field_name].is_required()
config_values[field_name] = ValueTypeAndDescription(
description=desc, type=type_str, value_default=default, required=req
)
python_cls = PythonClass.from_class(config_cls)
return KiaraModuleConfigMetadata(
python_class=python_cls, config_values=config_values
)
python_class: PythonClass = Field(description="The config model python class.")
config_values: Dict[str, ValueTypeAndDescription] = Field(
description="The available configuration values."
)
def _retrieve_id(self) -> str:
return self.python_class.full_name
def _retrieve_data_to_hash(self) -> Any:
return self.python_class.full_name
class DataTypeClassInfo(TypeInfo[Type["DataType"]]):
_kiara_model_id: ClassVar = "info.data_type"
@classmethod
def create_from_type_class(
self, type_cls: Type["DataType"], kiara: Union["Kiara", None] = None
) -> "DataTypeClassInfo":
authors = AuthorsMetadataModel.from_class(type_cls)
doc = DocumentationMetadataModel.from_class_doc(type_cls)
properties_md = ContextMetadataModel.from_class(type_cls)
if kiara is not None:
qual_profiles = kiara.type_registry.get_associated_profiles(type_cls._data_type_name) # type: ignore
lineage = kiara.type_registry.get_type_lineage(type_cls._data_type_name) # type: ignore
else:
qual_profiles = None
lineage = None
try:
result = DataTypeClassInfo(
type_name=type_cls._data_type_name, # type: ignore
python_class=PythonClass.from_class(type_cls),
value_cls=PythonClass.from_class(type_cls.python_class()),
data_type_config_cls=PythonClass.from_class(
type_cls.data_type_config_class()
),
lineage=lineage, # type: ignore
qualifier_profiles=qual_profiles,
documentation=doc,
authors=authors,
context=properties_md,
)
except Exception as e:
if isinstance(
e, TypeError
) and "missing 1 required positional argument: 'cls'" in str(e):
raise Exception(
f"Invalid implementation of TypeValue subclass '{type_cls.__name__}': 'python_class' method must be marked as a '@classmethod'. This is a bug."
)
raise e
result._kiara = kiara
return result
@classmethod
def base_class(self) -> Type["DataType"]:
from kiara.data_types import DataType
return DataType
@classmethod
def category_name(cls) -> str:
return "data_type"
value_cls: PythonClass = Field(description="The python class of the value itself.")
data_type_config_cls: PythonClass = Field(
description="The python class holding the schema for configuring this type."
)
lineage: Union[List[str], None] = Field(description="This types lineage.")
qualifier_profiles: Union[Mapping[str, Mapping[str, Any]], None] = Field(
description="A map of qualifier profiles for this data types."
)
_kiara: Union["Kiara", None] = PrivateAttr(default=None)
def _retrieve_id(self) -> str:
return self.type_name
def _retrieve_data_to_hash(self) -> Any:
return self.type_name
def create_renderable(self, **config: Any) -> RenderableType:
include_doc = config.get("include_doc", True)
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 0, 0, 0))
table.add_column("property", style="i")
table.add_column("value")
if self.lineage:
table.add_row("lineage", "\n".join(self.lineage[0:]))
else:
table.add_row("lineage", "-- n/a --")
if self.qualifier_profiles:
qual_table = Table(show_header=False, box=box.SIMPLE)
qual_table.add_column("name")
qual_table.add_column("config")
for name, details in self.qualifier_profiles.items():
json_details = orjson_dumps(details, option=orjson.OPT_INDENT_2)
qual_table.add_row(
name, Syntax(json_details, "json", background_color="default")
)
table.add_row("qualifier profile(s)", qual_table)
else:
table.add_row("qualifier profile(s)", "-- n/a --")
if include_doc:
table.add_row(
"Documentation",
Panel(self.documentation.create_renderable(), box=box.SIMPLE),
)
table.add_row("Author(s)", self.authors.create_renderable())
table.add_row("Context", self.context.create_renderable())
table.add_row("Python class", self.python_class.create_renderable())
table.add_row("Config class", self.data_type_config_cls.create_renderable())
table.add_row("Value class", self.value_cls.create_renderable())
return table
class DataTypeClassesInfo(TypeInfoItemGroup):
_kiara_model_id: ClassVar = "info.data_types"
# @classmethod
# def create_from_type_items(
# cls,
# group_title: Union[str, None] = None,
# **items: Type,
# ) -> "TypeInfoModelGroup":
#
# type_infos = {
# k: cls.base_info_class().create_from_type_class(v) for k, v in items.items() # type: ignore
# }
# data_types_info = cls(group_alias=group_title, item_infos=type_infos) # type: ignore
# return data_types_info
#
# @classmethod
# def create_augmented_from_type_items(
# cls,
# kiara: Union["Kiara", None] = None,
# group_alias: Union[str, None] = None,
# **items: Type,
# ) -> "TypeInfoModelGroup":
#
# type_infos = {
# k: cls.base_info_class().create_from_type_class(v, kiara=kiara) for k, v in items.items() # type: ignore
# }
# data_types_info = cls(group_alias=group_alias, item_infos=type_infos) # type: ignore
# data_types_info._kiara = kiara
# return data_types_info
@classmethod
def base_info_class(cls) -> Type[DataTypeClassInfo]:
return DataTypeClassInfo
type_name: Literal["data_type"] = "data_type"
item_infos: Mapping[str, DataTypeClassInfo] = Field( # type: ignore
description="The data_type info instances for each type."
)
# _kiara: Union["Kiara", None] = PrivateAttr(default=None)
def create_renderable(self, **config: Any) -> RenderableType:
full_doc = config.get("full_doc", False)
show_subtypes_inline = config.get("show_qualifier_profiles_inline", True)
show_lineage = config.get("show_type_lineage", True)
show_lines = full_doc or show_subtypes_inline or show_lineage
table = Table(show_header=True, box=box.SIMPLE, show_lines=show_lines)
table.add_column("type name", style="i")
if show_lineage:
table.add_column("type lineage")
if show_subtypes_inline:
table.add_column("(qualifier) profiles")
if full_doc:
table.add_column("documentation")
else:
table.add_column("description")
all_types = self.item_infos.keys()
for type_name in sorted(all_types): # type: ignore
t_md = self.item_infos[type_name] # type: ignore
row: List[Any] = [type_name]
if show_lineage:
if self._kiara is None:
lineage_str = "-- n/a --"
else:
lineage = list(
self._kiara.type_registry.get_type_lineage(type_name)
)
lineage_str = ", ".join(reversed(lineage[1:]))
row.append(lineage_str)
if show_subtypes_inline:
if self._kiara is None:
qual_profiles = "-- n/a --"
else:
qual_p = self._kiara.type_registry.get_associated_profiles(
data_type_name=type_name
).keys()
if qual_p:
qual_profiles = "\n".join(qual_p)
else:
qual_profiles = "-- n/a --"
row.append(qual_profiles)
if full_doc:
md = Markdown(t_md.documentation.full_doc)
else:
md = Markdown(t_md.documentation.description)
row.append(md)
table.add_row(*row)
return table
class ModuleTypeInfo(TypeInfo[Type["KiaraModule"]]):
_kiara_model_id: ClassVar = "info.kiara_module_type"
@classmethod
def create_from_type_class(cls, type_cls: Type["KiaraModule"], kiara: "Kiara") -> "ModuleTypeInfo": # type: ignore
module_attrs = cls.extract_module_attributes(module_cls=type_cls)
return cls(**module_attrs)
@classmethod
def base_class(self) -> Type["KiaraModule"]:
from kiara.modules import KiaraModule
return KiaraModule
@classmethod
def category_name(cls) -> str:
return "module"
@classmethod
def extract_module_attributes(
self, module_cls: Type["KiaraModule"]
) -> Dict[str, Any]:
if not hasattr(module_cls, "process"):
raise Exception(f"Module class '{module_cls}' misses 'process' method.")
module_src = textwrap.dedent(inspect.getsource(module_cls)) # type: ignore
authors_md = AuthorsMetadataModel.from_class(module_cls)
doc = DocumentationMetadataModel.from_class_doc(module_cls)
python_class = PythonClass.from_class(module_cls)
properties_md = ContextMetadataModel.from_class(module_cls)
config = KiaraModuleConfigMetadata.from_config_class(module_cls._config_cls)
return {
"type_name": module_cls._module_type_name, # type: ignore
"documentation": doc,
"authors": authors_md,
"context": properties_md,
"python_class": python_class,
"module_config": config,
"module_src": module_src,
}
module_config: KiaraModuleConfigMetadata = Field(
description="The module config metadata."
)
module_src: str = Field(
description="The source code of the process method of the module."
)
def create_renderable(self, **config: Any) -> RenderableType:
include_config_schema = config.get("include_config_schema", True)
include_src = config.get("include_src", False)
include_doc = config.get("include_doc", True)
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 0, 0, 0))
table.add_column("property", style="i")
table.add_column("value")
if include_doc:
table.add_row(
"Documentation",
Panel(self.documentation.create_renderable(), box=box.SIMPLE),
)
table.add_row("Author(s)", self.authors.create_renderable())
table.add_row("Context", self.context.create_renderable())
if include_config_schema:
config_cls = self.python_class.get_class()._config_cls # type: ignore
from kiara.utils.output import create_table_from_base_model_cls
table.add_row(
"Module config schema", create_table_from_base_model_cls(config_cls)
)
table.add_row("Python class", self.python_class.create_renderable())
if include_src:
from kiara.context.config import KIARA_SETTINGS
_config = Syntax(
self.module_src,
"python",
background_color=KIARA_SETTINGS.syntax_highlight_background,
)
table.add_row("Processing source code", Panel(_config, box=box.HORIZONTALS))
return table
class ModuleTypesInfo(TypeInfoItemGroup):
_kiara_model_id: ClassVar = "info.module_types"
@classmethod
def base_info_class(cls) -> Type[TypeInfo]:
return ModuleTypeInfo
type_name: Literal["module_type"] = "module_type"
item_infos: Mapping[str, ModuleTypeInfo] = Field( # type: ignore
description="The module type info instances for each type."
)
class OperationTypeInfo(TypeInfo[Type["OperationType"]]):
_kiara_model_id: ClassVar = "info.operation_type"
@classmethod
def create_from_type_class( # type: ignore
cls, kiara: "Kiara", type_cls: Type["OperationType"] # type: ignore
) -> "OperationTypeInfo":
authors_md = AuthorsMetadataModel.from_class(type_cls)
doc = DocumentationMetadataModel.from_class_doc(type_cls)
python_class = PythonClass.from_class(type_cls)
properties_md = ContextMetadataModel.from_class(type_cls)
return OperationTypeInfo(
type_name=type_cls._operation_type_name, # type: ignore
documentation=doc,
authors=authors_md,
context=properties_md,
python_class=python_class,
)
@classmethod
def base_class(self) -> Type["OperationType"]:
from kiara.operations import OperationType
return OperationType
@classmethod
def category_name(cls) -> str:
return "operation_type"
def _retrieve_id(self) -> str:
return self.type_name
def _retrieve_data_to_hash(self) -> Any:
return self.type_name
class OperationTypeClassesInfo(TypeInfoItemGroup):
_kiara_model_id: ClassVar = "info.operation_types"
@classmethod
def base_info_class(cls) -> Type[OperationTypeInfo]: # type: ignore
return OperationTypeInfo
type_name: Literal["operation_type"] = "operation_type"
item_infos: Mapping[str, OperationTypeInfo] = Field( # type: ignore
description="The operation info instances for each type."
)
class FieldInfo(BaseModel):
field_name: str = Field(description="The field name.")
field_schema: ValueSchema = Field(description="The schema of the field.")
data_type_info: DataTypeInfo = Field(
description="Information about the data type instance of the associated value."
)
value_required: bool = Field(
description="Whether user input is required (meaning: 'optional' is False, and no default set)."
)
class PipelineStructureInfo(ItemInfo):
_kiara_model_id: ClassVar = "info.pipeline_structure"
@classmethod
def base_instance_class(cls) -> Type[PipelineStructure]:
return PipelineStructure
@classmethod
def create_from_instance(
cls, kiara: "Kiara", instance: PipelineStructure, **kwargs
):
authors = AuthorsMetadataModel()
context = ContextMetadataModel()
execution_graph: Dict[str, Any] = {}
data_flow_graph: Dict[str, Any] = {}
data_flow_graph_simple: Dict[str, Any] = {}
input_fields = {}
for field_name, schema in instance.pipeline_inputs_schema.items():
dt = kiara.type_registry.get_data_type_instance(
type_name=schema.type, type_config=schema.type_config
)
dt_info = FieldInfo(
field_name=field_name,
field_schema=schema,
data_type_info=dt.info,
value_required=schema.is_required(),
)
input_fields[field_name] = dt_info
output_fields = {}
for field_name, schema in instance.pipeline_outputs_schema.items():
dt = kiara.type_registry.get_data_type_instance(
type_name=schema.type, type_config=schema.type_config
)
dt_info = FieldInfo(
field_name=field_name,
field_schema=schema,
data_type_info=dt.info,
value_required=schema.is_required(),
)
output_fields[field_name] = dt_info
return cls(
type_name=instance.instance_id,
documentation=instance.pipeline_config.doc,
authors=authors,
context=context,
pipeline_config=instance.pipeline_config,
pipeline_config_orig=instance.pipeline_config.get_raw_config(),
# steps={step.step_id: step for step in instance.steps},
step_details=instance.steps_details,
input_aliases=instance.input_aliases,
output_aliases=instance.output_aliases,
constants=instance.constants,
defaults=instance.defaults,
pipeline_input_fields=input_fields,
pipeline_output_fields=output_fields,
pipeline_input_refs=instance.pipeline_input_refs,
pipeline_output_refs=instance.pipeline_output_refs,
execution_graph=execution_graph,
data_flow_graph=data_flow_graph,
data_flow_graph_simple=data_flow_graph_simple,
processing_stages=instance.processing_stages,
# processing_stages_info=instance.processing_stages_info,
)
pipeline_config: PipelineConfig = Field(
description="The underlying pipeline config."
)
pipeline_config_orig: Dict[str, Any] = Field(
description="The original, user-provided pipeline config."
)
# steps: Mapping[str, PipelineStep] = Field(
# description="All steps for this pipeline, indexed by their step_id.", exclude=True
# )
step_details: Mapping[str, StepInfo] = Field(
description="Additional information for each step."
)
input_aliases: Dict[str, str] = Field(description="The input aliases.")
output_aliases: Dict[str, str] = Field(description="The output aliases.")
constants: Mapping[str, Any] = Field(
description="The input constants for this pipeline."
)
defaults: Mapping[str, Any] = Field(
description="The default inputs for this pipeline."
)
pipeline_input_fields: Mapping[str, FieldInfo] = Field(
description="The pipeline inputs schema."
)
pipeline_output_fields: Mapping[str, FieldInfo] = Field(
description="The pipeline outputs schema."
)
pipeline_input_refs: Mapping[str, PipelineInputRef] = Field(
description="References to the step inputs that are linked to pipeline inputs."
)
pipeline_output_refs: Mapping[str, PipelineOutputRef] = Field(
description="References to the step outputs that are linked to pipeline outputs."
)
execution_graph: Dict[str, Any] = Field(
description="Data describing the execution graph of this pipeline."
)
data_flow_graph: Dict[str, Any] = Field(
description="Data describing the data flow of this pipeline."
)
data_flow_graph_simple: Dict[str, Any] = Field(
description="Data describing the (simplified) data flow of this pipeline."
)
processing_stages: List[List[str]] = Field(
description="A list of lists, containing all the step_ids per stage, in the order of execution."
)
# processing_stages_info: Mapping[int, PipelineStage] = Field(
# description="More detailed information about each step of this pipelines execution graph."
# )
def get_step(self, step_id) -> PipelineStep:
return self.step_details[step_id].step
def get_step_details(self, step_id: str) -> StepInfo:
return self.step_details[step_id]
def create_renderable(self, **config: Any) -> RenderableType:
tree = Tree("pipeline")
inputs = tree.add("inputs")
for field_name, field_info in self.pipeline_input_fields.items():
inputs.add(f"[i]{field_name}[i] (type: {field_info.field_schema.type})")
steps = tree.add("steps")
for idx, stage in enumerate(self.processing_stages, start=1):
stage_node = steps.add(f"stage {idx}")
for step_id in stage:
step_node = stage_node.add(f"step: {step_id}")
step = self.get_step(step_id=step_id)
if step.doc.is_set:
step_node.add(f"desc: {step.doc.description}")
step_node.add(f"operation: {step.manifest_src.module_type}")
outputs = tree.add("outputs")
for field_name, field_info in self.pipeline_output_fields.items():
outputs.add(f"[i]{field_name}[i] (type: {field_info.field_schema.type})")
return tree
class OperationInfo(ItemInfo):
_kiara_model_id: ClassVar = "info.operation"
@classmethod
def base_instance_class(cls) -> Type[Operation]:
return Operation
@classmethod
def create_from_instance(cls, kiara: "Kiara", instance: Operation, **kwargs):
return cls.create_from_operation(kiara=kiara, operation=instance)
@classmethod
def create_from_operation(
cls, kiara: "Kiara", operation: Operation
) -> "OperationInfo":
module = operation.module
module_cls = module.__class__
authors_md = AuthorsMetadataModel.from_class(module_cls)
properties_md = ContextMetadataModel.from_class(module_cls)
op_types = kiara.operation_registry.find_all_operation_types(
operation_id=operation.operation_id
)
input_fields = {}
for field_name, schema in operation.inputs_schema.items():
try:
dt = kiara.type_registry.get_data_type_instance(
type_name=schema.type, type_config=schema.type_config
)
dt_info = FieldInfo(
field_name=field_name,
field_schema=schema,
data_type_info=dt.info,
value_required=schema.is_required(),
)
except Exception:
dtc = PythonClass.from_class(object)
dti = DataTypeInfo(
data_type_name=f"{schema.type} (invalid)",
data_type_config=schema.type_config,
characteristics=DataTypeCharacteristics(),
data_type_class=dtc,
)
dt_info = FieldInfo(
field_name=field_name,
field_schema=schema,
data_type_info=dti,
value_required=schema.is_required(),
)
input_fields[field_name] = dt_info
output_fields = {}
for field_name, schema in operation.outputs_schema.items():
dt = kiara.type_registry.get_data_type_instance(
type_name=schema.type, type_config=schema.type_config
)
dt_info = FieldInfo(
field_name=field_name,
field_schema=schema,
data_type_info=dt.info,
value_required=schema.is_required(),
)
output_fields[field_name] = dt_info
op_info = OperationInfo(
type_name=operation.operation_id,
operation_types=list(op_types),
input_fields=input_fields,
output_fields=output_fields,
operation=operation,
documentation=operation.doc,
authors=authors_md,
context=properties_md,
)
return op_info
@classmethod
def category_name(cls) -> str:
return "operation"
operation: Operation = Field(description="The operation instance.")
operation_types: List[str] = Field(
description="The operation types this operation belongs to."
)
input_fields: Mapping[str, FieldInfo] = Field(
description="The inputs schema for this operation."
)
output_fields: Mapping[str, FieldInfo] = Field(
description="The outputs schema for this operation."
)
def create_renderable(self, **config: Any) -> RenderableType:
include_doc = config.get("include_doc", False)
include_module_details = config.get("include_module_details", True)
include_op_details = config.get("include_op_details", True)
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 0, 0, 0))
table.add_column("property", style="i")
table.add_column("value")
if include_doc:
table.add_row(
"Documentation",
Panel(self.documentation.create_renderable(), box=box.SIMPLE),
)
table.add_row("Author(s)", self.authors.create_renderable(**config))
table.add_row("Context", self.context.create_renderable(**config))
if include_module_details:
table.add_row("Module type", self.operation.module_type)
if self.operation.module_config:
table.add_row(
"Module config",
Syntax(
orjson_dumps(
self.operation.module_config, option=orjson.OPT_INDENT_2
),
"json",
background_color="default",
),
)
if include_op_details:
table.add_row(
"Operation details", self.operation.create_renderable(**config)
)
return table
class OperationGroupInfo(InfoItemGroup):
_kiara_model_id: ClassVar = "info.operations"
@classmethod
def base_info_class(cls) -> Type[ItemInfo]:
return OperationInfo
@classmethod
def create_from_operations(
cls, kiara: "Kiara", group_title: Union[str, None] = None, **items: Operation
) -> "OperationGroupInfo":
op_infos = {
k: OperationInfo.create_from_operation(kiara=kiara, operation=v)
for k, v in items.items()
}
op_group_info = cls(group_title=group_title, item_infos=op_infos)
return op_group_info
# type_name: Literal["operation_type"] = "operation_type"
item_infos: Mapping[str, OperationInfo] = Field(
description="The operation info instances for each type."
)
def create_renderable(self, **config: Any) -> RenderableType:
by_type = config.get("by_type", False)
if by_type:
return self._create_renderable_by_type(**config)
else:
return self._create_renderable_list(**config)
def _create_renderable_list(self, **config) -> RenderableType:
include_internal_operations = config.get("include_internal_operations", True)
full_doc = config.get("full_doc", False)
filter = config.get("filter", [])
show_internal_column = config.get("show_internal_column", False)
table = Table(box=box.SIMPLE, show_header=True)
table.add_column("Id", no_wrap=True, style="i")
table.add_column("Type(s)", style="green")
if show_internal_column:
table.add_column("Internal", justify="center")
table.add_column("Description")
for op_id, op_info in self.item_infos.items():
if (
not include_internal_operations
and op_info.operation.operation_details.is_internal_operation
):
continue
types = op_info.operation_types
if "custom_module" in types:
types.remove("custom_module")
desc_str = op_info.documentation.description
if full_doc:
desc = Markdown(op_info.documentation.full_doc)
else:
desc = Markdown(op_info.documentation.description)
is_internal = op_info.operation.module.characteristics.is_internal
is_internal_str = "\u2714" if is_internal else ""
if filter:
match = True
for f in filter:
if (
f.lower() not in op_id.lower()
and f.lower() not in desc_str.lower()
):
match = False
break
if match:
if not show_internal_column:
table.add_row(op_id, ", ".join(types), desc)
else:
table.add_row(op_id, ", ".join(types), is_internal_str, desc)
else:
if not show_internal_column:
table.add_row(op_id, ", ".join(types), desc)
else:
table.add_row(op_id, ", ".join(types), is_internal_str, desc)
return table
def _create_renderable_by_type(self, **config) -> Table:
include_internal_operations = config.get("include_internal_operations", True)
full_doc = config.get("full_doc", False)
filter = config.get("filter", [])
by_type: Dict[str, Dict[str, OperationInfo]] = {}
for op_id, op in self.item_infos.items():
if filter:
match = True
for f in filter:
if (
f.lower() not in op_id.lower()
and f.lower() not in op.documentation.description.lower()
):
match = False
break
if not match:
continue
for op_type in op.operation_types:
by_type.setdefault(op_type, {})[op_id] = op
table = Table(box=box.SIMPLE, show_header=True)
table.add_column("Type", no_wrap=True, style="b green")
table.add_column("Id", no_wrap=True, style="i")
if full_doc:
table.add_column("Documentation", no_wrap=False, style="i")
else:
table.add_column("Description", no_wrap=False, style="i")
for operation_name in sorted(by_type.keys()):
# if operation_name == "custom_module":
# continue
first_line_value = True
op_infos = by_type[operation_name]
for op_id in sorted(op_infos.keys()):
op_info: OperationInfo = op_infos[op_id]
if (
not include_internal_operations
and op_info.operation.operation_details.is_internal_operation
):
continue
if full_doc:
desc = Markdown(op_info.documentation.full_doc)
else:
desc = Markdown(op_info.documentation.description)
row: List[RenderableType] = []
if first_line_value:
row.append(operation_name)
else:
row.append("")
row.append(op_id)
row.append(desc)
table.add_row(*row)
first_line_value = False
return table
class RendererInfo(ItemInfo):
renderer_config: Mapping[str, Any] = Field(description="The renderer config.")
renderer_cls: PythonClass = Field(
description="The Python class that implements the renderer."
)
supported_inputs: List[str] = Field(
description="Descriptions of the supported inputs."
)
supported_source_types: List[str] = Field(
description="Descriptions of the supported source types."
)
supported_target_types: List[str] = Field(
description="Descriptions of the supported target types."
)
supported_python_classes: List[PythonClass] = Field(
description="A list of supported Python types that are acceptable as inputs."
)
@classmethod
def base_instance_class(cls) -> Type[KiaraRenderer]:
return KiaraRenderer
@classmethod
def create_from_instance(cls, kiara: "Kiara", instance: KiaraRenderer, **kwargs):
doc = instance.doc
authors = AuthorsMetadataModel.from_class(instance.__class__)
properties_md = ContextMetadataModel.from_class(instance.__class__)
renderer_name = instance._renderer_name # type: ignore
renderer_config = instance.renderer_config.model_dump()
renderer_cls = PythonClass.from_class(item_cls=instance.__class__)
supported_inputs = list(instance.supported_inputs_descs)
supported_python_classes = [
PythonClass.from_class(x)
for x in instance.retrieve_supported_python_classes()
]
supported_input_types = instance.retrieve_supported_render_sources()
if isinstance(supported_input_types, str):
supported_input_types = [supported_input_types]
supported_target_types = instance.retrieve_supported_render_targets()
if isinstance(supported_target_types, str):
supported_target_types = [supported_target_types]
return cls(
type_name=renderer_name,
documentation=doc,
authors=authors,
context=properties_md,
renderer_config=renderer_config,
renderer_cls=renderer_cls,
supported_inputs=supported_inputs,
supported_python_classes=supported_python_classes,
supported_source_types=supported_input_types,
supported_target_types=supported_target_types,
)
def create_renderable(self, **config: Any) -> RenderableType:
show_metadata = config.get("show_metadata", False)
table = Table(box=box.SIMPLE, show_header=False)
table.add_column("key", style="i")
table.add_column("value")
table.add_row("Documentation", self.documentation.create_renderable(**config))
inputs_md = ""
for inp in self.supported_inputs:
inputs_md += f"- {inp}\n"
table.add_row("Supported inputs", Markdown(inputs_md))
if show_metadata:
table.add_row("Renderer name", self.type_name)
if self.renderer_config:
json = orjson_dumps(self.renderer_config, option=orjson.OPT_INDENT_2)
table.add_row(
"Renderer config", Syntax(json, "json", background_color="default")
)
table.add_row(
"Renderer class", self.renderer_cls.create_renderable(**config)
)
table.add_row("Author(s)", self.authors.create_renderable(**config))
table.add_row("Context", self.context.create_renderable(**config))
python_cls_md = ""
for inp_cls in self.supported_python_classes:
python_cls_md += f"- {inp_cls.full_name}\n"
table.add_row(python_cls_md)
return table
class RendererInfos(InfoItemGroup[RendererInfo]):
@classmethod
def base_info_class(cls) -> Type[RendererInfo]:
return RendererInfo
def get_render_source_types(self) -> List[str]:
all_source_types = set()
item: RendererInfo
for item in self.item_infos.values(): # type: ignore
all_source_types.update(item.supported_source_types)
return sorted(all_source_types)
def create_renderable(self, **config: Any) -> RenderableType:
table = Table(
show_header=True, box=HORIZONTALS_NO_TO_AND_BOTTOM, show_lines=True
)
table.add_column("Source type(s)")
table.add_column("Target type(s)")
table.add_column("Description")
rows: Dict[str, Dict[str, List[RenderableType]]] = {}
info: RendererInfo
for info in self.item_infos.values(): # type: ignore
row: List[RenderableType] = []
source_types = "\n".join(info.supported_source_types)
target_types = "\n".join(info.supported_target_types)
row.append(source_types) # type: ignore
row.append(target_types) # type: ignore
row.append(info.documentation.create_renderable(**config))
rows.setdefault(source_types, {})[target_types] = row
for source in sorted(rows.keys()):
for target in sorted(rows[source].keys()):
row = rows[source][target]
table.add_row(*row)
return table
class KiaraPluginInfo(ItemInfo):
@classmethod
def base_instance_class(cls) -> Type[str]:
return str
@classmethod
def create_from_instance(
cls, kiara: "Kiara", instance: str, **kwargs
) -> "KiaraPluginInfo":
registry = kiara.environment_registry
python_env: PythonRuntimeEnvironment = registry.environments["python"] # type: ignore
match: Union[str, None] = None
for pkg in python_env.packages:
pkg_name = pkg.name
if pkg_name == instance:
match = pkg.name
elif pkg_name.startswith("kiara-plugin") or pkg_name.startswith(
"kiara_plugin"
):
underscored = pkg_name.replace("-", "_")
if underscored == instance:
match = underscored
break
if not match:
raise KiaraException(
msg=f"Can't provide information for plugin '{instance}'.",
reason="Plugin not installed.",
)
match = match.replace("kiara-plugin", "kiara_plugin")
from kiara.utils.operations import filter_operations
data_types = kiara.type_registry.get_context_metadata(only_for_package=match)
modules = kiara.module_registry.get_context_metadata(only_for_package=match)
operation_types = kiara.operation_registry.get_context_metadata(
only_for_package=match
)
operations = filter_operations(
kiara=kiara, pkg_name=match, **kiara.operation_registry.operations
)
model_registry = kiara.kiara_model_registry
kiara_models = model_registry.get_models_for_package(package_name=match)
final_pkg_name = match.replace("-", "_")
base_module = importlib.import_module(final_pkg_name)
from importlib.metadata import metadata
pkg_metadata = metadata(final_pkg_name)
summary = pkg_metadata.get("Summary", None)
desc = pkg_metadata.get("Description", None)
if not summary and not desc:
doc = DocumentationMetadataModel.create()
elif not summary:
doc = DocumentationMetadataModel.create(desc)
elif not desc:
doc = DocumentationMetadataModel.create(summary)
else:
doc = DocumentationMetadataModel.create(f"{summary}\n\n{desc}")
def parse_name_email(s):
match = re.match(r"(.*)\s*<(.+)>", s)
if match:
name = match.group(1).strip()
email = match.group(2).strip()
return name, email
else:
return None, None
version = pkg_metadata["Version"]
authors: List[AuthorModel] = []
for key in pkg_metadata.keys():
if key == "Author-email":
author, email = parse_name_email(pkg_metadata[key])
if not author:
author = email.split("@")[0]
elif key == "Author":
author = pkg_metadata[key]
email = None
else:
continue
author_obj = AuthorModel(name=author, email=email)
authors.append(author_obj)
author_md = AuthorsMetadataModel(authors=authors)
context = ContextMetadataModel.from_class(base_module) # type: ignore
info = KiaraPluginInfo(
type_name=instance,
version=version,
documentation=doc,
authors=author_md,
context=context,
data_types=data_types,
module_types=modules,
kiara_model_types=kiara_models,
operation_types=operation_types,
operations=operations,
)
return info
version: str = Field(description="The version of the plugin.")
data_types: DataTypeClassesInfo = Field(description="The included data types.")
module_types: ModuleTypesInfo = Field(
description="The included kiara module types."
)
kiara_model_types: KiaraModelClassesInfo = Field(
description="The included model classes."
)
# metadata_types: MetadataTypeClassesInfo = Field(
# description="The included value metadata types."
# )
operation_types: OperationTypeClassesInfo = Field(
description="The included operation types."
)
operations: OperationGroupInfo = Field(description="The included operations.")
def create_renderable(self, **config: Any) -> RenderableType:
include_doc = config.get("include_doc", True)
include_full_doc = config.get("include_full_doc", False)
include_data_types = config.get("include_data_types", True)
include_module_types = config.get("include_module_types", True)
include_operations = config.get("include_operations", True)
include_operation_types = config.get("include_operation_types", False)
include_model_types = config.get("include_model_types", False)
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 0, 0, 0))
table.add_column("property", style="i")
table.add_column("value")
if include_doc:
if include_full_doc:
title = "Documentation"
doc_str = self.documentation.full_doc
else:
title = "Description"
doc_str = self.documentation.description
table.add_row(
title,
Panel(doc_str, box=box.SIMPLE),
)
table.add_row("Version", Panel(self.version, box.SIMPLE))
table.add_row("Author(s)", self.authors.create_renderable())
table.add_row("Context", self.context.create_renderable())
if include_data_types:
table.add_row("data_types", self.data_types.create_renderable(**config))
if include_module_types:
table.add_row("module_types", self.module_types.create_renderable(**config))
if include_operations:
config_ops = config.copy()
config_ops["show_internal_column"] = True
table.add_row("operations", self.operations.create_renderable(**config_ops))
if include_operation_types:
table.add_row(
"operation_types", self.operation_types.create_renderable(**config)
)
if include_model_types:
table.add_row(
"kiara_model_types", self.kiara_model_types.create_renderable(**config)
)
return table
class KiaraPluginInfos(InfoItemGroup[KiaraPluginInfo]):
@classmethod
def get_available_plugin_names(
cls, kiara: "Kiara", regex: str = "^kiara[-_]plugin\\..*"
) -> List[str]:
"""
Get a list of all available plugins.
Arguments:
regex: an optional regex to indicate the plugin naming scheme (default: /$kiara[_-]plugin\..*/)
Returns:
a list of plugin names
"""
registry = kiara.environment_registry
python_env: PythonRuntimeEnvironment = registry.environments["python"] # type: ignore
if not regex:
regex = "^kiara[-_]plugin\\..*"
regex_c = re.compile(regex)
result = []
for pkg in python_env.packages:
pkg_name = pkg.name
if pkg_name == "kiara":
continue
# check if the package is a kiara plugin
match = regex_c.search(pkg_name)
if match:
result.append(pkg_name)
return result
@classmethod
def base_info_class(cls) -> Type[KiaraPluginInfo]:
return KiaraPluginInfo
@classmethod
def create_group(
cls,
kiara: "Kiara",
group_title: Union[str, None] = None,
plugin_name_regex: str = "^kiara[-_]plugin\\..*",
) -> "KiaraPluginInfos":
names = cls.get_available_plugin_names(kiara=kiara, regex=plugin_name_regex)
result = cls.create_from_plugin_names(kiara, group_title, *names)
return result
@classmethod
def create_from_plugin_names(
cls, kiara: "Kiara", group_title: Union[str, None] = None, *items: str
) -> "KiaraPluginInfos":
"""Create the info group from a list of plugin names."""
plugin_infos = {
k: KiaraPluginInfo.create_from_instance(kiara=kiara, instance=k)
for k in items
}
op_group_info = cls(group_title=group_title, item_infos=plugin_infos)
return op_group_info
def create_renderable(self, **config: Any) -> RenderableType:
full_doc = config.get("full_doc", False)
table = Table(show_header=True, box=box.SIMPLE, show_lines=full_doc)
table.add_column("Name", style="i")
table.add_column("Version")
table.add_column("Description")
for type_name in sorted(self.item_infos.keys()):
t_md = self.item_infos[type_name]
version = t_md.version
if full_doc:
md = Markdown(t_md.documentation.full_doc)
else:
md = Markdown(t_md.documentation.description)
table.add_row(type_name, version, md)
return table
class JobInfo(ItemInfo):
job_record: JobRecord = Field(description="The job record instance.")
operation: OperationInfo = Field(description="The operation info instance.")
inputs: Mapping[str, ValueInfo] = Field(description="The inputs.")
outputs: Mapping[str, ValueInfo] = Field(description="The result(s).")
@classmethod
def base_instance_class(cls) -> Type[JobRecord]:
return JobRecord
@classmethod
def create_from_instance(cls, kiara: "Kiara", instance: JobRecord, **kwargs):
type_name = str(instance.job_id)
module = kiara.module_registry.create_module(instance)
op = Operation.create_from_module(module=module)
operation_info: OperationInfo = OperationInfo.create_from_operation(
kiara=kiara, operation=op
)
doc = operation_info.documentation
authors = operation_info.authors
context = operation_info.context
inputs = {}
for k, v in instance.inputs.items():
value = kiara.data_registry.get_value(v)
inputs[k] = ValueInfo.create_from_instance(kiara=kiara, instance=value)
outputs = {}
for k, v in instance.outputs.items():
value = kiara.data_registry.get_value(v)
outputs[k] = ValueInfo.create_from_instance(kiara=kiara, instance=value)
return cls(
type_name=type_name,
documentation=doc,
authors=authors,
context=context,
job_record=instance,
operation=operation_info,
inputs=inputs,
outputs=outputs,
)
def create_renderable(self, **config: Any) -> RenderableType:
table = Table(show_header=False, box=box.SIMPLE)
table.add_column("Key", style="i")
table.add_column("Value")
table.add_row("Job ID", str(self.job_record.job_id))
runtime_details = self.job_record.runtime_details
assert runtime_details is not None
table.add_row("Job details", runtime_details.create_renderable(**config))
table.add_row("Operation details", self.operation)
values_table = Table(show_header=False, box=box.SIMPLE)
values_table.add_column("field", style="i")
values_table.add_column("value")
for k, v in self.inputs.items():
rendered_value = str(v.value_id)
values_table.add_row(k, rendered_value)
table.add_row("Inputs", values_table)
values_table = Table(show_header=False, box=box.SIMPLE)
values_table.add_column("field", style="i")
values_table.add_column("value")
for k, v in self.outputs.items():
rendered_value = str(v.value_id)
values_table.add_row(k, rendered_value)
table.add_row("Outputs", values_table)
return table
class JobInfos(InfoItemGroup[JobInfo]):
@classmethod
def base_info_class(cls) -> Type[JobInfo]:
return JobInfo
def create_renderable(self, **config: Any) -> RenderableType:
table = Table(
show_header=True, box=HORIZONTALS_NO_TO_AND_BOTTOM, show_lines=True
)
table.add_column("Job ID")
table.add_column("Module type")
table.add_column("Runtime (in seconds)")
table.add_column("Details")
info: JobInfo
for info in self.item_infos.values(): # type: ignore
runtime_details = info.job_record.runtime_details
assert runtime_details is not None
row: List[RenderableType] = []
row.append(str(info.job_record.job_id))
row.append(info.operation.operation.module_type)
row.append(str(runtime_details.runtime))
row.append(runtime_details.create_renderable(**config))
table.add_row(*row)
return table