Skip to content
v0.2.1

Plugins

class PluginMethod(BaseModel):
Source
class PluginMethod(BaseModel):
name: str
label: Optional[str] = None
description: Optional[str] = None
input_ocels: dict[str, OCELAnnotation] = Field(default_factory=dict)
input_resources: dict[str, tuple[str, ResourceAnnotation]] = Field(default_factory=dict)
results: list[PluginResult] = Field(default_factory=list)
_input_model: Optional[type[PluginInput]] = PrivateAttr(default=None)
_method: Callable[..., PluginReturnType] = PrivateAttr()
_resource_types: set[type[Resource]] = PrivateAttr(default_factory=set)
@computed_field
def input_schema(self) -> dict[str, Any] | None:
return self._input_model.model_json_schema() if self._input_model is not None else None
class PluginMeta(BaseModel):
Source
class PluginMeta(BaseModel):
name: str
version: str
label: str
description: Optional[str]
class Plugin(ABC):
Source
class Plugin(ABC):
version: ClassVar[str]
label: ClassVar[str]
description: ClassVar[Optional[str]] = None
@classmethod
def meta(cls):
return PluginMeta(
name=cls.__name__, version=cls.version, description=cls.description, label=cls.label
)
@classmethod
def method_map(cls) -> dict[str, PluginMethod]:
method_map: dict[str, PluginMethod] = {}
for _, method in inspect.getmembers(cls, predicate=inspect.isfunction):
method_meta = getattr(method, "__meta__", None)
if not isinstance(method_meta, PluginMethod):
continue
method_map[method_meta.name] = method_meta
return method_map
def plugin_method(label: Optional[str] = None, description: Optional[str] = None):

Decorator that marks a plugin class method as an Ocelescope runnable function.

Parameters:

  • label Optional[str] — Human-readable label shown in the UI for the method. If not provided, the UI may fall back to the Python method name.
  • description Optional[str] — Human-readable description shown in the UI for the method.
Source
def plugin_method(
label: Optional[str] = None,
description: Optional[str] = None,
):
"""Decorator that marks a plugin class method as an Ocelescope runnable function.
Args:
label: Human-readable label shown in the UI for the method. If not provided,
the UI may fall back to the Python method name.
description: Human-readable description shown in the UI for the method.
"""
def decorator(func: Callable[..., PluginReturnType]):
plugin_method_meta = PluginMethod(name=func.__name__, label=label, description=description) # ty: ignore[unresolved-attribute]
method_hints = get_type_hints(func, include_extras=True)
for arg_name, hint in method_hints.items():
base_type, annotation = extract_info(hint)
if not isinstance(base_type, type) or arg_name == "return":
continue
if issubclass(base_type, PluginInput):
plugin_method_meta._input_model = base_type
elif issubclass(base_type, OCEL):
plugin_method_meta.input_ocels[arg_name] = (
OCELAnnotation(**annotation.model_dump())
if annotation is not None
else OCELAnnotation(label=arg_name)
)
elif issubclass(base_type, Resource):
plugin_method_meta._resource_types.add(base_type)
plugin_method_meta.input_resources[arg_name] = (
base_type.get_type(),
ResourceAnnotation(**annotation.model_dump())
if annotation is not None
else ResourceAnnotation(label=arg_name),
)
else:
raise TypeError(
f"Argument {arg_name} must be either an OCEL, Resource or Input Schema"
)
return_type = method_hints.get("return", None)
if return_type is not None:
origin = get_origin(return_type)
types_to_parse = []
if origin is tuple:
types_to_parse = get_args(return_type)
else:
types_to_parse = [return_type]
for typ in types_to_parse:
base_type, annotation = extract_info(typ)
if get_origin(base_type) is list:
inner_type = get_args(base_type)[0]
base_type, annotation = extract_info(inner_type)
is_list = True
else:
is_list = False
# Now determine what kind of result it is
if issubclass(base_type, OCEL):
plugin_method_meta.results.append(
OCELResult(
type="ocel",
is_list=is_list,
annotation=OCELAnnotation(**annotation.model_dump())
if annotation is not None
else None,
)
)
elif issubclass(base_type, Resource):
plugin_method_meta._resource_types.add(base_type)
annotation_obj = (
annotation if isinstance(annotation, ResourceAnnotation) else None
)
if annotation_obj and annotation_obj.annotation_resources:
plugin_method_meta._resource_types.update(
annotation_obj.annotation_resources
)
plugin_method_meta.results.append(
ResourceResult(
type="resource",
is_list=is_list,
annotation=annotation_obj,
resource_type=base_type.get_type(),
)
)
else:
raise TypeError(f"Unsupported return type: {base_type}")
plugin_method_meta._method = func
setattr(
func,
"__meta__",
plugin_method_meta,
)
return func
return decorator
class OCELAnnotation(Annotation):

UI annotation metadata for an OCEL-typed parameter or result.

In addition to the base Annotation fields, this annotation may specify an OCEL extension. To keep the annotation JSON-serializable and stable for frontend consumption, the constructor accepts an OCELExtension class and coerces it to its class name (a string).

