From 7e3766509d45fbbd2975b0ab43e8adb3ebf4cff6 Mon Sep 17 00:00:00 2001 From: realitivity Date: Wed, 8 May 2019 01:29:00 +0200 Subject: [PATCH 01/10] Renamed PropertyBase to Property, started allowing identifying properties by their objects. --- modules/ravestate/consumable.py | 4 +- modules/ravestate/context.py | 20 +++--- modules/ravestate/icontext.py | 4 +- modules/ravestate/module.py | 6 +- modules/ravestate/property.py | 10 +-- modules/ravestate/testfixtures.py | 4 +- modules/ravestate/wrappers.py | 62 +++++++++++-------- modules/ravestate_akinator/__init__.py | 8 +-- modules/ravestate_interloc/__init__.py | 8 +-- modules/ravestate_nlp/__init__.py | 18 +++--- modules/ravestate_persqa/__init__.py | 12 ++-- modules/ravestate_rawio/__init__.py | 8 +-- modules/ravestate_ros2/ros2_properties.py | 8 +-- modules/ravestate_sendpics/__init__.py | 4 +- modules/ravestate_stalker/__init__.py | 14 ++--- modules/ravestate_telegramio/telegram_bot.py | 4 +- modules/ravestate_verbaliser/__init__.py | 4 +- test/modules/ravestate/test_context.py | 4 +- .../ravestate/test_context_integrated.py | 2 +- .../ravestate/test_wrappers_context.py | 20 +++--- .../ravestate/test_wrappers_property.py | 22 +++---- .../ravestate_verbaliser/test_verbaliser.py | 2 +- 22 files changed, 129 insertions(+), 119 deletions(-) diff --git a/modules/ravestate/consumable.py b/modules/ravestate/consumable.py index 5b966fe..4c38514 100644 --- a/modules/ravestate/consumable.py +++ b/modules/ravestate/consumable.py @@ -1,13 +1,13 @@ # Dummy resource which allowed CausalGroups to track acquisitions # for states that don't have any write-props. -from ravestate.property import PropertyBase +from ravestate.property import Property from reggol import get_logger logger = get_logger(__name__) -class Consumable(PropertyBase): +class Consumable(Property): """ Dummy resource which allows CausalGroups to track acquisitions for states that don't have any write-props. diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 4cf8362..49f4e63 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -11,7 +11,7 @@ from ravestate.icontext import IContext from ravestate.module import Module, has_module, get_module, import_module from ravestate.state import State -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.iactivation import IActivation from ravestate.activation import Activation from ravestate import argparser @@ -76,8 +76,8 @@ def __eq__(self, other): class Context(IContext): _default_signals: Tuple[Signal] = (startup(), shutdown()) - _default_properties: Tuple[PropertyBase] = ( - PropertyBase( + _default_properties: Tuple[Property] = ( + Property( name="pressure", allow_read=True, allow_write=True, @@ -87,7 +87,7 @@ class Context(IContext): always_signal_changed=False, is_flag_property=True ), - PropertyBase( + Property( name="activity", allow_read=True, allow_write=True, @@ -104,7 +104,7 @@ class Context(IContext): _lock: RLock - _properties: Dict[str, PropertyBase] + _properties: Dict[str, Property] _spikes_per_signal: Dict[ Signal, Set[Spike] @@ -361,16 +361,18 @@ def rm_state(self, *, st: State) -> None: # unregister the state's consumable dummy self.rm_prop(prop=st.consumable) - def add_prop(self, *, prop: PropertyBase) -> None: + def add_prop(self, *, prop: Property) -> None: """ Add a property to this context. An error message will be generated, if a property with - the same name has already been added previously. + the same name has already been added previously. Note: Context will adopt a __copy__ + of the given property, the actual property will not be changed. * `prop`: The property object that should be added. """ if prop.id() in self._properties: logger.error(f"Attempt to add property {prop.id()} twice!") return + # Do not adopt the with self._lock: # register property self._properties[prop.id()] = prop @@ -378,7 +380,7 @@ def add_prop(self, *, prop: PropertyBase) -> None: for signal in prop.signals(): self._add_sig(signal) - def rm_prop(self, *, prop: PropertyBase) -> None: + def rm_prop(self, *, prop: Property) -> None: """ Remove a property from this context. Generates error message, if the property was not added with add_prop() to the context previously @@ -402,7 +404,7 @@ def rm_prop(self, *, prop: PropertyBase) -> None: for st in states_to_remove: self.rm_state(st=st) - def __getitem__(self, key: str) -> Optional[PropertyBase]: + def __getitem__(self, key: str) -> Optional[Property]: """ Retrieve a property object by name, that was previously added through add_prop() by it's full name. The full name is always the combination of the property's diff --git a/modules/ravestate/icontext.py b/modules/ravestate/icontext.py index ff9ef80..ec683d4 100644 --- a/modules/ravestate/icontext.py +++ b/modules/ravestate/icontext.py @@ -29,7 +29,7 @@ def __getitem__(self, key): """ pass - def add_prop(self, *, prop: property.PropertyBase): + def add_prop(self, *, prop: property.Property): """ Add a property to this context. An error message will be generated, if a property with the same name has already been added previously. @@ -38,7 +38,7 @@ def add_prop(self, *, prop: property.PropertyBase): """ pass - def rm_prop(self, *, prop: property.PropertyBase): + def rm_prop(self, *, prop: property.Property): """ Remove a property from this context. Generates error message, if the property was not added with add_prop() diff --git a/modules/ravestate/module.py b/modules/ravestate/module.py index 440152a..38ad6d0 100644 --- a/modules/ravestate/module.py +++ b/modules/ravestate/module.py @@ -2,7 +2,7 @@ from typing import Dict, Any, Union, Iterable, Callable import importlib -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.state import State from ravestate.threadlocal import ravestate_thread_local @@ -60,14 +60,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): if self.registration_callback: self.registration_callback(self) - def add(self, property_or_state: Union[PropertyBase, State, Iterable[PropertyBase], Iterable[State]]): + def add(self, property_or_state: Union[Property, State, Iterable[Property], Iterable[State]]): try: for obj_to_add in property_or_state: self.add(obj_to_add) return except TypeError: pass - if isinstance(property_or_state, PropertyBase): + if isinstance(property_or_state, Property): property_or_state.set_parent_path(self.name) self.props.append(property_or_state) elif isinstance(property_or_state, State): diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 6802286..a07845b 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -45,7 +45,7 @@ def popped(property_name, **kwargs) -> Signal: return s(f"{property_name}:popped", **kwargs) -class PropertyBase: +class Property: """ Base class for context properties. Controls read/write/push/pop/delete permissions, property name basic impls. for the property value, parent/child mechanism. @@ -53,7 +53,7 @@ class PropertyBase: _Example (Creating a module containing a property named my_property):_ ```python with Module(name="my_module"): - my_property = PropertyBase(name="my_property") + my_property = Property(name="my_property") ``` """ @@ -75,7 +75,7 @@ def __init__( self.allow_push = allow_push self.allow_pop = allow_pop self.value = default_value - self.children: Dict[str, PropertyBase] = dict() + self.children: Dict[str, Property] = dict() self._lock = Lock() self.parent_path: str = "" self.always_signal_changed = always_signal_changed @@ -101,7 +101,7 @@ def set_parent_path(self, path): else: logger.error(f'Tried to override parent_path of {self.id()}') - def gather_children(self) -> List['PropertyBase']: + def gather_children(self) -> List['Property']: """ Collect this property, and all of it's children. """ @@ -142,7 +142,7 @@ def write(self, value): else: return False - def push(self, child: 'PropertyBase'): + def push(self, child: 'Property'): """ Add a child to the property diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index 0883611..a29131f 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -6,7 +6,7 @@ from ravestate.constraint import s from ravestate.context import Context -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.state import State, state from ravestate.wrappers import PropertyWrapper, ContextWrapper from ravestate.activation import Activation @@ -71,7 +71,7 @@ def context_fixture(mocker): @pytest.fixture def context_with_property_fixture(mocker, context_fixture) -> Context: - prop = PropertyBase(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) + prop = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) prop.set_parent_path(DEFAULT_MODULE_NAME) context_fixture.add_prop(prop=prop) mocker.patch.object(context_fixture, 'add_prop') diff --git a/modules/ravestate/wrappers.py b/modules/ravestate/wrappers.py index 4fc311b..88ba8fb 100644 --- a/modules/ravestate/wrappers.py +++ b/modules/ravestate/wrappers.py @@ -1,6 +1,6 @@ # Ravestate wrapper classes which limit a state's context access -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.state import State from ravestate.constraint import Signal from ravestate.module import get_module @@ -22,7 +22,7 @@ class PropertyWrapper: """ def __init__(self, *, spike_parents: Set[Spike] = None, - prop: PropertyBase, + prop: Property, ctx: IContext, allow_read: bool, allow_write: bool): @@ -92,7 +92,7 @@ def set(self, value: Any): return True return False - def push(self, child: PropertyBase): + def push(self, child: Property): """ Add a child to the property or to children of the property @@ -149,7 +149,7 @@ class ContextWrapper: as declared by the state beforehand. """ - def __init__(self, *, ctx: IContext, state: State, spike_parents: Set[Spike]=None, spike_payloads: Dict[str, Any]=None): + def __init__(self, *, ctx: IContext, state: State, spike_parents: Set[Spike] = None, spike_payloads: Dict[str, Any] = None): self.state = state self.ctx = ctx self.properties = dict() @@ -170,7 +170,9 @@ def __init__(self, *, ctx: IContext, state: State, spike_parents: Set[Spike]=Non allow_read=propname in state.read_props, allow_write=propname in state.write_props) - def __setitem__(self, key, value): + def __setitem__(self, key: Union[str, Property], value: Any): + if isinstance(key, Property): + key = key.id() if key in self.properties: return self.properties[key].set(value) else: @@ -210,72 +212,78 @@ def conf(self, *, mod=None, key=None): mod = self.state.module_name return self.ctx.conf(mod=mod, key=key) - def push(self, parentpath: str, child: PropertyBase): + def push(self, parent_property_or_path: Union[str, Property], child: Property): """ Add a child to a property. Note: Child must not yet have a parent or children of itself. Write-access to parent is needed. - * `parentpath`: Path of the parent that should receive the new child. + * `parentpath`: (Path of the) parent property that should receive the new child. * `child`: Parent-less, child-less property object to add. **Returns:** True if the push was successful, False otherwise """ if child.parent_path: - logger.error(f"State {self.state.name} attempted to push child property {child.name} to parent {parentpath}, but it already has parent {child.parent_path}!") + logger.error(f"State {self.state.name} attempted to push child property {child.name} to parent {parent_property_or_path}, but it already has parent {child.parent_path}!") return False - if parentpath in self.properties: - if self.properties[parentpath].push(child): + if isinstance(parent_property_or_path, Property): + parent_property_or_path = parent_property_or_path.id() + if parent_property_or_path in self.properties: + if self.properties[parent_property_or_path].push(child): self.properties[child.id()] = PropertyWrapper( prop=child, ctx=self.ctx, spike_parents=self.spike_parents, - allow_read=self.properties[parentpath].allow_read, - allow_write=self.properties[parentpath].allow_write) + allow_read=self.properties[parent_property_or_path].allow_read, + allow_write=self.properties[parent_property_or_path].allow_write) self.ctx.add_prop(prop=child) return True else: - logger.error(f'State {self.state.name} attempted to add child-property {child.name} to non-accessible parent {parentpath}!') + logger.error(f'State {self.state.name} attempted to add child-property {child.name} to non-accessible parent {parent_property_or_path}!') return False - def pop(self, path: str): + def pop(self, property_or_path: Union[str, Property]): """ Delete a property (remove it from context and it's parent). Note: Write-access to parent is needed! - * `path`: Path to the property. Must be nested (not root-level)! + * `path`: (Path to) the property. Must be nested (not root-level)! **Returns:** True if the pop was successful, False otherwise """ - path_parts = path.split(":") + if isinstance(property_or_path, Property): + property_or_path = property_or_path.id() + path_parts = property_or_path.split(":") if len(path_parts) < 3: - logger.error(f"State {self.state.name}: Path to pop is not a nested property: {path}") + logger.error(f"State {self.state.name}: Path to pop is not a nested property: {property_or_path}") return False parentpath = ":".join(path_parts[:-1]) if parentpath in self.properties: if self.properties[parentpath].pop(path_parts[-1]): - self.ctx.rm_prop(prop=self.properties[path].prop) + self.ctx.rm_prop(prop=self.properties[property_or_path].prop) # Remove property from own dict - del self.properties[path] + del self.properties[property_or_path] # Also remove the deleted propertie's children for childpath in list(self.properties.keys()): - if childpath.startswith(path+":"): + if childpath.startswith(property_or_path + ":"): self.ctx.rm_prop(prop=self.properties[childpath].prop) del self.properties[childpath] return True else: - logger.error(f'State {self.state.name} attempted to remove non-existent child-property {path}') + logger.error(f'State {self.state.name} attempted to remove non-existent child-property {property_or_path}') return False else: - logger.error(f'State {self.state.name} attempted to remove child-property {path} from non-existent parent-property {parentpath}') + logger.error(f'State {self.state.name} attempted to remove child-property {property_or_path} from non-existent parent-property {parentpath}') return False - def enum(self, path) -> Generator[str, None, None]: + def enum(self, property_or_path: Union[str, Property]) -> Generator[str, None, None]: """ - Enumerate a propertie's children by their full pathes. + Enumerate a property's children by their full pathes. """ - if path in self.properties: - return self.properties[path].enum() + if isinstance(property_or_path, Property): + property_or_path = property_or_path.id() + if property_or_path in self.properties: + return self.properties[property_or_path].enum() else: - logger.error(f"State {self.state.name} attempted to enumerate property {path} without permission!") + logger.error(f"State {self.state.name} attempted to enumerate property {property_or_path} without permission!") diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 0284f4e..e2a1706 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -1,5 +1,5 @@ from ravestate.module import Module -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.state import state, Resign, Emit, Delete from ravestate.constraint import s from ravestate_akinator.api import Api @@ -16,7 +16,7 @@ with Module(name="akinator", config={CERTAINTY: 90}): - is_it = PropertyBase( + is_it = Property( name="is_it", default_value="", always_signal_changed=True, @@ -24,7 +24,7 @@ allow_push=False, is_flag_property=True) - question = PropertyBase( + question = Property( name="question", default_value="", always_signal_changed=True, @@ -32,7 +32,7 @@ allow_push=False, is_flag_property=True) - initiate_play_again = PropertyBase( + initiate_play_again = Property( name="initiate_play_again", default_value="", always_signal_changed=True, diff --git a/modules/ravestate_interloc/__init__.py b/modules/ravestate_interloc/__init__.py index e11efde..9cd4b21 100644 --- a/modules/ravestate_interloc/__init__.py +++ b/modules/ravestate_interloc/__init__.py @@ -1,4 +1,4 @@ -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.module import Module from ravestate.wrappers import ContextWrapper from ravestate.receptor import receptor @@ -20,7 +20,7 @@ with Module(name="interloc"): # TODO: Make interloc:all a special property type, that only accepts ScientioNodeProperty as children - all = PropertyBase(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True) + all = Property(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True) def handle_single_interlocutor_input(ctx: ContextWrapper, input_value: str, id="anonymous_interlocutor") -> None: @@ -45,8 +45,8 @@ def write_input(ctx_input, value: str): @receptor(ctx_wrap=ctx, write="interloc:all") def push_interloc(ctx: ContextWrapper, interlocutor_node: Node): - if ctx.push(parentpath="interloc:all", - child=PropertyBase(name=id, default_value=interlocutor_node)): + if ctx.push(parent_property_or_path="interloc:all", + child=Property(name=id, default_value=interlocutor_node)): logger.debug(f"Pushed {interlocutor_node} to interloc:all") @receptor(ctx_wrap=ctx, write="interloc:all") diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 9925581..d68a0e5 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -1,6 +1,6 @@ from ravestate.module import Module -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.state import state from ravestate_nlp.question_word import QuestionWord from ravestate_nlp.triple import Triple @@ -45,14 +45,14 @@ def roboy_getter(doc) -> bool: with Module(name="nlp"): - tokens = PropertyBase(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - postags = PropertyBase(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - lemmas = PropertyBase(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - tags = PropertyBase(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - ner = PropertyBase(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - triples = PropertyBase(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - roboy = PropertyBase(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - yesno = PropertyBase(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + tokens = Property(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + postags = Property(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + lemmas = Property(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + tags = Property(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + ner = Property(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + triples = Property(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + roboy = Property(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + yesno = Property(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) @state( diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py index 2a8d6ee..ad9155b 100644 --- a/modules/ravestate_persqa/__init__.py +++ b/modules/ravestate_persqa/__init__.py @@ -1,5 +1,5 @@ from ravestate.module import Module -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.wrappers import ContextWrapper from ravestate.state import state, Emit, Delete, Resign from ravestate.constraint import s @@ -46,9 +46,9 @@ # that did not trigger new_interloc. Therefore, new_interloc and inference # are mutually exclusive. This is enfored by having both of them # consume the inference_mutex property. - inference_mutex = PropertyBase(name="inference_mutex") + inference_mutex = Property(name="inference_mutex") - subject = PropertyBase( + subject = Property( name="subject", default_value="", always_signal_changed=True, @@ -56,7 +56,7 @@ allow_push=False, is_flag_property=True) - predicate = PropertyBase( + predicate = Property( name="predicate", default_value="", always_signal_changed=True, @@ -64,7 +64,7 @@ allow_push=False, is_flag_property=True) - answer = PropertyBase( + answer = Property( name="answer", default_value="", always_signal_changed=True, @@ -72,7 +72,7 @@ allow_push=False, is_flag_property=True) - follow_up = PropertyBase( + follow_up = Property( name="follow_up", default_value="", always_signal_changed=True, diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index 8084bd8..836c0d0 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -1,18 +1,18 @@ from ravestate.module import Module -from ravestate.property import PropertyBase +from ravestate.property import Property with Module(name="rawio"): - input = PropertyBase( + input = Property( name="in", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True) - output = PropertyBase( + output = Property( name="out", default_value="", allow_pop=False, @@ -20,7 +20,7 @@ always_signal_changed=True, wipe_on_changed=False) - pic_in = PropertyBase( + pic_in = Property( name="pic_in", default_value=None, allow_pop=False, diff --git a/modules/ravestate_ros2/ros2_properties.py b/modules/ravestate_ros2/ros2_properties.py index 1eac5ac..90a35e4 100644 --- a/modules/ravestate_ros2/ros2_properties.py +++ b/modules/ravestate_ros2/ros2_properties.py @@ -3,7 +3,7 @@ from ravestate.state import state, Delete from ravestate.constraint import s -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.receptor import receptor from ravestate.wrappers import ContextWrapper @@ -125,7 +125,7 @@ def ros_to_ctx_callback(ctx, msg, prop_name: str): rclpy.shutdown() -class Ros2SubProperty(PropertyBase): +class Ros2SubProperty(Property): def __init__(self, name: str, topic: str, msg_type, default_value=None, always_signal_changed: bool = True): """ Initialize Property @@ -170,7 +170,7 @@ def ros_subscription_callback(self, msg): self.ros_to_ctx_callback(msg=msg, prop_name=self.id()) -class Ros2PubProperty(PropertyBase): +class Ros2PubProperty(Property): def __init__(self, name: str, topic: str, msg_type): """ Initialize Property @@ -214,7 +214,7 @@ def write(self, value): f"cannot be published because publisher was not registered in ROS") -class Ros2CallProperty(PropertyBase): +class Ros2CallProperty(Property): def __init__(self, name: str, service_name: str, service_type, call_timeout: float = 10.0): """ Initialize Property diff --git a/modules/ravestate_sendpics/__init__.py b/modules/ravestate_sendpics/__init__.py index ff5a03e..b5384d8 100644 --- a/modules/ravestate_sendpics/__init__.py +++ b/modules/ravestate_sendpics/__init__.py @@ -1,6 +1,6 @@ from ravestate.module import Module from ravestate.state import s, state, Emit -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.wrappers import ContextWrapper import ravestate_ontology import pickle @@ -31,7 +31,7 @@ with Module(name="sendpics", config=CONFIG): - face_vec = PropertyBase(name="face_vec", always_signal_changed=True) + face_vec = Property(name="face_vec", always_signal_changed=True) @state(cond=s("idle:bored"), write="rawio:out", weight=1.2, cooldown=30.) def prompt_send(ctx): diff --git a/modules/ravestate_stalker/__init__.py b/modules/ravestate_stalker/__init__.py index e77d3e3..c77bf12 100644 --- a/modules/ravestate_stalker/__init__.py +++ b/modules/ravestate_stalker/__init__.py @@ -3,7 +3,7 @@ from typing import List, Dict from ravestate.module import Module -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate_ros2.ros2_properties import Ros2SubProperty from ravestate.context import startup from ravestate.state import state, s @@ -40,7 +40,7 @@ # Create a dummy parent, under which we can push the actual recognized faces topic, # once a context with a configuration is available. - subscriber_parent = PropertyBase(name="face_names_parent") + subscriber_parent = Property(name="face_names_parent") @state(cond=startup(), write=subscriber_parent.id()) def create_subscriber(ctx: ContextWrapper): @@ -51,11 +51,11 @@ def create_subscriber(ctx: ContextWrapper): msg_type=RecognizedFaces, always_signal_changed=False) - rec_faces = PropertyBase(name="rec_faces", - default_value={}, - always_signal_changed=False, - allow_pop=True, - allow_push=True) + rec_faces = Property(name="rec_faces", + default_value={}, + always_signal_changed=False, + allow_pop=True, + allow_push=True) ctx.push(subscriber_parent.id(), face_names) ctx.push(subscriber_parent.id(), rec_faces) diff --git a/modules/ravestate_telegramio/telegram_bot.py b/modules/ravestate_telegramio/telegram_bot.py index 70f53f4..34eca31 100644 --- a/modules/ravestate_telegramio/telegram_bot.py +++ b/modules/ravestate_telegramio/telegram_bot.py @@ -8,7 +8,7 @@ from ravestate.constraint import s from ravestate.context import Context, create_and_run_context -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.receptor import receptor from ravestate.state import state, Delete from ravestate.wrappers import ContextWrapper @@ -83,7 +83,7 @@ def push_telegram_interloc(ctx: ContextWrapper, telegram_node: Node, name: str): """ Push the telegram_node into interloc:all:name """ - if ctx.push(parentpath="interloc:all", child=PropertyBase(name=name, default_value=telegram_node)): + if ctx.push(parent_property_or_path="interloc:all", child=Property(name=name, default_value=telegram_node)): logger.debug(f"Pushed {telegram_node} to interloc:all") def make_sure_effective_user_exists(update: Update): diff --git a/modules/ravestate_verbaliser/__init__.py b/modules/ravestate_verbaliser/__init__.py index cfbfb7f..51f7b59 100644 --- a/modules/ravestate_verbaliser/__init__.py +++ b/modules/ravestate_verbaliser/__init__.py @@ -1,14 +1,14 @@ import logging from ravestate.module import Module -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.state import state from ravestate_verbaliser import verbaliser with Module(name="verbaliser"): - intent = PropertyBase( + intent = Property( name="intent", default_value="", allow_push=False, diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index d6eeb82..ff50946 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -66,7 +66,7 @@ def test_add_module_present(mocker, context_fixture): def test_remove_dependent_state(context_fixture: Context, state_fixture: State): - prop = PropertyBase(name=DEFAULT_PROPERTY_NAME) + prop = Property(name=DEFAULT_PROPERTY_NAME) prop.set_parent_path(DEFAULT_MODULE_NAME) context_fixture.add_prop(prop=prop) context_fixture.add_state(st=state_fixture) @@ -184,7 +184,7 @@ def test_add_state_unknown_property(context_fixture: Context, state_fixture: Sta def test_add_prop_twice(context_fixture: Context): with LogCapture(attributes=strip_prefix) as log_capture: - prop = PropertyBase(name=DEFAULT_PROPERTY_NAME) + prop = Property(name=DEFAULT_PROPERTY_NAME) prop.set_parent_path(DEFAULT_MODULE_NAME) context_fixture.add_prop(prop=prop) context_fixture.add_prop(prop=prop) diff --git a/test/modules/ravestate/test_context_integrated.py b/test/modules/ravestate/test_context_integrated.py index 07f307b..a13f238 100644 --- a/test/modules/ravestate/test_context_integrated.py +++ b/test/modules/ravestate/test_context_integrated.py @@ -11,7 +11,7 @@ def test_run_with_pressure(): with Module(name=DEFAULT_MODULE_NAME): - PropertyBase(name=DEFAULT_PROPERTY_NAME) + Property(name=DEFAULT_PROPERTY_NAME) @state(cond=startup(), signal_name="a") def signal_a(ctx): diff --git a/test/modules/ravestate/test_wrappers_context.py b/test/modules/ravestate/test_wrappers_context.py index e1fa3da..872f511 100644 --- a/test/modules/ravestate/test_wrappers_context.py +++ b/test/modules/ravestate/test_wrappers_context.py @@ -30,8 +30,8 @@ def test_context_shutting_down(mocker, context_wrapper_fixture, context_fixture) def test_property_push_pop(mocker, context_wrapper_fixture, context_with_property_fixture): with mocker.patch.object(context_with_property_fixture, 'emit'): # push child - assert context_wrapper_fixture.push(parentpath=DEFAULT_PROPERTY_ID, - child=PropertyBase(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) + assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, + child=Property(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( s(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, @@ -62,8 +62,8 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert def test_property_nested(mocker, context_wrapper_fixture, context_with_property_fixture): with mocker.patch.object(context_with_property_fixture, 'emit'): # push child - assert context_wrapper_fixture.push(parentpath=DEFAULT_PROPERTY_ID, - child=PropertyBase(name=CHILD_PROPERTY_NAME)) + assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, + child=Property(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( s(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, @@ -71,8 +71,8 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ payload=CHILD_PROPERTY_ID) # push grandchild - assert context_wrapper_fixture.push(parentpath=CHILD_PROPERTY_ID, - child=PropertyBase(name=GRANDCHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) + assert context_wrapper_fixture.push(parent_property_or_path=CHILD_PROPERTY_ID, + child=Property(name=GRANDCHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( s(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, @@ -104,8 +104,8 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_property_fixture): with mocker.patch.object(context_with_property_fixture, 'emit'): # push child - assert context_wrapper_fixture.push(parentpath=DEFAULT_PROPERTY_ID, - child=PropertyBase(name=CHILD_PROPERTY_NAME)) + assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, + child=Property(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( s(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, @@ -113,8 +113,8 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert payload=CHILD_PROPERTY_ID) # push grandchild - assert context_wrapper_fixture.push(parentpath=CHILD_PROPERTY_ID, - child=PropertyBase(name=GRANDCHILD_PROPERTY_NAME)) + assert context_wrapper_fixture.push(parent_property_or_path=CHILD_PROPERTY_ID, + child=Property(name=GRANDCHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( s(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index 31f9bd3..b79ce88 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -2,7 +2,7 @@ from ravestate.constraint import s from ravestate.icontext import IContext from ravestate.state import State -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate.wrappers import PropertyWrapper, ContextWrapper DEFAULT_MODULE_NAME = 'module' @@ -38,7 +38,7 @@ def state_mock(mocker): @pytest.fixture def default_property_base(): - prop = PropertyBase(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) + prop = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) prop.set_parent_path(DEFAULT_MODULE_NAME) return prop @@ -63,18 +63,18 @@ def under_test_context_wrapper(context_mock, state_mock): return ContextWrapper(ctx=context_mock, st=state_mock) -def test_property(under_test_read_only: PropertyWrapper, default_property_base: PropertyBase): +def test_property(under_test_read_only: PropertyWrapper, default_property_base: Property): assert (default_property_base.id() == f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}") assert (not default_property_base._lock.locked()) assert (default_property_base.read() == DEFAULT_PROPERTY_VALUE) -def test_property_get(under_test_read_only: PropertyWrapper, default_property_base: PropertyBase): +def test_property_get(under_test_read_only: PropertyWrapper, default_property_base: Property): assert (not default_property_base._lock.locked()) assert (under_test_read_only.get() == DEFAULT_PROPERTY_VALUE) -def test_property_no_read(under_test_nothing: PropertyWrapper, default_property_base: PropertyBase): +def test_property_no_read(under_test_nothing: PropertyWrapper, default_property_base: Property): assert (not default_property_base._lock.locked()) with LogCapture(attributes=strip_prefix) as log_capture: under_test_nothing.get() @@ -107,7 +107,7 @@ def test_property_write(under_test_read_write: PropertyWrapper, default_property def test_flag_property(context_mock): - prop_base = PropertyBase(name="flag_prop", is_flag_property=True) + prop_base = Property(name="flag_prop", is_flag_property=True) prop_base.set_parent_path(DEFAULT_MODULE_NAME) prop_wrapper = PropertyWrapper(prop=prop_base, ctx=context_mock, allow_read=True, allow_write=True) assert (prop_base._lock.locked()) @@ -148,27 +148,27 @@ def test_flag_property(context_mock): def test_property_child(under_test_read_write: PropertyWrapper, default_property_base, context_mock): - assert under_test_read_write.push(PropertyBase(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) + assert under_test_read_write.push(Property(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) assert list(under_test_read_write.enum())[0] == CHILD_PROPERTY_ID assert under_test_read_write.prop.children[CHILD_PROPERTY_NAME].read() == DEFAULT_PROPERTY_VALUE def test_property_illegal_push(context_mock): - prop_no_push = PropertyBase(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE, allow_push=False) + prop_no_push = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE, allow_push=False) prop_no_push.set_parent_path(DEFAULT_MODULE_NAME) wrapper = PropertyWrapper(prop=prop_no_push, ctx=context_mock, allow_read=True, allow_write=True) with LogCapture(attributes=strip_prefix) as log_capture: - assert not wrapper.push(child=PropertyBase(name=CHILD_PROPERTY_NAME)) + assert not wrapper.push(child=Property(name=CHILD_PROPERTY_NAME)) log_capture.check( f'Unauthorized push in property {DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}!', ) def test_property_illegal_pop(context_mock): - prop_no_pop = PropertyBase(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE, allow_pop=False) + prop_no_pop = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE, allow_pop=False) prop_no_pop.set_parent_path(DEFAULT_MODULE_NAME) wrapper = PropertyWrapper(prop=prop_no_pop, ctx=context_mock, allow_read=True, allow_write=True) - assert wrapper.push(child=PropertyBase(name=CHILD_PROPERTY_NAME)) + assert wrapper.push(child=Property(name=CHILD_PROPERTY_NAME)) with LogCapture(attributes=strip_prefix) as log_capture: assert not wrapper.pop(CHILD_PROPERTY_NAME) log_capture.check( diff --git a/test/modules/ravestate_verbaliser/test_verbaliser.py b/test/modules/ravestate_verbaliser/test_verbaliser.py index 11f752a..4c228b0 100644 --- a/test/modules/ravestate_verbaliser/test_verbaliser.py +++ b/test/modules/ravestate_verbaliser/test_verbaliser.py @@ -1,7 +1,7 @@ from os.path import join, dirname, realpath from typing import List -from ravestate.property import PropertyBase +from ravestate.property import Property from ravestate_verbaliser import verbaliser From 1525fcc8fd073634a8f58082007324107e237cbd Mon Sep 17 00:00:00 2001 From: Andreas Dolp Date: Fri, 10 May 2019 14:18:55 +0200 Subject: [PATCH 02/10] signal construction in module signal parameter setting through chaining use property objects instead of referencing by name --- modules/ravestate/activation.py | 14 +-- modules/ravestate/causal.py | 2 +- modules/ravestate/constraint.py | 65 ++++++++--- modules/ravestate/context.py | 103 +++++++++--------- modules/ravestate/iactivation.py | 4 +- modules/ravestate/module.py | 23 ++-- modules/ravestate/property.py | 37 +++++-- modules/ravestate/receptor.py | 6 +- modules/ravestate/spike.py | 2 +- modules/ravestate/state.py | 69 ++++++------ modules/ravestate/testfixtures.py | 19 +++- modules/ravestate/wrappers.py | 16 ++- modules/ravestate_akinator/__init__.py | 8 +- modules/ravestate_conio/__init__.py | 8 +- modules/ravestate_fillers/__init__.py | 8 +- modules/ravestate_genqa/__init__.py | 19 ++-- modules/ravestate_hibye/__init__.py | 14 +-- modules/ravestate_idle/__init__.py | 19 ++-- modules/ravestate_interloc/__init__.py | 15 ++- modules/ravestate_nlp/__init__.py | 69 +++++------- modules/ravestate_ontology/__init__.py | 4 +- modules/ravestate_persqa/__init__.py | 99 +++++++++-------- modules/ravestate_roboyqa/__init__.py | 18 +-- modules/ravestate_sendpics/__init__.py | 2 +- modules/ravestate_stalker/__init__.py | 2 +- modules/ravestate_ui/service.py | 8 +- modules/ravestate_ui/ui_context.py | 4 +- modules/ravestate_verbaliser/__init__.py | 3 +- modules/ravestate_wildtalk/__init__.py | 8 +- test/modules/ravestate/test_context.py | 30 ++--- .../ravestate/test_context_integrated.py | 11 +- test/modules/ravestate/test_state.py | 16 +-- test/modules/ravestate_hibye/test_hibye.py | 5 +- .../ravestate_nlp/test_preprocessing.py | 17 +-- test/modules/ravestate_persqa/test_qa.py | 17 +-- .../modules/ravestate_roboyqa/test_roboyqa.py | 3 +- .../ravestate_wildtalk/test_wildtalk.py | 5 +- 37 files changed, 423 insertions(+), 349 deletions(-) diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index e410dfd..93da5cf 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -66,7 +66,7 @@ def resources(self) -> Set[str]: Return's the set of the activation's write-access property names. """ if self.state_to_activate.write_props: - return set(self.state_to_activate.write_props) + return self.state_to_activate.get_write_props_ids() else: # Return a dummy resource that will be "consumed" by the activation. # This allows CausalGroup to track the spike acquisitions for this @@ -236,7 +236,7 @@ def update(self) -> bool: self.death_clock = None # Ask each spike's causal group for activation consent - spikes_for_conjunct = set((sig.spike, sig.detached) for sig in conjunction.signals()) + spikes_for_conjunct = set((sig.spike, sig.detached_value) for sig in conjunction.signals()) consenting_causal_groups = set() all_consented = True for spike, detached in spikes_for_conjunct: @@ -255,7 +255,7 @@ def update(self) -> bool: # Gather payloads for all spikes self.spike_payloads = { - spike.name(): spike.payload() + spike.id(): spike.payload() for spike, _ in spikes_for_conjunct if spike.payload()} # Notify all consenting causal groups that activation is going forward @@ -334,17 +334,17 @@ def _run_private(self): # Process state function result if isinstance(result, Emit): - if self.state_to_activate.signal(): + if self.state_to_activate.signal: self.ctx.emit( - self.state_to_activate.signal(), + self.state_to_activate.signal, parents=self.parent_spikes, wipe=result.wipe) else: logger.error(f"Attempt to emit spike from state {self.name}, which does not specify a signal name!") elif isinstance(result, Wipe): - if self.state_to_activate.signal(): - self.ctx.wipe(self.state_to_activate.signal()) + if self.state_to_activate.signal: + self.ctx.wipe(self.state_to_activate.signal) else: logger.error(f"Attempt to wipe spikes from state {self.name}, which does not specify a signal name!") diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index cafe723..c487f72 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -438,7 +438,7 @@ def _change_effect_causes(self, act: IActivation, change: Optional[int]): del self._uncaused_spikes[sig][act] if len(self._uncaused_spikes[sig]) == 0: for act in set(self._non_detached_activations()): - act.effect_not_caused(self, sig.name) + act.effect_not_caused(self, sig.id) del self._uncaused_spikes[sig] def _non_detached_activations(self) -> Generator[IActivation, None, None]: diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 24e49e9..c6527bc 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -1,6 +1,9 @@ +import copy from typing import List, Set, Generator, Optional, Tuple, Union, Callable, Any from ravestate.spike import Spike from ravestate.iactivation import IActivation, ICausalGroup +from ravestate.threadlocal import ravestate_thread_local + from reggol import get_logger logger = get_logger(__name__) @@ -81,9 +84,10 @@ class Signal(Constraint): """ name: str spike: Optional[Spike] - min_age: float - max_age: float - detached: bool + min_age_value: float + max_age_value: float + detached_value: bool + module_name: str # tells whether this signal has been completed, and may therefore # not introduce a new causal group into the parent conjunct. @@ -101,17 +105,23 @@ class Signal(Constraint): def __init__(self, name: str, *, min_age=0., max_age=5., detached=False): self.name = name - self.min_age = min_age - self.max_age = max_age + self.min_age_value = min_age + self.max_age_value = max_age self.spike = None - self.detached = detached + self.detached_value = detached self.completed_by = None self.is_completion = False self._min_age_ticks = 0 + self.module_name = "" # TODO: Deal with ConfigurableAge # if min_age > max_age and max_age > .0: # logger.warning(f"{self}: max_age={max_age} < min_age={min_age}!") + # add signal to module in current `with Module(...)` clause + module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) + if module_under_construction: + module_under_construction.add(self) + def __or__(self, other): if isinstance(other, Signal): return Disjunct(Conjunct(self), Conjunct(other)) @@ -132,30 +142,34 @@ def __and__(self, other): return Disjunct(*conjunct_list) def __eq__(self, other): - return (isinstance(other, Signal) and self.name == other.name) or\ - (isinstance(other, str) and self.name == other) + return (isinstance(other, Signal) and self.id() == other.id()) or\ + (isinstance(other, str) and self.id() == other) def __hash__(self): - return hash(self.name) + return hash(self.id()) def __repr__(self): - return f"Signal({self.name}, {self.min_age}, {self.max_age}, {self.detached})" + return f"Signal({self.id()}, {self.min_age_value}, {self.max_age_value}, {self.detached_value})" + + def id(self): + prefix = f'{self.module_name}:' if self.module_name else '' + return f'{prefix}{self.name}' def signals(self) -> Generator['Signal', None, None]: yield self def conjunctions(self, filter_detached=False) -> Generator['Conjunct', None, None]: - if not filter_detached or not self.detached: + if not filter_detached or not self.detached_value: yield Conjunct(self) def acquire(self, spike: Spike, act: IActivation): - if not self.spike and self.name == spike.name() and (self.max_age < 0 or spike.age() <= act.secs_to_ticks(self.max_age)): + if not self.spike and self.id() == spike.id() and (self.max_age_value < 0 or spike.age() <= act.secs_to_ticks(self.max_age_value)): assert not spike.is_wiped() with spike.causal_group() as cg: # Causal group might refuse acquisition, if one of act's state's write-props is unavailable. - if not cg.acquired(spike, act, self.detached): + if not cg.acquired(spike, act, self.detached_value): return False - self._min_age_ticks = act.secs_to_ticks(self.min_age) + self._min_age_ticks = act.secs_to_ticks(self.min_age_value) self.spike = spike return True return False @@ -184,7 +198,7 @@ def dereference(self, *, def update(self, act: IActivation) -> Generator['Signal', None, None]: # Reject spike, once it has become too old - if self.spike and self.max_age >= 0 and self.spike.age() > act.secs_to_ticks(self.max_age): + if self.spike and self.max_age_value >= 0 and self.spike.age() > act.secs_to_ticks(self.max_age_value): with self.spike.causal_group() as cg: cg.rejected(self.spike, act, reason=1) self.spike = None @@ -207,8 +221,23 @@ def effect_not_caused(self, act: IActivation, group: ICausalGroup, effect: str) cause.spike = None yield cause + def min_age(self, min_age: Union[float, ConfigurableAge]) -> 'Signal': + new_sig = copy.deepcopy(self) + new_sig.min_age_value = min_age + return new_sig + + def max_age(self, max_age: Union[float, ConfigurableAge]) -> 'Signal': + new_sig = copy.deepcopy(self) + new_sig.max_age_value = max_age + return new_sig + + def detached(self) -> 'Signal': + new_sig = copy.deepcopy(self) + new_sig.detached_value = True + return new_sig + def __str__(self): - return self.name + return self.id() class Conjunct(Constraint): @@ -231,7 +260,7 @@ def __init__(self, *args): logger.error("Conjunct can only be constructed with Signals.") raise ValueError self._signals = set(args) - self._hash = hash(tuple(sorted(sig.name for sig in self._signals))) + self._hash = hash(tuple(sorted(sig.id() for sig in self._signals))) self._allowed_causal_groups = set() def __iter__(self): @@ -272,7 +301,7 @@ def signals(self) -> Generator['Signal', None, None]: def conjunctions(self, filter_detached=False) -> Generator['Conjunct', None, None]: result = self if filter_detached: - result = Conjunct(*(sig for sig in self._signals if not sig.detached)) + result = Conjunct(*(sig for sig in self._signals if not sig.detached_value)) if result._signals: yield result diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 49f4e63..81c41fd 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -22,14 +22,19 @@ from reggol import get_logger logger = get_logger(__name__) +_startup_signal = Signal(":startup") +_shutdown_signal = Signal(":shutdown") + +# TODO do startup and shutdown have to be methods? +# Maybe put them so that they can be imported with from ravestate import startup? def startup(**kwargs) -> Signal: """ Obtain the startup signal, which is fired once when `Context.run()` is executed.
__Hint:__ All key-word arguments of #constraint.s(...) (`min_age`, `max_age`, `detached`) are supported. """ - return s(":startup", **kwargs) + return _startup_signal def shutdown(**kwargs) -> Signal: @@ -38,7 +43,13 @@ def shutdown(**kwargs) -> Signal: __Hint:__ All key-word arguments of #constraint.s(...) (`min_age`, `max_age`, `detached`) are supported. """ - return s(":shutdown", **kwargs) + return _shutdown_signal + + +pressure_property = Property(name="pressure", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, + default_value=False, always_signal_changed=False, is_flag_property=True) +activity_property = Property(name="activity", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, + default_value=0, always_signal_changed=False) def create_and_run_context(*args, runtime_overrides=None): @@ -76,27 +87,7 @@ def __eq__(self, other): class Context(IContext): _default_signals: Tuple[Signal] = (startup(), shutdown()) - _default_properties: Tuple[Property] = ( - Property( - name="pressure", - allow_read=True, - allow_write=True, - allow_push=False, - allow_pop=False, - default_value=False, - always_signal_changed=False, - is_flag_property=True - ), - Property( - name="activity", - allow_read=True, - allow_write=True, - allow_push=False, - allow_pop=False, - default_value=0, - always_signal_changed=False - ) - ) + _default_properties: Tuple[Property] = (activity_property, pressure_property) core_module_name = "core" import_modules_config = "import" @@ -205,7 +196,7 @@ def emit(self, signal: Signal, parents: Set[Spike]=None, wipe: bool=False, paylo self.wipe(signal) with self._lock: new_spike = Spike( - sig=signal.name, + sig=signal.id(), parents=parents, consumable_resources=set(self._properties.keys()), payload=payload) @@ -224,7 +215,7 @@ def wipe(self, signal: Signal): with self._lock: for spikes in self._spikes_per_signal.values(): for spike in spikes: - if spike.name() == signal.name: + if spike.id() == signal.id(): spike.wipe() # Final cleanup will be performed while update is running, # and cg.stale(spike) returns true. @@ -282,32 +273,32 @@ def add_state(self, *, st: State) -> None: return # make sure that all of the state's depended-upon properties exist - for prop in st.read_props+st.write_props: - if prop not in self._properties: - logger.error(f"Attempt to add state which depends on unknown property `{prop}`!") + for prop_id in st.get_all_props_ids(): + if prop_id not in self._properties: + logger.error(f"Attempt to add state which depends on unknown property `{prop_id}`!") return # replace configurable ages with their config values for signal in st.constraint.signals(): - if isinstance(signal.min_age, ConfigurableAge): - conf_entry = self.conf(mod=st.module_name, key=signal.min_age.key) + if isinstance(signal.min_age_value, ConfigurableAge): + conf_entry = self.conf(mod=st.module_name, key=signal.min_age_value.key) if conf_entry is None: logger.error(f"Could not set min_age for cond of state {st.name} in module {st.module_name}") - signal.min_age = 0. + signal.min_age_value = 0. else: - signal.min_age = conf_entry - if isinstance(signal.max_age, ConfigurableAge): - conf_entry = self.conf(mod=st.module_name, key=signal.max_age.key) + signal.min_age_value = conf_entry + if isinstance(signal.max_age_value, ConfigurableAge): + conf_entry = self.conf(mod=st.module_name, key=signal.max_age_value.key) if conf_entry is None: logger.error(f"Could not set max_age for cond of state {st.name} in module {st.module_name}") - signal.max_age = 5. + signal.max_age_value = 5. else: - signal.max_age = conf_entry + signal.max_age_value = conf_entry # register the state's signal with self._lock: - if st.signal(): - self._add_sig(st.signal()) + if st.signal: + self._add_sig(st.signal) # add state's constraints as causes for the written prop's :changed signals, # as well as the state's own signal. @@ -352,8 +343,8 @@ def rm_state(self, *, st: State) -> None: return with self._lock: # Remove the state's signal - if st.signal(): - self._rm_sig(st.signal()) + if st.signal: + self._rm_sig(st.signal) # Remove state activations for the state self._del_state_activations(st) # Actually forget about the state @@ -399,7 +390,7 @@ def rm_prop(self, *, prop: Property) -> None: self._rm_sig(signal) # remove all states that depend upon property for st in self._activations_per_state: - if prop.id() in st.read_props + st.write_props: + if prop.id() in st.get_all_props_ids(): states_to_remove.add(st) for st in states_to_remove: self.rm_state(st=st) @@ -470,7 +461,7 @@ def reacquire(self, act: IActivation, sig: Signal): """ assert isinstance(act, Activation) # No way around it to avoid import loop if sig not in self._needy_acts_per_state_per_signal: - logger.error(f"Attempt to reacquire for unknown signal {sig.name}!") + logger.error(f"Attempt to reacquire for unknown signal {sig.id()}!") return interested_acts = self._needy_acts_per_state_per_signal[sig][act.state_to_activate] interested_acts.add(act) @@ -491,7 +482,7 @@ def withdraw(self, act: IActivation, sig: Signal): """ assert isinstance(act, Activation) # No way around it to avoid import loop if sig not in self._needy_acts_per_state_per_signal: - logger.warning(f"Attempt to withdraw for unknown signal {sig.name}!") + logger.warning(f"Attempt to withdraw for unknown signal {sig.id()}!") return interested_acts = self._needy_acts_per_state_per_signal[sig][act.state_to_activate] interested_acts.discard(act) @@ -515,12 +506,16 @@ def possible_signals(self, state: State) -> Generator[Signal, None, None]: * `state`: The state, which should be analyzed for it's possibly generated signals (declared signal + property-changed signals). """ - for propname in state.write_props: - if propname in self._properties: - for signal in self._properties[propname].signals(): - yield signal - if state.signal(): - yield state.signal() + for prop in state.write_props: + if isinstance(prop, str): + if prop in self._properties: + prop = self._properties[prop] + else: + continue # TODO does this happen? (was there previously) + for signal in prop.signals(): # TODO also check here if in self._properties? + yield signal + if state.signal: + yield state.signal def run_once(self, seconds_passed=1.) -> None: """ @@ -571,7 +566,7 @@ def run_once(self, seconds_passed=1.) -> None: for spike in spikes: if spike.is_wiped() or spike.age() > 0: continue - for state, acts in self._needy_acts_per_state_per_signal[s(spike.name())].items(): + for state, acts in self._needy_acts_per_state_per_signal[s(spike.id())].items(): old_acts = acts.copy() for act in old_acts: if act.acquire(spike): @@ -617,7 +612,7 @@ def _state_activated(self, act: Activation): def _add_sig(self, sig: Signal): if sig in self._needy_acts_per_state_per_signal: - logger.error(f"Attempt to add signal f{sig.name} twice!") + logger.error(f"Attempt to add signal f{sig.id()} twice!") return self._signal_causes[sig] = [] self._needy_acts_per_state_per_signal[sig] = defaultdict(set) @@ -626,7 +621,7 @@ def _rm_sig(self, sig: Signal) -> None: affected_states: Set[State] = set(self._needy_acts_per_state_per_signal[sig].keys()) if affected_states: logger.warning( - f"Since signal {sig.name} was removed, the following states will have dangling constraints: " + + f"Since signal {sig.id()} was removed, the following states will have dangling constraints: " + ",".join(st.name for st in affected_states)) # Remove signal as a cause for other signals for _, causes in self._signal_causes.items(): @@ -711,7 +706,7 @@ def copy_conjunct_set_with_completion(original_conj: Set[Signal], completion_con if sig == completed_signal: sig.completed_by = completion_conj if sig in completion_conj: - sig.max_age = -1 # See #52 (§3) + sig.max_age_value = -1 # See #52 (§3) sig.is_completion = True return original_conj @@ -733,7 +728,7 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[ assert sig in self._signal_causes # a signal without cause (a primary signal) needs no further completion - if not self._signal_causes[sig] or sig.detached: + if not self._signal_causes[sig] or sig.detached_value: return None # a signal with at least one secondary cause needs at least one non-cyclic diff --git a/modules/ravestate/iactivation.py b/modules/ravestate/iactivation.py index d1365de..369b38c 100644 --- a/modules/ravestate/iactivation.py +++ b/modules/ravestate/iactivation.py @@ -9,9 +9,9 @@ class ISpike: Base interface class for spikes. """ - def name(self) -> str: + def id(self) -> str: """ - Returns the name of this spike's signal. + Returns the id of this spike's signal. """ pass diff --git a/modules/ravestate/module.py b/modules/ravestate/module.py index 38ad6d0..843d4d9 100644 --- a/modules/ravestate/module.py +++ b/modules/ravestate/module.py @@ -2,6 +2,7 @@ from typing import Dict, Any, Union, Iterable, Callable import importlib +from ravestate.constraint import Signal from ravestate.property import Property from ravestate.state import State from ravestate.threadlocal import ravestate_thread_local @@ -41,6 +42,7 @@ def __init__(self, *, name: str, config: Dict[str, Any]=None): config = {} self.props = [] self.states = [] + self.signals = [] self.name = name self.conf = config if name in self.registered_modules: @@ -60,21 +62,24 @@ def __exit__(self, exc_type, exc_val, exc_tb): if self.registration_callback: self.registration_callback(self) - def add(self, property_or_state: Union[Property, State, Iterable[Property], Iterable[State]]): + def add(self, ownable: Union[Property, State, Signal, Iterable[Property], Iterable[State], Iterable[Signal]]): try: - for obj_to_add in property_or_state: + for obj_to_add in ownable: self.add(obj_to_add) return except TypeError: pass - if isinstance(property_or_state, Property): - property_or_state.set_parent_path(self.name) - self.props.append(property_or_state) - elif isinstance(property_or_state, State): - property_or_state.module_name = self.name - self.states.append(property_or_state) + if isinstance(ownable, Property): + ownable.set_parent_path(self.name) + self.props.append(ownable) + elif isinstance(ownable, State): + ownable.module_name = self.name + self.states.append(ownable) + elif isinstance(ownable, Signal): + ownable.module_name = self.name + self.signals.append(ownable) else: - logger.error(f"Module.add() called with invalid argument {property_or_state}!") + logger.error(f"Module.add() called with invalid argument {ownable}!") def import_module(*, module_name: str, callback): diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index a07845b..01bcdf0 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -9,7 +9,7 @@ logger = get_logger(__name__) -def changed(property_name, **kwargs) -> Signal: +def changed(property_name, module_name, **kwargs) -> Signal: """ Returns the `changed` Signal for the given property. This signal is emitted, when the Property is written to, @@ -18,10 +18,12 @@ def changed(property_name, **kwargs) -> Signal: __Hint:__ All key-word arguments of #constraint.s(...) (`min_age`, `max_age`, `detached`) are supported. """ - return s(f"{property_name}:changed", **kwargs) + sig = s(f"{property_name}:changed", **kwargs) + sig.module_name = module_name + return sig -def pushed(property_name, **kwargs) -> Signal: +def pushed(property_name, module_name, **kwargs) -> Signal: """ Returns the `pushed` Signal for the given property. This signal is emitted, when a new child property is added to it. @@ -30,10 +32,12 @@ def pushed(property_name, **kwargs) -> Signal: __Hint:__ All key-word arguments of #constraint.s(...) (`min_age`, `max_age`, `detached`) are supported. """ - return s(f"{property_name}:pushed", **kwargs) + sig = s(f"{property_name}:pushed", **kwargs) + sig.module_name = module_name + return sig -def popped(property_name, **kwargs) -> Signal: +def popped(property_name, module_name, **kwargs) -> Signal: """ Returns the `popped` Signal for the given property. This signal is emitted, when a child property removed from it. @@ -42,7 +46,9 @@ def popped(property_name, **kwargs) -> Signal: __Hint:__ All key-word arguments of #constraint.s(...) (`min_age`, `max_age`, `detached`) are supported. """ - return s(f"{property_name}:popped", **kwargs) + sig = s(f"{property_name}:popped", **kwargs) + sig.module_name = module_name + return sig class Property: @@ -182,31 +188,40 @@ def changed_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #write() returns True. """ - return changed(self.id()) + module_name, name_without_module = self.id().split(':', 1) + return changed(name_without_module, module_name) def pushed_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #push() returns True. """ - return pushed(self.id()) + module_name, name_without_module = self.id().split(':', 1) + return pushed(name_without_module, module_name) def popped_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #pop() returns True. """ - return popped(self.id()) + module_name, name_without_module = self.id().split(':', 1) + return popped(name_without_module, module_name) def flag_true_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when it is a flag-property and #self.value is set to True. """ - return s(f"{self.id()}:true") + module_name, name_without_module = self.id().split(':', 1) + sig = s(f"{name_without_module}:true") + sig.module_name = module_name + return sig def flag_false_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when it is a flag-property and #self.value is set to False. """ - return s(f"{self.id()}:false") + module_name, name_without_module = self.id().split(':', 1) + sig = s(f"{name_without_module}:false") + sig.module_name = module_name + return sig def signals(self) -> Generator[Signal, None, None]: """ diff --git a/modules/ravestate/receptor.py b/modules/ravestate/receptor.py index 3cdeec6..cc7ef6f 100644 --- a/modules/ravestate/receptor.py +++ b/modules/ravestate/receptor.py @@ -1,5 +1,5 @@ # Ravestate receptor state decorator - +from ravestate.property import Property from ravestate.state import State from ravestate.wrappers import ContextWrapper from ravestate.context import Context @@ -7,7 +7,7 @@ from typing import Union, Set, Tuple -def receptor(*, ctx_wrap: Union[ContextWrapper, Context], write: Union[str, Tuple[str]]): +def receptor(*, ctx_wrap: Union[ContextWrapper, Context], write: Union[str, Property, Tuple[Union[str, Property]]]): """ A receptor is a special state which can be invoked from outside, to push values into the context. @@ -42,7 +42,7 @@ def receptor_decorator(action): receptor_state = State( write=write, read=(), - signal_name=None, + signal=None, cond=None, action=action, is_receptor=True) diff --git a/modules/ravestate/spike.py b/modules/ravestate/spike.py index b767152..10ba206 100644 --- a/modules/ravestate/spike.py +++ b/modules/ravestate/spike.py @@ -89,7 +89,7 @@ def __del__(self): def __repr__(self): return self._name + f"[t+{self._age}]" - def name(self): + def id(self): return self._signal def causal_group(self) -> CausalGroup: diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 5d64a77..252f5e3 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -1,7 +1,9 @@ # Ravestate State-related definitions -from typing import Optional, Tuple, Union +from typing import Optional, Tuple, Union, Set from threading import Semaphore, Lock + +from ravestate.property import Property from ravestate.threadlocal import ravestate_thread_local from ravestate.constraint import Conjunct, Disjunct, Signal, s, Constraint from ravestate.consumable import Consumable @@ -64,9 +66,9 @@ class State: Do not use this class - instead use the `@state` decorator. """ - signal_name: str - write_props: Tuple - read_props: Tuple + signal: Signal + write_props: Tuple[Union[str, Property]] + read_props: Tuple[Union[str, Property]] constraint: Constraint emit_detached: bool cooldown: float @@ -74,7 +76,6 @@ class State: is_receptor: bool module_name: str # The module which this state belongs to - signal_object: Signal # Created on the fly during runtime from signal_name completed_constraint: Constraint # Updated by context, to add constraint causes to constraint activated: Semaphore # Semaphore which counts finished activations lock: Lock # Mutex to lock access to the current weight/cooldown state @@ -85,9 +86,9 @@ class State: consumable: Consumable def __init__(self, *, - signal_name: Optional[str], - write: Union[str, Tuple[str]], - read: Union[str, Tuple[str]], + signal: Optional[Signal], + write: Union[str, Property, Tuple[Union[str, Property]]], + read: Union[str, Property, Tuple[Union[str, Property]]], cond: Optional[Constraint], action, is_receptor: bool=False, @@ -104,31 +105,31 @@ def __init__(self, *, logger.error(f"Attempt to create state {self.name} which has a string as condition, not a constraint!") cond = None + # convert read/write properties to tuples + if isinstance(write, Property) or isinstance(write, str): + write = (write,) + if isinstance(read, Property) or isinstance(read, str): + read = (read,) + # catch the insane case if not len(read) and not cond and not is_receptor: logger.warning( f"The state `{self.name}` is not reading any properties, nor waiting for any signals. " + "It will never be activated!") - # convert read/write properties to tuples - if isinstance(write, str): - write = (write,) - if isinstance(read, str): - read = (read,) - # listen to default changed-signals if no signals are given. # convert triggers to disjunctive normal form. if not cond and len(read) > 0: - cond = Disjunct(*list(Conjunct(s(f"{rprop_name}:changed")) for rprop_name in read)) + cond = Disjunct(*(Conjunct(rprop.changed_signal()) if isinstance(rprop, Property) + else Conjunct(s(f"{rprop}:changed")) for rprop in read)) - self.signal_name = signal_name + self.signal = signal self.write_props = write self.read_props = read self.constraint = cond self.completed_constraint = cond self.action = action self.module_name = "" - self.signal_object = None self.emit_detached = emit_detached self.activated = Semaphore(0) self.weight = self.current_weight = weight @@ -145,6 +146,15 @@ def __call__(self, context, *args, **kwargs) -> Optional[_StateActivationResult] args = (context,) + args return self.action(*args, **kwargs) + def get_read_props_ids(self) -> Set[str]: + return set(prop.id() if isinstance(prop, Property) else prop for prop in self.read_props) + + def get_write_props_ids(self) -> Set[str]: + return set(prop.id() if isinstance(prop, Property) else prop for prop in self.write_props) + + def get_all_props_ids(self) -> Set[str]: + return self.get_read_props_ids().union(self.get_write_props_ids()) + def update_weight(self, seconds_passed: float): """ Called once per tick by context, where #seconds_passed is @@ -179,13 +189,6 @@ def activation_finished(self): # over the course of repeated update_weight() calls. self.current_weight = .0 - def signal(self) -> Optional[Signal]: - if not self.is_receptor: - assert self.module_name - if not self.signal_object and self.signal_name: - self.signal_object = s(f"{self.module_name}:{self.signal_name}") - return self.signal_object - def wait(self, timeout=5.): """ Wait for the state's activation function to be run at least once. @@ -201,13 +204,13 @@ def wait(self, timeout=5.): def state(*, - signal_name: Optional[str]="", - write: tuple=(), - read: tuple=(), - cond: Constraint=None, - emit_detached=False, - weight: float=1., - cooldown: float=0.): + signal: Optional[Signal] = "", + write: Tuple[Union[Property, str]] = (), + read: Tuple[Union[Property, str]] = (), + cond: Constraint = None, + emit_detached: bool = False, + weight: float = 1., + cooldown: float = 0.): """ Decorator to declare a new state, which may emit a certain signal, @@ -223,9 +226,9 @@ def after_startup(context, write=OUTPUT_PROPERTY): ``` """ def state_decorator(action): - nonlocal signal_name, write, read, cond + nonlocal signal, write, read, cond return State( - signal_name=signal_name, + signal=signal, write=write, read=read, cond=cond, diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index a29131f..37b9570 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -4,7 +4,7 @@ from reggol import strip_prefix from testfixtures import LogCapture -from ravestate.constraint import s +from ravestate.constraint import s, Signal from ravestate.context import Context from ravestate.property import Property from ravestate.state import State, state @@ -18,6 +18,15 @@ DEFAULT_PROPERTY_CHANGED = f"{DEFAULT_PROPERTY_ID}:changed" NEW_PROPERTY_VALUE = 'Dorfmeister' +SIGNAL_A = Signal("a") +SIGNAL_A.module_name = DEFAULT_MODULE_NAME +SIGNAL_B = Signal("b") +SIGNAL_B.module_name = DEFAULT_MODULE_NAME +SIGNAL_C = Signal("c") +SIGNAL_C.module_name = DEFAULT_MODULE_NAME +SIGNAL_D = Signal("d") +SIGNAL_D.module_name = DEFAULT_MODULE_NAME + @pytest.fixture def state_fixture(mocker): @@ -30,7 +39,7 @@ def state_mock_fn(ctx): @pytest.fixture def state_signal_a_fixture(mocker): - @state(read=(DEFAULT_PROPERTY_ID,), signal_name="a") + @state(read=(DEFAULT_PROPERTY_ID,), signal=SIGNAL_A) def state_mock_a_fn(ctx): pass state_mock_a_fn.module_name = DEFAULT_MODULE_NAME @@ -39,7 +48,7 @@ def state_mock_a_fn(ctx): @pytest.fixture def state_signal_b_fixture(mocker): - @state(signal_name="b", cond=s("module:a")) + @state(signal=SIGNAL_B, cond=SIGNAL_A) def state_mock_b_fn(ctx): pass state_mock_b_fn.module_name = DEFAULT_MODULE_NAME @@ -48,7 +57,7 @@ def state_mock_b_fn(ctx): @pytest.fixture def state_signal_c_fixture(mocker): - @state(signal_name="c", cond=s("module:a")) + @state(signal=SIGNAL_C, cond=SIGNAL_A) def state_mock_c_fn(ctx): pass state_mock_c_fn.module_name = DEFAULT_MODULE_NAME @@ -57,7 +66,7 @@ def state_mock_c_fn(ctx): @pytest.fixture def state_signal_d_fixture(mocker): - @state(signal_name="d", cond=s("module:b")|s("module:c")) + @state(signal=SIGNAL_D, cond=SIGNAL_B | SIGNAL_C) def state_mock_c_fn(ctx): pass state_mock_c_fn.module_name = DEFAULT_MODULE_NAME diff --git a/modules/ravestate/wrappers.py b/modules/ravestate/wrappers.py index 88ba8fb..1643344 100644 --- a/modules/ravestate/wrappers.py +++ b/modules/ravestate/wrappers.py @@ -157,18 +157,18 @@ def __init__(self, *, ctx: IContext, state: State, spike_parents: Set[Spike] = N self.spike_payloads = spike_payloads # Recursively complete properties dict with children: - for propname in state.write_props + state.read_props: + for prop_parent_id in state.get_all_props_ids(): # May have been covered by a parent before - if propname not in self.properties: - prop_and_children = ctx[propname].gather_children() + if prop_parent_id not in self.properties: + prop_and_children = ctx[prop_parent_id].gather_children() for prop in prop_and_children: # Child may have been covered by a parent before if prop.id() not in self.properties: self.properties[prop.id()] = PropertyWrapper( prop=prop, ctx=self.ctx, spike_parents=self.spike_parents, - allow_read=propname in state.read_props, - allow_write=propname in state.write_props) + allow_read=prop_parent_id in state.get_read_props_ids(), + allow_write=prop_parent_id in state.get_write_props_ids()) def __setitem__(self, key: Union[str, Property], value: Any): if isinstance(key, Property): @@ -178,7 +178,9 @@ def __setitem__(self, key: Union[str, Property], value: Any): else: logger.error(f"State {self.state.name} attempted to write property {key} without permission!") - def __getitem__(self, key) -> Any: + def __getitem__(self, key: Union[str, Property, Signal]) -> Any: + if isinstance(key, Signal) or isinstance(key, Property): + key = key.id() if key in self.properties: return self.properties[key].get() elif key in self.spike_payloads: @@ -199,6 +201,8 @@ def add_state(self, state: State): mod = get_module(self.state.module_name) assert mod mod.add(state) + if state.signal: + mod.add(state.signal) self.ctx.add_state(st=state) def shutdown(self): diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index e2a1706..89a4da5 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -43,7 +43,7 @@ @state(cond=s("nlp:intent-play") | s("idle:bored"), write="rawio:out", - signal_name="initiate-play", + signal="initiate-play", emit_detached=True, weight=1., cooldown=30.) @@ -89,7 +89,7 @@ def start(ctx): read="nlp:yesno", write=("rawio:out", "akinator:is_it", "akinator:question"), emit_detached=True, - signal_name="wrong-input") + signal="wrong-input") def question_answered(ctx): """ Reads the answer to a question and outputs the next question @@ -113,7 +113,7 @@ def question_answered(ctx): @state(cond=s("akinator:is_it:changed"), write="rawio:out", - signal_name="is-it", + signal="is-it", emit_detached=True) def is_it(ctx): """ @@ -151,7 +151,7 @@ def is_it_answered(ctx): @state(cond=s("akinator:wrong-input"), write="rawio:out", emit_detached=True, - signal_name="exit-game") + signal="exit-game") def wrong_input(ctx): """ Catches wrong inputs from the interlocutor during questions answering and loops back to the question state diff --git a/modules/ravestate_conio/__init__.py b/modules/ravestate_conio/__init__.py index b0b6253..21bdf6e 100644 --- a/modules/ravestate_conio/__init__.py +++ b/modules/ravestate_conio/__init__.py @@ -2,7 +2,9 @@ from ravestate.constraint import s from ravestate.state import state from ravestate.wrappers import ContextWrapper -from ravestate_interloc import handle_single_interlocutor_input +from ravestate_interloc import handle_single_interlocutor_input, all as interloc_all +from ravestate_rawio import output as raw_out +from ravestate.context import startup from reggol import get_logger logger = get_logger(__name__) @@ -10,13 +12,13 @@ with Module(name="consoleio"): - @state(cond=s(":startup"), read="interloc:all") + @state(cond=startup(), read=interloc_all) def console_input(ctx: ContextWrapper): while not ctx.shutting_down(): input_value = input("> ") handle_single_interlocutor_input(ctx, input_value) - @state(read="rawio:out") + @state(read=raw_out) def console_output(ctx): print(ctx["rawio:out:changed"]) diff --git a/modules/ravestate_fillers/__init__.py b/modules/ravestate_fillers/__init__.py index b84ef79..dbe3b2c 100644 --- a/modules/ravestate_fillers/__init__.py +++ b/modules/ravestate_fillers/__init__.py @@ -3,13 +3,13 @@ from ravestate.constraint import s from ravestate.state import state -import ravestate_idle -import ravestate_verbaliser +from ravestate_idle import impatient +from ravestate_verbaliser import intent import ravestate_phrases_basic_en with Module(name="fillers"): - @state(cond=s("idle:impatient"), write=("verbaliser:intent",)) + @state(cond=impatient, write=intent) def impatient_fillers(ctx: ContextWrapper): - ctx["verbaliser:intent"] = "fillers" + ctx[intent] = "fillers" diff --git a/modules/ravestate_genqa/__init__.py b/modules/ravestate_genqa/__init__.py index 160314c..b88988b 100644 --- a/modules/ravestate_genqa/__init__.py +++ b/modules/ravestate_genqa/__init__.py @@ -2,10 +2,13 @@ from ravestate.state import state, Delete from ravestate_verbaliser import verbaliser from ravestate.constraint import s +from ravestate.context import startup import requests -import ravestate_idle +from ravestate_idle import bored +from ravestate_rawio import input as raw_in, output as raw_out +from ravestate_nlp import is_question from reggol import get_logger logger = get_logger(__name__) @@ -21,7 +24,7 @@ with Module(name="genqa", config=CONFIG): - @state(cond=s(":startup")) + @state(cond=startup()) def hello_world_genqa(ctx): server = ctx.conf(key=DRQA_SERVER_ADDRESS) if not server: @@ -30,11 +33,11 @@ def hello_world_genqa(ctx): if not server_up(server): return Delete() - @state(cond=s("idle:bored"), write="rawio:out", weight=1.15, cooldown=30.) + @state(cond=bored, write=raw_out, weight=1.15, cooldown=30.) def prompt(ctx): - ctx["rawio:out"] = verbaliser.get_random_phrase("question-answering-prompt") + ctx[raw_out] = verbaliser.get_random_phrase("question-answering-prompt") - @state(cond=s("nlp:is-question"), read="rawio:in", write="rawio:out") + @state(cond=is_question, read=raw_in, write=raw_out) def drqa_module(ctx): """ general question answering using DrQA through a HTTP server @@ -45,17 +48,17 @@ def drqa_module(ctx): server = ctx.conf(key=DRQA_SERVER_ADDRESS) if not server_up(server): return Delete(resign=True) - params = {'question': str(ctx["rawio:in"]).lower()} + params = {'question': str(ctx[raw_in]).lower()} response = requests.get(server, params=params) response_json = response.json() certainty = response_json["answers"][0]["span_score"] # sane answer if certainty > ctx.conf(key=ROBOY_ANSWER_SANITY): - ctx["rawio:out"] = verbaliser.get_random_phrase("question-answering-starting-phrases") + " " + \ + ctx[raw_out] = verbaliser.get_random_phrase("question-answering-starting-phrases") + " " + \ response_json["answers"][0]["span"] # insane/unsure answer else: - ctx["rawio:out"] = verbaliser.get_random_phrase("unsure-question-answering-phrases") \ + ctx[raw_out] = verbaliser.get_random_phrase("unsure-question-answering-phrases") \ % response_json["answers"][0]["span"] \ + "\n" + "Maybe I can find out more if your rephrase the question for me." diff --git a/modules/ravestate_hibye/__init__.py b/modules/ravestate_hibye/__init__.py index 3f632d7..1aa4b3a 100644 --- a/modules/ravestate_hibye/__init__.py +++ b/modules/ravestate_hibye/__init__.py @@ -3,17 +3,17 @@ from ravestate.constraint import s from ravestate.wrappers import ContextWrapper -import ravestate_verbaliser +from ravestate_verbaliser import intent import ravestate_phrases_basic_en -import ravestate_interloc - +from ravestate_interloc import all as interloc_all +from ravestate_rawio import input as raw_in with Module(name="hibye"): - @state(cond=s("interloc:all:pushed") & s("rawio:in:changed"), write="verbaliser:intent") + @state(cond=interloc_all.pushed_signal() & raw_in.changed_signal(), write=intent) def greeting(ctx: ContextWrapper): - ctx["verbaliser:intent"] = "greeting" + ctx[intent] = "greeting" - @state(cond=s("interloc:all:popped") & s("rawio:in:changed"), write="verbaliser:intent") + @state(cond=interloc_all.popped_signal() & raw_in.changed_signal(), write=intent) def farewell(ctx: ContextWrapper): - ctx["verbaliser:intent"] = "farewells" + ctx[intent] = "farewells" diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index 8f7efa2..aac8169 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -1,8 +1,8 @@ from ravestate.module import Module -from ravestate.constraint import ConfigurableAge -from ravestate.constraint import s +from ravestate.constraint import ConfigurableAge, Signal, s from ravestate.wrappers import ContextWrapper from ravestate.state import state, Emit +from ravestate.context import activity_property, pressure_property from reggol import get_logger logger = get_logger(__name__) @@ -17,17 +17,20 @@ with Module(name="idle", config=CONFIG): - @state(cond=s(signal_name=":activity:changed", min_age=ConfigurableAge(key=BORED_THRESHOLD_CONFIG_KEY), max_age=-1), read=":activity", signal_name="bored") + impatient = Signal(name="impatient") + bored = Signal(name="bored") + + @state(cond=activity_property.changed_signal().min_age(ConfigurableAge(key=BORED_THRESHOLD_CONFIG_KEY)).max_age(-1), + read=activity_property, signal=bored) def am_i_bored(ctx: ContextWrapper): """ Emits idle:bored signal if no states are currently partially fulfilled """ - if ctx[":activity"] == 0: + if ctx[activity_property] == 0: return Emit(wipe=True) - @state(cond=s(signal_name=":pressure:true", - min_age=ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY), - max_age=-1.), - signal_name="impatient") + @state(cond=pressure_property.flag_true_signal(). + min_age(ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY)).max_age(-1.), + signal=impatient) def am_i_impatient(ctx: ContextWrapper): return Emit(wipe=True) diff --git a/modules/ravestate_interloc/__init__.py b/modules/ravestate_interloc/__init__.py index 9cd4b21..22d556f 100644 --- a/modules/ravestate_interloc/__init__.py +++ b/modules/ravestate_interloc/__init__.py @@ -3,8 +3,7 @@ from ravestate.wrappers import ContextWrapper from ravestate.receptor import receptor -import ravestate_rawio -import ravestate_interloc +from ravestate_rawio import input as raw_in from ravestate_verbaliser.verbaliser import get_phrase_list import ravestate_phrases_basic_en import ravestate_ontology @@ -39,24 +38,24 @@ def handle_single_interlocutor_input(ctx: ContextWrapper, input_value: str, id=" the interlocutor's Neo4j node (until a proper name is set by persqa). """ - @receptor(ctx_wrap=ctx, write="rawio:in") + @receptor(ctx_wrap=ctx, write=raw_in) def write_input(ctx_input, value: str): - ctx_input["rawio:in"] = value + ctx_input[raw_in] = value - @receptor(ctx_wrap=ctx, write="interloc:all") + @receptor(ctx_wrap=ctx, write=all) def push_interloc(ctx: ContextWrapper, interlocutor_node: Node): - if ctx.push(parent_property_or_path="interloc:all", + if ctx.push(parent_property_or_path=all, child=Property(name=id, default_value=interlocutor_node)): logger.debug(f"Pushed {interlocutor_node} to interloc:all") - @receptor(ctx_wrap=ctx, write="interloc:all") + @receptor(ctx_wrap=ctx, write=all) def pop_interloc(ctx: ContextWrapper): if ctx.pop(f"interloc:all:{id}"): logger.debug(f"Popped interloc:all:{id}") write_input(input_value) - interloc_exists = f"interloc:all:{id}" in ctx.enum("interloc:all") + interloc_exists = f"interloc:all:{id}" in ctx.enum(all) # push Node if you got a greeting if input_value.strip() in get_phrase_list("greeting") and not interloc_exists: diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index d68a0e5..ddf2371 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -1,13 +1,12 @@ from ravestate.module import Module from ravestate.property import Property -from ravestate.state import state +from ravestate.state import state, Emit +from ravestate.constraint import s, Signal +from ravestate_rawio import input as raw_in from ravestate_nlp.question_word import QuestionWord from ravestate_nlp.triple import Triple from ravestate_nlp.extract_triples import extract_triples -from ravestate_nlp.triple import Triple -from ravestate.state import Emit -from ravestate.constraint import s from ravestate_nlp.yes_no import yes_no from reggol import get_logger @@ -45,85 +44,77 @@ def roboy_getter(doc) -> bool: with Module(name="nlp"): - tokens = Property(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - postags = Property(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - lemmas = Property(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - tags = Property(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - ner = Property(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - triples = Property(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - roboy = Property(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + tokens = Property(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + postags = Property(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + lemmas = Property(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + tags = Property(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + ner = Property(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + triples = Property(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + roboy = Property(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) yesno = Property(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + contains_roboy = Signal(name="contains-roboy") + is_question = Signal(name="is-question") + intent_play = Signal(name="intent-play") + - @state( - cond=s("rawio:in:changed"), - read="rawio:in", - write=( - "nlp:tokens", - "nlp:postags", - "nlp:lemmas", - "nlp:tags", - "nlp:ner", - "nlp:triples", - "nlp:roboy", - "nlp:yesno" - )) + @state(read=raw_in, write=(tokens, postags, lemmas, tags, ner, triples, roboy, yesno)) def nlp_preprocess(ctx): - text = ctx["rawio:in"] + text = ctx[raw_in] if not text: return False text = text.lower() nlp_doc = spacy_nlp_en(text) nlp_tokens = tuple(str(token) for token in nlp_doc) - ctx["nlp:tokens"] = nlp_tokens + ctx[tokens] = nlp_tokens logger.info(f"[NLP:tokens]: {nlp_tokens}") nlp_postags = tuple(str(token.pos_) for token in nlp_doc) - ctx["nlp:postags"] = nlp_postags + ctx[postags] = nlp_postags logger.info(f"[NLP:postags]: {nlp_postags}") nlp_lemmas = tuple(str(token.lemma_) for token in nlp_doc) - ctx["nlp:lemmas"] = nlp_lemmas + ctx[lemmas] = nlp_lemmas logger.info(f"[NLP:lemmas]: {nlp_lemmas}") nlp_tags = tuple(str(token.tag_) for token in nlp_doc) - ctx["nlp:tags"] = nlp_tags + ctx[tags] = nlp_tags logger.info(f"[NLP:tags]: {nlp_tags}") nlp_ner = tuple((str(ents.text), str(ents.label_)) for ents in nlp_doc.ents) - ctx["nlp:ner"] = nlp_ner + ctx[ner] = nlp_ner logger.info(f"[NLP:ner]: {nlp_ner}") nlp_triples = nlp_doc._.triples - ctx["nlp:triples"] = nlp_triples + ctx[triples] = nlp_triples logger.info(f"[NLP:triples]: {nlp_triples}") nlp_roboy = nlp_doc._.about_roboy - ctx["nlp:roboy"] = nlp_roboy + ctx[roboy] = nlp_roboy logger.info(f"[NLP:roboy]: {nlp_roboy}") nlp_yesno = nlp_doc._.yesno - ctx["nlp:yesno"] = nlp_yesno + ctx[yesno] = nlp_yesno logger.info(f"[NLP:yesno]: {nlp_yesno}") - @state(signal_name="contains-roboy", read="nlp:roboy") + @state(signal=contains_roboy, read=roboy) def nlp_contains_roboy_signal(ctx): - if ctx["nlp:roboy"]: + if ctx[roboy]: return Emit() return False - @state(signal_name="is-question", read="nlp:triples") + @state(signal=is_question, read=triples) def nlp_is_question_signal(ctx): - if ctx["nlp:triples"][0].is_question(): + if ctx[triples][0].is_question(): return Emit() return False - @state(signal_name="intent-play", read="nlp:triples") + @state(signal=intent_play, read=triples) def nlp_intent_play_signal(ctx): - nlp_triples = ctx["nlp:triples"] + nlp_triples = ctx[triples] if nlp_triples[0].match_either_lemma(pred={"play"}, obj={"game"}): return Emit() return False diff --git a/modules/ravestate_ontology/__init__.py b/modules/ravestate_ontology/__init__.py index ff3bb53..71e6145 100644 --- a/modules/ravestate_ontology/__init__.py +++ b/modules/ravestate_ontology/__init__.py @@ -1,6 +1,6 @@ from ravestate.module import Module from ravestate.state import state -from ravestate.constraint import s +from ravestate.context import startup from scientio.ontology.ontology import Ontology from scientio.session import Session @@ -27,7 +27,7 @@ with Module(name="ontology", config=CONFIG): - @state(cond=s(":startup")) + @state(cond=startup()) def hello_world_ontology(ctx): """ Creates a scientio session with neo4j backend. diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py index ad9155b..44bccc4 100644 --- a/modules/ravestate_persqa/__init__.py +++ b/modules/ravestate_persqa/__init__.py @@ -2,7 +2,7 @@ from ravestate.property import Property from ravestate.wrappers import ContextWrapper from ravestate.state import state, Emit, Delete, Resign -from ravestate.constraint import s +from ravestate.constraint import s, Signal from ravestate_verbaliser import verbaliser import ravestate_ontology @@ -17,9 +17,10 @@ from reggol import get_logger logger = get_logger(__name__) -import ravestate_idle -import ravestate_nlp -import ravestate_rawio +from ravestate_idle import bored +from ravestate_rawio import input as raw_in, output as raw_out +from ravestate_nlp import triples, yesno, tokens +from ravestate_interloc import all as interloc_all verbaliser.add_folder(join(dirname(realpath(__file__)), "persqa_phrases")) @@ -72,13 +73,15 @@ allow_push=False, is_flag_property=True) - follow_up = Property( + follow_up_prop = Property( name="follow_up", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + follow_up_signal = Signal(name="follow-up") + def find_empty_relationship(dictonary: Dict): for key in dictonary: if not dictonary[key] and key in PREDICATE_SET: @@ -98,13 +101,13 @@ def create_small_talk_states(ctx: ContextWrapper, interloc_path: str): used_follow_up_preds = set() - @state(cond=s("idle:bored"), - write=("rawio:out", "persqa:predicate", "persqa:subject"), - read=(interloc_path, "persqa:predicate"), + @state(cond=bored, + write=(raw_out, predicate, subject), + read=(interloc_path, predicate), weight=1.2, cooldown=40., emit_detached=True, - signal_name="follow-up") + signal=follow_up_signal) def small_talk(ctx: ContextWrapper): sess: Session = ravestate_ontology.get_session() interloc: Node = ctx[interloc_path] @@ -112,12 +115,12 @@ def small_talk(ctx: ContextWrapper): pred = "NAME" else: pred = find_empty_relationship(interloc.get_relationships()) - ctx["persqa:subject"] = interloc_path - if not ctx["persqa:predicate"]: + ctx[subject] = interloc_path + if not ctx[predicate]: if pred: logger.info(f"Personal question: intent={pred}") - ctx["persqa:predicate"] = pred - ctx["rawio:out"] = verbaliser.get_random_question(pred) + ctx[predicate] = pred + ctx[raw_out] = verbaliser.get_random_question(pred) else: unused_fup_preds = PREDICATE_SET.difference(used_follow_up_preds) if not unused_fup_preds: @@ -126,12 +129,12 @@ def small_talk(ctx: ContextWrapper): pred = random.sample(PREDICATE_SET.difference(used_follow_up_preds), 1) pred = pred[0] used_follow_up_preds.add(pred) - ctx["persqa:predicate"] = pred + ctx[predicate] = pred relationship_ids: Set[int] = interloc.get_relationships(pred) if len(relationship_ids) > 0: # Just to be safe ... object_node_list = sess.retrieve(node_id=list(relationship_ids)[0]) if len(object_node_list) > 0: - ctx["rawio:out"] = verbaliser.get_random_followup_question(pred).format( + ctx[raw_out] = verbaliser.get_random_followup_question(pred).format( name=interloc.get_name(), obj=object_node_list[0].get_name()) logger.info(f"Follow-up: intent={pred}") @@ -140,53 +143,53 @@ def small_talk(ctx: ContextWrapper): else: # While the predicate is set, repeat the question. Once the predicate is answered, # it will be set to None, such that a new predicate is entered. - ctx["rawio:out"] = verbaliser.get_random_question(ctx["persqa:predicate"]) + ctx[raw_out] = verbaliser.get_random_question(ctx[predicate]) - @state(cond=s("persqa:follow-up", max_age=-1.) & s("nlp:triples:changed"), - write=("rawio:out", "persqa:predicate", inference_mutex.id()), - read=(interloc_path, "persqa:predicate", "nlp:yesno")) + @state(cond=follow_up_signal.max_age(-1.) & triples.changed_signal(), + write=(raw_out, predicate, inference_mutex), + read=(interloc_path, predicate, yesno)) def fup_react(ctx: ContextWrapper): sess: Session = ravestate_ontology.get_session() subject_node: Node = ctx[interloc_path] - pred = ctx["persqa:predicate"] + pred = ctx[predicate] object_node_list = [] relationship_ids: Set[int] = subject_node.get_relationships(pred) if len(relationship_ids) > 0: object_node_list = sess.retrieve(node_id=list(relationship_ids)[0]) if len(object_node_list) > 0: - ctx["rawio:out"] = verbaliser.get_random_followup_answer(pred).format( + ctx[raw_out] = verbaliser.get_random_followup_answer(pred).format( name=subject_node.get_name(), obj=object_node_list[0].get_name()) else: - ctx["rawio:out"] = "Oh, I see!" - ctx["persqa:predicate"] = None + ctx[raw_out] = "Oh, I see!" + ctx[predicate] = None ctx.add_state(small_talk) ctx.add_state(fup_react) - @state(cond=s("rawio:in:changed") & s("interloc:all:pushed"), - write=inference_mutex.id(), - read="interloc:all", + @state(cond=raw_in.changed_signal() & interloc_all.pushed_signal(), + write=inference_mutex, + read=interloc_all, emit_detached=True) def new_interloc(ctx: ContextWrapper): """ reacts to interloc:pushed and creates persqa:ask_name state """ - interloc_path = ctx["interloc:all:pushed"] + interloc_path = ctx[interloc_all.pushed_signal()] create_small_talk_states(ctx=ctx, interloc_path=interloc_path) - @state(cond=s("rawio:in:changed") & s("interloc:all:popped"), - write=(inference_mutex.id(), "persqa:predicate", "persqa:subject")) + @state(cond=raw_in.changed_signal() & interloc_all.popped_signal(), + write=(inference_mutex, predicate, subject)) def removed_interloc(ctx: ContextWrapper): """ reacts to interloc:popped and makes sure that """ - ctx["persqa:subject"] = None - ctx["persqa:predicate"] = None + ctx[subject] = None + ctx[predicate] = None - @state(cond=s("nlp:triples:changed"), - write=("persqa:answer", inference_mutex.id()), - read=("persqa:predicate", "nlp:triples", "nlp:tokens", "nlp:yesno")) + @state(cond=triples.changed_signal(), + write=(answer, inference_mutex), + read=(predicate, triples, tokens, yesno)) def inference(ctx: ContextWrapper): """ recognizes name in sentences like: @@ -194,26 +197,26 @@ def inference(ctx: ContextWrapper): - my name toseban - dino """ - triple = ctx["nlp:triples"][0] + triple = ctx[triples][0] if triple.is_question(): return Resign() - pred = ctx["persqa:predicate"] + pred = ctx[predicate] answer_str = None if pred == "NAME" or pred in PREDICATE_SET: # TODO City, Country -> NLP NER also only recognizes locations... if triple.has_object(): answer_str = triple.get_object().text - elif len(ctx["nlp:tokens"]) == 1: - answer_str = ctx["nlp:tokens"][0] - elif len(ctx["nlp:tokens"]) == 2: - answer_str = "%s %s" % (ctx["nlp:tokens"][0], ctx["nlp:tokens"][1]) + elif len(ctx[tokens]) == 1: + answer_str = ctx[tokens][0] + elif len(ctx[tokens]) == 2: + answer_str = "%s %s" % (ctx[tokens][0], ctx[tokens][1]) if answer_str: logger.debug(f"Inference: extracted answer '{answer_str}' for predicate {pred}") - ctx["persqa:answer"] = answer_str + ctx[answer] = answer_str @state(cond=answer.changed_signal(), - write=("rawio:out", "persqa:predicate"), - read=("persqa:predicate", "persqa:subject", "persqa:answer", "interloc:all")) + write=(raw_out, predicate), + read=(predicate, subject, answer, interloc_all)) def react(ctx: ContextWrapper): """ Retrieves memory node with the name, or creates a new one @@ -221,9 +224,9 @@ def react(ctx: ContextWrapper): """ onto: Ontology = ravestate_ontology.get_ontology() sess: Session = ravestate_ontology.get_session() - inferred_answer = ctx["persqa:answer"] - pred = ctx["persqa:predicate"] - subject_path: str = ctx["persqa:subject"] + inferred_answer = ctx[answer] + pred = ctx[predicate] + subject_path: str = ctx[subject] if not subject_path: return Resign() subject_node: Node = ctx[subject_path] @@ -248,9 +251,9 @@ def react(ctx: ContextWrapper): subject_node.add_relationships({pred: {relationship_node.get_id()}}) sess.update(subject_node) - ctx["rawio:out"] = verbaliser.get_random_successful_answer(pred).format( + ctx[raw_out] = verbaliser.get_random_successful_answer(pred).format( name=subject_node.get_name(), obj=inferred_answer) - ctx["persqa:predicate"] = None + ctx[predicate] = None diff --git a/modules/ravestate_roboyqa/__init__.py b/modules/ravestate_roboyqa/__init__.py index ed459c9..9f40c95 100644 --- a/modules/ravestate_roboyqa/__init__.py +++ b/modules/ravestate_roboyqa/__init__.py @@ -3,14 +3,16 @@ from ravestate.constraint import s, ConfigurableAge from ravestate_nlp.question_word import QuestionWord import ravestate_ontology +from ravestate_nlp import contains_roboy, is_question, triples from ravestate_verbaliser import verbaliser +from ravestate_idle import bored +from ravestate_rawio import output as raw_out + from os.path import realpath, dirname, join import random import datetime -import ravestate_idle - from reggol import get_logger logger = get_logger(__name__) @@ -21,11 +23,11 @@ with Module(name="roboyqa", config={ROBOY_NODE_CONF_KEY: 356}): - @state(cond=s("idle:bored"), write="rawio:out", weight=1.1, cooldown=30.) + @state(cond=bored, write=raw_out, weight=0.6, cooldown=30.) def hello_world_roboyqa(ctx): - ctx["rawio:out"] = "Ask me something about myself!" + ctx[raw_out] = "Ask me something about myself!" - @state(cond=s("nlp:contains-roboy") & s("nlp:is-question"), read="nlp:triples", write="rawio:out") + @state(cond=contains_roboy & is_question, read=triples, write=raw_out) def roboyqa(ctx): """ answers question regarding roboy by retrieving the information out of the neo4j roboy memory graph @@ -62,7 +64,7 @@ def roboyqa(ctx): logger.error(f"Seems like you do not have my memory running, or no node with ID {ctx.conf(key=ROBOY_NODE_CONF_KEY)} exists!") return Resign() - triple = ctx["nlp:triples"][0] + triple = ctx[triples][0] category = None memory_info = None @@ -123,9 +125,9 @@ def roboyqa(ctx): memory_info = random.sample(property_list, 1)[0] if memory_info: - ctx["rawio:out"] = verbaliser.get_random_successful_answer("roboy_"+category) % memory_info + ctx[raw_out] = verbaliser.get_random_successful_answer("roboy_"+category) % memory_info elif category == "well_being": - ctx["rawio:out"] = verbaliser.get_random_successful_answer("roboy_"+category) + ctx[raw_out] = verbaliser.get_random_successful_answer("roboy_"+category) else: return Resign() diff --git a/modules/ravestate_sendpics/__init__.py b/modules/ravestate_sendpics/__init__.py index b5384d8..b9c1544 100644 --- a/modules/ravestate_sendpics/__init__.py +++ b/modules/ravestate_sendpics/__init__.py @@ -55,7 +55,7 @@ def prompt_name(ctx): cond=( s("sendpics:repeat_name", max_age=30.) | s("sendpics:face_vec:changed", max_age=30.) ) & s("nlp:tokens:changed"), - signal_name="repeat_name", + signal="repeat_name", read=("nlp:triples", "nlp:tokens", "sendpics:face_vec"), write="rawio:out", emit_detached=True) diff --git a/modules/ravestate_stalker/__init__.py b/modules/ravestate_stalker/__init__.py index c77bf12..bf09f32 100644 --- a/modules/ravestate_stalker/__init__.py +++ b/modules/ravestate_stalker/__init__.py @@ -60,7 +60,7 @@ def create_subscriber(ctx: ContextWrapper): ctx.push(subscriber_parent.id(), face_names) ctx.push(subscriber_parent.id(), rec_faces) - @state(read=(face_names.id(), rec_faces.id()), write=(rec_faces.id(), raw_out.id()), signal_name="daddy") + @state(read=(face_names.id(), rec_faces.id()), write=(rec_faces.id(), raw_out.id()), signal="daddy") def react_to_recognized_face(ctx: ContextWrapper): nonlocal face_names faces: RecognizedFaces = ctx[face_names.id()] diff --git a/modules/ravestate_ui/service.py b/modules/ravestate_ui/service.py index 66e3362..bab40a5 100644 --- a/modules/ravestate_ui/service.py +++ b/modules/ravestate_ui/service.py @@ -60,16 +60,16 @@ def parse_data(): for signal in state.constraint.signals(): # Add depended-on signals set = {} - set['source'] = signal.name + set['source'] = signal.id() # set['target'] = format_whitespace(state.name, "_", 2).replace("_", " ") set['target'] = state.name set['type'] = 'triggers' sets.append(set) - if state.signal(): + if state.signal: set = {} # set['source'] = format_whitespace(state.name, "_", 2).replace("_", " ") set['source'] = state.name - set['target'] = state.signal().name + set['target'] = state.signal.id() set['type'] = 'emits' sets.append(set) @@ -77,7 +77,7 @@ def parse_data(): for signal in property.signals(): set = {} set['source'] = str(property.id()) - set['target'] = str(signal.name) + set['target'] = str(signal.id()) set['type'] = 'sets' sets.append(set) diff --git a/modules/ravestate_ui/ui_context.py b/modules/ravestate_ui/ui_context.py index 21b5d34..747a0b9 100644 --- a/modules/ravestate_ui/ui_context.py +++ b/modules/ravestate_ui/ui_context.py @@ -21,12 +21,12 @@ def emit(self, signal, parents=None, wipe: bool=False, payload=None) -> None: self.wipe(signal) with self._lock: new_spike = Spike( - sig=signal.name, + sig=signal.id(), parents=parents, consumable_resources=set(self._properties.keys()), payload=payload) logger.debug(f"Emitting {new_spike}") - service.spike(new_spike.name()) + service.spike(new_spike.id()) self._spikes_per_signal[signal].add(new_spike) def _state_activated(self, act: Activation): diff --git a/modules/ravestate_verbaliser/__init__.py b/modules/ravestate_verbaliser/__init__.py index 51f7b59..8eace72 100644 --- a/modules/ravestate_verbaliser/__init__.py +++ b/modules/ravestate_verbaliser/__init__.py @@ -4,6 +4,7 @@ from ravestate.property import Property from ravestate.state import state from ravestate_verbaliser import verbaliser +from ravestate_rawio import output as raw_out with Module(name="verbaliser"): @@ -16,7 +17,7 @@ always_signal_changed=True, wipe_on_changed=False) - @state(read="verbaliser:intent", write="rawio:out") + @state(read=intent, write=raw_out) def react_to_intent(ctx): """ Looks for intents written to the verbaliser:intent property and diff --git a/modules/ravestate_wildtalk/__init__.py b/modules/ravestate_wildtalk/__init__.py index bed9a07..38f93fa 100644 --- a/modules/ravestate_wildtalk/__init__.py +++ b/modules/ravestate_wildtalk/__init__.py @@ -4,14 +4,14 @@ from ravestate.constraint import s from roboy_parlai import wildtalk -import ravestate_rawio +from ravestate_rawio import input as raw_in, output as raw_out with Module(name="wildtalk"): - @state(cond=s("rawio:in:changed", max_age=-1), read="rawio:in", write="rawio:out") + @state(cond=raw_in.changed_signal().max_age(-1.), read=raw_in, write=raw_out) def wildtalk_state(ctx): - text = ctx["rawio:in"] + text = ctx[raw_in] if not text: # make sure that text is not empty text = " " - ctx["rawio:out"] = wildtalk(text) + ctx[raw_out] = wildtalk(text) diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index ff50946..ffdb025 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -2,7 +2,7 @@ from ravestate.spike import Spike from ravestate.module import Module from ravestate.testfixtures import * -from ravestate.context import create_and_run_context +from ravestate.context import create_and_run_context, startup, shutdown from ravestate.config import Configuration @@ -19,7 +19,7 @@ def test_run(mocker, context_fixture): context_fixture.emit = mocker.stub() context_fixture.shutdown_flag = True context_fixture.run() - context_fixture.emit.assert_called_once_with(s(':startup')) + context_fixture.emit.assert_called_once_with(startup()) context_fixture.shutdown() @@ -43,7 +43,7 @@ def test_shutdown(mocker, context_fixture): mocker.patch.object(context_fixture._run_task, 'join') context_fixture.emit = mocker.stub(name='emit') context_fixture.shutdown() - context_fixture.emit.assert_called_once_with(s(':shutdown')) + context_fixture.emit.assert_called_once_with(shutdown()) context_fixture._run_task.join.assert_called_once() @@ -85,9 +85,9 @@ def test_remove_unknown_state(context_fixture: Context, state_fixture: State): def test_remove_state_with_signal(context_with_property_fixture: Context, state_signal_a_fixture: State): context_with_property_fixture.add_state(st=state_signal_a_fixture) - assert state_signal_a_fixture.signal() in context_with_property_fixture._needy_acts_per_state_per_signal + assert state_signal_a_fixture.signal in context_with_property_fixture._needy_acts_per_state_per_signal context_with_property_fixture.rm_state(st=state_signal_a_fixture) - assert state_signal_a_fixture.signal() not in context_with_property_fixture._needy_acts_per_state_per_signal + assert state_signal_a_fixture.signal not in context_with_property_fixture._needy_acts_per_state_per_signal def test_add_state( @@ -108,7 +108,7 @@ def test_add_state( # Make sure, that module:property:changed was added as a cause for module:a assert s(DEFAULT_PROPERTY_CHANGED) in \ - context_with_property_fixture._signal_causes[state_signal_a_fixture.signal()][0] + context_with_property_fixture._signal_causes[state_signal_a_fixture.signal][0] context_with_property_fixture.add_state(st=state_signal_b_fixture) context_with_property_fixture.add_state(st=state_signal_c_fixture) @@ -123,12 +123,12 @@ def test_add_state( if len(tuple(conj.signals())) > 2] assert len(d_conjunctions) == 2 # 2 completed assert s(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[0] - assert state_signal_a_fixture.signal() in d_conjunctions[0] + assert state_signal_a_fixture.signal in d_conjunctions[0] assert s(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[1] - assert state_signal_a_fixture.signal() in d_conjunctions[1] + assert state_signal_a_fixture.signal in d_conjunctions[1] assert \ - state_signal_b_fixture.signal() in d_conjunctions[0] and state_signal_c_fixture.signal() in d_conjunctions[1] or \ - state_signal_b_fixture.signal() in d_conjunctions[1] and state_signal_c_fixture.signal() in d_conjunctions[0] + state_signal_b_fixture.signal in d_conjunctions[0] and state_signal_c_fixture.signal in d_conjunctions[1] or \ + state_signal_b_fixture.signal in d_conjunctions[1] and state_signal_c_fixture.signal in d_conjunctions[0] # Basic specificity sanity checks assert len(list(context_with_property_fixture._states_for_signal(s(DEFAULT_PROPERTY_CHANGED)))) == 5 @@ -142,7 +142,7 @@ def test_add_state( assert len(d_acts) == 1 propchange_sig_spec = context_with_property_fixture.signal_specificity(s(DEFAULT_PROPERTY_CHANGED)) assert a_acts[0].specificity() == propchange_sig_spec - a_sig_spec = context_with_property_fixture.signal_specificity(state_signal_a_fixture.signal()) + a_sig_spec = context_with_property_fixture.signal_specificity(state_signal_a_fixture.signal) assert a_sig_spec == 1/3 assert b_acts[0].specificity() == a_sig_spec assert d_acts[0].specificity() == 1.0 @@ -159,8 +159,8 @@ def conf_st(ctx): context_with_property_fixture._config.add_conf(mod=Module(name=DEFAULT_MODULE_NAME, config={"min_age_key": 2.5, "max_age_key": 4.5})) context_with_property_fixture.add_state(st=conf_st) - assert my_cond.min_age == 2.5 - assert my_cond.max_age == 4.5 + assert my_cond.min_age_value == 2.5 + assert my_cond.max_age_value == 4.5 def test_add_state_configurable_age_not_in_config(context_with_property_fixture: Context): @@ -172,8 +172,8 @@ def conf_st(ctx): pass conf_st.module_name = DEFAULT_MODULE_NAME context_with_property_fixture.add_state(st=conf_st) - assert my_cond.min_age == 0. - assert my_cond.max_age == 5. + assert my_cond.min_age_value == 0. + assert my_cond.max_age_value == 5. def test_add_state_unknown_property(context_fixture: Context, state_fixture: State): diff --git a/test/modules/ravestate/test_context_integrated.py b/test/modules/ravestate/test_context_integrated.py index a13f238..9ee7b66 100644 --- a/test/modules/ravestate/test_context_integrated.py +++ b/test/modules/ravestate/test_context_integrated.py @@ -13,19 +13,22 @@ def test_run_with_pressure(): Property(name=DEFAULT_PROPERTY_NAME) - @state(cond=startup(), signal_name="a") + a = Signal("a") + b = Signal("b") + + @state(cond=startup(), signal=a) def signal_a(ctx): return Emit() - @state(cond=s(f"{DEFAULT_MODULE_NAME}:a"), signal_name="b") + @state(cond=a, signal=b) def signal_b(ctx): return Emit() - @state(cond=s(f"{DEFAULT_MODULE_NAME}:a"), write=DEFAULT_PROPERTY_ID) + @state(cond=a, write=DEFAULT_PROPERTY_ID) def pressuring_state(ctx): pass - @state(cond=s(f"{DEFAULT_MODULE_NAME}:a") & s(f"{DEFAULT_MODULE_NAME}:b"), write=DEFAULT_PROPERTY_ID) + @state(cond=a & b, write=DEFAULT_PROPERTY_ID) def specific_state(ctx): pass diff --git a/test/modules/ravestate/test_state.py b/test/modules/ravestate/test_state.py index dae1acd..b74b3a1 100644 --- a/test/modules/ravestate/test_state.py +++ b/test/modules/ravestate/test_state.py @@ -1,5 +1,5 @@ import pytest -from ravestate.constraint import s +from ravestate.constraint import s, Signal from ravestate.state import State, state from ravestate.wrappers import ContextWrapper @@ -7,7 +7,7 @@ @pytest.fixture def default_signal(): - return s("test-signal") + return Signal("test-signal") @pytest.fixture @@ -22,7 +22,7 @@ def default_write(): @pytest.fixture def default_triggers(): - return s(":idle") + return Signal("idle") @pytest.fixture @@ -38,7 +38,7 @@ def default_action(mocker): @pytest.fixture def under_test(default_signal, default_read, default_write, default_triggers): return State( - signal_name=default_signal, + signal=default_signal, read=default_read, write=default_write, cond=default_triggers, @@ -47,14 +47,14 @@ def under_test(default_signal, default_read, default_write, default_triggers): def test_decorator(under_test, default_signal, default_read, default_write, default_triggers, default_action): - @state(signal_name=default_signal, + @state(signal=default_signal, read=default_read, write=default_write, cond=default_triggers) def test_state(_): return "Hello world!" - assert (test_state.signal_name == under_test.signal_name) + assert (test_state.signal == under_test.signal) assert (test_state.read_props == under_test.read_props) assert (test_state.write_props == under_test.write_props) assert (test_state.constraint == under_test.constraint) @@ -64,7 +64,7 @@ def test_state(_): def test_decorator_illegal_trigger(under_test, default_signal, default_read, default_write, default_action): with pytest.raises(ValueError): - @state(signal_name=default_signal, + @state(signal=default_signal, read=default_read, write=default_write, cond=(s("rawio:in:changed") | s("facerec:face:changed")) & (s("sys:has-internet") | s("foo:poo"))) @@ -77,7 +77,7 @@ def test_decorator_default(under_test): def test_state(_): return "Hello world!" - assert (test_state.signal_name == "") + assert (test_state.signal == "") assert (test_state.read_props == ()) assert (test_state.write_props == ()) assert (test_state.constraint is None) diff --git a/test/modules/ravestate_hibye/test_hibye.py b/test/modules/ravestate_hibye/test_hibye.py index e915de6..68a6928 100644 --- a/test/modules/ravestate_hibye/test_hibye.py +++ b/test/modules/ravestate_hibye/test_hibye.py @@ -1,15 +1,16 @@ from pytest_mock import mocker +from ravestate_verbaliser import intent def test_react_to_pushed_interloc(mocker): import ravestate_hibye test_dict = {} ravestate_hibye.greeting(test_dict) - assert test_dict["verbaliser:intent"] == "greeting" + assert test_dict[intent] == "greeting" def test_react_to_popped_interloc(mocker): test_dict = {} import ravestate_hibye ravestate_hibye.farewell(test_dict) - assert test_dict["verbaliser:intent"] == "farewells" + assert test_dict[intent] == "farewells" diff --git a/test/modules/ravestate_nlp/test_preprocessing.py b/test/modules/ravestate_nlp/test_preprocessing.py index 0b426f9..5cfec3c 100644 --- a/test/modules/ravestate_nlp/test_preprocessing.py +++ b/test/modules/ravestate_nlp/test_preprocessing.py @@ -1,5 +1,6 @@ import pytest -from ravestate_nlp import nlp_preprocess +from ravestate_nlp import nlp_preprocess, tokens, postags, lemmas, tags, ner, roboy +from ravestate_rawio import input as raw_in from testfixtures import log_capture FILE_NAME = 'ravestate_nlp' @@ -7,14 +8,14 @@ @pytest.fixture def basic_input(): - return {'rawio:in': 'Hello world my name is Roboy'} + return {raw_in: 'Hello world my name is Roboy'} @log_capture() def test_tokenization(capture, basic_input): nlp_preprocess(basic_input) expected = ('hello', 'world', 'my', 'name', 'is', 'roboy') - assert basic_input["nlp:tokens"] == expected + assert basic_input[tokens] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:tokens]: {expected}")) @@ -22,7 +23,7 @@ def test_tokenization(capture, basic_input): def test_postags(capture, basic_input): nlp_preprocess(basic_input) expected = ('INTJ', 'NOUN', 'DET', 'NOUN', 'VERB', 'ADJ') - assert basic_input["nlp:postags"] == expected + assert basic_input[postags] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:postags]: {expected}")) @@ -30,7 +31,7 @@ def test_postags(capture, basic_input): def test_lemmas(capture, basic_input): nlp_preprocess(basic_input) expected = ('hello', 'world', '-PRON-', 'name', 'be', 'roboy') - assert basic_input["nlp:lemmas"] == expected + assert basic_input[lemmas] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:lemmas]: {expected}")) @@ -38,7 +39,7 @@ def test_lemmas(capture, basic_input): def test_tags(capture, basic_input): nlp_preprocess(basic_input) expected = ('UH', 'NN', 'PRP$', 'NN', 'VBZ', 'JJ') - assert basic_input["nlp:tags"] == expected + assert basic_input[tags] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:tags]: {expected}")) @@ -47,7 +48,7 @@ def test_tags(capture, basic_input): def test_ner(capture, basic_input): nlp_preprocess(basic_input) expected = (('roboy', 'ORG'),) - assert basic_input["nlp:ner"] == expected + assert basic_input[ner] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:ner]: {expected}")) @@ -55,5 +56,5 @@ def test_ner(capture, basic_input): def test_roboy(capture, basic_input): nlp_preprocess(basic_input) expected = True - assert basic_input["nlp:roboy"] == expected + assert basic_input[roboy] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:roboy]: {expected}")) diff --git a/test/modules/ravestate_persqa/test_qa.py b/test/modules/ravestate_persqa/test_qa.py index 7e0c04e..c81e53c 100644 --- a/test/modules/ravestate_persqa/test_qa.py +++ b/test/modules/ravestate_persqa/test_qa.py @@ -1,6 +1,7 @@ from ravestate_interloc import handle_single_interlocutor_input import ravestate_nlp -import ravestate_rawio +from ravestate_rawio import output as rawio_output, input as rawio_input +from ravestate_interloc import all import ravestate_persqa import ravestate_idle import ravestate_ontology @@ -19,21 +20,23 @@ def test_run_qa(): + # TODO fix testcase + assert False last_output = "" with Module(name="persqa_test"): - @state(cond=startup(), read="interloc:all") + @state(cond=startup(), read=all) def persqa_hi(ctx: ContextWrapper): ravestate_ontology.initialized.wait() handle_single_interlocutor_input(ctx, "hi") - @state(read="rawio:out") + @state(read=rawio_output) def raw_out(ctx: ContextWrapper): nonlocal last_output - last_output = ctx['rawio:out'] - logger.info(f"Output: {ctx['rawio:out']}") + last_output = ctx[rawio_output] + logger.info(f"Output: {ctx[rawio_output]}") ctx = Context( "rawio", @@ -45,9 +48,9 @@ def raw_out(ctx: ContextWrapper): "persqa_test" ) - @receptor(ctx_wrap=ctx, write="rawio:in") + @receptor(ctx_wrap=ctx, write=rawio_input) def say(ctx: ContextWrapper, what: str): - ctx["rawio:in"] = what + ctx[rawio_input] = what ctx.emit(startup()) ctx.run_once() diff --git a/test/modules/ravestate_roboyqa/test_roboyqa.py b/test/modules/ravestate_roboyqa/test_roboyqa.py index a5d54d5..3750fcf 100644 --- a/test/modules/ravestate_roboyqa/test_roboyqa.py +++ b/test/modules/ravestate_roboyqa/test_roboyqa.py @@ -1,9 +1,10 @@ from ravestate.testfixtures import * +from ravestate_nlp import triples def test_roboyqa(mocker, context_fixture, triple_fixture): mocker.patch.object(context_fixture, 'conf', will_return='test') - context_fixture._properties["nlp:triples"] = [triple_fixture] + context_fixture._properties[triples] = [triple_fixture] import ravestate_roboyqa with mocker.patch('ravestate_ontology.get_session'): ravestate_roboyqa.roboyqa(context_fixture) diff --git a/test/modules/ravestate_wildtalk/test_wildtalk.py b/test/modules/ravestate_wildtalk/test_wildtalk.py index 80ccb43..d5a0c0c 100644 --- a/test/modules/ravestate_wildtalk/test_wildtalk.py +++ b/test/modules/ravestate_wildtalk/test_wildtalk.py @@ -1,4 +1,5 @@ from pytest_mock import mocker +from ravestate_rawio import input as raw_in, output as raw_out def test_wildtalk_state(mocker): @@ -6,6 +7,6 @@ def test_wildtalk_state(mocker): mocker.patch.dict('sys.modules', {'roboy_parlai': import_mock}) wildtalk_mock = mocker.patch('roboy_parlai.wildtalk', return_value='test') import ravestate_wildtalk - test_dict = {"rawio:in": 'test'} + test_dict = {raw_in: 'test'} ravestate_wildtalk.wildtalk_state(test_dict) - assert test_dict["rawio:out"] == 'test' + assert test_dict[raw_out] == 'test' From da440df1f5699b88c85bad2e6e4654836fcd6ea1 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Fri, 10 May 2019 19:01:15 +0200 Subject: [PATCH 03/10] Introduced , changing signal/property symbols acc. to sig_/prop_ nomenclature, reducing calls to s(..) --- modules/ravestate/__init__.py | 11 ++ modules/ravestate/context.py | 49 +++-- modules/ravestate_conio/__init__.py | 22 ++- modules/ravestate_fillers/__init__.py | 19 +- modules/ravestate_genqa/__init__.py | 36 ++-- modules/ravestate_hibye/__init__.py | 27 ++- modules/ravestate_idle/__init__.py | 32 ++-- modules/ravestate_interloc/__init__.py | 48 +++-- modules/ravestate_nlp/__init__.py | 71 ++++---- modules/ravestate_ontology/__init__.py | 5 +- modules/ravestate_persqa/__init__.py | 169 +++++++++--------- .../ravestate_phrases_basic_en/__init__.py | 16 ++ modules/ravestate_rawio/__init__.py | 13 +- modules/ravestate_roboyio/__init__.py | 33 ++-- modules/ravestate_roboyqa/__init__.py | 44 ++--- modules/ravestate_ros2/__init__.py | 7 +- modules/ravestate_ros2/ros2_properties.py | 25 ++- modules/ravestate_sendpics/__init__.py | 57 +++--- modules/ravestate_stalker/__init__.py | 50 +++--- modules/ravestate_telegramio/telegram_bot.py | 50 +++--- modules/ravestate_verbaliser/__init__.py | 24 ++- modules/ravestate_wildtalk/__init__.py | 18 +- test/modules/ravestate/test_context.py | 6 +- .../ravestate/test_context_integrated.py | 6 +- test/modules/ravestate_hibye/test_hibye.py | 6 +- .../ravestate_nlp/test_preprocessing.py | 16 +- test/modules/ravestate_persqa/test_qa.py | 13 +- .../modules/ravestate_roboyqa/test_roboyqa.py | 4 +- .../ravestate_wildtalk/test_wildtalk.py | 2 +- 29 files changed, 423 insertions(+), 456 deletions(-) diff --git a/modules/ravestate/__init__.py b/modules/ravestate/__init__.py index e69de29..9e67e3d 100644 --- a/modules/ravestate/__init__.py +++ b/modules/ravestate/__init__.py @@ -0,0 +1,11 @@ +from .activation import * +from .argparser import * +from .causal import * +from .config import * +from .constraint import * +from .context import * +from .module import * +from .property import * +from .receptor import * +from .spike import * +from .state import * diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 81c41fd..769f164 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -22,35 +22,26 @@ from reggol import get_logger logger = get_logger(__name__) -_startup_signal = Signal(":startup") -_shutdown_signal = Signal(":shutdown") +""" +The startup signal, which is fired once when `Context.run()` is executed.
+__Hint:__ All key-word arguments of #constraint.s(...) + (`min_age`, `max_age`, `detached`) are supported. +""" +sig_startup = Signal(":startup") - -# TODO do startup and shutdown have to be methods? -# Maybe put them so that they can be imported with from ravestate import startup? -def startup(**kwargs) -> Signal: - """ - Obtain the startup signal, which is fired once when `Context.run()` is executed.
- __Hint:__ All key-word arguments of #constraint.s(...) - (`min_age`, `max_age`, `detached`) are supported. - """ - return _startup_signal - - -def shutdown(**kwargs) -> Signal: - """ - Obtain the shutdown signal, which is fired once when `Context.shutdown()` is called.
- __Hint:__ All key-word arguments of #constraint.s(...) - (`min_age`, `max_age`, `detached`) are supported. - """ - return _shutdown_signal +""" +Obtain the shutdown signal, which is fired once when `Context.shutdown()` is called.
+__Hint:__ All key-word arguments of #constraint.s(...) + (`min_age`, `max_age`, `detached`) are supported. +""" +sig_shutdown = Signal(":shutdown") -pressure_property = Property(name="pressure", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, - default_value=False, always_signal_changed=False, is_flag_property=True) -activity_property = Property(name="activity", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, - default_value=0, always_signal_changed=False) +prop_pressure = Property(name="pressure", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, + default_value=False, always_signal_changed=False, is_flag_property=True) +prop_activity = Property(name="activity", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, + default_value=0, always_signal_changed=False) def create_and_run_context(*args, runtime_overrides=None): """ @@ -86,8 +77,8 @@ def __eq__(self, other): class Context(IContext): - _default_signals: Tuple[Signal] = (startup(), shutdown()) - _default_properties: Tuple[Property] = (activity_property, pressure_property) + _default_signals: Tuple[Signal] = (sig_startup, sig_shutdown) + _default_properties: Tuple[Property] = (prop_activity, prop_pressure) core_module_name = "core" import_modules_config = "import" @@ -231,7 +222,7 @@ def run(self) -> None: return self._run_task = Thread(target=self._run_loop) self._run_task.start() - self.emit(startup()) + self.emit(sig_startup) def shutting_down(self) -> bool: """ @@ -244,7 +235,7 @@ def shutdown(self) -> None: Sets the shutdown flag and waits for the signal processing thread to join. """ self._shutdown_flag.set() - self.emit(shutdown()) + self.emit(sig_shutdown) self._run_task.join() def add_module(self, module_name: str) -> None: diff --git a/modules/ravestate_conio/__init__.py b/modules/ravestate_conio/__init__.py index 21bdf6e..b194cd1 100644 --- a/modules/ravestate_conio/__init__.py +++ b/modules/ravestate_conio/__init__.py @@ -1,24 +1,22 @@ -from ravestate.module import Module -from ravestate.constraint import s -from ravestate.state import state -from ravestate.wrappers import ContextWrapper -from ravestate_interloc import handle_single_interlocutor_input, all as interloc_all -from ravestate_rawio import output as raw_out -from ravestate.context import startup + +import ravestate as rs + +import ravestate_rawio as rawio +import ravestate_interloc as interloc from reggol import get_logger logger = get_logger(__name__) -with Module(name="consoleio"): +with rs.Module(name="consoleio"): - @state(cond=startup(), read=interloc_all) - def console_input(ctx: ContextWrapper): + @rs.state(cond=rs.sig_startup, read=interloc.prop_all) + def console_input(ctx: rs.ContextWrapper): while not ctx.shutting_down(): input_value = input("> ") - handle_single_interlocutor_input(ctx, input_value) + interloc.handle_single_interlocutor_input(ctx, input_value) - @state(read=raw_out) + @rs.state(read=rawio.prop_out) def console_output(ctx): print(ctx["rawio:out:changed"]) diff --git a/modules/ravestate_fillers/__init__.py b/modules/ravestate_fillers/__init__.py index dbe3b2c..0f243a4 100644 --- a/modules/ravestate_fillers/__init__.py +++ b/modules/ravestate_fillers/__init__.py @@ -1,15 +1,12 @@ -from ravestate.module import Module -from ravestate.wrappers import ContextWrapper -from ravestate.constraint import s -from ravestate.state import state +import ravestate as rs -from ravestate_idle import impatient -from ravestate_verbaliser import intent -import ravestate_phrases_basic_en +from ravestate_idle import sig_impatient +from ravestate_verbaliser import prop_intent +import ravestate_phrases_basic_en as lang -with Module(name="fillers"): +with rs.Module(name="fillers"): - @state(cond=impatient, write=intent) - def impatient_fillers(ctx: ContextWrapper): - ctx[intent] = "fillers" + @rs.state(cond=sig_impatient, write=prop_intent) + def impatient_fillers(ctx: rs.ContextWrapper): + ctx[prop_intent] = lang.intent_fillers diff --git a/modules/ravestate_genqa/__init__.py b/modules/ravestate_genqa/__init__.py index b88988b..1a57529 100644 --- a/modules/ravestate_genqa/__init__.py +++ b/modules/ravestate_genqa/__init__.py @@ -1,14 +1,10 @@ -from ravestate.module import Module -from ravestate.state import state, Delete -from ravestate_verbaliser import verbaliser -from ravestate.constraint import s -from ravestate.context import startup - +import ravestate as rs import requests -from ravestate_idle import bored -from ravestate_rawio import input as raw_in, output as raw_out -from ravestate_nlp import is_question +import ravestate_idle as idle +import ravestate_rawio as rawio +import ravestate_nlp as nlp +import ravestate_verbaliser as verbaliser from reggol import get_logger logger = get_logger(__name__) @@ -22,22 +18,22 @@ SERVER_AVAILABLE_CODE = 200 -with Module(name="genqa", config=CONFIG): +with rs.Module(name="genqa", config=CONFIG): - @state(cond=startup()) + @rs.state(cond=rs.sig_startup) def hello_world_genqa(ctx): server = ctx.conf(key=DRQA_SERVER_ADDRESS) if not server: logger.error('Server address is not set. Shutting down GenQA.') - return Delete() + return rs.Delete() if not server_up(server): - return Delete() + return rs.Delete() - @state(cond=bored, write=raw_out, weight=1.15, cooldown=30.) + @rs.state(cond=idle.sig_bored, write=rawio.prop_out, weight=1.15, cooldown=30.) def prompt(ctx): - ctx[raw_out] = verbaliser.get_random_phrase("question-answering-prompt") + ctx[rawio.prop_out] = verbaliser.get_random_phrase("question-answering-prompt") - @state(cond=is_question, read=raw_in, write=raw_out) + @rs.state(cond=nlp.sig_is_question, read=rawio.prop_in, write=rawio.prop_out) def drqa_module(ctx): """ general question answering using DrQA through a HTTP server @@ -47,18 +43,18 @@ def drqa_module(ctx): """ server = ctx.conf(key=DRQA_SERVER_ADDRESS) if not server_up(server): - return Delete(resign=True) - params = {'question': str(ctx[raw_in]).lower()} + return rs.Delete(resign=True) + params = {'question': str(ctx[rawio.prop_in]).lower()} response = requests.get(server, params=params) response_json = response.json() certainty = response_json["answers"][0]["span_score"] # sane answer if certainty > ctx.conf(key=ROBOY_ANSWER_SANITY): - ctx[raw_out] = verbaliser.get_random_phrase("question-answering-starting-phrases") + " " + \ + ctx[rawio.prop_out] = verbaliser.get_random_phrase("question-answering-starting-phrases") + " " + \ response_json["answers"][0]["span"] # insane/unsure answer else: - ctx[raw_out] = verbaliser.get_random_phrase("unsure-question-answering-phrases") \ + ctx[rawio.prop_out] = verbaliser.get_random_phrase("unsure-question-answering-phrases") \ % response_json["answers"][0]["span"] \ + "\n" + "Maybe I can find out more if your rephrase the question for me." diff --git a/modules/ravestate_hibye/__init__.py b/modules/ravestate_hibye/__init__.py index 1aa4b3a..b53a6a0 100644 --- a/modules/ravestate_hibye/__init__.py +++ b/modules/ravestate_hibye/__init__.py @@ -1,19 +1,16 @@ -from ravestate.state import state -from ravestate.module import Module -from ravestate.constraint import s -from ravestate.wrappers import ContextWrapper +import ravestate as rs +from ravestate_verbaliser import prop_intent -from ravestate_verbaliser import intent -import ravestate_phrases_basic_en -from ravestate_interloc import all as interloc_all -from ravestate_rawio import input as raw_in +import ravestate_phrases_basic_en as lang +from ravestate_interloc import prop_all as interloc_all +from ravestate_rawio import prop_in -with Module(name="hibye"): +with rs.Module(name="hibye"): - @state(cond=interloc_all.pushed_signal() & raw_in.changed_signal(), write=intent) - def greeting(ctx: ContextWrapper): - ctx[intent] = "greeting" + @rs.state(cond=interloc_all.pushed_signal() & prop_in.changed_signal(), write=prop_intent) + def greeting(ctx: rs.ContextWrapper): + ctx[prop_intent] = lang.intent_greeting - @state(cond=interloc_all.popped_signal() & raw_in.changed_signal(), write=intent) - def farewell(ctx: ContextWrapper): - ctx[intent] = "farewells" + @rs.state(cond=interloc_all.popped_signal() & prop_in.changed_signal(), write=prop_intent) + def farewell(ctx: rs.ContextWrapper): + ctx[prop_intent] = lang.intent_farewells diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index aac8169..4ba200c 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -1,8 +1,4 @@ -from ravestate.module import Module -from ravestate.constraint import ConfigurableAge, Signal, s -from ravestate.wrappers import ContextWrapper -from ravestate.state import state, Emit -from ravestate.context import activity_property, pressure_property +import ravestate as rs from reggol import get_logger logger = get_logger(__name__) @@ -15,22 +11,22 @@ BORED_THRESHOLD_CONFIG_KEY: 1.0 } -with Module(name="idle", config=CONFIG): +with rs.Module(name="idle", config=CONFIG): - impatient = Signal(name="impatient") - bored = Signal(name="bored") + sig_impatient = rs.Signal(name="impatient") + sig_bored = rs.Signal(name="bored") - @state(cond=activity_property.changed_signal().min_age(ConfigurableAge(key=BORED_THRESHOLD_CONFIG_KEY)).max_age(-1), - read=activity_property, signal=bored) - def am_i_bored(ctx: ContextWrapper): + @rs.state(cond=rs.prop_activity.changed_signal().min_age(rs.ConfigurableAge(key=BORED_THRESHOLD_CONFIG_KEY)).max_age(-1), + read=rs.prop_activity, signal=sig_bored) + def am_i_bored(ctx: rs.ContextWrapper): """ Emits idle:bored signal if no states are currently partially fulfilled """ - if ctx[activity_property] == 0: - return Emit(wipe=True) + if ctx[rs.prop_activity] == 0: + return rs.Emit(wipe=True) - @state(cond=pressure_property.flag_true_signal(). - min_age(ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY)).max_age(-1.), - signal=impatient) - def am_i_impatient(ctx: ContextWrapper): - return Emit(wipe=True) + @rs.state(cond=rs.prop_pressure.flag_true_signal(). + min_age(rs.ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY)).max_age(-1.), + signal=sig_impatient) + def am_i_impatient(ctx: rs.ContextWrapper): + return rs.Emit(wipe=True) diff --git a/modules/ravestate_interloc/__init__.py b/modules/ravestate_interloc/__init__.py index 22d556f..7500ba0 100644 --- a/modules/ravestate_interloc/__init__.py +++ b/modules/ravestate_interloc/__init__.py @@ -1,12 +1,8 @@ -from ravestate.property import Property -from ravestate.module import Module -from ravestate.wrappers import ContextWrapper -from ravestate.receptor import receptor - -from ravestate_rawio import input as raw_in -from ravestate_verbaliser.verbaliser import get_phrase_list -import ravestate_phrases_basic_en -import ravestate_ontology +import ravestate as rs +import ravestate_rawio as rawio +import ravestate_verbaliser as verbaliser +import ravestate_phrases_basic_en as lang +import ravestate_ontology as mem from scientio.ontology.node import Node from scientio.session import Session @@ -16,13 +12,13 @@ logger = get_logger(__name__) -with Module(name="interloc"): +with rs.Module(name="interloc"): # TODO: Make interloc:all a special property type, that only accepts ScientioNodeProperty as children - all = Property(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True) + prop_all = rs.Property(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True) -def handle_single_interlocutor_input(ctx: ContextWrapper, input_value: str, id="anonymous_interlocutor") -> None: +def handle_single_interlocutor_input(ctx: rs.ContextWrapper, input_value: str, id="anonymous_interlocutor") -> None: """ Forwards input to `rawio:in` and manages creation/deletion of a singleton interlocutor. A new interlocutor node is pushed, when the input is a greeting, @@ -38,31 +34,31 @@ def handle_single_interlocutor_input(ctx: ContextWrapper, input_value: str, id=" the interlocutor's Neo4j node (until a proper name is set by persqa). """ - @receptor(ctx_wrap=ctx, write=raw_in) + @rs.receptor(ctx_wrap=ctx, write=rawio.prop_in) def write_input(ctx_input, value: str): - ctx_input[raw_in] = value + ctx_input[rawio.prop_in] = value - @receptor(ctx_wrap=ctx, write=all) - def push_interloc(ctx: ContextWrapper, interlocutor_node: Node): - if ctx.push(parent_property_or_path=all, - child=Property(name=id, default_value=interlocutor_node)): + @rs.receptor(ctx_wrap=ctx, write=prop_all) + def push_interloc(ctx: rs.ContextWrapper, interlocutor_node: Node): + if ctx.push( + parent_property_or_path=prop_all, + child=rs.Property(name=id, default_value=interlocutor_node)): logger.debug(f"Pushed {interlocutor_node} to interloc:all") - @receptor(ctx_wrap=ctx, write=all) - def pop_interloc(ctx: ContextWrapper): + @rs.receptor(ctx_wrap=ctx, write=prop_all) + def pop_interloc(ctx: rs.ContextWrapper): if ctx.pop(f"interloc:all:{id}"): logger.debug(f"Popped interloc:all:{id}") write_input(input_value) - interloc_exists = f"interloc:all:{id}" in ctx.enum(all) + interloc_exists = f"interloc:all:{id}" in ctx.enum(prop_all) # push Node if you got a greeting - if input_value.strip() in get_phrase_list("greeting") and not interloc_exists: + if input_value.strip() in verbaliser.get_phrase_list(lang.intent_greeting) and not interloc_exists: # set up scientio - ravestate_ontology.initialized.wait() - sess: Session = ravestate_ontology.get_session() - onto: Ontology = ravestate_ontology.get_ontology() + mem.initialized.wait() + onto: Ontology = mem.get_ontology() # create scientio Node of type Person new_interloc = Node(metatype=onto.get_type("Person")) @@ -70,5 +66,5 @@ def pop_interloc(ctx: ContextWrapper): push_interloc(new_interloc) # pop Node if you got a farewell - elif input_value.strip() in get_phrase_list("farewells") and interloc_exists: + elif input_value.strip() in verbaliser.get_phrase_list("farewells") and interloc_exists: pop_interloc() diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index ddf2371..1dc0af1 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -1,9 +1,6 @@ +import ravestate as rs -from ravestate.module import Module -from ravestate.property import Property -from ravestate.state import state, Emit -from ravestate.constraint import s, Signal -from ravestate_rawio import input as raw_in +import ravestate_rawio as rawio from ravestate_nlp.question_word import QuestionWord from ravestate_nlp.triple import Triple from ravestate_nlp.extract_triples import extract_triples @@ -42,80 +39,78 @@ def roboy_getter(doc) -> bool: spacy_nlp_en = init_spacy() -with Module(name="nlp"): +with rs.Module(name="nlp"): - tokens = Property(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - postags = Property(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - lemmas = Property(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - tags = Property(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - ner = Property(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - triples = Property(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - roboy = Property(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - yesno = Property(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_tokens = rs.Property(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + porp_postags = rs.Property(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_lemmas = rs.Property(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_tags = rs.Property(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_ner = rs.Property(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_triples = rs.Property(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_roboy = rs.Property(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + prop_yesno = rs.Property(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - contains_roboy = Signal(name="contains-roboy") - is_question = Signal(name="is-question") - intent_play = Signal(name="intent-play") + sig_contains_roboy = rs.Signal(name="contains-roboy") + sig_is_question = rs.Signal(name="is-question") + sig_intent_play = rs.Signal(name="intent-play") - @state(read=raw_in, write=(tokens, postags, lemmas, tags, ner, triples, roboy, yesno)) + @rs.state(read=rawio.prop_in, write=(prop_tokens, porp_postags, prop_lemmas, prop_tags, prop_ner, prop_triples, prop_roboy, prop_yesno)) def nlp_preprocess(ctx): - text = ctx[raw_in] + text = ctx[rawio.prop_out] if not text: return False text = text.lower() nlp_doc = spacy_nlp_en(text) nlp_tokens = tuple(str(token) for token in nlp_doc) - ctx[tokens] = nlp_tokens + ctx[prop_tokens] = nlp_tokens logger.info(f"[NLP:tokens]: {nlp_tokens}") nlp_postags = tuple(str(token.pos_) for token in nlp_doc) - ctx[postags] = nlp_postags + ctx[porp_postags] = nlp_postags logger.info(f"[NLP:postags]: {nlp_postags}") nlp_lemmas = tuple(str(token.lemma_) for token in nlp_doc) - ctx[lemmas] = nlp_lemmas + ctx[prop_lemmas] = nlp_lemmas logger.info(f"[NLP:lemmas]: {nlp_lemmas}") nlp_tags = tuple(str(token.tag_) for token in nlp_doc) - ctx[tags] = nlp_tags + ctx[prop_tags] = nlp_tags logger.info(f"[NLP:tags]: {nlp_tags}") nlp_ner = tuple((str(ents.text), str(ents.label_)) for ents in nlp_doc.ents) - ctx[ner] = nlp_ner + ctx[prop_ner] = nlp_ner logger.info(f"[NLP:ner]: {nlp_ner}") nlp_triples = nlp_doc._.triples - ctx[triples] = nlp_triples + ctx[prop_triples] = nlp_triples logger.info(f"[NLP:triples]: {nlp_triples}") nlp_roboy = nlp_doc._.about_roboy - ctx[roboy] = nlp_roboy + ctx[prop_roboy] = nlp_roboy logger.info(f"[NLP:roboy]: {nlp_roboy}") nlp_yesno = nlp_doc._.yesno - ctx[yesno] = nlp_yesno + ctx[prop_yesno] = nlp_yesno logger.info(f"[NLP:yesno]: {nlp_yesno}") - - @state(signal=contains_roboy, read=roboy) + @rs.state(signal=sig_contains_roboy, read=prop_roboy) def nlp_contains_roboy_signal(ctx): - if ctx[roboy]: - return Emit() + if ctx[prop_roboy]: + return rs.Emit() return False - - @state(signal=is_question, read=triples) + @rs.state(signal=sig_is_question, read=prop_triples) def nlp_is_question_signal(ctx): - if ctx[triples][0].is_question(): - return Emit() + if ctx[prop_triples][0].is_question(): + return rs.Emit() return False - @state(signal=intent_play, read=triples) + @rs.state(signal=sig_intent_play, read=prop_triples) def nlp_intent_play_signal(ctx): - nlp_triples = ctx[triples] + nlp_triples = ctx[prop_triples] if nlp_triples[0].match_either_lemma(pred={"play"}, obj={"game"}): - return Emit() + return rs.Emit() return False diff --git a/modules/ravestate_ontology/__init__.py b/modules/ravestate_ontology/__init__.py index 71e6145..f3719a9 100644 --- a/modules/ravestate_ontology/__init__.py +++ b/modules/ravestate_ontology/__init__.py @@ -1,6 +1,6 @@ from ravestate.module import Module from ravestate.state import state -from ravestate.context import startup +from ravestate.context import sig_startup from scientio.ontology.ontology import Ontology from scientio.session import Session @@ -16,6 +16,7 @@ onto = None sess = None initialized: Event = Event() + NEO4J_ADDRESS_KEY: str = "neo4j_address" NEO4J_USERNAME_KEY: str = "neo4j_username" NEO4J_PASSWORD_KEY: str = "neo4j_pw" @@ -27,7 +28,7 @@ with Module(name="ontology", config=CONFIG): - @state(cond=startup()) + @state(cond=sig_startup) def hello_world_ontology(ctx): """ Creates a scientio session with neo4j backend. diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py index 44bccc4..0677f41 100644 --- a/modules/ravestate_persqa/__init__.py +++ b/modules/ravestate_persqa/__init__.py @@ -1,10 +1,10 @@ -from ravestate.module import Module -from ravestate.property import Property -from ravestate.wrappers import ContextWrapper -from ravestate.state import state, Emit, Delete, Resign -from ravestate.constraint import s, Signal -from ravestate_verbaliser import verbaliser -import ravestate_ontology +import ravestate as rs +import ravestate_verbaliser as verbaliser +import ravestate_ontology as mem +import ravestate_idle as idle +import ravestate_rawio as rawio +import ravestate_nlp as nlp +import ravestate_interloc as interloc from os.path import realpath, dirname, join from typing import Dict, Set @@ -14,14 +14,10 @@ from scientio.ontology.ontology import Ontology from scientio.session import Session from scientio.ontology.node import Node + from reggol import get_logger logger = get_logger(__name__) -from ravestate_idle import bored -from ravestate_rawio import input as raw_in, output as raw_out -from ravestate_nlp import triples, yesno, tokens -from ravestate_interloc import all as interloc_all - verbaliser.add_folder(join(dirname(realpath(__file__)), "persqa_phrases")) PREDICATE_SET = {"FROM", "HAS_HOBBY", "LIVE_IN", "FRIEND_OF", "STUDY_AT", "MEMBER_OF", "WORK_FOR", "OCCUPIED_AS"} @@ -40,16 +36,16 @@ # TODO "EQUALS" -with Module(name="persqa") as mod: +with rs.Module(name="persqa") as mod: # This is a nice demo of using properties as synchronization # primitives. The problem: inference must only run for inputs # that did not trigger new_interloc. Therefore, new_interloc and inference # are mutually exclusive. This is enfored by having both of them # consume the inference_mutex property. - inference_mutex = Property(name="inference_mutex") + prop_inference_mutex = rs.Property(name="inference_mutex") - subject = Property( + prop_subject = rs.Property( name="subject", default_value="", always_signal_changed=True, @@ -57,7 +53,7 @@ allow_push=False, is_flag_property=True) - predicate = Property( + prop_predicate = rs.Property( name="predicate", default_value="", always_signal_changed=True, @@ -65,7 +61,7 @@ allow_push=False, is_flag_property=True) - answer = Property( + prop_answer = rs.Property( name="answer", default_value="", always_signal_changed=True, @@ -73,14 +69,14 @@ allow_push=False, is_flag_property=True) - follow_up_prop = Property( + prop_follow_up_prop = rs.Property( name="follow_up", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - follow_up_signal = Signal(name="follow-up") + sig_follow_up = rs.Signal(name="follow-up") def find_empty_relationship(dictonary: Dict): for key in dictonary: @@ -89,7 +85,7 @@ def find_empty_relationship(dictonary: Dict): return None def retrieve_or_create_node(node: Node): - sess: Session = ravestate_ontology.get_session() + sess: Session = mem.get_session() node_list = sess.retrieve(node) if not node_list: node = sess.create(node) @@ -97,138 +93,143 @@ def retrieve_or_create_node(node: Node): node = node_list[0] return node - def create_small_talk_states(ctx: ContextWrapper, interloc_path: str): + def create_small_talk_states(ctx: rs.ContextWrapper, interloc_path: str): used_follow_up_preds = set() - @state(cond=bored, - write=(raw_out, predicate, subject), - read=(interloc_path, predicate), - weight=1.2, - cooldown=40., - emit_detached=True, - signal=follow_up_signal) - def small_talk(ctx: ContextWrapper): - sess: Session = ravestate_ontology.get_session() + @rs.state( + cond=idle.sig_bored, + write=(rawio.prop_out, prop_predicate, prop_subject), + read=(interloc_path, prop_predicate), + weight=1.2, + cooldown=40., + emit_detached=True, + signal=sig_follow_up) + def small_talk(ctx: rs.ContextWrapper): + sess: Session = mem.get_session() interloc: Node = ctx[interloc_path] if interloc.get_id() < 0: # ask for name, if the interlocutor is not (yet) a persistent instance pred = "NAME" else: pred = find_empty_relationship(interloc.get_relationships()) - ctx[subject] = interloc_path - if not ctx[predicate]: + ctx[prop_subject] = interloc_path + if not ctx[prop_predicate]: if pred: logger.info(f"Personal question: intent={pred}") - ctx[predicate] = pred - ctx[raw_out] = verbaliser.get_random_question(pred) + ctx[prop_predicate] = pred + ctx[rawio.prop_out] = verbaliser.get_random_question(pred) else: unused_fup_preds = PREDICATE_SET.difference(used_follow_up_preds) if not unused_fup_preds: logger.info(f"Ran out of smalltalk predicates for {interloc_path}, committing suicide...") - return Delete(resign=True) + return rs.Delete(resign=True) pred = random.sample(PREDICATE_SET.difference(used_follow_up_preds), 1) pred = pred[0] used_follow_up_preds.add(pred) - ctx[predicate] = pred + ctx[prop_predicate] = pred relationship_ids: Set[int] = interloc.get_relationships(pred) if len(relationship_ids) > 0: # Just to be safe ... object_node_list = sess.retrieve(node_id=list(relationship_ids)[0]) if len(object_node_list) > 0: - ctx[raw_out] = verbaliser.get_random_followup_question(pred).format( + ctx[rawio.prop_out] = verbaliser.get_random_followup_question(pred).format( name=interloc.get_name(), obj=object_node_list[0].get_name()) logger.info(f"Follow-up: intent={pred}") - return Emit() - return Resign() + return rs.Emit() + return rs.Resign() else: # While the predicate is set, repeat the question. Once the predicate is answered, # it will be set to None, such that a new predicate is entered. - ctx[raw_out] = verbaliser.get_random_question(ctx[predicate]) + ctx[rawio.prop_out] = verbaliser.get_random_question(ctx[prop_predicate]) - @state(cond=follow_up_signal.max_age(-1.) & triples.changed_signal(), - write=(raw_out, predicate, inference_mutex), - read=(interloc_path, predicate, yesno)) - def fup_react(ctx: ContextWrapper): - sess: Session = ravestate_ontology.get_session() + @rs.state( + cond=sig_follow_up.max_age(-1.) & nlp.prop_triples.changed_signal(), + write=(rawio.prop_out, prop_predicate, prop_inference_mutex), + read=(interloc_path, prop_predicate, nlp.prop_yesno)) + def fup_react(ctx: rs.ContextWrapper): + sess: Session = mem.get_session() subject_node: Node = ctx[interloc_path] - pred = ctx[predicate] + pred = ctx[prop_predicate] object_node_list = [] relationship_ids: Set[int] = subject_node.get_relationships(pred) if len(relationship_ids) > 0: object_node_list = sess.retrieve(node_id=list(relationship_ids)[0]) if len(object_node_list) > 0: - ctx[raw_out] = verbaliser.get_random_followup_answer(pred).format( + ctx[rawio.prop_out] = verbaliser.get_random_followup_answer(pred).format( name=subject_node.get_name(), obj=object_node_list[0].get_name()) else: - ctx[raw_out] = "Oh, I see!" - ctx[predicate] = None + ctx[rawio.prop_out] = "Oh, I see!" + ctx[prop_predicate] = None ctx.add_state(small_talk) ctx.add_state(fup_react) - @state(cond=raw_in.changed_signal() & interloc_all.pushed_signal(), - write=inference_mutex, - read=interloc_all, - emit_detached=True) - def new_interloc(ctx: ContextWrapper): + @rs.state( + cond=rawio.prop_in.changed_signal() & interloc.prop_all.pushed_signal(), + write=prop_inference_mutex, + read=interloc.prop_all) + def new_interloc(ctx: rs.ContextWrapper): """ reacts to interloc:pushed and creates persqa:ask_name state """ - interloc_path = ctx[interloc_all.pushed_signal()] + interloc_path = ctx[interloc.prop_all.pushed_signal()] create_small_talk_states(ctx=ctx, interloc_path=interloc_path) - @state(cond=raw_in.changed_signal() & interloc_all.popped_signal(), - write=(inference_mutex, predicate, subject)) - def removed_interloc(ctx: ContextWrapper): + @rs.state( + cond=rawio.prop_in.changed_signal() & interloc.prop_all.popped_signal(), + write=(prop_inference_mutex, prop_predicate, prop_subject)) + def removed_interloc(ctx: rs.ContextWrapper): """ reacts to interloc:popped and makes sure that """ - ctx[subject] = None - ctx[predicate] = None + ctx[prop_subject] = None + ctx[prop_predicate] = None - @state(cond=triples.changed_signal(), - write=(answer, inference_mutex), - read=(predicate, triples, tokens, yesno)) - def inference(ctx: ContextWrapper): + @rs.state( + cond=nlp.prop_triples.changed_signal(), + write=(prop_answer, prop_inference_mutex), + read=(prop_predicate, nlp.prop_triples, nlp.prop_tokens, nlp.prop_yesno)) + def inference(ctx: rs.ContextWrapper): """ recognizes name in sentences like: - i am jj - my name toseban - dino """ - triple = ctx[triples][0] + triple = ctx[nlp.prop_triples][0] if triple.is_question(): - return Resign() - pred = ctx[predicate] + return rs.Resign() + pred = ctx[prop_predicate] answer_str = None if pred == "NAME" or pred in PREDICATE_SET: # TODO City, Country -> NLP NER also only recognizes locations... if triple.has_object(): answer_str = triple.get_object().text - elif len(ctx[tokens]) == 1: - answer_str = ctx[tokens][0] - elif len(ctx[tokens]) == 2: - answer_str = "%s %s" % (ctx[tokens][0], ctx[tokens][1]) + elif len(ctx[nlp.prop_tokens]) == 1: + answer_str = ctx[nlp.prop_tokens][0] + elif len(ctx[nlp.prop_tokens]) == 2: + answer_str = "%s %s" % (ctx[nlp.prop_tokens][0], ctx[nlp.prop_tokens][1]) if answer_str: logger.debug(f"Inference: extracted answer '{answer_str}' for predicate {pred}") - ctx[answer] = answer_str + ctx[prop_answer] = answer_str - @state(cond=answer.changed_signal(), - write=(raw_out, predicate), - read=(predicate, subject, answer, interloc_all)) - def react(ctx: ContextWrapper): + @rs.state( + cond=prop_answer.changed_signal(), + write=(rawio.prop_out, prop_predicate), + read=(prop_predicate, prop_subject, prop_answer, interloc.prop_all)) + def react(ctx: rs.ContextWrapper): """ Retrieves memory node with the name, or creates a new one outputs a polite response. """ - onto: Ontology = ravestate_ontology.get_ontology() - sess: Session = ravestate_ontology.get_session() - inferred_answer = ctx[answer] - pred = ctx[predicate] - subject_path: str = ctx[subject] + onto: Ontology = mem.get_ontology() + sess: Session = mem.get_session() + inferred_answer = ctx[prop_answer] + pred = ctx[prop_predicate] + subject_path: str = ctx[prop_subject] if not subject_path: - return Resign() + return rs.Resign() subject_node: Node = ctx[subject_path] assert inferred_answer @@ -251,9 +252,9 @@ def react(ctx: ContextWrapper): subject_node.add_relationships({pred: {relationship_node.get_id()}}) sess.update(subject_node) - ctx[raw_out] = verbaliser.get_random_successful_answer(pred).format( + ctx[rawio.prop_out] = verbaliser.get_random_successful_answer(pred).format( name=subject_node.get_name(), obj=inferred_answer) - ctx[predicate] = None + ctx[prop_predicate] = None diff --git a/modules/ravestate_phrases_basic_en/__init__.py b/modules/ravestate_phrases_basic_en/__init__.py index 037e77e..47471a0 100644 --- a/modules/ravestate_phrases_basic_en/__init__.py +++ b/modules/ravestate_phrases_basic_en/__init__.py @@ -3,3 +3,19 @@ from os.path import realpath, dirname, join verbaliser.add_folder(join(dirname(realpath(__file__)), "en")) + +intent_qa_start = "question-answering-starting-phrases" +intent_qa_unsure = "unsure-question-answering-phrases" +intent_avoid_answer = "segue-avoid-answer" +intent_consent = "consent" +intent_distract = "distract" +intent_facts = "facts" +intent_farewells = "farewells" +intent_fillers = "fillers" +intent_flattery = "flattery" +intent_greeting = "greeting" +intent_jokes = "jokes" +intent_offer_fact = "offer-fact" +intent_prompt_qa = "question-answering-prompt" +intent_topic_change = "topicchange" +intent_unclear = "unclear" diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index 836c0d0..5502fb3 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -1,18 +1,15 @@ +import ravestate as rs -from ravestate.module import Module -from ravestate.property import Property +with rs.Module(name="rawio"): - -with Module(name="rawio"): - - input = Property( + prop_in = rs.Property( name="in", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True) - output = Property( + prop_out = rs.Property( name="out", default_value="", allow_pop=False, @@ -20,7 +17,7 @@ always_signal_changed=True, wipe_on_changed=False) - pic_in = Property( + prop_pic_in = rs.Property( name="pic_in", default_value=None, allow_pop=False, diff --git a/modules/ravestate_roboyio/__init__.py b/modules/ravestate_roboyio/__init__.py index dd979b9..bd7e7b2 100644 --- a/modules/ravestate_roboyio/__init__.py +++ b/modules/ravestate_roboyio/__init__.py @@ -1,15 +1,12 @@ -from ravestate.module import Module -from ravestate.state import state -from ravestate.wrappers import ContextWrapper -from ravestate_interloc import handle_single_interlocutor_input -from ravestate_ros2.ros2_properties import Ros2SubProperty -from ravestate.context import startup -import ravestate_ros2 +import ravestate as rs +import ravestate_interloc as interloc +import ravestate_ros2 as ros2 from unidecode import unidecode +from time import sleep + from reggol import get_logger logger = get_logger(__name__) -from time import sleep PYROBOY_AVAILABLE = False try: @@ -31,30 +28,30 @@ if PYROBOY_AVAILABLE: - ravestate_ros2.set_node_once(node) + ros2.set_node_once(node) - with Module(name="roboyio"): + with rs.Module(name="roboyio"): - recognized_speech = Ros2SubProperty( + recognized_speech = ros2.Ros2SubProperty( name="recognized_speech", topic="/roboy/cognition/speech/recognition", msg_type=RecognizedSpeech, always_signal_changed=True) - @state(cond=recognized_speech.changed_signal(), read=("interloc:all", recognized_speech.id())) - def roboy_input(ctx: ContextWrapper): - result = ctx[recognized_speech.id()] + @rs.state(cond=recognized_speech.changed_signal(), read=(interloc.prop_all, recognized_speech)) + def roboy_input(ctx: rs.ContextWrapper): + result = ctx[recognized_speech] if result: - handle_single_interlocutor_input(ctx, result.text) + interloc.handle_single_interlocutor_input(ctx, result.text) - # @state(cond=startup(), read="interloc:all") + # @rs.state(cond=startup(), read=interloc.prop_all) # def roboy_input(ctx: ContextWrapper): # while not ctx.shutting_down(): # result = listen() # if result: - # handle_single_interlocutor_input(ctx, result) + # interloc.handle_single_interlocutor_input(ctx, result) - @state(read="rawio:out") + @rs.state(read="rawio:out") def roboy_output(ctx): ret = say(unidecode(ctx["rawio:out:changed"])) logger.info(f"pyroboy.say() -> {ret}") diff --git a/modules/ravestate_roboyqa/__init__.py b/modules/ravestate_roboyqa/__init__.py index 9f40c95..2188f1b 100644 --- a/modules/ravestate_roboyqa/__init__.py +++ b/modules/ravestate_roboyqa/__init__.py @@ -1,12 +1,9 @@ -from ravestate.module import Module -from ravestate.state import state, Resign -from ravestate.constraint import s, ConfigurableAge -from ravestate_nlp.question_word import QuestionWord +import ravestate as rs +import ravestate_nlp as nlp +import ravestate_verbaliser as verbaliser +import ravestate_idle as idle +import ravestate_rawio as rawio import ravestate_ontology -from ravestate_nlp import contains_roboy, is_question, triples -from ravestate_verbaliser import verbaliser -from ravestate_idle import bored -from ravestate_rawio import output as raw_out from os.path import realpath, dirname, join @@ -21,13 +18,13 @@ ROBOY_NODE_CONF_KEY = "roboy_node_id" -with Module(name="roboyqa", config={ROBOY_NODE_CONF_KEY: 356}): +with rs.Module(name="roboyqa", config={ROBOY_NODE_CONF_KEY: 356}): - @state(cond=bored, write=raw_out, weight=0.6, cooldown=30.) + @rs.state(cond=idle.sig_bored, write=rawio.prop_out, weight=0.6, cooldown=30.) def hello_world_roboyqa(ctx): - ctx[raw_out] = "Ask me something about myself!" + ctx[rawio.prop_out] = "Ask me something about myself!" - @state(cond=contains_roboy & is_question, read=triples, write=raw_out) + @rs.state(cond=nlp.sig_contains_roboy & nlp.sig_is_question, read=nlp.prop_triples, write=rawio.prop_out) def roboyqa(ctx): """ answers question regarding roboy by retrieving the information out of the neo4j roboy memory graph @@ -62,15 +59,15 @@ def roboyqa(ctx): roboy = node_list[0] else: logger.error(f"Seems like you do not have my memory running, or no node with ID {ctx.conf(key=ROBOY_NODE_CONF_KEY)} exists!") - return Resign() + return rs.Resign() - triple = ctx[triples][0] + triple = ctx[nlp.prop_triples][0] category = None memory_info = None # question word: What? - if triple.is_question(QuestionWord.OBJECT): + if triple.is_question(nlp.QuestionWord.OBJECT): if triple.match_either_lemma(pred={"like"}, subj={"hobby"}): category = "HAS_HOBBY" elif triple.match_either_lemma(pred={"learn"}, subj={"skill"}): @@ -85,14 +82,16 @@ def roboyqa(ctx): memory_info = roboy.get_properties(key=category) elif triple.match_either_lemma(pred={"become"}): category = "future" + # question word: Where? - elif triple.is_question(QuestionWord.PLACE): + elif triple.is_question(nlp.QuestionWord.PLACE): if triple.match_either_lemma(pred={"be"}): category = "FROM" elif triple.match_either_lemma(pred={"live"}): category = "LIVE_IN" + # question word: Who? - elif triple.is_question(QuestionWord.PERSON): + elif triple.is_question(nlp.QuestionWord.PERSON): if triple.match_either_lemma(obj={"father", "dad"}): category = "CHILD_OF" elif triple.match_either_lemma(obj={"brother", "sibling"}): @@ -104,8 +103,9 @@ def roboyqa(ctx): memory_info = roboy.get_properties(key=category) elif triple.match_either_lemma(obj={"part", "member"}): category = "MEMBER_OF" + # question word: How? - elif triple.is_question(QuestionWord.FORM): + elif triple.is_question(nlp.QuestionWord.FORM): if triple.match_either_lemma(pred={"old"}): category = "age" memory_info = roboy_age(roboy.get_properties(key="birthdate")) @@ -117,7 +117,7 @@ def roboyqa(ctx): category = "abilities" if category and category.isupper() and not isinstance(roboy.get_relationships(key=category), dict): - node_id = random.sample(roboy.get_relationships(key=category),1)[0] + node_id = random.sample(roboy.get_relationships(key=category), 1)[0] memory_info = sess.retrieve(node_id=int(node_id))[0].get_name() elif category and category.islower() and not isinstance(roboy.get_properties(key=category), dict): @@ -125,11 +125,11 @@ def roboyqa(ctx): memory_info = random.sample(property_list, 1)[0] if memory_info: - ctx[raw_out] = verbaliser.get_random_successful_answer("roboy_"+category) % memory_info + ctx[rawio.prop_out] = verbaliser.get_random_successful_answer("roboy_"+category) % memory_info elif category == "well_being": - ctx[raw_out] = verbaliser.get_random_successful_answer("roboy_"+category) + ctx[rawio.prop_out] = verbaliser.get_random_successful_answer("roboy_"+category) else: - return Resign() + return rs.Resign() def roboy_age(birth_date: str): diff --git a/modules/ravestate_ros2/__init__.py b/modules/ravestate_ros2/__init__.py index 76a1c10..2a4e597 100644 --- a/modules/ravestate_ros2/__init__.py +++ b/modules/ravestate_ros2/__init__.py @@ -1,5 +1,6 @@ -from ravestate.module import Module -from ravestate_ros2.ros2_properties import sync_ros_properties, set_node_once +import ravestate as rs + +from ravestate_ros2.ros2_properties import sync_ros_properties, set_node_once, Ros2SubProperty, Ros2PubProperty, Ros2CallProperty CONFIG = { # name of the ROS2-Node that is created by ravestate_ros2 @@ -8,6 +9,6 @@ ros2_properties.SPIN_FREQUENCY_CONFIG_KEY: 10 } -with Module(name="ros2", config=CONFIG) as mod: +with rs.Module(name="ros2", config=CONFIG) as mod: mod.add(sync_ros_properties) diff --git a/modules/ravestate_ros2/ros2_properties.py b/modules/ravestate_ros2/ros2_properties.py index 90a35e4..6d2687b 100644 --- a/modules/ravestate_ros2/ros2_properties.py +++ b/modules/ravestate_ros2/ros2_properties.py @@ -1,12 +1,7 @@ import time from typing import Dict -from ravestate.state import state, Delete -from ravestate.constraint import s -from ravestate.property import Property - -from ravestate.receptor import receptor -from ravestate.wrappers import ContextWrapper +import ravestate as rs from reggol import get_logger logger = get_logger(__name__) @@ -40,8 +35,8 @@ def set_node_once(ros2_node): global_node = ros2_node -@state(cond=s(":startup")) -def sync_ros_properties(ctx: ContextWrapper): +@rs.state(cond=rs.sig_startup) +def sync_ros_properties(ctx: rs.ContextWrapper): """ State that creates a ROS2-Node, registers all Ros2SubProperties and Ros2PubProperties in ROS2 and keeps them synced """ @@ -51,17 +46,17 @@ def sync_ros_properties(ctx: ContextWrapper): if not ROS2_AVAILABLE: logger.error("ROS2 is not available, therefore all ROS2-Properties " "will be just normal properties without connection to ROS2!") - return Delete() + return rs.Delete() # get config stuff node_name = ctx.conf(key=NODE_NAME_CONFIG_KEY) if not node_name: logger.error(f"{NODE_NAME_CONFIG_KEY} is not set. Shutting down ravestate_ros2") - return Delete() + return rs.Delete() spin_frequency = ctx.conf(key=SPIN_FREQUENCY_CONFIG_KEY) if spin_frequency is None or spin_frequency < 0: logger.error(f"{SPIN_FREQUENCY_CONFIG_KEY} is not set or less than 0. Shutting down ravestate_ros2") - return Delete() + return rs.Delete() if spin_frequency == 0: spin_sleep_time = 0 else: @@ -97,7 +92,7 @@ def sync_ros_properties(ctx: ContextWrapper): # register subscribers in ROS if isinstance(prop, Ros2SubProperty): # register in context - @receptor(ctx_wrap=ctx, write=prop.id()) + @rs.receptor(ctx_wrap=ctx, write=prop.id()) def ros_to_ctx_callback(ctx, msg, prop_name: str): ctx[prop_name] = msg @@ -125,7 +120,7 @@ def ros_to_ctx_callback(ctx, msg, prop_name: str): rclpy.shutdown() -class Ros2SubProperty(Property): +class Ros2SubProperty(rs.Property): def __init__(self, name: str, topic: str, msg_type, default_value=None, always_signal_changed: bool = True): """ Initialize Property @@ -170,7 +165,7 @@ def ros_subscription_callback(self, msg): self.ros_to_ctx_callback(msg=msg, prop_name=self.id()) -class Ros2PubProperty(Property): +class Ros2PubProperty(rs.Property): def __init__(self, name: str, topic: str, msg_type): """ Initialize Property @@ -214,7 +209,7 @@ def write(self, value): f"cannot be published because publisher was not registered in ROS") -class Ros2CallProperty(Property): +class Ros2CallProperty(rs.Property): def __init__(self, name: str, service_name: str, service_type, call_timeout: float = 10.0): """ Initialize Property diff --git a/modules/ravestate_sendpics/__init__.py b/modules/ravestate_sendpics/__init__.py index b9c1544..df62624 100644 --- a/modules/ravestate_sendpics/__init__.py +++ b/modules/ravestate_sendpics/__init__.py @@ -1,14 +1,11 @@ -from ravestate.module import Module -from ravestate.state import s, state, Emit -from ravestate.property import Property -from ravestate.wrappers import ContextWrapper +import ravestate as rs import ravestate_ontology import pickle import redis -import ravestate_rawio -import ravestate_nlp -import ravestate_idle +import ravestate_rawio as rawio +import ravestate_nlp as nlp +import ravestate_idle as idle from scientio.ontology.node import Node from scientio.session import Session @@ -29,46 +26,48 @@ } -with Module(name="sendpics", config=CONFIG): +with rs.Module(name="sendpics", config=CONFIG): - face_vec = Property(name="face_vec", always_signal_changed=True) + prop_face_vec = rs.Property(name="face_vec", always_signal_changed=True) - @state(cond=s("idle:bored"), write="rawio:out", weight=1.2, cooldown=30.) + sig_repeat_name = rs.Signal("repeat_name") + + @rs.state(cond=idle.sig_bored, write=rawio.prop_out, weight=1.2, cooldown=30.) def prompt_send(ctx): - ctx["rawio:out"] = "Why don't you send me a picture? I'm really good at recognizing faces!" + ctx[rawio.prop_out] = "Why don't you send me a picture? I'm really good at recognizing faces!" - @state( - read="rawio:pic_in", - write=("rawio:out", "sendpics:face_vec"), + @rs.state( + read=rawio.prop_pic_in, + write=(rawio.prop_out, prop_face_vec), emit_detached=True) def prompt_name(ctx): # Get face ancoding - face = recognize_face_from_image_file(ctx["rawio:pic_in"]) + face = recognize_face_from_image_file(ctx[rawio.prop_pic_in]) # Prompt name if face is not None: - ctx["rawio:out"] = "Nice picture! Who is that person?" - ctx["sendpics:face_vec"] = pickle.dumps(face) + ctx[rawio.prop_out] = "Nice picture! Who is that person?" + ctx[prop_face_vec] = pickle.dumps(face) else: - ctx["rawio:out"] = f"Huh, looks interesting. But maybe send me a cute face!" + ctx[rawio.prop_out] = f"Huh, looks interesting. But maybe send me a cute face!" - @state( + @rs.state( cond=( - s("sendpics:repeat_name", max_age=30.) | s("sendpics:face_vec:changed", max_age=30.) - ) & s("nlp:tokens:changed"), - signal="repeat_name", - read=("nlp:triples", "nlp:tokens", "sendpics:face_vec"), - write="rawio:out", + sig_repeat_name.max_age(30) | prop_face_vec.changed_signal().max_age(30) + ) & nlp.prop_tokens.changed_signal(), + signal=sig_repeat_name, + read=(nlp.prop_triples, nlp.prop_tokens, prop_face_vec), + write=rawio.prop_out, emit_detached=True) - def store_face_and_name(ctx: ContextWrapper): - tokens = ctx["nlp:tokens"] - triples = ctx["nlp:triples"] + def store_face_and_name(ctx: rs.ContextWrapper): + tokens = ctx[nlp.prop_tokens] + triples = ctx[nlp.prop_triples] if len(tokens) == 1: name = tokens[0] elif triples[0].get_object().text and triples[0].match_either_lemma(pred={"be"}): name = triples[0].get_object().text else: ctx["rawio:out"] = "Sorry, what was the name?" - return Emit() + return rs.Emit() ctx["rawio:out"] = f"Got it, I'm sure I'll remember {name} next time I see that face!" # Create memory entry @@ -97,4 +96,4 @@ def store_face_and_name(ctx: ContextWrapper): except redis.exceptions.ConnectionError as e: err_msg = "Looks like the redis connection is unavailable :-(" logger.error(err_msg) - ctx['rawio:out'] = err_msg + ctx[rawio.prop_out] = err_msg diff --git a/modules/ravestate_stalker/__init__.py b/modules/ravestate_stalker/__init__.py index bf09f32..509e60e 100644 --- a/modules/ravestate_stalker/__init__.py +++ b/modules/ravestate_stalker/__init__.py @@ -2,14 +2,9 @@ from random import randint from typing import List, Dict -from ravestate.module import Module -from ravestate.property import Property -from ravestate_ros2.ros2_properties import Ros2SubProperty -from ravestate.context import startup -from ravestate.state import state, s -from ravestate.wrappers import ContextWrapper - -from ravestate_rawio import output as raw_out +import ravestate as rs +import ravestate_rawio as rawio +import ravestate_ros2 as ros2 from reggol import get_logger logger = get_logger(__name__) @@ -36,35 +31,36 @@ FACE_CONFIDENCE_THRESHOLD: 0.85 } - with Module(name="stalker", config=CONFIG) as mod: + with rs.Module(name="stalker", config=CONFIG) as mod: # Create a dummy parent, under which we can push the actual recognized faces topic, # once a context with a configuration is available. - subscriber_parent = Property(name="face_names_parent") + subscriber_parent = rs.Property(name="face_names_parent") - @state(cond=startup(), write=subscriber_parent.id()) - def create_subscriber(ctx: ContextWrapper): + @rs.state(cond=rs.sig_startup, write=subscriber_parent) + def create_subscriber(ctx: rs.ContextWrapper): - face_names = Ros2SubProperty( + face_names = ros2.Ros2SubProperty( "face_names", topic=ctx.conf(key=ROS2_FACE_TOPIC_CONFIG), msg_type=RecognizedFaces, always_signal_changed=False) - rec_faces = Property(name="rec_faces", - default_value={}, - always_signal_changed=False, - allow_pop=True, - allow_push=True) + rec_faces = rs.Property( + name="rec_faces", + default_value={}, + always_signal_changed=False, + allow_pop=True, + allow_push=True) - ctx.push(subscriber_parent.id(), face_names) - ctx.push(subscriber_parent.id(), rec_faces) + ctx.push(subscriber_parent, face_names) + ctx.push(subscriber_parent, rec_faces) - @state(read=(face_names.id(), rec_faces.id()), write=(rec_faces.id(), raw_out.id()), signal="daddy") - def react_to_recognized_face(ctx: ContextWrapper): + @rs.state(read=(face_names, rec_faces), write=(rec_faces, rawio.prop_out), signal="daddy") + def react_to_recognized_face(ctx: rs.ContextWrapper): nonlocal face_names - faces: RecognizedFaces = ctx[face_names.id()] - rec_faces_dict: Dict = ctx[rec_faces.id()] + faces: RecognizedFaces = ctx[face_names] + rec_faces_dict: Dict = ctx[rec_faces] phrases: List = ["Hey, aren't you, {}?!", "How are you doing, {}?!", @@ -92,12 +88,12 @@ def react_to_recognized_face(ctx: ContextWrapper): if best_name_and_confidence[0] not in rec_faces_dict.keys() or \ datetime.timestamp(datetime.now()) - rec_faces_dict.get(best_name_and_confidence[0]) > 300: rec_faces_dict.update({best_name_and_confidence[0]: datetime.timestamp(datetime.now())}) - ctx[raw_out.id()] = phrases[randint(0, len(phrases) - 1)].format(best_name_and_confidence[0]) + ctx[rawio.prop_out] = phrases[randint(0, len(phrases) - 1)].format(best_name_and_confidence[0]) - ctx[rec_faces.id()] = rec_faces_dict + ctx[rec_faces] = rec_faces_dict if best_name_and_confidence[0].lower() == "daddy": - return Emit() + return rs.Emit() mod.add(react_to_recognized_face) ctx.add_state(react_to_recognized_face) diff --git a/modules/ravestate_telegramio/telegram_bot.py b/modules/ravestate_telegramio/telegram_bot.py index 34eca31..3466a5b 100644 --- a/modules/ravestate_telegramio/telegram_bot.py +++ b/modules/ravestate_telegramio/telegram_bot.py @@ -6,12 +6,8 @@ from tempfile import mkstemp import requests -from ravestate.constraint import s -from ravestate.context import Context, create_and_run_context -from ravestate.property import Property -from ravestate.receptor import receptor -from ravestate.state import state, Delete -from ravestate.wrappers import ContextWrapper +import ravestate as rs + from telegram import Bot, Update, TelegramError from telegram.ext import Updater, MessageHandler, Filters, Dispatcher @@ -22,8 +18,8 @@ from reggol import get_logger logger = get_logger(__name__) -import ravestate_rawio -import ravestate_interloc +import ravestate_rawio as rawio +import ravestate_interloc as interloc import ravestate_ontology @@ -58,32 +54,32 @@ def age(self): active_users: Dict[int, Set[int]] = dict() -@state(cond=s(":startup")) -def telegram_run(ctx: ContextWrapper): +@rs.state(cond=rs.sig_startup) +def telegram_run(ctx: rs.ContextWrapper): """ Starts up the telegram bot and adds a handler to write incoming messages to rawio:in """ - @receptor(ctx_wrap=ctx, write="rawio:in") - def text_receptor(ctx: ContextWrapper, message_text: str): + @rs.receptor(ctx_wrap=ctx, write=rawio.prop_in) + def text_receptor(ctx: rs.ContextWrapper, message_text: str): """ Writes the message_text to rawio:in """ - ctx["rawio:in"] = message_text + ctx[rawio.prop_in] = message_text - @receptor(ctx_wrap=ctx, write="rawio:pic_in") - def photo_receptor(ctx: ContextWrapper, photo_path): + @rs.receptor(ctx_wrap=ctx, write=rawio.prop_pic_in) + def photo_receptor(ctx: rs.ContextWrapper, photo_path): """ Handles photo messages, write to rawio:pic_in """ - ctx["rawio:pic_in"] = photo_path + ctx[rawio.prop_pic_in] = photo_path - @receptor(ctx_wrap=ctx, write="interloc:all") - def push_telegram_interloc(ctx: ContextWrapper, telegram_node: Node, name: str): + @rs.receptor(ctx_wrap=ctx, write=interloc.prop_all) + def push_telegram_interloc(ctx: rs.ContextWrapper, telegram_node: Node, name: str): """ Push the telegram_node into interloc:all:name """ - if ctx.push(parent_property_or_path="interloc:all", child=Property(name=name, default_value=telegram_node)): + if ctx.push(parent_property_or_path="interloc:all", child=rs.Property(name=name, default_value=telegram_node)): logger.debug(f"Pushed {telegram_node} to interloc:all") def make_sure_effective_user_exists(update: Update): @@ -176,7 +172,7 @@ def add_new_child_process(chat_id): for child_config_path in child_config_paths_list: args += ['-f', child_config_path] # set up new Process and override child_conn with the Pipe-Connection - p = mp_context.Process(target=create_and_run_context, + p = mp_context.Process(target=rs.create_and_run_context, args=(*args,), kwargs={'runtime_overrides': [(MODULE_NAME, CHILD_CONN_CONFIG_KEY, child_conn)]}) p.start() @@ -232,13 +228,13 @@ def _bootstrap_telegram_master(): token = ctx.conf(key=TOKEN_CONFIG_KEY) if not token: logger.error(f'{TOKEN_CONFIG_KEY} is not set. Shutting down telegramio') - return Delete() + return rs.Delete() child_config_paths_list = ctx.conf(key=CHILD_FILES_CONFIG_KEY) if not ctx.conf(key=ALL_IN_ONE_CONTEXT_CONFIG_KEY) and ( not child_config_paths_list or not isinstance(child_config_paths_list, list) or not all(os.path.isfile(child_config_path) for child_config_path in child_config_paths_list)): logger.error(f'{CHILD_FILES_CONFIG_KEY} is not set (correctly). Shutting down telegramio') - return Delete() + return rs.Delete() updater: Updater = Updater(token) # Get the dispatcher to register handlers @@ -285,8 +281,8 @@ def _bootstrap_telegram_child(): _bootstrap_telegram_child() -@state(read="rawio:out") -def telegram_output(ctx: ContextWrapper): +@rs.state(read=rawio.prop_out) +def telegram_output(ctx: rs.ContextWrapper): """ If all telegram chats should be in the same context, sends the content of rawio:out to every currently active chat. Otherwise it only sends output using the Pipe if it is a child process @@ -296,7 +292,7 @@ def telegram_output(ctx: ContextWrapper): token = ctx.conf(key=TOKEN_CONFIG_KEY) if not token: logger.error('telegram-token is not set. Shutting down telegramio') - return Delete() + return rs.Delete() updater: Updater = Updater(token) for chat_id in active_chats.keys(): @@ -306,7 +302,7 @@ def telegram_output(ctx: ContextWrapper): if child_conn: # Child Process -> write to Pipe - child_conn.send(ctx["rawio:out:changed"]) + child_conn.send(ctx[rawio.prop_out.changed_signal()]) else: # Master Process -> State not needed - return Delete() + return rs.Delete() diff --git a/modules/ravestate_verbaliser/__init__.py b/modules/ravestate_verbaliser/__init__.py index 8eace72..732c956 100644 --- a/modules/ravestate_verbaliser/__init__.py +++ b/modules/ravestate_verbaliser/__init__.py @@ -1,15 +1,13 @@ -import logging +import ravestate as rs +import ravestate_rawio as rawio +from .verbaliser import * -from ravestate.module import Module -from ravestate.property import Property -from ravestate.state import state -from ravestate_verbaliser import verbaliser -from ravestate_rawio import output as raw_out +from reggol import get_logger +logger = get_logger(__name__) +with rs.Module(name="verbaliser"): -with Module(name="verbaliser"): - - intent = Property( + prop_intent = rs.Property( name="intent", default_value="", allow_push=False, @@ -17,15 +15,15 @@ always_signal_changed=True, wipe_on_changed=False) - @state(read=intent, write=raw_out) + @rs.state(read=prop_intent, write=rawio.prop_out) def react_to_intent(ctx): """ Looks for intents written to the verbaliser:intent property and writes a random phrase for that intent to rawio:out """ - intent = ctx["verbaliser:intent:changed"] + intent = ctx[prop_intent.changed_signal()] phrase = verbaliser.get_random_phrase(intent) if phrase: - ctx["rawio:out"] = phrase + ctx[rawio.prop_out] = phrase else: - logging.error('No phrase for intent ' + intent + ' found.') + logger.error(f'No phrase for intent {intent} found.') diff --git a/modules/ravestate_wildtalk/__init__.py b/modules/ravestate_wildtalk/__init__.py index 38f93fa..6c3efa2 100644 --- a/modules/ravestate_wildtalk/__init__.py +++ b/modules/ravestate_wildtalk/__init__.py @@ -1,17 +1,13 @@ - -from ravestate.state import state -from ravestate.module import Module -from ravestate.constraint import s - +import ravestate as rs +import ravestate_rawio as rawio from roboy_parlai import wildtalk -from ravestate_rawio import input as raw_in, output as raw_out -with Module(name="wildtalk"): +with rs.Module(name="wildtalk"): - @state(cond=raw_in.changed_signal().max_age(-1.), read=raw_in, write=raw_out) + @rs.state(cond=rawio.prop_out.changed_signal().max_age(-1.), read=rawio.prop_in, write=rawio.prop_out) def wildtalk_state(ctx): - text = ctx[raw_in] + text = ctx[rawio.prop_in] if not text: # make sure that text is not empty - text = " " - ctx[raw_out] = wildtalk(text) + return rs.Resign() + ctx[rawio.prop_out] = wildtalk(text) diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index ffdb025..2261219 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -2,7 +2,7 @@ from ravestate.spike import Spike from ravestate.module import Module from ravestate.testfixtures import * -from ravestate.context import create_and_run_context, startup, shutdown +from ravestate.context import create_and_run_context, sig_startup, sig_shutdown from ravestate.config import Configuration @@ -19,7 +19,7 @@ def test_run(mocker, context_fixture): context_fixture.emit = mocker.stub() context_fixture.shutdown_flag = True context_fixture.run() - context_fixture.emit.assert_called_once_with(startup()) + context_fixture.emit.assert_called_once_with(sig_startup) context_fixture.shutdown() @@ -43,7 +43,7 @@ def test_shutdown(mocker, context_fixture): mocker.patch.object(context_fixture._run_task, 'join') context_fixture.emit = mocker.stub(name='emit') context_fixture.shutdown() - context_fixture.emit.assert_called_once_with(shutdown()) + context_fixture.emit.assert_called_once_with(sig_shutdown) context_fixture._run_task.join.assert_called_once() diff --git a/test/modules/ravestate/test_context_integrated.py b/test/modules/ravestate/test_context_integrated.py index 9ee7b66..18cf488 100644 --- a/test/modules/ravestate/test_context_integrated.py +++ b/test/modules/ravestate/test_context_integrated.py @@ -1,7 +1,7 @@ from ravestate.constraint import Signal, ConfigurableAge from ravestate.spike import Spike from ravestate.state import s, Emit -from ravestate.context import startup +from ravestate.context import sig_startup from ravestate.module import Module from ravestate.testfixtures import * from threading import Lock @@ -16,7 +16,7 @@ def test_run_with_pressure(): a = Signal("a") b = Signal("b") - @state(cond=startup(), signal=a) + @state(cond=sig_startup, signal=a) def signal_a(ctx): return Emit() @@ -33,7 +33,7 @@ def specific_state(ctx): pass ctx = Context(DEFAULT_MODULE_NAME) - ctx.emit(startup()) + ctx.emit(sig_startup) ctx.run_once() assert signal_a.wait() diff --git a/test/modules/ravestate_hibye/test_hibye.py b/test/modules/ravestate_hibye/test_hibye.py index 68a6928..ce30b07 100644 --- a/test/modules/ravestate_hibye/test_hibye.py +++ b/test/modules/ravestate_hibye/test_hibye.py @@ -1,16 +1,16 @@ from pytest_mock import mocker -from ravestate_verbaliser import intent +from ravestate_verbaliser import prop_intent def test_react_to_pushed_interloc(mocker): import ravestate_hibye test_dict = {} ravestate_hibye.greeting(test_dict) - assert test_dict[intent] == "greeting" + assert test_dict[prop_intent] == "greeting" def test_react_to_popped_interloc(mocker): test_dict = {} import ravestate_hibye ravestate_hibye.farewell(test_dict) - assert test_dict[intent] == "farewells" + assert test_dict[prop_intent] == "farewells" diff --git a/test/modules/ravestate_nlp/test_preprocessing.py b/test/modules/ravestate_nlp/test_preprocessing.py index 5cfec3c..a726411 100644 --- a/test/modules/ravestate_nlp/test_preprocessing.py +++ b/test/modules/ravestate_nlp/test_preprocessing.py @@ -1,6 +1,6 @@ import pytest -from ravestate_nlp import nlp_preprocess, tokens, postags, lemmas, tags, ner, roboy -from ravestate_rawio import input as raw_in +from ravestate_nlp import nlp_preprocess, prop_tokens, porp_postags, prop_lemmas, prop_tags, prop_ner, prop_roboy +from ravestate_rawio import prop_in as raw_in from testfixtures import log_capture FILE_NAME = 'ravestate_nlp' @@ -15,7 +15,7 @@ def basic_input(): def test_tokenization(capture, basic_input): nlp_preprocess(basic_input) expected = ('hello', 'world', 'my', 'name', 'is', 'roboy') - assert basic_input[tokens] == expected + assert basic_input[prop_tokens] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:tokens]: {expected}")) @@ -23,7 +23,7 @@ def test_tokenization(capture, basic_input): def test_postags(capture, basic_input): nlp_preprocess(basic_input) expected = ('INTJ', 'NOUN', 'DET', 'NOUN', 'VERB', 'ADJ') - assert basic_input[postags] == expected + assert basic_input[porp_postags] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:postags]: {expected}")) @@ -31,7 +31,7 @@ def test_postags(capture, basic_input): def test_lemmas(capture, basic_input): nlp_preprocess(basic_input) expected = ('hello', 'world', '-PRON-', 'name', 'be', 'roboy') - assert basic_input[lemmas] == expected + assert basic_input[prop_lemmas] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:lemmas]: {expected}")) @@ -39,7 +39,7 @@ def test_lemmas(capture, basic_input): def test_tags(capture, basic_input): nlp_preprocess(basic_input) expected = ('UH', 'NN', 'PRP$', 'NN', 'VBZ', 'JJ') - assert basic_input[tags] == expected + assert basic_input[prop_tags] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:tags]: {expected}")) @@ -48,7 +48,7 @@ def test_tags(capture, basic_input): def test_ner(capture, basic_input): nlp_preprocess(basic_input) expected = (('roboy', 'ORG'),) - assert basic_input[ner] == expected + assert basic_input[prop_ner] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:ner]: {expected}")) @@ -56,5 +56,5 @@ def test_ner(capture, basic_input): def test_roboy(capture, basic_input): nlp_preprocess(basic_input) expected = True - assert basic_input[roboy] == expected + assert basic_input[prop_roboy] == expected capture.check_present((f"{FILE_NAME}", 'INFO', f"[NLP:roboy]: {expected}")) diff --git a/test/modules/ravestate_persqa/test_qa.py b/test/modules/ravestate_persqa/test_qa.py index c81e53c..41f9f55 100644 --- a/test/modules/ravestate_persqa/test_qa.py +++ b/test/modules/ravestate_persqa/test_qa.py @@ -1,7 +1,7 @@ from ravestate_interloc import handle_single_interlocutor_input import ravestate_nlp -from ravestate_rawio import output as rawio_output, input as rawio_input -from ravestate_interloc import all +from ravestate_rawio import prop_out as rawio_output, prop_in as rawio_input +from ravestate_interloc import prop_all import ravestate_persqa import ravestate_idle import ravestate_ontology @@ -11,7 +11,7 @@ from ravestate.testfixtures import * from ravestate.receptor import receptor from ravestate.state import s, Emit -from ravestate.context import startup, shutdown +from ravestate.context import sig_startup, sig_shutdown from ravestate.module import Module from ravestate.wrappers import ContextWrapper, PropertyWrapper @@ -20,14 +20,11 @@ def test_run_qa(): - # TODO fix testcase - assert False - last_output = "" with Module(name="persqa_test"): - @state(cond=startup(), read=all) + @state(cond=sig_startup, read=prop_all) def persqa_hi(ctx: ContextWrapper): ravestate_ontology.initialized.wait() handle_single_interlocutor_input(ctx, "hi") @@ -52,7 +49,7 @@ def raw_out(ctx: ContextWrapper): def say(ctx: ContextWrapper, what: str): ctx[rawio_input] = what - ctx.emit(startup()) + ctx.emit(sig_startup) ctx.run_once() assert persqa_hi.wait() diff --git a/test/modules/ravestate_roboyqa/test_roboyqa.py b/test/modules/ravestate_roboyqa/test_roboyqa.py index 3750fcf..a112002 100644 --- a/test/modules/ravestate_roboyqa/test_roboyqa.py +++ b/test/modules/ravestate_roboyqa/test_roboyqa.py @@ -1,10 +1,10 @@ from ravestate.testfixtures import * -from ravestate_nlp import triples +from ravestate_nlp import prop_triples def test_roboyqa(mocker, context_fixture, triple_fixture): mocker.patch.object(context_fixture, 'conf', will_return='test') - context_fixture._properties[triples] = [triple_fixture] + context_fixture._properties[prop_triples] = [triple_fixture] import ravestate_roboyqa with mocker.patch('ravestate_ontology.get_session'): ravestate_roboyqa.roboyqa(context_fixture) diff --git a/test/modules/ravestate_wildtalk/test_wildtalk.py b/test/modules/ravestate_wildtalk/test_wildtalk.py index d5a0c0c..85f33b2 100644 --- a/test/modules/ravestate_wildtalk/test_wildtalk.py +++ b/test/modules/ravestate_wildtalk/test_wildtalk.py @@ -1,5 +1,5 @@ from pytest_mock import mocker -from ravestate_rawio import input as raw_in, output as raw_out +from ravestate_rawio import prop_in as raw_in, prop_out as raw_out def test_wildtalk_state(mocker): From 1b0b9f1d89c40ad12033b85caff224ab7c32e00f Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Fri, 10 May 2019 20:50:17 +0200 Subject: [PATCH 04/10] Simplified signal accessor API for Property. --- modules/ravestate/context.py | 2 +- modules/ravestate/property.py | 91 ++++++-------------- modules/ravestate/state.py | 2 +- modules/ravestate/wrappers.py | 14 +-- modules/ravestate_hibye/__init__.py | 4 +- modules/ravestate_idle/__init__.py | 12 +-- modules/ravestate_nlp/__init__.py | 2 +- modules/ravestate_persqa/__init__.py | 12 +-- modules/ravestate_roboyio/__init__.py | 2 +- modules/ravestate_sendpics/__init__.py | 4 +- modules/ravestate_telegramio/telegram_bot.py | 2 +- modules/ravestate_verbaliser/__init__.py | 2 +- modules/ravestate_wildtalk/__init__.py | 2 +- test/modules/ravestate_persqa/test_qa.py | 56 ++++++------ 14 files changed, 83 insertions(+), 124 deletions(-) diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 769f164..c913808 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -739,7 +739,7 @@ def _update_core_properties(self, debug=False): pressured_acts = [] partially_fulfilled_acts = [] for act in self._state_activations(): - if self[":activity"].changed_signal() not in set(act.constraint.signals()): + if self[":activity"].changed() not in set(act.constraint.signals()): if act.is_pressured(): pressured_acts.append(act.id) if act.spiky(): diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 01bcdf0..1091ac4 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -9,48 +9,6 @@ logger = get_logger(__name__) -def changed(property_name, module_name, **kwargs) -> Signal: - """ - Returns the `changed` Signal for the given property. - This signal is emitted, when the Property is written to, - and the new property value is different from the old one, - or the propertie's `always_signal_changed` flag is True.
- __Hint:__ All key-word arguments of #constraint.s(...) - (`min_age`, `max_age`, `detached`) are supported. - """ - sig = s(f"{property_name}:changed", **kwargs) - sig.module_name = module_name - return sig - - -def pushed(property_name, module_name, **kwargs) -> Signal: - """ - Returns the `pushed` Signal for the given property. This signal - is emitted, when a new child property is added to it. - From the perspective of a state, this can be achieved - with the `ContextWrapper.push(...)` function.
- __Hint:__ All key-word arguments of #constraint.s(...) - (`min_age`, `max_age`, `detached`) are supported. - """ - sig = s(f"{property_name}:pushed", **kwargs) - sig.module_name = module_name - return sig - - -def popped(property_name, module_name, **kwargs) -> Signal: - """ - Returns the `popped` Signal for the given property. This signal - is emitted, when a child property removed from it. - From the perspective of a state, this can be achieved - with the `ContextWrapper.pop(...)` function.
- __Hint:__ All key-word arguments of #constraint.s(...) - (`min_age`, `max_age`, `detached`) are supported. - """ - sig = s(f"{property_name}:popped", **kwargs) - sig.module_name = module_name - return sig - - class Property: """ Base class for context properties. Controls read/write/push/pop/delete permissions, @@ -87,6 +45,11 @@ def __init__( self.always_signal_changed = always_signal_changed self.is_flag_property = is_flag_property self.wipe_on_changed = wipe_on_changed + self.changed_signal = Signal(f"{name}:changed") + self.pushed_signal = Signal(f"{name}:pushed") + self.popped_signal = Signal(f"{name}:popped") + self.true_signal = Signal(f"{name}:true") + self.false_signal = Signal(f"{name}:false") # add property to module in current `with Module(...)` clause module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) @@ -104,6 +67,11 @@ def set_parent_path(self, path): """ if not self.parent_path: self.parent_path = path + self.changed_signal.module_name = path + self.pushed_signal.module_name = path + self.popped_signal.module_name = path + self.true_signal.module_name = path + self.false_signal.module_name = path else: logger.error(f'Tried to override parent_path of {self.id()}') @@ -184,44 +152,35 @@ def pop(self, child_name: str): logger.error(f"Tried to remove non-existent child-property {self.id()}:{child_name}") return False - def changed_signal(self) -> Signal: + def changed(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #write() returns True. """ - module_name, name_without_module = self.id().split(':', 1) - return changed(name_without_module, module_name) + return self.changed_signal - def pushed_signal(self) -> Signal: + def pushed(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #push() returns True. """ - module_name, name_without_module = self.id().split(':', 1) - return pushed(name_without_module, module_name) + return self.pushed_signal - def popped_signal(self) -> Signal: + def popped(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #pop() returns True. """ - module_name, name_without_module = self.id().split(':', 1) - return popped(name_without_module, module_name) + return self.popped_signal - def flag_true_signal(self) -> Signal: + def true(self) -> Signal: """ Signal that is emitted by PropertyWrapper when it is a flag-property and #self.value is set to True. """ - module_name, name_without_module = self.id().split(':', 1) - sig = s(f"{name_without_module}:true") - sig.module_name = module_name - return sig + return self.true_signal - def flag_false_signal(self) -> Signal: + def false(self) -> Signal: """ Signal that is emitted by PropertyWrapper when it is a flag-property and #self.value is set to False. """ - module_name, name_without_module = self.id().split(':', 1) - sig = s(f"{name_without_module}:false") - sig.module_name = module_name - return sig + return self.false_signal def signals(self) -> Generator[Signal, None, None]: """ @@ -229,12 +188,12 @@ def signals(self) -> Generator[Signal, None, None]: this property, given it's write/push/pop permissions. """ if self.allow_write: - yield self.changed_signal() + yield self.changed() if self.allow_push: - yield self.pushed_signal() + yield self.pushed() if self.allow_pop: - yield self.popped_signal() + yield self.popped() if self.is_flag_property: - yield self.flag_true_signal() - yield self.flag_false_signal() + yield self.true() + yield self.false() diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 252f5e3..a88f012 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -120,7 +120,7 @@ def __init__(self, *, # listen to default changed-signals if no signals are given. # convert triggers to disjunctive normal form. if not cond and len(read) > 0: - cond = Disjunct(*(Conjunct(rprop.changed_signal()) if isinstance(rprop, Property) + cond = Disjunct(*(Conjunct(rprop.changed()) if isinstance(rprop, Property) else Conjunct(s(f"{rprop}:changed")) for rprop in read)) self.signal = signal diff --git a/modules/ravestate/wrappers.py b/modules/ravestate/wrappers.py index 1643344..6bae908 100644 --- a/modules/ravestate/wrappers.py +++ b/modules/ravestate/wrappers.py @@ -71,21 +71,21 @@ def set(self, value: Any): # emit flag signals if it is a flag property if self.prop.is_flag_property and value is True: # wipe false signal, emit true signal - self.ctx.wipe(self.prop.flag_false_signal()) + self.ctx.wipe(self.prop.false()) self.ctx.emit( - self.prop.flag_true_signal(), + self.prop.true(), parents=self.spike_parents, wipe=self.prop.wipe_on_changed) if self.prop.is_flag_property and value is False: # wipe true signal, emit false signal - self.ctx.wipe(self.prop.flag_true_signal()) + self.ctx.wipe(self.prop.true()) self.ctx.emit( - self.prop.flag_false_signal(), + self.prop.false(), parents=self.spike_parents, wipe=self.prop.wipe_on_changed) self.ctx.emit( - self.prop.changed_signal(), + self.prop.changed(), parents=self.spike_parents, wipe=self.prop.wipe_on_changed, payload=value) @@ -106,7 +106,7 @@ def push(self, child: Property): return False if self.prop.push(child): self.ctx.emit( - self.prop.pushed_signal(), + self.prop.pushed(), parents=self.spike_parents, wipe=False, payload=child.id()) @@ -126,7 +126,7 @@ def pop(self, childname: str): return False if self.prop.pop(childname): self.ctx.emit( - self.prop.popped_signal(), + self.prop.popped(), parents=self.spike_parents, wipe=False, payload=f"{self.prop.id()}:{childname}") diff --git a/modules/ravestate_hibye/__init__.py b/modules/ravestate_hibye/__init__.py index b53a6a0..02cf079 100644 --- a/modules/ravestate_hibye/__init__.py +++ b/modules/ravestate_hibye/__init__.py @@ -7,10 +7,10 @@ with rs.Module(name="hibye"): - @rs.state(cond=interloc_all.pushed_signal() & prop_in.changed_signal(), write=prop_intent) + @rs.state(cond=interloc_all.pushed() & prop_in.changed(), write=prop_intent) def greeting(ctx: rs.ContextWrapper): ctx[prop_intent] = lang.intent_greeting - @rs.state(cond=interloc_all.popped_signal() & prop_in.changed_signal(), write=prop_intent) + @rs.state(cond=interloc_all.popped() & prop_in.changed(), write=prop_intent) def farewell(ctx: rs.ContextWrapper): ctx[prop_intent] = lang.intent_farewells diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index 4ba200c..9c25392 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -16,8 +16,10 @@ sig_impatient = rs.Signal(name="impatient") sig_bored = rs.Signal(name="bored") - @rs.state(cond=rs.prop_activity.changed_signal().min_age(rs.ConfigurableAge(key=BORED_THRESHOLD_CONFIG_KEY)).max_age(-1), - read=rs.prop_activity, signal=sig_bored) + @rs.state( + cond=rs.prop_activity.changed().min_age(rs.ConfigurableAge(key=BORED_THRESHOLD_CONFIG_KEY)).max_age(-1), + read=rs.prop_activity, + signal=sig_bored) def am_i_bored(ctx: rs.ContextWrapper): """ Emits idle:bored signal if no states are currently partially fulfilled @@ -25,8 +27,8 @@ def am_i_bored(ctx: rs.ContextWrapper): if ctx[rs.prop_activity] == 0: return rs.Emit(wipe=True) - @rs.state(cond=rs.prop_pressure.flag_true_signal(). - min_age(rs.ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY)).max_age(-1.), - signal=sig_impatient) + @rs.state( + cond=rs.prop_pressure.true().min_age(rs.ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY)).max_age(-1.), + signal=sig_impatient) def am_i_impatient(ctx: rs.ContextWrapper): return rs.Emit(wipe=True) diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 1dc0af1..d13cd4a 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -57,7 +57,7 @@ def roboy_getter(doc) -> bool: @rs.state(read=rawio.prop_in, write=(prop_tokens, porp_postags, prop_lemmas, prop_tags, prop_ner, prop_triples, prop_roboy, prop_yesno)) def nlp_preprocess(ctx): - text = ctx[rawio.prop_out] + text = ctx[rawio.prop_in] if not text: return False text = text.lower() diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py index 0677f41..7a18963 100644 --- a/modules/ravestate_persqa/__init__.py +++ b/modules/ravestate_persqa/__init__.py @@ -143,7 +143,7 @@ def small_talk(ctx: rs.ContextWrapper): ctx[rawio.prop_out] = verbaliser.get_random_question(ctx[prop_predicate]) @rs.state( - cond=sig_follow_up.max_age(-1.) & nlp.prop_triples.changed_signal(), + cond=sig_follow_up.max_age(-1.) & nlp.prop_triples.changed(), write=(rawio.prop_out, prop_predicate, prop_inference_mutex), read=(interloc_path, prop_predicate, nlp.prop_yesno)) def fup_react(ctx: rs.ContextWrapper): @@ -166,18 +166,18 @@ def fup_react(ctx: rs.ContextWrapper): ctx.add_state(fup_react) @rs.state( - cond=rawio.prop_in.changed_signal() & interloc.prop_all.pushed_signal(), + cond=rawio.prop_in.changed() & interloc.prop_all.pushed(), write=prop_inference_mutex, read=interloc.prop_all) def new_interloc(ctx: rs.ContextWrapper): """ reacts to interloc:pushed and creates persqa:ask_name state """ - interloc_path = ctx[interloc.prop_all.pushed_signal()] + interloc_path = ctx[interloc.prop_all.pushed()] create_small_talk_states(ctx=ctx, interloc_path=interloc_path) @rs.state( - cond=rawio.prop_in.changed_signal() & interloc.prop_all.popped_signal(), + cond=rawio.prop_in.changed() & interloc.prop_all.popped(), write=(prop_inference_mutex, prop_predicate, prop_subject)) def removed_interloc(ctx: rs.ContextWrapper): """ @@ -187,7 +187,7 @@ def removed_interloc(ctx: rs.ContextWrapper): ctx[prop_predicate] = None @rs.state( - cond=nlp.prop_triples.changed_signal(), + cond=nlp.prop_triples.changed(), write=(prop_answer, prop_inference_mutex), read=(prop_predicate, nlp.prop_triples, nlp.prop_tokens, nlp.prop_yesno)) def inference(ctx: rs.ContextWrapper): @@ -215,7 +215,7 @@ def inference(ctx: rs.ContextWrapper): ctx[prop_answer] = answer_str @rs.state( - cond=prop_answer.changed_signal(), + cond=prop_answer.changed(), write=(rawio.prop_out, prop_predicate), read=(prop_predicate, prop_subject, prop_answer, interloc.prop_all)) def react(ctx: rs.ContextWrapper): diff --git a/modules/ravestate_roboyio/__init__.py b/modules/ravestate_roboyio/__init__.py index bd7e7b2..a34b4d0 100644 --- a/modules/ravestate_roboyio/__init__.py +++ b/modules/ravestate_roboyio/__init__.py @@ -38,7 +38,7 @@ msg_type=RecognizedSpeech, always_signal_changed=True) - @rs.state(cond=recognized_speech.changed_signal(), read=(interloc.prop_all, recognized_speech)) + @rs.state(cond=recognized_speech.changed(), read=(interloc.prop_all, recognized_speech)) def roboy_input(ctx: rs.ContextWrapper): result = ctx[recognized_speech] if result: diff --git a/modules/ravestate_sendpics/__init__.py b/modules/ravestate_sendpics/__init__.py index df62624..ee53c66 100644 --- a/modules/ravestate_sendpics/__init__.py +++ b/modules/ravestate_sendpics/__init__.py @@ -52,8 +52,8 @@ def prompt_name(ctx): @rs.state( cond=( - sig_repeat_name.max_age(30) | prop_face_vec.changed_signal().max_age(30) - ) & nlp.prop_tokens.changed_signal(), + sig_repeat_name.max_age(30) | prop_face_vec.changed().max_age(30) + ) & nlp.prop_tokens.changed(), signal=sig_repeat_name, read=(nlp.prop_triples, nlp.prop_tokens, prop_face_vec), write=rawio.prop_out, diff --git a/modules/ravestate_telegramio/telegram_bot.py b/modules/ravestate_telegramio/telegram_bot.py index 3466a5b..eb420e4 100644 --- a/modules/ravestate_telegramio/telegram_bot.py +++ b/modules/ravestate_telegramio/telegram_bot.py @@ -302,7 +302,7 @@ def telegram_output(ctx: rs.ContextWrapper): if child_conn: # Child Process -> write to Pipe - child_conn.send(ctx[rawio.prop_out.changed_signal()]) + child_conn.send(ctx[rawio.prop_out.changed()]) else: # Master Process -> State not needed return rs.Delete() diff --git a/modules/ravestate_verbaliser/__init__.py b/modules/ravestate_verbaliser/__init__.py index 732c956..2bccf25 100644 --- a/modules/ravestate_verbaliser/__init__.py +++ b/modules/ravestate_verbaliser/__init__.py @@ -21,7 +21,7 @@ def react_to_intent(ctx): Looks for intents written to the verbaliser:intent property and writes a random phrase for that intent to rawio:out """ - intent = ctx[prop_intent.changed_signal()] + intent = ctx[prop_intent.changed()] phrase = verbaliser.get_random_phrase(intent) if phrase: ctx[rawio.prop_out] = phrase diff --git a/modules/ravestate_wildtalk/__init__.py b/modules/ravestate_wildtalk/__init__.py index 6c3efa2..6abad51 100644 --- a/modules/ravestate_wildtalk/__init__.py +++ b/modules/ravestate_wildtalk/__init__.py @@ -5,7 +5,7 @@ with rs.Module(name="wildtalk"): - @rs.state(cond=rawio.prop_out.changed_signal().max_age(-1.), read=rawio.prop_in, write=rawio.prop_out) + @rs.state(cond=rawio.prop_out.changed().max_age(-1.), read=rawio.prop_in, write=rawio.prop_out) def wildtalk_state(ctx): text = ctx[rawio.prop_in] if not text: # make sure that text is not empty diff --git a/test/modules/ravestate_persqa/test_qa.py b/test/modules/ravestate_persqa/test_qa.py index 41f9f55..38b8d77 100644 --- a/test/modules/ravestate_persqa/test_qa.py +++ b/test/modules/ravestate_persqa/test_qa.py @@ -1,19 +1,11 @@ -from ravestate_interloc import handle_single_interlocutor_input -import ravestate_nlp -from ravestate_rawio import prop_out as rawio_output, prop_in as rawio_input -from ravestate_interloc import prop_all -import ravestate_persqa -import ravestate_idle +import ravestate as rs +import ravestate_interloc as interloc +import ravestate_rawio as rawio +import ravestate_persqa as persqa +import ravestate_idle as idle import ravestate_ontology -from ravestate_verbaliser import verbaliser +import ravestate_verbaliser as verbaliser -from ravestate.context import Context -from ravestate.testfixtures import * -from ravestate.receptor import receptor -from ravestate.state import s, Emit -from ravestate.context import sig_startup, sig_shutdown -from ravestate.module import Module -from ravestate.wrappers import ContextWrapper, PropertyWrapper from reggol import get_logger, set_default_loglevel logger = get_logger(__name__) @@ -22,20 +14,20 @@ def test_run_qa(): last_output = "" - with Module(name="persqa_test"): + with rs.Module(name="persqa_test"): - @state(cond=sig_startup, read=prop_all) - def persqa_hi(ctx: ContextWrapper): + @rs.state(cond=rs.sig_startup, read=interloc.prop_all) + def persqa_hi(ctx: rs.ContextWrapper): ravestate_ontology.initialized.wait() - handle_single_interlocutor_input(ctx, "hi") + interloc.handle_single_interlocutor_input(ctx, "hi") - @state(read=rawio_output) - def raw_out(ctx: ContextWrapper): + @rs.state(read=rawio.prop_out) + def raw_out(ctx: rs.ContextWrapper): nonlocal last_output - last_output = ctx[rawio_output] - logger.info(f"Output: {ctx[rawio_output]}") + last_output = ctx[rawio.prop_out] + logger.info(f"Output: {ctx[rawio.prop_out]}") - ctx = Context( + ctx = rs.Context( "rawio", "ontology", "idle", @@ -45,11 +37,11 @@ def raw_out(ctx: ContextWrapper): "persqa_test" ) - @receptor(ctx_wrap=ctx, write=rawio_input) - def say(ctx: ContextWrapper, what: str): - ctx[rawio_input] = what + @rs.receptor(ctx_wrap=ctx, write=rawio.prop_in) + def say(ctx: rs.ContextWrapper, what: str): + ctx[rawio.prop_in] = what - ctx.emit(sig_startup) + ctx.emit(rs.sig_startup) ctx.run_once() assert persqa_hi.wait() @@ -65,6 +57,9 @@ def say(ctx: ContextWrapper, what: str): # Wait for acknowledgement of name while not raw_out.wait(.1): ctx.run_once() + + assert persqa.inference.wait(0) + assert persqa.react.wait(0) assert "herbert" in last_output.lower() # Wait for any other question via idle:bored @@ -78,10 +73,13 @@ def say(ctx: ContextWrapper, what: str): while not raw_out.wait(.1): ctx.run_once() + assert persqa.inference.wait(0) + assert persqa.react.wait(0) + if __name__ == "__main__": - #from hanging_threads import start_monitoring - #monitoring_thread = start_monitoring() + from hanging_threads import start_monitoring + monitoring_thread = start_monitoring() set_default_loglevel("DEBUG") test_run_qa() exit() From a78db42d5fa2ecb5de8a6b29c202e648d2ffa6b8 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Sat, 11 May 2019 11:29:16 +0200 Subject: [PATCH 05/10] Removed s(..) function. --- .gitignore | 3 +- modules/ravestate/constraint.py | 21 +-- modules/ravestate/context.py | 8 +- modules/ravestate/property.py | 2 +- modules/ravestate/state.py | 17 +- modules/ravestate/testfixtures.py | 15 +- modules/ravestate_akinator/__init__.py | 151 ++++++++++-------- modules/reggol/colored_formatter.py | 2 +- modules/reggol/logger.py | 2 +- test/modules/ravestate/test_constraint.py | 56 +++---- test/modules/ravestate/test_context.py | 25 +-- .../ravestate/test_context_integrated.py | 8 +- test/modules/ravestate/test_state.py | 4 +- .../ravestate/test_wrappers_context.py | 20 +-- .../ravestate/test_wrappers_property.py | 14 +- .../ravestate_akinator/test_akinator.py | 15 +- .../modules/ravestate_fillers/test_fillers.py | 2 +- test/modules/ravestate_persqa/test_qa.py | 4 +- 18 files changed, 182 insertions(+), 187 deletions(-) diff --git a/.gitignore b/.gitignore index ceb0687..46846de 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .idea __pycache__ tts.wav +keys* keys.yml dist/* build/* @@ -15,4 +16,4 @@ ros2/build ros2/install ros2/log venv/* -mkdocs.yml \ No newline at end of file +mkdocs.yml diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index c6527bc..8509e60 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -19,25 +19,6 @@ def __init__(self, key: str): self.key = key -def s(signal_name: str, *, min_age: Union[float, ConfigurableAge] = 0., max_age: Union[float, ConfigurableAge] = 5., - detached: bool = False) -> 'Signal': - """ - Alias to call Signal-constructor - - * `signal_name`: Name of the Signal - - * `min_age`: Minimum age for the signal, in seconds. Can also be ConfigurableAge that gets the age from the config. - - * `max_age`: Maximum age for the signal, in seconds. Can also be ConfigurableAge that gets the age from the config. - Set to less-than zero for unrestricted age. - - * `detached`: Flag which indicates, whether spikes that fulfill this signal - are going to have a separate causal group from spikes that - are generated by a state that uses this signal as a constraint. - """ - return Signal(signal_name, min_age=min_age, max_age=max_age, detached=detached) - - class Constraint: """ Superclass for Signal, Conjunct and Disjunct @@ -142,7 +123,7 @@ def __and__(self, other): return Disjunct(*conjunct_list) def __eq__(self, other): - return (isinstance(other, Signal) and self.id() == other.id()) or\ + return (isinstance(other, Signal) and self.id() == other.id()) or \ (isinstance(other, str) and self.id() == other) def __hash__(self): diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index c913808..8f036b2 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -16,7 +16,7 @@ from ravestate.activation import Activation from ravestate import argparser from ravestate.config import Configuration -from ravestate.constraint import s, Signal, Conjunct, Disjunct, ConfigurableAge +from ravestate.constraint import Signal, Conjunct, Disjunct, ConfigurableAge from ravestate.spike import Spike from reggol import get_logger @@ -24,14 +24,14 @@ """ The startup signal, which is fired once when `Context.run()` is executed.
-__Hint:__ All key-word arguments of #constraint.s(...) +__Hint:__ All key-word arguments of #constraint.Signal(...) (`min_age`, `max_age`, `detached`) are supported. """ sig_startup = Signal(":startup") """ Obtain the shutdown signal, which is fired once when `Context.shutdown()` is called.
-__Hint:__ All key-word arguments of #constraint.s(...) +__Hint:__ All key-word arguments of #constraint.Signal(...) (`min_age`, `max_age`, `detached`) are supported. """ sig_shutdown = Signal(":shutdown") @@ -557,7 +557,7 @@ def run_once(self, seconds_passed=1.) -> None: for spike in spikes: if spike.is_wiped() or spike.age() > 0: continue - for state, acts in self._needy_acts_per_state_per_signal[s(spike.id())].items(): + for state, acts in self._needy_acts_per_state_per_signal[spike.id()].items(): old_acts = acts.copy() for act in old_acts: if act.acquire(spike): diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 1091ac4..940c029 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -2,7 +2,7 @@ from threading import Lock from typing import Dict, List, Generator -from ravestate.constraint import s, Signal +from ravestate.constraint import Signal from ravestate.threadlocal import ravestate_thread_local from reggol import get_logger diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index a88f012..73899cc 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -5,7 +5,7 @@ from ravestate.property import Property from ravestate.threadlocal import ravestate_thread_local -from ravestate.constraint import Conjunct, Disjunct, Signal, s, Constraint +from ravestate.constraint import Conjunct, Disjunct, Signal, Constraint from ravestate.consumable import Consumable from reggol import get_logger @@ -87,8 +87,8 @@ class State: def __init__(self, *, signal: Optional[Signal], - write: Union[str, Property, Tuple[Union[str, Property]]], - read: Union[str, Property, Tuple[Union[str, Property]]], + write: Union[Property, Tuple[Property]], + read: Union[Property, Tuple[Property]], cond: Optional[Constraint], action, is_receptor: bool=False, @@ -106,9 +106,9 @@ def __init__(self, *, cond = None # convert read/write properties to tuples - if isinstance(write, Property) or isinstance(write, str): + if isinstance(write, Property): write = (write,) - if isinstance(read, Property) or isinstance(read, str): + if isinstance(read, Property): read = (read,) # catch the insane case @@ -120,8 +120,7 @@ def __init__(self, *, # listen to default changed-signals if no signals are given. # convert triggers to disjunctive normal form. if not cond and len(read) > 0: - cond = Disjunct(*(Conjunct(rprop.changed()) if isinstance(rprop, Property) - else Conjunct(s(f"{rprop}:changed")) for rprop in read)) + cond = Disjunct(*(Conjunct(read_prop.changed()) for read_prop in read)) self.signal = signal self.write_props = write @@ -205,8 +204,8 @@ def wait(self, timeout=5.): def state(*, signal: Optional[Signal] = "", - write: Tuple[Union[Property, str]] = (), - read: Tuple[Union[Property, str]] = (), + write: Tuple[Property] = (), + read: Tuple[Property] = (), cond: Constraint = None, emit_detached: bool = False, weight: float = 1., diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index 37b9570..13eb7ba 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -4,7 +4,7 @@ from reggol import strip_prefix from testfixtures import LogCapture -from ravestate.constraint import s, Signal +from ravestate.constraint import Signal from ravestate.context import Context from ravestate.property import Property from ravestate.state import State, state @@ -18,6 +18,9 @@ DEFAULT_PROPERTY_CHANGED = f"{DEFAULT_PROPERTY_ID}:changed" NEW_PROPERTY_VALUE = 'Dorfmeister' +DEFAULT_PROPERTY = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) +DEFAULT_PROPERTY.set_parent_path(DEFAULT_MODULE_NAME) + SIGNAL_A = Signal("a") SIGNAL_A.module_name = DEFAULT_MODULE_NAME SIGNAL_B = Signal("b") @@ -30,16 +33,16 @@ @pytest.fixture def state_fixture(mocker): - @state(write=(DEFAULT_PROPERTY_ID,), read=(DEFAULT_PROPERTY_ID,)) + @state(write=(DEFAULT_PROPERTY,), read=(DEFAULT_PROPERTY,)) def state_mock_fn(ctx): - ctx[DEFAULT_PROPERTY_ID] = "test" + ctx[DEFAULT_PROPERTY] = "test" state_mock_fn.module_name = DEFAULT_MODULE_NAME return state_mock_fn @pytest.fixture def state_signal_a_fixture(mocker): - @state(read=(DEFAULT_PROPERTY_ID,), signal=SIGNAL_A) + @state(read=(DEFAULT_PROPERTY,), signal=SIGNAL_A) def state_mock_a_fn(ctx): pass state_mock_a_fn.module_name = DEFAULT_MODULE_NAME @@ -80,9 +83,7 @@ def context_fixture(mocker): @pytest.fixture def context_with_property_fixture(mocker, context_fixture) -> Context: - prop = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) - prop.set_parent_path(DEFAULT_MODULE_NAME) - context_fixture.add_prop(prop=prop) + context_fixture.add_prop(prop=DEFAULT_PROPERTY) mocker.patch.object(context_fixture, 'add_prop') return context_fixture diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 89a4da5..a58bb74 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -1,22 +1,20 @@ -from ravestate.module import Module -from ravestate.property import Property -from ravestate.state import state, Resign, Emit, Delete -from ravestate.constraint import s -from ravestate_akinator.api import Api +from .api import Api + +import ravestate as rs +import ravestate_idle as idle +import ravestate_nlp as nlp +import ravestate_rawio as rawio from reggol import get_logger logger = get_logger(__name__) -import ravestate_idle -import ravestate_nlp -import ravestate_rawio - akinator_api: Api CERTAINTY = "certainty_percentage" -with Module(name="akinator", config={CERTAINTY: 90}): - is_it = Property( +with rs.Module(name="akinator", config={CERTAINTY: 90}): + + prop_is_it = rs.Property( name="is_it", default_value="", always_signal_changed=True, @@ -24,7 +22,7 @@ allow_push=False, is_flag_property=True) - question = Property( + prop_question = rs.Property( name="question", default_value="", always_signal_changed=True, @@ -32,7 +30,7 @@ allow_push=False, is_flag_property=True) - initiate_play_again = Property( + prop_init_play_again = rs.Property( name="initiate_play_again", default_value="", always_signal_changed=True, @@ -40,31 +38,36 @@ allow_push=False, is_flag_property=True) - - @state(cond=s("nlp:intent-play") | s("idle:bored"), - write="rawio:out", - signal="initiate-play", - emit_detached=True, - weight=1., - cooldown=30.) + sig_initiate_play = rs.Signal("initiate-play") + sig_exit_game = rs.Signal("exit-game") + sig_wrong_input = rs.Signal("wrong-input") + sig_is_it_asked = rs.Signal("is-it") + + @rs.state( + cond=nlp.sig_intent_play | idle.sig_bored, + write=rawio.prop_out, + signal=sig_initiate_play, + emit_detached=True, + weight=1., + cooldown=30.) def play_ask(ctx): """ Asks if interlocutor wants to play 20 question / akinator Triggered when nlp:play property is changed by "i want to play a game" or a similar input """ - ctx["rawio:out"] = "Do you want to play 20 questions?" - return Emit() + ctx[rawio.prop_out] = "Do you want to play 20 questions?" + return rs.Emit() - @state(cond=s("akinator:initiate-play", min_age=10., max_age=13.), write="rawio:out") + @rs.state(cond=sig_initiate_play.min_age(10).max_age(13), write=rawio.prop_out) def akinator_play_question_ignored(ctx): - ctx["rawio:out"] = "Oh well, maybe later!" - + ctx[rawio.prop_out] = "Oh well, maybe later!" - @state(cond=s("nlp:yesno:changed") & (s("akinator:initiate-play", max_age=13.) | s("akinator:initiate_play_again:changed", max_age=13.)), - read="nlp:yesno", - write=("rawio:out", "akinator:question"), - emit_detached=True) + @rs.state( + cond=nlp.prop_yesno.changed() & (sig_initiate_play.max_age(13) | prop_init_play_again.changed().max_age(13.)), + read=nlp.prop_yesno, + write=(rawio.prop_out, "akinator:question"), + emit_detached=True) def start(ctx): """ Starts akinator session @@ -73,23 +76,24 @@ def start(ctx): Output: First question """ global akinator_api - if ctx["nlp:yesno"] == "yes": + if ctx[nlp.prop_yesno] == "yes": logger.info("Akinator session is started.") akinator_api = Api() - ctx["akinator:question"] = True - ctx["rawio:out"] = "You can answer the questions with:" \ + ctx[prop_question] = True + ctx[rawio.prop_out] = "You can answer the questions with:" \ + ' "yes", "no", "i do not know", "probably", "probably not"' \ + " Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + ": " + akinator_api.get_parameter('question') else: - return Resign() + return rs.Resign() - @state(cond=s("nlp:triples:changed") & s("akinator:question:changed", max_age=13.), - read="nlp:yesno", - write=("rawio:out", "akinator:is_it", "akinator:question"), - emit_detached=True, - signal="wrong-input") + @rs.state( + cond=nlp.prop_triples.changed() & prop_question.changed().max_age(13), + read=nlp.prop_yesno, + write=(rawio.prop_out, prop_is_it, prop_question), + emit_detached=True, + signal=sig_wrong_input) def question_answered(ctx): """ Reads the answer to a question and outputs the next question @@ -98,23 +102,24 @@ def question_answered(ctx): If the answer certainty of akinator is over the configurable CERTAINTY threshold the is_it state is triggered """ global akinator_api - response = akinator_api.answer_to_int_str(ctx["nlp:yesno"]) + response = akinator_api.answer_to_int_str(ctx[nlp.prop_yesno]) if not response == "-1": akinator_api.response_get_request(response) if float(akinator_api.get_parameter('progression')) <= ctx.conf(key=CERTAINTY) and int(akinator_api.get_parameter('step')) <= 20: - ctx["akinator:question"] = True - ctx["rawio:out"] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + ctx[prop_question] = True + ctx[rawio.prop_out] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + ": " + akinator_api.get_parameter('question') else: ctx["akinator:is_it"] = True else: - return Emit() + return rs.Emit() - @state(cond=s("akinator:is_it:changed"), - write="rawio:out", - signal="is-it", - emit_detached=True) + @rs.state( + cond=prop_is_it.changed(), + write=rawio.prop_out, + signal=sig_is_it_asked, + emit_detached=True) def is_it(ctx): """ Outputs the solution guess of akinator: "Is this your character? ..." @@ -122,53 +127,57 @@ def is_it(ctx): """ global akinator_api guess = akinator_api.guess_get_request() - ctx["rawio:out"] = "Is this your character? " + guess['name'] + " " + guess['desc'] + "." \ - + " Please answer with 'yes' or 'no'." - return Emit() + ctx[rawio.prop_out] = \ + f"Is this your character? {guess['name']}: {guess['desc']}." \ + " Please answer with 'yes' or 'no'." + return rs.Emit() - @state(cond=s("nlp:yesno:changed") & s("akinator:is-it", max_age=13.), - read="nlp:yesno", - write=("rawio:out", "akinator:initiate_play_again"), - emit_detached=True) + @rs.state( + cond=nlp.prop_yesno.changed() & sig_is_it_asked.max_age(13), + read=nlp.prop_yesno, + write=(rawio.prop_out, "akinator:initiate_play_again"), + emit_detached=True) def is_it_answered(ctx): """ Gets input from interlocutor on the "is it" question and posts the result Asks if the interlocutor wants to play again. """ - if ctx["nlp:yesno"] == "yes": + if ctx[nlp.prop_yesno] == "yes": akinator_api.choice_get_request() out = "Yeah! I guessed right! Thanks for playing with me! Do you want to play again?" - elif ctx["nlp:yesno"] == "no": + elif ctx[nlp.prop_yesno] == "no": akinator_api.exclusion_get_request() out = "Oh no! I guessed wrong but do you want to play again?" else: out = "What? But do you want to play again?" - ctx["rawio:out"] = out + ctx[rawio.prop_out] = out ctx["akinator:initiate_play_again"] = True - @state(cond=s("akinator:wrong-input"), - write="rawio:out", - emit_detached=True, - signal="exit-game") + @rs.state( + cond=sig_wrong_input, + write=rawio.prop_out, + emit_detached=True, + signal=sig_exit_game) def wrong_input(ctx): """ Catches wrong inputs from the interlocutor during questions answering and loops back to the question state """ - ctx["rawio:out"] = "Sadly I could not process that answer. Do you want to stop playing Akinator?" - return Emit() + ctx[rawio.prop_out] = "Sadly I could not process that answer. Do you want to stop playing Akinator?" + return rs.Emit() - @state(cond=s("nlp:yesno:changed") & s("akinator:exit-game", max_age=13.), - write=("rawio:out", "akinator:question"), - read="nlp:yesno", - emit_detached=True) + @rs.state( + cond=nlp.prop_yesno.changed() & sig_exit_game.max_age(13), + write=(rawio.prop_out, prop_question), + read=nlp.prop_yesno, + emit_detached=True) def exit_game_answered(ctx): - if ctx["nlp:yesno"] == "yes": - ctx["rawio:out"] = "Thanks for playing with me!" - return Resign() + if ctx[nlp.prop_yesno] == "yes": + ctx[rawio.prop_out] = "Thanks for playing with me!" + return rs.Resign() else: - ctx["rawio:out"] = "Yeah! Let's keep playing! Remember that you have these five answering choices:" \ + ctx[rawio.prop_out] = "Yeah! Let's keep playing! Remember that you have these five answering choices:" \ " 'yes', 'no', 'i do not know', 'probably', 'probably not'" - ctx["akinator:question"] = True + ctx[prop_question] = True diff --git a/modules/reggol/colored_formatter.py b/modules/reggol/colored_formatter.py index 70e9807..8c1d6eb 100755 --- a/modules/reggol/colored_formatter.py +++ b/modules/reggol/colored_formatter.py @@ -17,7 +17,7 @@ class ColoredFormatter(logging.Formatter): GROUP_COLOR = CYAN - FORMAT = "[%(levelname_color)-5s] [%(name_color)s] %(msg)s" + FORMAT = "[%(levelname_color)-5s] [%(name_color)Signal] %(msg)Signal" def __init__(self, log_format=FORMAT, colors=None): super().__init__(log_format) diff --git a/modules/reggol/logger.py b/modules/reggol/logger.py index 7f97d11..b16ec71 100755 --- a/modules/reggol/logger.py +++ b/modules/reggol/logger.py @@ -5,7 +5,7 @@ from reggol.colored_formatter import ColoredFormatter TIME_FORMAT = "%Y-%m-%d %H:%M:%S" -LOG_FORMAT = "%(asctime)s - %(name)s:%(lineno)d - [%(levelname)s] - %(msg)s" +LOG_FORMAT = "%(asctime)Signal - %(name)Signal:%(lineno)d - [%(levelname)Signal] - %(msg)Signal" DEFAULT_FILE_NAME = f"log_{time.strftime('%Y%m%d_%H%M%S')}.log" DEFAULT_DIRECTORY = os.path.join(os.path.dirname(__file__), 'log') diff --git a/test/modules/ravestate/test_constraint.py b/test/modules/ravestate/test_constraint.py index fafbd43..dc1bb92 100644 --- a/test/modules/ravestate/test_constraint.py +++ b/test/modules/ravestate/test_constraint.py @@ -1,6 +1,6 @@ from ravestate.iactivation import IActivation from ravestate.testfixtures import * -from ravestate.constraint import s, Constraint, Disjunct, Conjunct +from ravestate.constraint import Signal, Constraint, Disjunct, Conjunct from ravestate.spike import Spike @@ -31,17 +31,17 @@ def test_parent(constraint_fixture, spike_fixture: Spike, activation_fixture: IA def test_signal(mocker, activation_fixture): - sig = s("mysig") + sig = Signal("mysig") with mocker.patch.object(activation_fixture, "resources", return_value=set()): assert not sig.evaluate() - assert set(sig.signals()) == {s("mysig")} + assert set(sig.signals()) == {Signal("mysig")} sig.acquire(Spike(sig="notmysig"), activation_fixture) assert not sig.evaluate() sig.acquire(Spike(sig="mysig"), activation_fixture) assert sig.evaluate() - sig_and_dis = s("sig") & (s("dis") | s("junct")) + sig_and_dis = Signal("sig") & (Signal("dis") | Signal("junct")) assert not sig_and_dis.evaluate() sig_and_dis.acquire(Spike(sig="sig"), activation_fixture) assert not sig_and_dis.evaluate() @@ -61,9 +61,9 @@ def test_signal(mocker, activation_fixture): def test_signal_or(mocker): - sig = s("mysig") + sig = Signal("mysig") with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - conjunct = s("sig1") & s("sig2") + conjunct = Signal("sig1") & Signal("sig2") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = sig | conjunct Conjunct.__init__.assert_called_once_with(sig) @@ -71,15 +71,15 @@ def test_signal_or(mocker): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): - disjunct = s("sig1") | s("sig2") + disjunct = Signal("sig1") | Signal("sig2") _ = sig | disjunct Disjunct.__init__.assert_called_with(sig, 1) def test_signal_and(mocker): - sig = s("mysig") - conjunct = s("sig1") & s("sig2") - disjunct = s("sig1") | s("sig2") + sig = Signal("mysig") + conjunct = Signal("sig1") & Signal("sig2") + disjunct = Signal("sig1") | Signal("sig2") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = sig & sig @@ -98,10 +98,10 @@ def test_signal_and(mocker): def test_conjunct(mocker, activation_fixture): - conjunct = s("sig1") & s("sig2") & s("sig3") + conjunct = Signal("sig1") & Signal("sig2") & Signal("sig3") with mocker.patch.object(activation_fixture, "resources", return_value=set()): assert not conjunct.evaluate() - assert set(conjunct.signals()) == {s("sig1"), s("sig2"), s("sig3")} + assert set(conjunct.signals()) == {Signal("sig1"), Signal("sig2"), Signal("sig3")} conjunct.acquire(Spike(sig="sig1"), activation_fixture) assert not conjunct.evaluate() conjunct.acquire(Spike(sig="sig2"), activation_fixture) @@ -113,23 +113,23 @@ def test_conjunct(mocker, activation_fixture): def test_conjunct_or(mocker): - conjunct = s("sig1") & s("sig2") & s("sig3") + conjunct = Signal("sig1") & Signal("sig2") & Signal("sig3") with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - conjunct2 = s("sig1") & s("sig2") + conjunct2 = Signal("sig1") & Signal("sig2") _ = conjunct | conjunct2 Disjunct.__init__.assert_called_once_with(conjunct, conjunct2) with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): - disjunct = s("sig1") | s("sig2") + disjunct = Signal("sig1") | Signal("sig2") _ = conjunct | disjunct Disjunct.__init__.assert_called_with(conjunct, 1) def test_conjunct_and(mocker): - sig = s("mysig") - conjunct = s("sig1") & s("sig2") - disjunct = s("sig1") | s("sig2") + sig = Signal("mysig") + conjunct = Signal("sig1") & Signal("sig2") + disjunct = Signal("sig1") | Signal("sig2") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = conjunct & sig @@ -148,10 +148,10 @@ def test_conjunct_and(mocker): def test_disjunct(mocker, activation_fixture): - disjunct = (s("sig1") & s("sig2")) | s("sig3") + disjunct = (Signal("sig1") & Signal("sig2")) | Signal("sig3") with mocker.patch.object(activation_fixture, "resources", return_value=set()): assert not disjunct.evaluate() - assert set(disjunct.signals()) == {s("sig1"), s("sig2"), s("sig3")} + assert set(disjunct.signals()) == {Signal("sig1"), Signal("sig2"), Signal("sig3")} disjunct.acquire(Spike(sig="sig1"), activation_fixture) assert not disjunct.evaluate() disjunct.acquire(Spike(sig="sig3"), activation_fixture) @@ -159,16 +159,16 @@ def test_disjunct(mocker, activation_fixture): def test_disjunct_or(mocker): - disjunct = (s("sig1") & s("sig2")) | s("sig3") + disjunct = (Signal("sig1") & Signal("sig2")) | Signal("sig3") with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - conjunct = s("sig1") & s("sig2") + conjunct = Signal("sig1") & Signal("sig2") _ = disjunct | conjunct Disjunct.__init__.assert_called_with(1, conjunct) with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - signal = s("sig1") + signal = Signal("sig1") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = disjunct | signal Conjunct.__init__.assert_called_once_with(signal) @@ -176,15 +176,15 @@ def test_disjunct_or(mocker): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): - disjunct2 = s("sig1") | s("sig2") + disjunct2 = Signal("sig1") | Signal("sig2") _ = disjunct | disjunct2 Disjunct.__init__.assert_called_with(1) def test_disjunct_and(mocker): - sig = s("mysig") - conjunct = s("sig1") & s("sig2") - disjunct = s("sig1") | s("sig2") + sig = Signal("mysig") + conjunct = Signal("sig1") & Signal("sig2") + disjunct = Signal("sig1") | Signal("sig2") with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch.object(disjunct, '_conjunctions', return_value={}): @@ -204,4 +204,4 @@ def test_disjunct_and(mocker): def test_legal(): with pytest.raises(ValueError): - _ = (s("i") | s("am")) & (s("also") | s("illegal")) + _ = (Signal("i") | Signal("am")) & (Signal("also") | Signal("illegal")) diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 2261219..5fdb294 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -7,7 +7,7 @@ def test_emit(context_fixture, spike_fixture): - sig = s(DEFAULT_PROPERTY_CHANGED) + sig = Signal(DEFAULT_PROPERTY_CHANGED) context_fixture.emit(sig) assert len(context_fixture._spikes_per_signal[sig]) == 1 list(context_fixture._spikes_per_signal[sig])[0].adopt(spike_fixture) @@ -107,8 +107,8 @@ def test_add_state( context_with_property_fixture.add_state(st=state_fixture) # Make sure, that module:property:changed was added as a cause for module:a - assert s(DEFAULT_PROPERTY_CHANGED) in \ - context_with_property_fixture._signal_causes[state_signal_a_fixture.signal][0] + assert Signal(DEFAULT_PROPERTY_CHANGED) in \ + context_with_property_fixture._signal_causes[state_signal_a_fixture.signal][0] context_with_property_fixture.add_state(st=state_signal_b_fixture) context_with_property_fixture.add_state(st=state_signal_c_fixture) @@ -122,16 +122,16 @@ def test_add_state( conj for conj in d_conjunctions if len(tuple(conj.signals())) > 2] assert len(d_conjunctions) == 2 # 2 completed - assert s(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[0] + assert Signal(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[0] assert state_signal_a_fixture.signal in d_conjunctions[0] - assert s(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[1] + assert Signal(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[1] assert state_signal_a_fixture.signal in d_conjunctions[1] assert \ state_signal_b_fixture.signal in d_conjunctions[0] and state_signal_c_fixture.signal in d_conjunctions[1] or \ state_signal_b_fixture.signal in d_conjunctions[1] and state_signal_c_fixture.signal in d_conjunctions[0] # Basic specificity sanity checks - assert len(list(context_with_property_fixture._states_for_signal(s(DEFAULT_PROPERTY_CHANGED)))) == 5 + assert len(list(context_with_property_fixture._states_for_signal(Signal(DEFAULT_PROPERTY_CHANGED)))) == 5 a_acts = list(context_with_property_fixture._state_activations(st=state_signal_a_fixture)) b_acts = list(context_with_property_fixture._state_activations(st=state_signal_b_fixture)) c_acts = list(context_with_property_fixture._state_activations(st=state_signal_c_fixture)) @@ -140,7 +140,7 @@ def test_add_state( assert len(b_acts) == 1 assert len(c_acts) == 1 assert len(d_acts) == 1 - propchange_sig_spec = context_with_property_fixture.signal_specificity(s(DEFAULT_PROPERTY_CHANGED)) + propchange_sig_spec = context_with_property_fixture.signal_specificity(Signal(DEFAULT_PROPERTY_CHANGED)) assert a_acts[0].specificity() == propchange_sig_spec a_sig_spec = context_with_property_fixture.signal_specificity(state_signal_a_fixture.signal) assert a_sig_spec == 1/3 @@ -149,8 +149,8 @@ def test_add_state( def test_add_state_configurable_age(context_with_property_fixture: Context): - my_cond = s(signal_name=DEFAULT_PROPERTY_CHANGED, min_age=ConfigurableAge(key="min_age_key"), - max_age=ConfigurableAge(key="max_age_key")) + my_cond = Signal(DEFAULT_PROPERTY_CHANGED, min_age=ConfigurableAge(key="min_age_key"), + max_age=ConfigurableAge(key="max_age_key")) @state(cond=my_cond) def conf_st(ctx): @@ -164,12 +164,15 @@ def conf_st(ctx): def test_add_state_configurable_age_not_in_config(context_with_property_fixture: Context): - my_cond = s(signal_name=DEFAULT_PROPERTY_CHANGED, min_age=ConfigurableAge(key="min_age_key"), - max_age=ConfigurableAge(key="max_age_key")) + my_cond = Signal( + DEFAULT_PROPERTY_CHANGED, + min_age=ConfigurableAge(key="min_age_key"), + max_age=ConfigurableAge(key="max_age_key")) @state(cond=my_cond) def conf_st(ctx): pass + conf_st.module_name = DEFAULT_MODULE_NAME context_with_property_fixture.add_state(st=conf_st) assert my_cond.min_age_value == 0. diff --git a/test/modules/ravestate/test_context_integrated.py b/test/modules/ravestate/test_context_integrated.py index 18cf488..966e1a6 100644 --- a/test/modules/ravestate/test_context_integrated.py +++ b/test/modules/ravestate/test_context_integrated.py @@ -1,6 +1,6 @@ from ravestate.constraint import Signal, ConfigurableAge from ravestate.spike import Spike -from ravestate.state import s, Emit +from ravestate.state import Signal, Emit from ravestate.context import sig_startup from ravestate.module import Module from ravestate.testfixtures import * @@ -11,7 +11,7 @@ def test_run_with_pressure(): with Module(name=DEFAULT_MODULE_NAME): - Property(name=DEFAULT_PROPERTY_NAME) + prop = Property(name=DEFAULT_PROPERTY_NAME) a = Signal("a") b = Signal("b") @@ -24,11 +24,11 @@ def signal_a(ctx): def signal_b(ctx): return Emit() - @state(cond=a, write=DEFAULT_PROPERTY_ID) + @state(cond=a, write=prop) def pressuring_state(ctx): pass - @state(cond=a & b, write=DEFAULT_PROPERTY_ID) + @state(cond=a & b, write=prop) def specific_state(ctx): pass diff --git a/test/modules/ravestate/test_state.py b/test/modules/ravestate/test_state.py index b74b3a1..49007c5 100644 --- a/test/modules/ravestate/test_state.py +++ b/test/modules/ravestate/test_state.py @@ -1,5 +1,5 @@ import pytest -from ravestate.constraint import s, Signal +from ravestate.constraint import Signal from ravestate.state import State, state from ravestate.wrappers import ContextWrapper @@ -67,7 +67,7 @@ def test_decorator_illegal_trigger(under_test, default_signal, default_read, def @state(signal=default_signal, read=default_read, write=default_write, - cond=(s("rawio:in:changed") | s("facerec:face:changed")) & (s("sys:has-internet") | s("foo:poo"))) + cond=(Signal("rawio:in:changed") | Signal("facerec:face:changed")) & (Signal("sys:has-internet") | Signal("foo:poo"))) def test_state(_): return "Hello world!" diff --git a/test/modules/ravestate/test_wrappers_context.py b/test/modules/ravestate/test_wrappers_context.py index 872f511..3052508 100644 --- a/test/modules/ravestate/test_wrappers_context.py +++ b/test/modules/ravestate/test_wrappers_context.py @@ -33,7 +33,7 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, child=Property(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_ID}:pushed"), + Signal(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -43,7 +43,7 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture[CHILD_PROPERTY_ID] == DEFAULT_PROPERTY_VALUE context_wrapper_fixture[CHILD_PROPERTY_ID] = CHILD_PROPERTY_VALUE context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_ID}:changed"), + Signal(f"{CHILD_PROPERTY_ID}:changed"), parents=None, wipe=True, payload=CHILD_PROPERTY_VALUE) @@ -52,7 +52,7 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert # pop child assert context_wrapper_fixture.pop(CHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_ID}:popped"), + Signal(f"{DEFAULT_PROPERTY_ID}:popped"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -65,7 +65,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, child=Property(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_ID}:pushed"), + Signal(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -74,7 +74,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ assert context_wrapper_fixture.push(parent_property_or_path=CHILD_PROPERTY_ID, child=Property(name=GRANDCHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_ID}:pushed"), + Signal(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=GRANDCHILD_PROPERTY_ID) @@ -85,7 +85,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ assert context_wrapper_fixture[GRANDCHILD_PROPERTY_ID] == DEFAULT_PROPERTY_VALUE context_wrapper_fixture[GRANDCHILD_PROPERTY_ID] = GRANDCHILD_PROPERTY_VALUE context_with_property_fixture.emit.assert_called_with( - s(f"{GRANDCHILD_PROPERTY_ID}:changed"), + Signal(f"{GRANDCHILD_PROPERTY_ID}:changed"), parents=None, wipe=True, payload=GRANDCHILD_PROPERTY_VALUE) @@ -94,7 +94,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ # pop child assert context_wrapper_fixture.pop(CHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_ID}:popped"), + Signal(f"{DEFAULT_PROPERTY_ID}:popped"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -107,7 +107,7 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, child=Property(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_ID}:pushed"), + Signal(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -116,7 +116,7 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture.push(parent_property_or_path=CHILD_PROPERTY_ID, child=Property(name=GRANDCHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_ID}:pushed"), + Signal(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=GRANDCHILD_PROPERTY_ID) @@ -128,7 +128,7 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert # pop grandchild assert context_wrapper_fixture.pop(GRANDCHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_ID}:popped"), + Signal(f"{CHILD_PROPERTY_ID}:popped"), parents=None, wipe=False, payload=GRANDCHILD_PROPERTY_ID) diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index b79ce88..f0b5292 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -1,5 +1,5 @@ from ravestate.testfixtures import * -from ravestate.constraint import s +from ravestate.constraint import Signal from ravestate.icontext import IContext from ravestate.state import State from ravestate.property import Property @@ -100,7 +100,7 @@ def test_property_write(under_test_read_write: PropertyWrapper, default_property under_test_read_write.set(NEW_PROPERTY_VALUE) assert (under_test_read_write.get() == NEW_PROPERTY_VALUE) context_mock.emit.assert_called_once_with( - s(f"{under_test_read_write.prop.id()}:changed"), + Signal(f"{under_test_read_write.prop.id()}:changed"), parents=None, wipe=True, payload=NEW_PROPERTY_VALUE) @@ -115,12 +115,12 @@ def test_flag_property(context_mock): prop_wrapper.set(True) assert (prop_wrapper.get() is True) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.id()}:changed"), + Signal(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True, payload=True) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.id()}:true"), + Signal(f"{prop_wrapper.prop.id()}:true"), parents=None, wipe=True) @@ -128,12 +128,12 @@ def test_flag_property(context_mock): prop_wrapper.set(False) assert (prop_wrapper.get() is False) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.id()}:changed"), + Signal(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True, payload=False) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.id()}:false"), + Signal(f"{prop_wrapper.prop.id()}:false"), parents=None, wipe=True) @@ -141,7 +141,7 @@ def test_flag_property(context_mock): prop_wrapper.set(None) assert (prop_wrapper.get() is None) context_mock.emit.assert_called_once_with( - s(f"{prop_wrapper.prop.id()}:changed"), + Signal(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True, payload=None) diff --git a/test/modules/ravestate_akinator/test_akinator.py b/test/modules/ravestate_akinator/test_akinator.py index 4acfd2c..ded36d1 100644 --- a/test/modules/ravestate_akinator/test_akinator.py +++ b/test/modules/ravestate_akinator/test_akinator.py @@ -2,17 +2,18 @@ def test_react_to_play_asked(mocker): - import ravestate_akinator + import ravestate_akinator as akin + import ravestate_rawio as rawio test_dict = {} - ravestate_akinator.play_ask(test_dict) - - assert test_dict["rawio:out"] == "Do you want to play 20 questions?" + akin.play_ask(test_dict) + assert test_dict[rawio.prop_out] == "Do you want to play 20 questions?" def test_play_question_ignored(mocker): - import ravestate_akinator + import ravestate_akinator as akin + import ravestate_rawio as rawio test_dict = {} - ravestate_akinator.akinator_play_question_ignored(test_dict) - assert test_dict["rawio:out"] == "Oh well, maybe later!" + akin.akinator_play_question_ignored(test_dict) + assert test_dict[rawio.prop_out] == "Oh well, maybe later!" diff --git a/test/modules/ravestate_fillers/test_fillers.py b/test/modules/ravestate_fillers/test_fillers.py index eed3124..f71da6f 100644 --- a/test/modules/ravestate_fillers/test_fillers.py +++ b/test/modules/ravestate_fillers/test_fillers.py @@ -9,7 +9,7 @@ def test_fillers(): ctx = Context("rawio", "idle", "verbaliser", "fillers") assert ctx["verbaliser:intent"].read() == "" - ctx.emit(s("idle:impatient")) + ctx.emit(Signal("idle:impatient")) ctx.run_once() assert impatient_fillers.wait() assert ctx["verbaliser:intent"].read() == "fillers" diff --git a/test/modules/ravestate_persqa/test_qa.py b/test/modules/ravestate_persqa/test_qa.py index 38b8d77..5d0f3b3 100644 --- a/test/modules/ravestate_persqa/test_qa.py +++ b/test/modules/ravestate_persqa/test_qa.py @@ -78,8 +78,8 @@ def say(ctx: rs.ContextWrapper, what: str): if __name__ == "__main__": - from hanging_threads import start_monitoring - monitoring_thread = start_monitoring() + # from hanging_threads import start_monitoring + # monitoring_thread = start_monitoring() set_default_loglevel("DEBUG") test_run_qa() exit() From 6de4ca761629e11ce1bb9979272faae42e34f5c4 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Sat, 11 May 2019 13:02:14 +0200 Subject: [PATCH 06/10] WIP Enforcing calls to Signal()/Property() to be within Module scope. --- modules/ravestate/causal.py | 2 +- modules/ravestate/constraint.py | 39 +++++-- modules/ravestate/context.py | 100 ++++++++++-------- modules/ravestate/property.py | 4 +- modules/ravestate/state.py | 30 +++--- modules/ravestate/testfixtures.py | 20 +--- modules/ravestate_nlp/__init__.py | 2 +- modules/reggol/colored_formatter.py | 2 +- modules/reggol/logger.py | 2 +- test/modules/ravestate/test_constraint.py | 54 +++++----- test/modules/ravestate/test_context.py | 16 +-- test/modules/ravestate/test_state.py | 8 +- .../ravestate/test_wrappers_context.py | 20 ++-- .../ravestate/test_wrappers_property.py | 14 +-- .../modules/ravestate_fillers/test_fillers.py | 2 +- 15 files changed, 171 insertions(+), 144 deletions(-) diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index c487f72..98d184f 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -438,7 +438,7 @@ def _change_effect_causes(self, act: IActivation, change: Optional[int]): del self._uncaused_spikes[sig][act] if len(self._uncaused_spikes[sig]) == 0: for act in set(self._non_detached_activations()): - act.effect_not_caused(self, sig.id) + act.effect_not_caused(self, sig.id()) del self._uncaused_spikes[sig] def _non_detached_activations(self) -> Generator[IActivation, None, None]: diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 8509e60..cf54a77 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -61,8 +61,10 @@ def effect_not_caused(self, act: IActivation, group: ICausalGroup, effect: str) class Signal(Constraint): """ - Class that represents a Signal + Class that represents a Signal. Should be constructed in a `with Module(..)` + context, such that it's module scope is set automatically. """ + name: str spike: Optional[Spike] min_age_value: float @@ -84,7 +86,7 @@ class Signal(Constraint): _min_age_ticks: int # written on acquire, when act.secs_to_ticks is available - def __init__(self, name: str, *, min_age=0., max_age=5., detached=False): + def __init__(self, name: str, *, min_age=0., max_age=5., detached=False, _skip_module_context=False): self.name = name self.min_age_value = min_age self.max_age_value = max_age @@ -98,9 +100,10 @@ def __init__(self, name: str, *, min_age=0., max_age=5., detached=False): # if min_age > max_age and max_age > .0: # logger.warning(f"{self}: max_age={max_age} < min_age={min_age}!") - # add signal to module in current `with Module(...)` clause - module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) - if module_under_construction: + if not _skip_module_context: + # add signal to module in current `with Module(...)` clause + module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) + assert module_under_construction module_under_construction.add(self) def __or__(self, other): @@ -132,9 +135,11 @@ def __hash__(self): def __repr__(self): return f"Signal({self.id()}, {self.min_age_value}, {self.max_age_value}, {self.detached_value})" + def __str__(self): + return self.id() + def id(self): - prefix = f'{self.module_name}:' if self.module_name else '' - return f'{prefix}{self.name}' + return f'{self.module_name}:{self.name}' def signals(self) -> Generator['Signal', None, None]: yield self @@ -217,8 +222,24 @@ def detached(self) -> 'Signal': new_sig.detached_value = True return new_sig - def __str__(self): - return self.id() + +class SignalRef(Signal): + """ + Signal reference. Almost the same as a signal, except that + it will not try to auto-discover it's module out of thread-local context + (module_name will stay unfilled). + """ + + def __init__(self, name: str, *, min_age=0., max_age=5., detached=False): + super().__init__( + name, + min_age=min_age, + max_age=max_age, + detached=detached, + _skip_module_context=True) + + def id(self): + return self.name class Conjunct(Constraint): diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 8f036b2..0bb92a5 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -16,32 +16,56 @@ from ravestate.activation import Activation from ravestate import argparser from ravestate.config import Configuration -from ravestate.constraint import Signal, Conjunct, Disjunct, ConfigurableAge +from ravestate.constraint import * from ravestate.spike import Spike from reggol import get_logger logger = get_logger(__name__) -""" -The startup signal, which is fired once when `Context.run()` is executed.
-__Hint:__ All key-word arguments of #constraint.Signal(...) - (`min_age`, `max_age`, `detached`) are supported. -""" -sig_startup = Signal(":startup") -""" -Obtain the shutdown signal, which is fired once when `Context.shutdown()` is called.
-__Hint:__ All key-word arguments of #constraint.Signal(...) - (`min_age`, `max_age`, `detached`) are supported. -""" -sig_shutdown = Signal(":shutdown") +CORE_MODULE_NAME = "core" +IMPORT_MODULES_CONFIG_KEY = "import" +TICK_RATE_CONFIG_KEY = "tickrate" +CORE_MODULE_CONFIG = { + IMPORT_MODULES_CONFIG_KEY: [], + TICK_RATE_CONFIG_KEY: 20 +} +with Module(name="core", config=CORE_MODULE_CONFIG): -prop_pressure = Property(name="pressure", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, - default_value=False, always_signal_changed=False, is_flag_property=True) + """ + The startup signal, which is fired once when `Context.run()` is executed.
+ __Hint:__ All key-word arguments of #constraint.Signal(...) + (`min_age`, `max_age`, `detached`) are supported. + """ + sig_startup = Signal("startup") + + """ + Obtain the shutdown signal, which is fired once when `Context.shutdown()` is called.
+ __Hint:__ All key-word arguments of #constraint.Signal(...) + (`min_age`, `max_age`, `detached`) are supported. + """ + sig_shutdown = Signal("shutdown") + + prop_pressure = Property( + name="pressure", + allow_read=True, + allow_write=True, + allow_push=False, + allow_pop=False, + default_value=False, + always_signal_changed=False, + is_flag_property=True) + + prop_activity = Property( + name="activity", + allow_read=True, + allow_write=True, + allow_push=False, + allow_pop=False, + default_value=0, + always_signal_changed=False) -prop_activity = Property(name="activity", allow_read=True, allow_write=True, allow_push=False, allow_pop=False, - default_value=0, always_signal_changed=False) def create_and_run_context(*args, runtime_overrides=None): """ @@ -80,10 +104,6 @@ class Context(IContext): _default_signals: Tuple[Signal] = (sig_startup, sig_shutdown) _default_properties: Tuple[Property] = (prop_activity, prop_pressure) - core_module_name = "core" - import_modules_config = "import" - tick_rate_config = "tickrate" - _lock: RLock _properties: Dict[str, Property] @@ -131,11 +151,6 @@ def __init__(self, *arguments, runtime_overrides: List[Tuple[str, str, Any]] = N """ modules, overrides, config_files = argparser.handle_args(*arguments) self._config = Configuration(config_files) - self._core_config = { - self.import_modules_config: [], - self.tick_rate_config: 20 - } - self._config.add_conf(Module(name=self.core_module_name, config=self._core_config)) self._lock = RLock() self._shutdown_flag = Event() self._properties = dict() @@ -145,16 +160,9 @@ def __init__(self, *arguments, runtime_overrides: List[Tuple[str, str, Any]] = N self._activations_per_state = dict() self._run_task = None - # Register default signals - for signal in self._default_signals: - self._add_sig(signal) - - # Register default properties - for prop in self._default_properties: - self.add_prop(prop=prop) - # Load required modules - for module_name in self._core_config[self.import_modules_config] + modules: + self.add_module(CORE_MODULE_NAME) + for module_name in self.conf(mod=CORE_MODULE_NAME, key=IMPORT_MODULES_CONFIG_KEY) + modules: self.add_module(module_name) # Set required config overrides @@ -165,9 +173,10 @@ def __init__(self, *arguments, runtime_overrides: List[Tuple[str, str, Any]] = N for module_name, key, value in runtime_overrides: self._config.set(module_name, key, value) - if self._core_config[self.tick_rate_config] < 1: + self.tick_rate = self.conf(mod=CORE_MODULE_NAME, key=TICK_RATE_CONFIG_KEY) + if self.tick_rate < 1: logger.error("Attempt to set core config `tickrate` to a value less-than 1!") - self._core_config[self.tick_rate_config] = 1 + self.tick_rate = 1 def emit(self, signal: Signal, parents: Set[Spike]=None, wipe: bool=False, payload: Any=None) -> None: """ @@ -215,7 +224,7 @@ def wipe(self, signal: Signal): def run(self) -> None: """ - Creates a signal processing thread, starts it, and emits the :startup signal. + Creates a signal processing thread, starts it, and emits the core:startup signal. """ if self._run_task: logger.error("Attempt to start context twice!") @@ -487,7 +496,7 @@ def secs_to_ticks(self, seconds: float) -> int: **Returns:** An integer tick count. """ - return ceil(seconds * float(self._core_config[self.tick_rate_config])) + return ceil(seconds * float(self.tick_rate)) def possible_signals(self, state: State) -> Generator[Signal, None, None]: """ @@ -518,7 +527,7 @@ def run_once(self, seconds_passed=1.) -> None: (4) forget spikes which have no suitors in their causal groups.
(5) age spikes.
(6) invoke garbage collection.
- (7) update the core `:activity` and `:pressure` variables. + (7) update the `core:activity` and `core:pressure` variables. * `seconds_passed`: Seconds, as floatiing point, since the last update. Will be used to determine the number of ticks to add/subtract to/from spike/activation age/cooldown/deathclock. @@ -603,7 +612,6 @@ def _state_activated(self, act: Activation): def _add_sig(self, sig: Signal): if sig in self._needy_acts_per_state_per_signal: - logger.error(f"Attempt to add signal f{sig.id()} twice!") return self._signal_causes[sig] = [] self._needy_acts_per_state_per_signal[sig] = defaultdict(set) @@ -626,6 +634,8 @@ def _module_registration_callback(self, mod: Module): self._config.add_conf(mod) for prop in mod.props: self.add_prop(prop=prop) + for sig in mod.signals: + self._add_sig(sig) for st in mod.states: self.add_state(st=st) logger.info(f"Module {mod.name} added to session.") @@ -739,19 +749,19 @@ def _update_core_properties(self, debug=False): pressured_acts = [] partially_fulfilled_acts = [] for act in self._state_activations(): - if self[":activity"].changed() not in set(act.constraint.signals()): + if prop_activity.changed() not in set(act.constraint.signals()): if act.is_pressured(): pressured_acts.append(act.id) if act.spiky(): partially_fulfilled_acts.append(act) PropertyWrapper( - prop=self[":pressure"], + prop=prop_pressure, ctx=self, allow_write=True, allow_read=True ).set(len(pressured_acts) > 0) PropertyWrapper( - prop=self[":activity"], + prop=prop_activity, ctx=self, allow_write=True, allow_read=True @@ -764,7 +774,7 @@ def _update_core_properties(self, debug=False): logger.info(partially_fulfilled_info) def _run_loop(self): - tick_interval = 1. / self._core_config[self.tick_rate_config] + tick_interval = 1. / self.tick_rate while not self._shutdown_flag.wait(tick_interval): self.run_once(tick_interval) diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 940c029..7fead7f 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -53,8 +53,8 @@ def __init__( # add property to module in current `with Module(...)` clause module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) - if module_under_construction: - module_under_construction.add(self) + assert module_under_construction + module_under_construction.add(self) def id(self): return f'{self.parent_path}:{self.name}' diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 73899cc..0e22164 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -5,7 +5,7 @@ from ravestate.property import Property from ravestate.threadlocal import ravestate_thread_local -from ravestate.constraint import Conjunct, Disjunct, Signal, Constraint +from ravestate.constraint import Conjunct, Disjunct, Signal, SignalRef, Constraint from ravestate.consumable import Consumable from reggol import get_logger @@ -67,8 +67,8 @@ class State: """ signal: Signal - write_props: Tuple[Union[str, Property]] - read_props: Tuple[Union[str, Property]] + write_props: Set[str] + read_props: Set[str] constraint: Constraint emit_detached: bool cooldown: float @@ -87,8 +87,8 @@ class State: def __init__(self, *, signal: Optional[Signal], - write: Union[Property, Tuple[Property]], - read: Union[Property, Tuple[Property]], + write: Union[Property, str, Tuple[Property, str]], + read: Union[Property, str, Tuple[Property, str]], cond: Optional[Constraint], action, is_receptor: bool=False, @@ -105,11 +105,17 @@ def __init__(self, *, logger.error(f"Attempt to create state {self.name} which has a string as condition, not a constraint!") cond = None - # convert read/write properties to tuples - if isinstance(write, Property): + # normalize read/write properties to sets of strings + if isinstance(write, Property) or isinstance(write, str): write = (write,) - if isinstance(read, Property): + if isinstance(read, Property) or isinstance(write, str): read = (read,) + write: Set[str] = set( + write_prop.id() if isinstance(write_prop, Property) else write_prop + for write_prop in write) + read: Set[str] = set( + read_prop.id() if isinstance(read_prop, Property) else read_prop + for read_prop in read) # catch the insane case if not len(read) and not cond and not is_receptor: @@ -120,7 +126,7 @@ def __init__(self, *, # listen to default changed-signals if no signals are given. # convert triggers to disjunctive normal form. if not cond and len(read) > 0: - cond = Disjunct(*(Conjunct(read_prop.changed()) for read_prop in read)) + cond = Disjunct(*(Conjunct(SignalRef(f"{read_prop}:changed")) for read_prop in read)) self.signal = signal self.write_props = write @@ -146,13 +152,13 @@ def __call__(self, context, *args, **kwargs) -> Optional[_StateActivationResult] return self.action(*args, **kwargs) def get_read_props_ids(self) -> Set[str]: - return set(prop.id() if isinstance(prop, Property) else prop for prop in self.read_props) + return self.read_props def get_write_props_ids(self) -> Set[str]: - return set(prop.id() if isinstance(prop, Property) else prop for prop in self.write_props) + return self.write_props def get_all_props_ids(self) -> Set[str]: - return self.get_read_props_ids().union(self.get_write_props_ids()) + return self.read_props | self.write_props def update_weight(self, seconds_passed: float): """ diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index 13eb7ba..6ed35d4 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -1,15 +1,9 @@ import pytest -from ravestate.spike import Spike from reggol import strip_prefix from testfixtures import LogCapture -from ravestate.constraint import Signal -from ravestate.context import Context -from ravestate.property import Property -from ravestate.state import State, state -from ravestate.wrappers import PropertyWrapper, ContextWrapper -from ravestate.activation import Activation +from ravestate import * DEFAULT_MODULE_NAME = 'module' DEFAULT_PROPERTY_NAME = 'property' @@ -21,14 +15,10 @@ DEFAULT_PROPERTY = Property(name=DEFAULT_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE) DEFAULT_PROPERTY.set_parent_path(DEFAULT_MODULE_NAME) -SIGNAL_A = Signal("a") -SIGNAL_A.module_name = DEFAULT_MODULE_NAME -SIGNAL_B = Signal("b") -SIGNAL_B.module_name = DEFAULT_MODULE_NAME -SIGNAL_C = Signal("c") -SIGNAL_C.module_name = DEFAULT_MODULE_NAME -SIGNAL_D = Signal("d") -SIGNAL_D.module_name = DEFAULT_MODULE_NAME +SIGNAL_A = SignalRef(f"{DEFAULT_MODULE_NAME}:a") +SIGNAL_B = SignalRef(f"{DEFAULT_MODULE_NAME}:b") +SIGNAL_C = SignalRef(f"{DEFAULT_MODULE_NAME}:c") +SIGNAL_D = SignalRef(f"{DEFAULT_MODULE_NAME}:d") @pytest.fixture diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index d13cd4a..63a481e 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -11,7 +11,7 @@ def init_spacy(): - # TODO: Create nlp instance in :startup state, save in context instead of global var + # TODO: Create nlp instance in core:startup state, save in context instead of global var global empty_token try: import en_core_web_sm as spacy_en diff --git a/modules/reggol/colored_formatter.py b/modules/reggol/colored_formatter.py index 8c1d6eb..70e9807 100755 --- a/modules/reggol/colored_formatter.py +++ b/modules/reggol/colored_formatter.py @@ -17,7 +17,7 @@ class ColoredFormatter(logging.Formatter): GROUP_COLOR = CYAN - FORMAT = "[%(levelname_color)-5s] [%(name_color)Signal] %(msg)Signal" + FORMAT = "[%(levelname_color)-5s] [%(name_color)s] %(msg)s" def __init__(self, log_format=FORMAT, colors=None): super().__init__(log_format) diff --git a/modules/reggol/logger.py b/modules/reggol/logger.py index b16ec71..7f97d11 100755 --- a/modules/reggol/logger.py +++ b/modules/reggol/logger.py @@ -5,7 +5,7 @@ from reggol.colored_formatter import ColoredFormatter TIME_FORMAT = "%Y-%m-%d %H:%M:%S" -LOG_FORMAT = "%(asctime)Signal - %(name)Signal:%(lineno)d - [%(levelname)Signal] - %(msg)Signal" +LOG_FORMAT = "%(asctime)s - %(name)s:%(lineno)d - [%(levelname)s] - %(msg)s" DEFAULT_FILE_NAME = f"log_{time.strftime('%Y%m%d_%H%M%S')}.log" DEFAULT_DIRECTORY = os.path.join(os.path.dirname(__file__), 'log') diff --git a/test/modules/ravestate/test_constraint.py b/test/modules/ravestate/test_constraint.py index dc1bb92..389a36d 100644 --- a/test/modules/ravestate/test_constraint.py +++ b/test/modules/ravestate/test_constraint.py @@ -31,17 +31,17 @@ def test_parent(constraint_fixture, spike_fixture: Spike, activation_fixture: IA def test_signal(mocker, activation_fixture): - sig = Signal("mysig") + sig = SignalRef("mysig") with mocker.patch.object(activation_fixture, "resources", return_value=set()): assert not sig.evaluate() - assert set(sig.signals()) == {Signal("mysig")} + assert set(sig.signals()) == {SignalRef("mysig")} sig.acquire(Spike(sig="notmysig"), activation_fixture) assert not sig.evaluate() sig.acquire(Spike(sig="mysig"), activation_fixture) assert sig.evaluate() - sig_and_dis = Signal("sig") & (Signal("dis") | Signal("junct")) + sig_and_dis = SignalRef("sig") & (SignalRef("dis") | SignalRef("junct")) assert not sig_and_dis.evaluate() sig_and_dis.acquire(Spike(sig="sig"), activation_fixture) assert not sig_and_dis.evaluate() @@ -61,9 +61,9 @@ def test_signal(mocker, activation_fixture): def test_signal_or(mocker): - sig = Signal("mysig") + sig = SignalRef("mysig") with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - conjunct = Signal("sig1") & Signal("sig2") + conjunct = SignalRef("sig1") & SignalRef("sig2") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = sig | conjunct Conjunct.__init__.assert_called_once_with(sig) @@ -71,15 +71,15 @@ def test_signal_or(mocker): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): - disjunct = Signal("sig1") | Signal("sig2") + disjunct = SignalRef("sig1") | SignalRef("sig2") _ = sig | disjunct Disjunct.__init__.assert_called_with(sig, 1) def test_signal_and(mocker): - sig = Signal("mysig") - conjunct = Signal("sig1") & Signal("sig2") - disjunct = Signal("sig1") | Signal("sig2") + sig = SignalRef("mysig") + conjunct = SignalRef("sig1") & SignalRef("sig2") + disjunct = SignalRef("sig1") | SignalRef("sig2") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = sig & sig @@ -98,10 +98,10 @@ def test_signal_and(mocker): def test_conjunct(mocker, activation_fixture): - conjunct = Signal("sig1") & Signal("sig2") & Signal("sig3") + conjunct = SignalRef("sig1") & SignalRef("sig2") & SignalRef("sig3") with mocker.patch.object(activation_fixture, "resources", return_value=set()): assert not conjunct.evaluate() - assert set(conjunct.signals()) == {Signal("sig1"), Signal("sig2"), Signal("sig3")} + assert set(conjunct.signals()) == {SignalRef("sig1"), SignalRef("sig2"), SignalRef("sig3")} conjunct.acquire(Spike(sig="sig1"), activation_fixture) assert not conjunct.evaluate() conjunct.acquire(Spike(sig="sig2"), activation_fixture) @@ -113,23 +113,23 @@ def test_conjunct(mocker, activation_fixture): def test_conjunct_or(mocker): - conjunct = Signal("sig1") & Signal("sig2") & Signal("sig3") + conjunct = SignalRef("sig1") & SignalRef("sig2") & SignalRef("sig3") with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - conjunct2 = Signal("sig1") & Signal("sig2") + conjunct2 = SignalRef("sig1") & SignalRef("sig2") _ = conjunct | conjunct2 Disjunct.__init__.assert_called_once_with(conjunct, conjunct2) with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): - disjunct = Signal("sig1") | Signal("sig2") + disjunct = SignalRef("sig1") | SignalRef("sig2") _ = conjunct | disjunct Disjunct.__init__.assert_called_with(conjunct, 1) def test_conjunct_and(mocker): - sig = Signal("mysig") - conjunct = Signal("sig1") & Signal("sig2") - disjunct = Signal("sig1") | Signal("sig2") + sig = SignalRef("mysig") + conjunct = SignalRef("sig1") & SignalRef("sig2") + disjunct = SignalRef("sig1") | SignalRef("sig2") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = conjunct & sig @@ -148,10 +148,10 @@ def test_conjunct_and(mocker): def test_disjunct(mocker, activation_fixture): - disjunct = (Signal("sig1") & Signal("sig2")) | Signal("sig3") + disjunct = (SignalRef("sig1") & SignalRef("sig2")) | SignalRef("sig3") with mocker.patch.object(activation_fixture, "resources", return_value=set()): assert not disjunct.evaluate() - assert set(disjunct.signals()) == {Signal("sig1"), Signal("sig2"), Signal("sig3")} + assert set(disjunct.signals()) == {SignalRef("sig1"), SignalRef("sig2"), SignalRef("sig3")} disjunct.acquire(Spike(sig="sig1"), activation_fixture) assert not disjunct.evaluate() disjunct.acquire(Spike(sig="sig3"), activation_fixture) @@ -159,16 +159,16 @@ def test_disjunct(mocker, activation_fixture): def test_disjunct_or(mocker): - disjunct = (Signal("sig1") & Signal("sig2")) | Signal("sig3") + disjunct = (SignalRef("sig1") & SignalRef("sig2")) | SignalRef("sig3") with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - conjunct = Signal("sig1") & Signal("sig2") + conjunct = SignalRef("sig1") & SignalRef("sig2") _ = disjunct | conjunct Disjunct.__init__.assert_called_with(1, conjunct) with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): - signal = Signal("sig1") + signal = SignalRef("sig1") with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): _ = disjunct | signal Conjunct.__init__.assert_called_once_with(signal) @@ -176,15 +176,15 @@ def test_disjunct_or(mocker): with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch('ravestate.constraint.Disjunct.__iter__', return_value=iter([1])): - disjunct2 = Signal("sig1") | Signal("sig2") + disjunct2 = SignalRef("sig1") | SignalRef("sig2") _ = disjunct | disjunct2 Disjunct.__init__.assert_called_with(1) def test_disjunct_and(mocker): - sig = Signal("mysig") - conjunct = Signal("sig1") & Signal("sig2") - disjunct = Signal("sig1") | Signal("sig2") + sig = SignalRef("mysig") + conjunct = SignalRef("sig1") & SignalRef("sig2") + disjunct = SignalRef("sig1") | SignalRef("sig2") with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): with mocker.patch.object(disjunct, '_conjunctions', return_value={}): @@ -204,4 +204,4 @@ def test_disjunct_and(mocker): def test_legal(): with pytest.raises(ValueError): - _ = (Signal("i") | Signal("am")) & (Signal("also") | Signal("illegal")) + _ = (SignalRef("i") | SignalRef("am")) & (SignalRef("also") | SignalRef("illegal")) diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 5fdb294..c86eb6e 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -7,7 +7,7 @@ def test_emit(context_fixture, spike_fixture): - sig = Signal(DEFAULT_PROPERTY_CHANGED) + sig = SignalRef(DEFAULT_PROPERTY_CHANGED) context_fixture.emit(sig) assert len(context_fixture._spikes_per_signal[sig]) == 1 list(context_fixture._spikes_per_signal[sig])[0].adopt(spike_fixture) @@ -107,7 +107,7 @@ def test_add_state( context_with_property_fixture.add_state(st=state_fixture) # Make sure, that module:property:changed was added as a cause for module:a - assert Signal(DEFAULT_PROPERTY_CHANGED) in \ + assert SignalRef(DEFAULT_PROPERTY_CHANGED) in \ context_with_property_fixture._signal_causes[state_signal_a_fixture.signal][0] context_with_property_fixture.add_state(st=state_signal_b_fixture) @@ -122,16 +122,16 @@ def test_add_state( conj for conj in d_conjunctions if len(tuple(conj.signals())) > 2] assert len(d_conjunctions) == 2 # 2 completed - assert Signal(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[0] + assert SignalRef(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[0] assert state_signal_a_fixture.signal in d_conjunctions[0] - assert Signal(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[1] + assert SignalRef(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[1] assert state_signal_a_fixture.signal in d_conjunctions[1] assert \ state_signal_b_fixture.signal in d_conjunctions[0] and state_signal_c_fixture.signal in d_conjunctions[1] or \ state_signal_b_fixture.signal in d_conjunctions[1] and state_signal_c_fixture.signal in d_conjunctions[0] # Basic specificity sanity checks - assert len(list(context_with_property_fixture._states_for_signal(Signal(DEFAULT_PROPERTY_CHANGED)))) == 5 + assert len(list(context_with_property_fixture._states_for_signal(SignalRef(DEFAULT_PROPERTY_CHANGED)))) == 5 a_acts = list(context_with_property_fixture._state_activations(st=state_signal_a_fixture)) b_acts = list(context_with_property_fixture._state_activations(st=state_signal_b_fixture)) c_acts = list(context_with_property_fixture._state_activations(st=state_signal_c_fixture)) @@ -140,7 +140,7 @@ def test_add_state( assert len(b_acts) == 1 assert len(c_acts) == 1 assert len(d_acts) == 1 - propchange_sig_spec = context_with_property_fixture.signal_specificity(Signal(DEFAULT_PROPERTY_CHANGED)) + propchange_sig_spec = context_with_property_fixture.signal_specificity(SignalRef(DEFAULT_PROPERTY_CHANGED)) assert a_acts[0].specificity() == propchange_sig_spec a_sig_spec = context_with_property_fixture.signal_specificity(state_signal_a_fixture.signal) assert a_sig_spec == 1/3 @@ -149,7 +149,7 @@ def test_add_state( def test_add_state_configurable_age(context_with_property_fixture: Context): - my_cond = Signal(DEFAULT_PROPERTY_CHANGED, min_age=ConfigurableAge(key="min_age_key"), + my_cond = SignalRef(DEFAULT_PROPERTY_CHANGED, min_age=ConfigurableAge(key="min_age_key"), max_age=ConfigurableAge(key="max_age_key")) @state(cond=my_cond) @@ -164,7 +164,7 @@ def conf_st(ctx): def test_add_state_configurable_age_not_in_config(context_with_property_fixture: Context): - my_cond = Signal( + my_cond = SignalRef( DEFAULT_PROPERTY_CHANGED, min_age=ConfigurableAge(key="min_age_key"), max_age=ConfigurableAge(key="max_age_key")) diff --git a/test/modules/ravestate/test_state.py b/test/modules/ravestate/test_state.py index 49007c5..d4557c6 100644 --- a/test/modules/ravestate/test_state.py +++ b/test/modules/ravestate/test_state.py @@ -1,5 +1,5 @@ import pytest -from ravestate.constraint import Signal +from ravestate.constraint import Signal, SignalRef from ravestate.state import State, state from ravestate.wrappers import ContextWrapper @@ -7,7 +7,7 @@ @pytest.fixture def default_signal(): - return Signal("test-signal") + return SignalRef("test-signal") @pytest.fixture @@ -22,7 +22,7 @@ def default_write(): @pytest.fixture def default_triggers(): - return Signal("idle") + return SignalRef("idle") @pytest.fixture @@ -67,7 +67,7 @@ def test_decorator_illegal_trigger(under_test, default_signal, default_read, def @state(signal=default_signal, read=default_read, write=default_write, - cond=(Signal("rawio:in:changed") | Signal("facerec:face:changed")) & (Signal("sys:has-internet") | Signal("foo:poo"))) + cond=(SignalRef("rawio:in:changed") | SignalRef("facerec:face:changed")) & (SignalRef("sys:has-internet") | SignalRef("foo:poo"))) def test_state(_): return "Hello world!" diff --git a/test/modules/ravestate/test_wrappers_context.py b/test/modules/ravestate/test_wrappers_context.py index 3052508..f6b6f31 100644 --- a/test/modules/ravestate/test_wrappers_context.py +++ b/test/modules/ravestate/test_wrappers_context.py @@ -33,7 +33,7 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, child=Property(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( - Signal(f"{DEFAULT_PROPERTY_ID}:pushed"), + SignalRef(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -43,7 +43,7 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture[CHILD_PROPERTY_ID] == DEFAULT_PROPERTY_VALUE context_wrapper_fixture[CHILD_PROPERTY_ID] = CHILD_PROPERTY_VALUE context_with_property_fixture.emit.assert_called_with( - Signal(f"{CHILD_PROPERTY_ID}:changed"), + SignalRef(f"{CHILD_PROPERTY_ID}:changed"), parents=None, wipe=True, payload=CHILD_PROPERTY_VALUE) @@ -52,7 +52,7 @@ def test_property_push_pop(mocker, context_wrapper_fixture, context_with_propert # pop child assert context_wrapper_fixture.pop(CHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - Signal(f"{DEFAULT_PROPERTY_ID}:popped"), + SignalRef(f"{DEFAULT_PROPERTY_ID}:popped"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -65,7 +65,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, child=Property(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - Signal(f"{DEFAULT_PROPERTY_ID}:pushed"), + SignalRef(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -74,7 +74,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ assert context_wrapper_fixture.push(parent_property_or_path=CHILD_PROPERTY_ID, child=Property(name=GRANDCHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( - Signal(f"{CHILD_PROPERTY_ID}:pushed"), + SignalRef(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=GRANDCHILD_PROPERTY_ID) @@ -85,7 +85,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ assert context_wrapper_fixture[GRANDCHILD_PROPERTY_ID] == DEFAULT_PROPERTY_VALUE context_wrapper_fixture[GRANDCHILD_PROPERTY_ID] = GRANDCHILD_PROPERTY_VALUE context_with_property_fixture.emit.assert_called_with( - Signal(f"{GRANDCHILD_PROPERTY_ID}:changed"), + SignalRef(f"{GRANDCHILD_PROPERTY_ID}:changed"), parents=None, wipe=True, payload=GRANDCHILD_PROPERTY_VALUE) @@ -94,7 +94,7 @@ def test_property_nested(mocker, context_wrapper_fixture, context_with_property_ # pop child assert context_wrapper_fixture.pop(CHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - Signal(f"{DEFAULT_PROPERTY_ID}:popped"), + SignalRef(f"{DEFAULT_PROPERTY_ID}:popped"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -107,7 +107,7 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture.push(parent_property_or_path=DEFAULT_PROPERTY_ID, child=Property(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - Signal(f"{DEFAULT_PROPERTY_ID}:pushed"), + SignalRef(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=CHILD_PROPERTY_ID) @@ -116,7 +116,7 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert assert context_wrapper_fixture.push(parent_property_or_path=CHILD_PROPERTY_ID, child=Property(name=GRANDCHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - Signal(f"{CHILD_PROPERTY_ID}:pushed"), + SignalRef(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, wipe=False, payload=GRANDCHILD_PROPERTY_ID) @@ -128,7 +128,7 @@ def test_property_nested_2(mocker, context_wrapper_fixture, context_with_propert # pop grandchild assert context_wrapper_fixture.pop(GRANDCHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - Signal(f"{CHILD_PROPERTY_ID}:popped"), + SignalRef(f"{CHILD_PROPERTY_ID}:popped"), parents=None, wipe=False, payload=GRANDCHILD_PROPERTY_ID) diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index f0b5292..b9c7bfd 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -1,5 +1,5 @@ from ravestate.testfixtures import * -from ravestate.constraint import Signal +from ravestate.constraint import SignalRef from ravestate.icontext import IContext from ravestate.state import State from ravestate.property import Property @@ -100,7 +100,7 @@ def test_property_write(under_test_read_write: PropertyWrapper, default_property under_test_read_write.set(NEW_PROPERTY_VALUE) assert (under_test_read_write.get() == NEW_PROPERTY_VALUE) context_mock.emit.assert_called_once_with( - Signal(f"{under_test_read_write.prop.id()}:changed"), + SignalRef(f"{under_test_read_write.prop.id()}:changed"), parents=None, wipe=True, payload=NEW_PROPERTY_VALUE) @@ -115,12 +115,12 @@ def test_flag_property(context_mock): prop_wrapper.set(True) assert (prop_wrapper.get() is True) context_mock.emit.assert_any_call( - Signal(f"{prop_wrapper.prop.id()}:changed"), + SignalRef(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True, payload=True) context_mock.emit.assert_any_call( - Signal(f"{prop_wrapper.prop.id()}:true"), + SignalRef(f"{prop_wrapper.prop.id()}:true"), parents=None, wipe=True) @@ -128,12 +128,12 @@ def test_flag_property(context_mock): prop_wrapper.set(False) assert (prop_wrapper.get() is False) context_mock.emit.assert_any_call( - Signal(f"{prop_wrapper.prop.id()}:changed"), + SignalRef(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True, payload=False) context_mock.emit.assert_any_call( - Signal(f"{prop_wrapper.prop.id()}:false"), + SignalRef(f"{prop_wrapper.prop.id()}:false"), parents=None, wipe=True) @@ -141,7 +141,7 @@ def test_flag_property(context_mock): prop_wrapper.set(None) assert (prop_wrapper.get() is None) context_mock.emit.assert_called_once_with( - Signal(f"{prop_wrapper.prop.id()}:changed"), + SignalRef(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True, payload=None) diff --git a/test/modules/ravestate_fillers/test_fillers.py b/test/modules/ravestate_fillers/test_fillers.py index f71da6f..90f271e 100644 --- a/test/modules/ravestate_fillers/test_fillers.py +++ b/test/modules/ravestate_fillers/test_fillers.py @@ -9,7 +9,7 @@ def test_fillers(): ctx = Context("rawio", "idle", "verbaliser", "fillers") assert ctx["verbaliser:intent"].read() == "" - ctx.emit(Signal("idle:impatient")) + ctx.emit(SignalRef("idle:impatient")) ctx.run_once() assert impatient_fillers.wait() assert ctx["verbaliser:intent"].read() == "fillers" From 0b33a6c3ebb7cb521e07dc079134124d925d083d Mon Sep 17 00:00:00 2001 From: realitivity Date: Sat, 11 May 2019 23:01:04 +0200 Subject: [PATCH 07/10] Fixed tests, missing id() call, relaxed module scope req. --- modules/ravestate/constraint.py | 11 ++++++----- modules/ravestate/context.py | 2 +- modules/ravestate/property.py | 4 ++-- modules/ravestate/state.py | 11 ++++++----- test/modules/ravestate/test_context.py | 9 ++------- test/modules/ravestate/test_state.py | 4 ++-- test/modules/ravestate_verbaliser/test_init.py | 3 ++- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index cf54a77..e1f749c 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -100,10 +100,9 @@ def __init__(self, name: str, *, min_age=0., max_age=5., detached=False, _skip_m # if min_age > max_age and max_age > .0: # logger.warning(f"{self}: max_age={max_age} < min_age={min_age}!") - if not _skip_module_context: - # add signal to module in current `with Module(...)` clause - module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) - assert module_under_construction + # add signal to module in current `with Module(...)` clause + module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) + if module_under_construction: module_under_construction.add(self) def __or__(self, other): @@ -227,7 +226,9 @@ class SignalRef(Signal): """ Signal reference. Almost the same as a signal, except that it will not try to auto-discover it's module out of thread-local context - (module_name will stay unfilled). + (module_name will stay unfilled). Needed, because sometimes + you need to reference a singal within a module scope + without assigning that signal to the contextual module. """ def __init__(self, name: str, *, min_age=0., max_age=5., detached=False): diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 0bb92a5..26d893c 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -605,7 +605,7 @@ def run_once(self, seconds_passed=1.) -> None: gc.collect() - self._update_core_properties(debug=False) + self._update_core_properties(debug=True) def _state_activated(self, act: Activation): self._activations_per_state[act.state_to_activate].discard(act) diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 7fead7f..940c029 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -53,8 +53,8 @@ def __init__( # add property to module in current `with Module(...)` clause module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) - assert module_under_construction - module_under_construction.add(self) + if module_under_construction: + module_under_construction.add(self) def id(self): return f'{self.parent_path}:{self.name}' diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 0e22164..565c343 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -91,14 +91,15 @@ def __init__(self, *, read: Union[Property, str, Tuple[Property, str]], cond: Optional[Constraint], action, - is_receptor: bool=False, - emit_detached: bool=False, - weight: float=1., - cooldown: float=0.): + is_receptor: bool = False, + emit_detached: bool = False, + weight: float = 1., + cooldown: float = 0.): assert(callable(action)) self.name = action.__name__ - self.consumable = Consumable(f"@{action.__name__}") + if not is_receptor: + self.consumable = Consumable(f"@{action.__name__}") # check to recognize states using old signal implementation if isinstance(cond, str): diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index c86eb6e..3c47ec6 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -1,9 +1,4 @@ -from ravestate.constraint import Signal, ConfigurableAge -from ravestate.spike import Spike -from ravestate.module import Module from ravestate.testfixtures import * -from ravestate.context import create_and_run_context, sig_startup, sig_shutdown -from ravestate.config import Configuration def test_emit(context_fixture, spike_fixture): @@ -220,6 +215,6 @@ def test_runtime_overriding_config(mocker): def test_invalid_tickrate(): with LogCapture(attributes=strip_prefix) as log_capture: - ctx = Context(runtime_overrides=[(Context.core_module_name, Context.tick_rate_config, 0)]) + ctx = Context(runtime_overrides=[(CORE_MODULE_NAME, TICK_RATE_CONFIG_KEY, 0)]) log_capture.check_present("Attempt to set core config `tickrate` to a value less-than 1!") - assert ctx._core_config[Context.tick_rate_config] == 1 + assert ctx.tick_rate == 1 diff --git a/test/modules/ravestate/test_state.py b/test/modules/ravestate/test_state.py index d4557c6..9a18ace 100644 --- a/test/modules/ravestate/test_state.py +++ b/test/modules/ravestate/test_state.py @@ -78,8 +78,8 @@ def test_state(_): return "Hello world!" assert (test_state.signal == "") - assert (test_state.read_props == ()) - assert (test_state.write_props == ()) + assert (test_state.read_props == set()) + assert (test_state.write_props == set()) assert (test_state.constraint is None) assert (test_state(default_context_wrapper) == "Hello world!") assert (isinstance(test_state.action, type(under_test.action))) diff --git a/test/modules/ravestate_verbaliser/test_init.py b/test/modules/ravestate_verbaliser/test_init.py index eb8b610..a40777d 100644 --- a/test/modules/ravestate_verbaliser/test_init.py +++ b/test/modules/ravestate_verbaliser/test_init.py @@ -8,9 +8,10 @@ def test_react_to_intent(mocker): verbaliser.add_file(join(dirname(realpath(__file__)), "verbaliser_testfiles", "phrases_test.yml")) import ravestate_verbaliser + import ravestate_rawio as rawio test_dict = {'verbaliser:intent:changed': 'test1'} ravestate_verbaliser.react_to_intent(test_dict) - assert test_dict["rawio:out"] in ravestate_verbaliser.verbaliser.get_phrase_list('test1') + assert test_dict[rawio.prop_out] in ravestate_verbaliser.verbaliser.get_phrase_list('test1') def test_react_to_intent_no_phrase(mocker): From c6689d8fa9aae949acdf63b3c2569e62a044e2c2 Mon Sep 17 00:00:00 2001 From: realitivity Date: Sun, 12 May 2019 19:24:23 +0200 Subject: [PATCH 08/10] Added stack size/threadid output to reggol, causal group self-test, fixed major leaking loop variable bug in CausalGroup. --- modules/ravestate/causal.py | 43 +++++++++++++++++++++--- modules/ravestate/constraint.py | 2 +- modules/ravestate/context.py | 20 +++++++++-- modules/ravestate/spike.py | 1 - modules/reggol/colored_formatter.py | 21 +++++++++++- modules/reggol/logger.py | 4 +-- test/modules/ravestate_persqa/test_qa.py | 3 +- 7 files changed, 81 insertions(+), 13 deletions(-) diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index 98d184f..914ebeb 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -249,9 +249,9 @@ def rejected(self, spike: 'ISpike', rejected_by: IActivation, reason: int) -> No Called by a state activation, to notify the group that a member spike is no longer being referenced for the given state's write props. This may be because ...
- ... the state activation's dereference function was called. (reason=0)
+ ... the activation's dereference function was called. (reason=0)
... the spike got too old. (reason=1)
- ... the activation is happening and dereferencing it's spikes. (reason=2) + ... the activation happened and is dereferencing it's spikes. (reason=2) * `spike`: The member spike whose ref-set should be reduced. @@ -281,7 +281,7 @@ def _decrement_refcount(refcount_for_act_for_spike): self._change_effect_causes(rejected_by, -1) - logger.debug(f"{self}.rejected({spike} by {rejected_by}): ") + logger.debug(f"{self}.rejected({spike} by {rejected_by})") def consent(self, ready_suitor: IActivation) -> bool: """ @@ -437,8 +437,8 @@ def _change_effect_causes(self, act: IActivation, change: Optional[int]): if self._uncaused_spikes[sig][act] <= 0: del self._uncaused_spikes[sig][act] if len(self._uncaused_spikes[sig]) == 0: - for act in set(self._non_detached_activations()): - act.effect_not_caused(self, sig.id()) + for act_to_notify in set(self._non_detached_activations()): + act_to_notify.effect_not_caused(self, sig.id()) del self._uncaused_spikes[sig] def _non_detached_activations(self) -> Generator[IActivation, None, None]: @@ -450,3 +450,36 @@ def _non_detached_activations(self) -> Generator[IActivation, None, None]: yielded_activations.add(act) yield act + def check_reference_sanity(self) -> bool: + """ + Make sure, that the refcount-per-act-per-spike-per-resource value sum + is equal to the number of spikes from this causal group acquired per activation + for each activation in the index. + :return: True if the criterion is fulfilled, False otherwise. + """ + result = True + signals_with_cause = set() + for act in self._non_detached_activations(): + sum_refcount = sum( + refc_per_act[act] + for resource in act.resources() + for refc_per_act in self._ref_index[resource].values() if act in refc_per_act) + sum_spikes = sum( + len(act.resources()) + for signal in act.constraint.signals() if signal.spike and signal.spike.causal_group() == self) + if sum_spikes != sum_refcount: + logger.error(f"Mutual refcount mismatch: {self} -> {sum_refcount} : {sum_spikes} <- {act}") + result = False + for sig in act.possible_signals(): + signals_with_cause.add(sig) + if sig in self._uncaused_spikes: + causes = sum_spikes / len(act.resources()) + if self._uncaused_spikes[sig][act] != causes: + logger.error(f"Signal cause mismatch for {sig} by {act}: is {self._uncaused_spikes[sig][act]}, " + f"should be {causes}") + result = False + for signal in set(self._uncaused_spikes.keys())-signals_with_cause: + logger.error(f"Signal cause mismatch for {signal}: is {self._uncaused_spikes[signal]}, " + f"should be ZERO") + result = False + return result diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index e1f749c..72ab527 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -154,8 +154,8 @@ def acquire(self, spike: Spike, act: IActivation): # Causal group might refuse acquisition, if one of act's state's write-props is unavailable. if not cg.acquired(spike, act, self.detached_value): return False + self.spike = spike self._min_age_ticks = act.secs_to_ticks(self.min_age_value) - self.spike = spike return True return False diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 26d893c..5cecd16 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -517,7 +517,7 @@ def possible_signals(self, state: State) -> Generator[Signal, None, None]: if state.signal: yield state.signal - def run_once(self, seconds_passed=1.) -> None: + def run_once(self, seconds_passed=1., debug=False) -> None: """ Run a single update for this context, which will ...
(0) progress cooled down state weights.
@@ -605,7 +605,7 @@ def run_once(self, seconds_passed=1.) -> None: gc.collect() - self._update_core_properties(debug=True) + self._update_core_properties(debug=debug) def _state_activated(self, act: Activation): self._activations_per_state[act.state_to_activate].discard(act) @@ -778,3 +778,19 @@ def _run_loop(self): while not self._shutdown_flag.wait(tick_interval): self.run_once(tick_interval) + def test(self) -> bool: + """ + Execute internal integrity checks. + """ + # Check all causal groups for refcount correctness + checked_causal_groups = set() + result = True + with self._lock: + for spikes in self._spikes_per_signal.values(): + for spike in spikes: + with spike.causal_group() as cg: + if cg not in checked_causal_groups: + checked_causal_groups.add(cg) + if not cg.check_reference_sanity(): + result = False + return result diff --git a/modules/ravestate/spike.py b/modules/ravestate/spike.py index 10ba206..f08e51f 100644 --- a/modules/ravestate/spike.py +++ b/modules/ravestate/spike.py @@ -76,7 +76,6 @@ def __init__(self, *, sig: str, parents: Set['Spike']=None, consumable_resources self._offspring = set() self._parents = parents.copy() if parents else set() self._causal_group = next(iter(parents)).causal_group() if parents else CausalGroup(consumable_resources) - self._suitors_per_property = {prop: set() for prop in consumable_resources} self._payload = payload for parent in parents: parent.adopt(self) diff --git a/modules/reggol/colored_formatter.py b/modules/reggol/colored_formatter.py index 70e9807..08ea31a 100755 --- a/modules/reggol/colored_formatter.py +++ b/modules/reggol/colored_formatter.py @@ -1,4 +1,22 @@ import logging +import sys + + +def get_stack_size(): + """Get stack size for caller's frame. + + %timeit len(inspect.stack()) + 8.86 ms ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) + %timeit get_stack_size() + 4.17 µs ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) + """ + size = 2 # current frame and caller's frame always exist + while True: + try: + sys._getframe(size) + size += 1 + except ValueError: + return size - 1 # subtract current frame class ColoredFormatter(logging.Formatter): @@ -17,7 +35,7 @@ class ColoredFormatter(logging.Formatter): GROUP_COLOR = CYAN - FORMAT = "[%(levelname_color)-5s] [%(name_color)s] %(msg)s" + FORMAT = "[%(threadName)s] %(stack_size)s %(name_color)s: [%(levelname_color)-5s] %(msg)s" def __init__(self, log_format=FORMAT, colors=None): super().__init__(log_format) @@ -32,4 +50,5 @@ def format(self, record): record.levelname_color = self.COLOR_SEQ % (30 + self.colors[levelname]) + levelname + self.RESET_SEQ else: record.levelname_color = record.levelname + record.stack_size = "|" * max(get_stack_size()-10, 0) return super(ColoredFormatter, self).format(record) diff --git a/modules/reggol/logger.py b/modules/reggol/logger.py index 7f97d11..5e3f4d2 100755 --- a/modules/reggol/logger.py +++ b/modules/reggol/logger.py @@ -5,7 +5,7 @@ from reggol.colored_formatter import ColoredFormatter TIME_FORMAT = "%Y-%m-%d %H:%M:%S" -LOG_FORMAT = "%(asctime)s - %(name)s:%(lineno)d - [%(levelname)s] - %(msg)s" +LOG_FORMAT = "%(asctime)s - %(threadName)s:%(name)s:%(lineno)d - [%(levelname)s] - %(msg)s" DEFAULT_FILE_NAME = f"log_{time.strftime('%Y%m%d_%H%M%S')}.log" DEFAULT_DIRECTORY = os.path.join(os.path.dirname(__file__), 'log') @@ -25,7 +25,7 @@ def set_file_formatter( self, file_path: str = DEFAULT_DIRECTORY, file_name: str = DEFAULT_FILE_NAME, - formatter: logging.Formatter = logging.Formatter(fmt = LOG_FORMAT) + formatter: logging.Formatter = logging.Formatter(fmt=LOG_FORMAT) ): self._file_formatter = formatter path = os.path.join(file_path, file_name) diff --git a/test/modules/ravestate_persqa/test_qa.py b/test/modules/ravestate_persqa/test_qa.py index 5d0f3b3..0bd242c 100644 --- a/test/modules/ravestate_persqa/test_qa.py +++ b/test/modules/ravestate_persqa/test_qa.py @@ -48,7 +48,8 @@ def say(ctx: rs.ContextWrapper, what: str): # Wait for name being asked while not raw_out.wait(.1): - ctx.run_once() + ctx.run_once(debug=True) + ctx.test() assert last_output in verbaliser.get_question_list("NAME") # Say name From 2b12e54156283ba75bdfc7f24f0b92d8b054d8e1 Mon Sep 17 00:00:00 2001 From: realitivity Date: Sun, 12 May 2019 20:35:04 +0200 Subject: [PATCH 09/10] Fixed wildtalk input prop, punctuation, starting point for adopting properties as clones. --- modules/ravestate/context.py | 3 ++- modules/ravestate/property.py | 20 ++++++++++++++ modules/ravestate_idle/__init__.py | 2 +- modules/ravestate_persqa/__init__.py | 10 ++++++- .../en/offer-qa.yml | 2 +- modules/ravestate_ros2/ros2_properties.py | 27 +++++++++++++++++++ modules/ravestate_wildtalk/__init__.py | 10 ++++--- 7 files changed, 67 insertions(+), 7 deletions(-) diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 5cecd16..50244bc 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -354,12 +354,13 @@ def rm_state(self, *, st: State) -> None: def add_prop(self, *, prop: Property) -> None: """ - Add a property to this context. An error message will be generated, if a property with + Add a copy of a property to this context. An error message will be generated, if a property with the same name has already been added previously. Note: Context will adopt a __copy__ of the given property, the actual property will not be changed. * `prop`: The property object that should be added. """ + # prop = prop.clone() if prop.id() in self._properties: logger.error(f"Attempt to add property {prop.id()} twice!") return diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 940c029..6155da3 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -59,6 +59,26 @@ def __init__( def id(self): return f'{self.parent_path}:{self.name}' + def clone(self): + result = Property( + name=self.name, + allow_read=self.allow_read, + allow_write=self.allow_write, + allow_push=self.allow_push, + allow_pop=self.allow_pop, + default_value=self.value, + always_signal_changed=self.always_signal_changed, + is_flag_property=self.is_flag_property, + wipe_on_changed=self.wipe_on_changed) + result.set_parent_path(self.parent_path) + return result + + def __hash__(self): + return hash(self.id()) + + def __eq__(self, other): + return self.__hash__() == other.__hash__() + def set_parent_path(self, path): """ Set the ancestors (including modulename) for a property diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index 9c25392..eb3d7d6 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -8,7 +8,7 @@ CONFIG = { # duration in seconds how long ":pressure" should be true before getting impatient IMPATIENCE_THRESHOLD_CONFIG_KEY: 1.0, - BORED_THRESHOLD_CONFIG_KEY: 1.0 + BORED_THRESHOLD_CONFIG_KEY: 3.0 } with rs.Module(name="idle", config=CONFIG): diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py index 7a18963..6243ffb 100644 --- a/modules/ravestate_persqa/__init__.py +++ b/modules/ravestate_persqa/__init__.py @@ -78,6 +78,8 @@ sig_follow_up = rs.Signal(name="follow-up") + sig_predicate_asked = rs.Signal(name="predicate-asked") + def find_empty_relationship(dictonary: Dict): for key in dictonary: if not dictonary[key] and key in PREDICATE_SET: @@ -186,8 +188,14 @@ def removed_interloc(ctx: rs.ContextWrapper): ctx[prop_subject] = None ctx[prop_predicate] = None + @rs.state(signal=sig_predicate_asked, read=prop_predicate) + def check_predicate_asked(ctx): + if ctx[prop_predicate]: + return rs.Emit() + @rs.state( - cond=nlp.prop_triples.changed(), + # optionally acquire sig_predicate_asked, such that active engagement does not run when a predicate was asked + cond=nlp.prop_triples.changed() | (sig_predicate_asked.max_age(-1) & nlp.prop_triples.changed()), write=(prop_answer, prop_inference_mutex), read=(prop_predicate, nlp.prop_triples, nlp.prop_tokens, nlp.prop_yesno)) def inference(ctx: rs.ContextWrapper): diff --git a/modules/ravestate_phrases_basic_en/en/offer-qa.yml b/modules/ravestate_phrases_basic_en/en/offer-qa.yml index 9dbcf1f..54b571b 100644 --- a/modules/ravestate_phrases_basic_en/en/offer-qa.yml +++ b/modules/ravestate_phrases_basic_en/en/offer-qa.yml @@ -6,7 +6,7 @@ opts: - "I know so much stuff, just ask me a question." - "Let me uncover my potential. Ask something really difficult!" - "Why don't you ask me about famous people!" -- "Why don't you ask me about ask me about famous places!" +- "Why don't you ask me about famous places!" - "I know a bit about pop stars. Wanna hear a story about someone?" - "I keep profiles of famous people. Ask me about some politician. I know quite a bunch." - "I know a lot about famous people, ask me!" diff --git a/modules/ravestate_ros2/ros2_properties.py b/modules/ravestate_ros2/ros2_properties.py index 6d2687b..5648e87 100644 --- a/modules/ravestate_ros2/ros2_properties.py +++ b/modules/ravestate_ros2/ros2_properties.py @@ -154,6 +154,16 @@ def __del__(self): global global_prop_set global_prop_set.remove(self.__hash__()) + def clone(self): + result = Ros2SubProperty( + name=self.name, + topic=self.topic, + msg_type=self.msg_type, + default_value=self.value, + always_signal_changed=self.always_signal_changed) + result.set_parent_path(self.parent_path) + return result + def ros_subscription_callback(self, msg): """ Writes the message from ROS to the property @@ -194,6 +204,14 @@ def __del__(self): global global_prop_set global_prop_set.remove(self.__hash__()) + def clone(self): + result = Ros2PubProperty( + name=self.name, + topic=self.topic, + msg_type=self.msg_type) + result.set_parent_path(self.parent_path) + return result + def write(self, value): """ Publish value on ROS @@ -241,6 +259,15 @@ def __del__(self): global global_prop_set global_prop_set.remove(self.__hash__()) + def clone(self): + result = Ros2CallProperty( + name=self.name, + service_name=self.service_name, + service_type=self.service_type, + call_timeout=self.call_timeout) + result.set_parent_path(self.parent_path) + return result + def write(self, value): """ Call Service and receive result directly in the property. diff --git a/modules/ravestate_wildtalk/__init__.py b/modules/ravestate_wildtalk/__init__.py index 6abad51..e2da3f2 100644 --- a/modules/ravestate_wildtalk/__init__.py +++ b/modules/ravestate_wildtalk/__init__.py @@ -1,13 +1,17 @@ import ravestate as rs import ravestate_rawio as rawio from roboy_parlai import wildtalk - +import re with rs.Module(name="wildtalk"): - @rs.state(cond=rawio.prop_out.changed().max_age(-1.), read=rawio.prop_in, write=rawio.prop_out) + fix_spaces = re.compile(r'\s*([?!.,]+(?:\s+[?!.,]+)*)\s*') + + @rs.state(cond=rawio.prop_in.changed().max_age(-1.), read=rawio.prop_in, write=rawio.prop_out) def wildtalk_state(ctx): text = ctx[rawio.prop_in] if not text: # make sure that text is not empty return rs.Resign() - ctx[rawio.prop_out] = wildtalk(text) + result = wildtalk(text) + result = fix_spaces.sub(lambda x: "{} ".format(x.group(1).replace(" ", "")), result) + ctx[rawio.prop_out] = result From 47b152948924067ddb9a9b597d49311c0e9c37fa Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 13 May 2019 10:39:18 +0200 Subject: [PATCH 10/10] Added missing wipe() to persqa sig_predicate_asked. --- modules/ravestate_persqa/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py index 6243ffb..a94d910 100644 --- a/modules/ravestate_persqa/__init__.py +++ b/modules/ravestate_persqa/__init__.py @@ -192,6 +192,8 @@ def removed_interloc(ctx: rs.ContextWrapper): def check_predicate_asked(ctx): if ctx[prop_predicate]: return rs.Emit() + else: + return rs.Wipe() @rs.state( # optionally acquire sig_predicate_asked, such that active engagement does not run when a predicate was asked