mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 17:04:54 +01:00
created base of event system
This commit is contained in:
parent
6b7af3657d
commit
e629e40b4f
4 changed files with 229 additions and 59 deletions
|
|
@ -1,5 +1,10 @@
|
|||
from .lib import attribute_definitions
|
||||
|
||||
from .events import (
|
||||
emit_event,
|
||||
register_event_callback
|
||||
)
|
||||
|
||||
from .create import (
|
||||
BaseCreator,
|
||||
Creator,
|
||||
|
|
@ -17,6 +22,9 @@ from .publish import (
|
|||
__all__ = (
|
||||
"attribute_definitions",
|
||||
|
||||
"emit_event",
|
||||
"register_event_callback",
|
||||
|
||||
"BaseCreator",
|
||||
"Creator",
|
||||
"AutoCreator",
|
||||
|
|
|
|||
221
openpype/pipeline/events.py
Normal file
221
openpype/pipeline/events.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
"""Events holding data about specific event."""
|
||||
import os
|
||||
import re
|
||||
import inspect
|
||||
import logging
|
||||
import weakref
|
||||
from uuid import uuid4
|
||||
try:
|
||||
from weakref import WeakMethod
|
||||
except Exception:
|
||||
from .python_2_comp import WeakMethod
|
||||
|
||||
|
||||
class EventCallback(object):
|
||||
def __init__(self, topic, func_ref, func_name, func_path):
|
||||
self._topic = topic
|
||||
# Replace '*' with any character regex and escape rest of text
|
||||
# - when callback is registered for '*' topic it will receive all
|
||||
# events
|
||||
# - it is possible to register to a partial topis 'my.event.*'
|
||||
# - it will receive all matching event topics
|
||||
# e.g. 'my.event.start' and 'my.event.end'
|
||||
topic_regex_str = "^{}$".format(
|
||||
".+".join(
|
||||
re.escape(part)
|
||||
for part in topic.split("*")
|
||||
)
|
||||
)
|
||||
topic_regex = re.compile(topic_regex_str)
|
||||
self._topic_regex = topic_regex
|
||||
self._func_ref = func_ref
|
||||
self._func_name = func_name
|
||||
self._func_path = func_path
|
||||
self._ref_valid = True
|
||||
self._enabled = True
|
||||
|
||||
self._log = None
|
||||
|
||||
def __repr__(self):
|
||||
return "< {} - {} > {}".format(
|
||||
self.__class__.__name__, self._func_name, self._func_path
|
||||
)
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
if self._log is None:
|
||||
self._log = logging.getLogger(self.__class__.__name__)
|
||||
return self._log
|
||||
|
||||
@property
|
||||
def is_ref_valid(self):
|
||||
return self._ref_valid
|
||||
|
||||
def validate_ref(self):
|
||||
if not self._ref_valid:
|
||||
return
|
||||
|
||||
callback = self._func_ref()
|
||||
if not callback:
|
||||
self._ref_valid = False
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
"""Is callback enabled."""
|
||||
return self._enabled
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
"""Change if callback is enabled."""
|
||||
self._enabled = enabled
|
||||
|
||||
def deregister(self):
|
||||
"""Calling this funcion will cause that callback will be removed."""
|
||||
# Fake reference
|
||||
self._ref_valid = False
|
||||
|
||||
def topic_matches(self, topic):
|
||||
"""Check if event topic matches callback's topic."""
|
||||
return self._topic_regex.match(topic)
|
||||
|
||||
def process_event(self, event):
|
||||
"""Process event.
|
||||
|
||||
Args:
|
||||
event(Event): Event that was triggered.
|
||||
"""
|
||||
# Skip if callback is not enabled or has invalid reference
|
||||
if not self._ref_valid or not self._enabled:
|
||||
return
|
||||
|
||||
# Get reference
|
||||
callback = self._func_ref()
|
||||
# Check if reference is valid or callback's topic matches the event
|
||||
if not callback:
|
||||
# Change state if is invalid so the callback is removed
|
||||
self._ref_valid = False
|
||||
|
||||
elif self.topic_matches(event.topic):
|
||||
# Try execute callback
|
||||
sig = inspect.signature(callback)
|
||||
try:
|
||||
if len(sig.parameters) == 0:
|
||||
callback()
|
||||
else:
|
||||
callback(event)
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Failed to execute event callback {}".format(
|
||||
str(repr(self))
|
||||
),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
|
||||
# Inherit from 'object' for Python 2 hosts
|
||||
class Event(object):
|
||||
"""Base event object.
|
||||
|
||||
Can be used to anything because data are not much specific. Only required
|
||||
argument is topic which defines why event is happening and may be used for
|
||||
filtering.
|
||||
|
||||
Arg:
|
||||
topic (str): Identifier of event.
|
||||
data (Any): Data specific for event. Dictionary is recommended.
|
||||
"""
|
||||
_data = {}
|
||||
|
||||
def __init__(self, topic, data=None, source=None):
|
||||
self._id = str(uuid4())
|
||||
self._topic = topic
|
||||
if data is None:
|
||||
data = {}
|
||||
self._data = data
|
||||
self._source = source
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def get(self, key, *args, **kwargs):
|
||||
return self._data.get(key, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
return self._source
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def topic(self):
|
||||
return self._topic
|
||||
|
||||
def emit(self):
|
||||
"""Emit event and trigger callbacks."""
|
||||
StoredCallbacks.emit_event(self)
|
||||
|
||||
|
||||
class StoredCallbacks:
|
||||
_registered_callbacks = []
|
||||
|
||||
@classmethod
|
||||
def add_callback(cls, topic, callback):
|
||||
# Convert callback into references
|
||||
# - deleted functions won't cause crashes
|
||||
if inspect.ismethod(callback):
|
||||
ref = WeakMethod(callback)
|
||||
elif callable(callback):
|
||||
ref = weakref.ref(callback)
|
||||
else:
|
||||
# TODO add logs
|
||||
return
|
||||
|
||||
function_name = callback.__name__
|
||||
function_path = os.path.abspath(inspect.getfile(callback))
|
||||
callback = EventCallback(topic, ref, function_name, function_path)
|
||||
cls._registered_callbacks.append(callback)
|
||||
return callback
|
||||
|
||||
@classmethod
|
||||
def validate(cls):
|
||||
invalid_callbacks = []
|
||||
for callbacks in cls._registered_callbacks:
|
||||
for callback in tuple(callbacks):
|
||||
callback.validate_ref()
|
||||
if not callback.is_ref_valid:
|
||||
invalid_callbacks.append(callback)
|
||||
|
||||
for callback in invalid_callbacks:
|
||||
cls._registered_callbacks.remove(callback)
|
||||
|
||||
@classmethod
|
||||
def emit_event(cls, event):
|
||||
invalid_callbacks = []
|
||||
for callback in cls._registered_callbacks:
|
||||
callback.process_event()
|
||||
if not callback.is_ref_valid:
|
||||
invalid_callbacks.append(callback)
|
||||
|
||||
for callback in invalid_callbacks:
|
||||
cls._registered_callbacks.remove(callback)
|
||||
|
||||
|
||||
def register_event_callback(topic, callback):
|
||||
"""Add callback that will be executed on specific topic."""
|
||||
return StoredCallbacks.add_callback(topic, callback)
|
||||
|
||||
|
||||
def emit_event(topic, data=None, source=None):
|
||||
"""Emit event with topic and data.
|
||||
|
||||
Returns:
|
||||
Event: Object of event that was emitted.
|
||||
"""
|
||||
event = Event(topic, data, source)
|
||||
event.emit()
|
||||
return event
|
||||
|
|
@ -1,8 +1,3 @@
|
|||
from .events import (
|
||||
BaseEvent,
|
||||
BeforeWorkfileSave
|
||||
)
|
||||
|
||||
from .attribute_definitions import (
|
||||
AbtractAttrDef,
|
||||
|
||||
|
|
@ -20,9 +15,6 @@ from .attribute_definitions import (
|
|||
|
||||
|
||||
__all__ = (
|
||||
"BaseEvent",
|
||||
"BeforeWorkfileSave",
|
||||
|
||||
"AbtractAttrDef",
|
||||
|
||||
"UIDef",
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
"""Events holding data about specific event."""
|
||||
|
||||
|
||||
# Inherit from 'object' for Python 2 hosts
|
||||
class BaseEvent(object):
|
||||
"""Base event object.
|
||||
|
||||
Can be used to anything because data are not much specific. Only required
|
||||
argument is topic which defines why event is happening and may be used for
|
||||
filtering.
|
||||
|
||||
Arg:
|
||||
topic (str): Identifier of event.
|
||||
data (Any): Data specific for event. Dictionary is recommended.
|
||||
"""
|
||||
_data = {}
|
||||
|
||||
def __init__(self, topic, data=None):
|
||||
self._topic = topic
|
||||
if data is None:
|
||||
data = {}
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def topic(self):
|
||||
return self._topic
|
||||
|
||||
@classmethod
|
||||
def emit(cls, *args, **kwargs):
|
||||
"""Create object of event and emit.
|
||||
|
||||
Args:
|
||||
Same args as '__init__' expects which may be class specific.
|
||||
"""
|
||||
from avalon import pipeline
|
||||
|
||||
obj = cls(*args, **kwargs)
|
||||
pipeline.emit(obj.topic, [obj])
|
||||
return obj
|
||||
|
||||
|
||||
class BeforeWorkfileSave(BaseEvent):
|
||||
"""Before workfile changes event data."""
|
||||
def __init__(self, filename, workdir):
|
||||
super(BeforeWorkfileSave, self).__init__("before.workfile.save")
|
||||
self.filename = filename
|
||||
self.workdir_path = workdir
|
||||
Loading…
Add table
Add a link
Reference in a new issue