Attributes:

  • label str — Human-readable label to display in the UI.
  • description Optional[str] — Optional longer text shown in the UI to explain the OCEL.
  • extension Optional[str] — Optional extension identifier. If constructed with an OCELExtension class, it is coerced to that class’ name.
Source
class OCELAnnotation(Annotation):
"""UI annotation metadata for an `OCEL`-typed parameter or result.
In addition to the base `Annotation` fields, this annotation may specify an
OCEL extension. To keep the annotation JSON-serializable and stable for
frontend consumption, the constructor accepts an `OCELExtension` class and
coerces it to its class name (a string).
Attributes:
label: Human-readable label to display in the UI.
description: Optional longer text shown in the UI to explain the OCEL.
extension: Optional extension identifier. If constructed with an
`OCELExtension` class, it is coerced to that class' name.
"""
extension: Optional[str] = None
@overload
def __init__(
self, *, label: str, description: Optional[str] = ..., extension: None = ...
) -> None: ...
@overload
def __init__(
self, *, label: str, description: Optional[str] = ..., extension: type[OCELExtension]
) -> None: ...
def __init__(self, **data: Any) -> None:
ext = data.get("extension", None)
if isinstance(ext, type) and issubclass(ext, OCELExtension):
data["extension"] = ext.__name__ # coerce class → str
super().__init__(**data)
class ResourceAnnotation(Annotation):

UI annotation metadata for a Resource-typed parameter or result.

This annotation is used to provide frontend-facing text (label/description) for resources.

Attributes:

  • label str — Human-readable label to display in the UI.
  • description Optional[str] — Optional longer text shown in the UI to explain the resource.
Source
class ResourceAnnotation(Annotation):
"""UI annotation metadata for a `Resource`-typed parameter or result.
This annotation is used to provide frontend-facing text (label/description)
for resources.
Attributes:
label: Human-readable label to display in the UI.
description: Optional longer text shown in the UI to explain the resource.
"""
annotation_resources: list[type[Resource]] | None = Field(exclude=True, default=None)
PluginResult: TypeAlias = Annotated[Union[OCELResult, ResourceResult], Field(discriminator='type')]
class PluginInput(ABC, BaseModel):
Source
class PluginInput(ABC, BaseModel):
pass
def OCEL_FIELD(field_type: Literal['object_type', 'event_type', 'event_id', 'object_id', 'event_attribute', 'object_attribute', 'time_frame'], ocel_id: str, default: Any = ..., title: Optional[str] = None, description: Optional[str] = None) -> Any:

Create a Pydantic Field with Ocelescope UI metadata for OCEL-based inputs.

Parameters:

  • field_type Literal['object_type', 'event_type', 'event_id', 'object_id', 'event_attribute', 'object_attribute', 'time_frame'] — What kind of OCEL field the user should select (e.g. "event_attribute" or "object_type").
  • ocel_id str — Identifier/name of the OCEL input this field depends on.
  • default Any — Default value, or ... to make the field required.
  • title Optional[str] — Optional UI title for the field.
  • description Optional[str] — Optional UI help text for the field.
Source
def OCEL_FIELD(
*,
field_type: Literal[
"object_type",
"event_type",
"event_id",
"object_id",
"event_attribute",
"object_attribute",
"time_frame",
],
ocel_id: str,
default: Any = ...,
title: Optional[str] = None,
description: Optional[str] = None,
) -> Any:
"""Create a Pydantic `Field` with Ocelescope UI metadata for OCEL-based inputs.
Args:
field_type: What kind of OCEL field the user should select (e.g.
`"event_attribute"` or `"object_type"`).
ocel_id: Identifier/name of the OCEL input this field depends on.
default: Default value, or `...` to make the field required.
title: Optional UI title for the field.
description: Optional UI help text for the field.
"""
extra: dict[str, Any] = {
"type": "ocel",
"field_type": field_type,
"ocel_id": ocel_id,
}
return Field(
default=default,
title=title,
description=description,
json_schema_extra={"x-ui-meta": extra},
)
def COMPUTED_SELECTION(title: Optional[str] = None, description: Optional[str] = None, provider: str, depends_on: list[str] | None = None, default: Any = ...):

Create a Pydantic Field for a UI selection computed by a provider.

Parameters:

  • title Optional[str] — Optional UI title for the field.
  • description Optional[str] — Optional UI help text for the field.
  • provider str — The name (ID) of the provider function used by the frontend to compute the available options.
  • depends_on list[str] | None — Optional list of field names this selection depends on.
  • default Any — Default value, or ... to make the field required.
Source
def COMPUTED_SELECTION(
*,
title: Optional[str] = None,
description: Optional[str] = None,
provider: str,
depends_on: list[str] | None = None,
default: Any = ...,
):
"""Create a Pydantic `Field` for a UI selection computed by a provider.
Args:
title: Optional UI title for the field.
description: Optional UI help text for the field.
provider: The name (ID) of the provider function used by the frontend to compute the available options.
depends_on: Optional list of field names this selection depends on.
default: Default value, or `...` to make the field required.
"""
meta = {
"type": "computed_select",
"provider": provider,
"dependsOn": depends_on or [],
}
return Field(
default=default,
title=title,
description=description,
json_schema_extra={"x-ui-meta": meta},
)