From 730668f79ce90a7b295e83ca0a0b2de0c9f1c7e8 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 22 Jan 2019 15:14:17 +0100 Subject: [PATCH 01/46] Added fix for pypi-noncompliant spacy URL dependency. --- modules/ravestate_nlp/__init__.py | 10 ++++++++-- requirements.txt | 3 +-- setup.py | 9 ++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 1bc621d..dda52bf 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -16,8 +16,14 @@ def init_model(): # TODO: Create nlp instance in :startup state, save in context instead of global var global nlp, empty_token - nlp = spacy.load('en_core_web_sm') - empty_token = nlp(u' ')[0] + try: + import en_core_web_sm as spacy_en + except ImportError: + from spacy.cli import download as spacy_download + spacy_download("en_core_web_sm") + import en_core_web_sm as spacy_en + nlp = spacy_en.load() + empty_token = nlp(u" ")[0] # TODO: Make agent id configurable, rename nlp:contains-roboy to nlp:agent-mentioned about_roboy = ('you', 'roboy', 'robot', 'roboboy', 'your') diff --git a/requirements.txt b/requirements.txt index 13e0c32..134c20a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,9 +8,8 @@ pyyaml-include roboy_parlai>=0.1.post3 scientio spacy -https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.0.0/en_core_web_sm-2.0.0.tar.gz pytest click flask requests -scientio \ No newline at end of file +scientio diff --git a/setup.py b/setup.py index 9296337..535025c 100644 --- a/setup.py +++ b/setup.py @@ -3,20 +3,16 @@ with open("README.md", "r") as fh: long_description = fh.read() -required_url = [] required = [] with open("requirements.txt", "r") as freq: for line in freq.read().split(): - if "://" in line: - required_url.append(line) - else: - required.append(line) + required.append(line) packages = setuptools.find_packages("modules", exclude=["reggol*"]) setuptools.setup( name="ravestate", - version="0.3.post", + version="0.3.post1", url="https://github.com/roboy/ravestate", author="Roboy", author_email="info@roboy.org", @@ -36,7 +32,6 @@ scripts=["rasta"], install_requires=required + ["reggol"], - dependency_links=required_url, python_requires='>=3.7', classifiers=[ From 19cc95b1552a10c206386a2fe47d50aed5911e3e Mon Sep 17 00:00:00 2001 From: Andreas Dolp Date: Tue, 22 Jan 2019 20:17:15 +0100 Subject: [PATCH 02/46] wip for idle module --- modules/ravestate/activation.py | 8 ++- modules/ravestate/causal.py | 11 ++-- modules/ravestate/constraint.py | 12 +++- modules/ravestate/context.py | 59 ++++++++++++++++++- modules/ravestate/property.py | 19 +++++- modules/ravestate/spike.py | 1 + modules/ravestate/wrappers.py | 10 ++++ modules/ravestate_fillers/__init__.py | 16 +++++ modules/ravestate_idle/__init__.py | 43 ++++++++++++++ .../ravestate_phrases_basic_en/en/fillers.yml | 7 +++ modules/ravestate_rawio/__init__.py | 4 +- modules/ravestate_verbaliser/__init__.py | 2 +- .../ravestate/test_wrappers_property.py | 12 ++++ 13 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 modules/ravestate_fillers/__init__.py create mode 100644 modules/ravestate_idle/__init__.py create mode 100644 modules/ravestate_phrases_basic_en/en/fillers.yml diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index 444cdee..f29c4a0 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -141,6 +141,9 @@ def pressure(self): if self.death_clock is None: self._reset_death_clock() + def is_pressured(self): + return self.death_clock is not None + def spiky(self) -> bool: """ Returns true, if the activation has acquired any spikes at all. @@ -245,11 +248,10 @@ def _run_private(self): # Process state function result if isinstance(result, Emit): if self.state_to_activate.signal(): - if result.wipe: - self.ctx.wipe(self.state_to_activate.signal()) self.ctx.emit( self.state_to_activate.signal(), - parents=self.spikes) + parents=self.spikes, + wipe=result.wipe) else: logger.error(f"Attempt to emit spike from state {self.name}, which does not specify a signal name!") diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index 3771b2d..4e81f6d 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -185,6 +185,7 @@ def acquired(self, spike: 'ISpike', acquired_by: IActivation) -> bool: return False for prop in acquired_by.resources(): self._ref_index[prop][spike][acquired_by] += 1 + logger.debug(f"{self}.acquired({spike} by {acquired_by})") return True def rejected(self, spike: 'ISpike', rejected_by: IActivation, reason: int) -> None: @@ -192,7 +193,7 @@ 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 auto-eliminated. (reason=0)
+ ... the state 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) @@ -300,14 +301,14 @@ def consumed(self, resources: Set[str]) -> None: """ if not resources: return - for prop in resources: + for resource in resources: # Notify all concerned activations, that the # spikes they are referencing are no longer available - for sig in self._ref_index[prop]: - for act in self._ref_index[prop][sig]: + for sig in self._ref_index[resource]: + for act in self._ref_index[resource][sig]: act.dereference(spike=sig, reacquire=True) # Remove the consumed prop from the index - del self._ref_index[prop] + del self._ref_index[resource] logger.debug(f"{self}.consumed({resources})") def wiped(self, spike: 'ISpike') -> None: diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index a4af393..60b3615 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -1,4 +1,4 @@ -from typing import List, Set, Generator, Optional, Tuple +from typing import List, Set, Generator, Optional, Tuple, Union, Callable, Any from ravestate.spike import Spike from ravestate.iactivation import IActivation @@ -6,7 +6,15 @@ logger = get_logger(__name__) -def s(signal_name: str, *, min_age=0, max_age=5., detached=False): +class ConfigurableAge: + key = "" + + 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): """ Alias to call Signal-constructor diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index a45720c..e449a0e 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -7,6 +7,8 @@ from copy import deepcopy import gc +from ravestate.wrappers import PropertyWrapper + from ravestate.icontext import IContext from ravestate.module import Module from ravestate.state import State @@ -16,7 +18,7 @@ from ravestate import registry from ravestate import argparse from ravestate.config import Configuration -from ravestate.constraint import s, Signal, Conjunct, Disjunct +from ravestate.constraint import s, Signal, Conjunct, Disjunct, ConfigurableAge from ravestate.spike import Spike from reggol import get_logger @@ -24,8 +26,23 @@ class Context(IContext): - _default_signal_names: Tuple[str] = (":startup", ":shutdown", ":idle") + _default_properties: Tuple[PropertyBase] = (PropertyBase(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), + PropertyBase(name="activity", + allow_read=True, + allow_write=True, + allow_push=False, + allow_pop=False, + default_value=0, + always_signal_changed=False) + ) _core_module_name = "core" _import_modules_config = "import" @@ -92,6 +109,10 @@ def __init__(self, *arguments): for signame in self._default_signal_names: self._add_sig(s(signame)) + # Register default properties + for prop in self._default_properties: + self.add_prop(prop=prop) + # Set required config overrides for module_name, key, value in overrides: self._config.set(module_name, key, value) @@ -118,6 +139,7 @@ def emit(self, signal: Signal, parents: Set[Spike]=None, wipe: bool=False) -> No if wipe: self.wipe(signal) with self._lock: + logger.debug(f"Emitting {signal}") self._spikes[ Spike(sig=signal.name, parents=parents, consumable_resources=set(self._properties.keys()))] = True @@ -198,6 +220,24 @@ def add_state(self, *, st: State) -> None: logger.error(f"Attempt to add state which depends on unknown property `{prop}`!") return + # replace configurable ages with their config values + # TODO Unit test + 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 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. + 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) + 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. + else: + signal.max_age = conf_entry + # register the state's signal with self._lock: if st.signal(): @@ -526,6 +566,8 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[ def _run_private(self): tick_interval = 1. / self._core_config[self._tick_rate_config] + + counter = 0 while not self._shutdown_flag.wait(tick_interval): with self._lock: @@ -589,3 +631,16 @@ def _run_private(self): # Force garbage collect gc.collect() + + + #activation_pressure_present = any(activation.is_pressured() for activation in self._state_activations()) + activation_pressure_present = (counter % 5) != 0 + counter += 1 + PropertyWrapper(prop=self[":pressure"], ctx=self, allow_write=True, allow_read=True)\ + .set(activation_pressure_present) + + number_of_partially_fulfilled_states = \ + sum(1 if any(activation.spiky() for activation in self._activations_per_state[st]) else 0 + for st in self._activations_per_state) + PropertyWrapper(prop=self[":activity"], ctx=self, allow_write=True, allow_read=True)\ + .set(number_of_partially_fulfilled_states) diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 3206cdd..351204e 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -22,7 +22,8 @@ def __init__( allow_push=True, allow_pop=True, default_value=None, - always_signal_changed=False): + always_signal_changed=False, + is_flag_property=False): self.name = name self.allow_read = allow_read @@ -34,6 +35,7 @@ def __init__( self._lock = Lock() self.parent_path: str = "" self.always_signal_changed = always_signal_changed + self.is_flag_property = is_flag_property def fullname(self): return f'{self.parent_path}:{self.name}' @@ -144,6 +146,18 @@ def popped_signal(self) -> Signal: """ return s(f"{self.fullname()}:popped") + def flag_true_signal(self) -> Signal: + """ TODO Docstring for flagprops + Signal that is emitted by PropertyWrapper when #self.value is set to True. + """ + return s(f"{self.fullname()}:true") + + def flag_false_signal(self) -> Signal: + """ TODO Docstring for flagprops + Signal that is emitted by PropertyWrapper when #self.value is set to False. + """ + return s(f"{self.fullname()}:false") + def signals(self) -> Generator[Signal, None, None]: """ Yields all signals that may be emitted because of @@ -155,3 +169,6 @@ def signals(self) -> Generator[Signal, None, None]: yield self.pushed_signal() if self.allow_pop: yield self.popped_signal() + if self.is_flag_property: + yield self.flag_true_signal() + yield self.flag_false_signal() diff --git a/modules/ravestate/spike.py b/modules/ravestate/spike.py index fa250df..1511afe 100644 --- a/modules/ravestate/spike.py +++ b/modules/ravestate/spike.py @@ -130,6 +130,7 @@ def wipe(self, already_wiped_in_causal_group: bool=False) -> None: if not already_wiped_in_causal_group: with self.causal_group() as causal: causal.wiped(self) + logger.debug(f"Wiped {self}") # del self._causal_group def has_offspring(self): diff --git a/modules/ravestate/wrappers.py b/modules/ravestate/wrappers.py index c346e20..565d98d 100644 --- a/modules/ravestate/wrappers.py +++ b/modules/ravestate/wrappers.py @@ -68,6 +68,16 @@ def set(self, value: Any): logger.error(f"Unauthorized write access in property-wrapper {self.prop.fullname()}!") return False if self.prop.write(value): + # 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.emit(self.prop.flag_true_signal(), parents=self.spike_parents, wipe=True) + 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.emit(self.prop.flag_false_signal(), parents=self.spike_parents, wipe=True) + self.ctx.emit(self.prop.changed_signal(), parents=self.spike_parents, wipe=True) return True return False diff --git a/modules/ravestate_fillers/__init__.py b/modules/ravestate_fillers/__init__.py new file mode 100644 index 0000000..b87eadc --- /dev/null +++ b/modules/ravestate_fillers/__init__.py @@ -0,0 +1,16 @@ +from ravestate import registry +from ravestate.wrappers import ContextWrapper +from ravestate.constraint import s +from ravestate.state import state + +import ravestate_idle +import ravestate_verbaliser +import ravestate_phrases_basic_en + + +@state(cond=s("idle:impatient"), write=("verbaliser:intent",)) +def impatient_fillers(ctx: ContextWrapper): + ctx["verbaliser:intent"] = "fillers" + + +registry.register(name="fillers", states=(impatient_fillers,)) diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py new file mode 100644 index 0000000..b7e46e8 --- /dev/null +++ b/modules/ravestate_idle/__init__.py @@ -0,0 +1,43 @@ +from ravestate import registry +from ravestate.constraint import ConfigurableAge +from ravestate.constraint import s +from ravestate.wrappers import ContextWrapper +from ravestate.state import state, Emit, Delete + +from reggol import get_logger + +logger = get_logger(__name__) + +IMPATIENCE_THRESHOLD_CONFIG_KEY = "impatience_threshold" + + +@state(read=":activity", signal_name="bored") +def am_i_bored(ctx: ContextWrapper): + """ + Emits idle:bored signal if no states are currently partially fulfilled + """ + if ctx[":activity"] == 0: + logger.debug("Emitting idle:bored") + 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") +def am_i_impatient(ctx: ContextWrapper): + logger.debug("Emitting idle:impatient") + return Emit(wipe=True) + + +# This state is just for testing the bored signal +@state(cond=s("idle:bored"), write="rawio:out") +def play_with_me(ctx: ContextWrapper): + ctx["rawio:out"] = "Play with me, I am bored!" + + +registry.register(name="idle", states=(am_i_bored, am_i_impatient, play_with_me), + config={ + # duration in seconds how long ":pressure" should be true before getting impatient + IMPATIENCE_THRESHOLD_CONFIG_KEY: 0.1 + }) diff --git a/modules/ravestate_phrases_basic_en/en/fillers.yml b/modules/ravestate_phrases_basic_en/en/fillers.yml new file mode 100644 index 0000000..3e0e26a --- /dev/null +++ b/modules/ravestate_phrases_basic_en/en/fillers.yml @@ -0,0 +1,7 @@ +type: phrases +name: "fillers" +opts: +- "I AM A FUCKING FILLER. NOTICE ME SENPAI!" +#- "hm" +#- "eeh" +#- "aah" \ No newline at end of file diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index 7054b3d..5152daf 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -6,6 +6,6 @@ registry.register( name="rawio", props=( - PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False), - PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False)) + PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True), + PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True)) ) diff --git a/modules/ravestate_verbaliser/__init__.py b/modules/ravestate_verbaliser/__init__.py index 2d0f7fe..78c9252 100644 --- a/modules/ravestate_verbaliser/__init__.py +++ b/modules/ravestate_verbaliser/__init__.py @@ -23,4 +23,4 @@ def react_to_intent(ctx): registry.register( name="verbaliser", states=(react_to_intent,), - props=(PropertyBase(name="intent", default_value="", allow_push=False, allow_pop=False),)) + props=(PropertyBase(name="intent", default_value="", allow_push=False, allow_pop=False, always_signal_changed=True),)) diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index e3a7765..ae1d0a4 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -105,6 +105,18 @@ def test_property_write(under_test_read_write: PropertyWrapper, default_property wipe=True) +def test_flag_property(context_mock): + prop_base = PropertyBase(name="flag_prop", is_flag_property=True) + prop_wrapper = PropertyWrapper(prop=prop_base, ctx=context_mock, allow_read=True, allow_write=True) + assert (prop_base._lock.locked()) + under_test_read_write.set(True) + assert (under_test_read_write.get() == True) + context_mock.emit.assert_called_with( + s(f"{under_test_read_write.prop.fullname()}:changed"), + parents=None, + wipe=True) + + 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 list(under_test_read_write.enum())[0] == CHILD_PROPERTY_FULLNAME From 7abc17c19ef2df51af988c28370c71f4a4299639 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Thu, 24 Jan 2019 16:21:09 +0100 Subject: [PATCH 03/46] Fixed wiped spike acquisition by adding spike.is_wiped(), moved core prop updates to own method. --- config/generic.yml | 4 +- config/roboy.yml | 2 + modules/ravestate/causal.py | 2 +- modules/ravestate/constraint.py | 1 + modules/ravestate/context.py | 92 +++++++++++++++++---------------- modules/ravestate/spike.py | 38 ++++++++++---- 6 files changed, 83 insertions(+), 56 deletions(-) diff --git a/config/generic.yml b/config/generic.yml index 6cfb2d5..6d086e4 100644 --- a/config/generic.yml +++ b/config/generic.yml @@ -16,13 +16,15 @@ --- module: core config: - tickrate: 5 + tickrate: 10 import: - ravestate_interloc - ravestate_conio - ravestate_wildtalk - ravestate_nlp - ravestate_hibye + - ravestate_idle + - ravestate_fillers --- module: genqa diff --git a/config/roboy.yml b/config/roboy.yml index abb2fbb..ab9a028 100644 --- a/config/roboy.yml +++ b/config/roboy.yml @@ -25,6 +25,8 @@ config: - ravestate_hibye - ravestate_roboyqa - ravestate_genqa + - ravestate_idle + - ravestate_fillers --- module: genqa diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index 4e81f6d..cfe3805 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -181,7 +181,7 @@ def acquired(self, spike: 'ISpike', acquired_by: IActivation) -> bool: # Make sure that all properties are actually still writable for prop in acquired_by.resources(): if prop not in self._ref_index: - logger.error(f"{prop} is unavailable, but {acquired_by} wants it from {self}!") + logger.error(f"{prop} is unavailable, but {acquired_by} wants it from {self} for {spike}!") return False for prop in acquired_by.resources(): self._ref_index[prop][spike][acquired_by] += 1 diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 60b3615..7432604 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -115,6 +115,7 @@ def conjunctions(self) -> Generator['Conjunct', None, None]: 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)): + assert not spike.is_wiped() self._min_age_ticks = act.secs_to_ticks(self.min_age) self.spike = spike with spike.causal_group() as cg: diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index e449a0e..711841b 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -27,22 +27,27 @@ class Context(IContext): _default_signal_names: Tuple[str] = (":startup", ":shutdown", ":idle") - _default_properties: Tuple[PropertyBase] = (PropertyBase(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), - PropertyBase(name="activity", - allow_read=True, - allow_write=True, - allow_push=False, - allow_pop=False, - default_value=0, - always_signal_changed=False) - ) + _default_properties: Tuple[PropertyBase] = ( + PropertyBase( + 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 + ), + PropertyBase( + name="activity", + allow_read=True, + allow_write=True, + allow_push=False, + allow_pop=False, + default_value=0, + always_signal_changed=False + ) + ) _core_module_name = "core" _import_modules_config = "import" @@ -51,7 +56,7 @@ class Context(IContext): _lock: Lock _properties: Dict[str, PropertyBase] - _spikes: Dict[Spike, bool] # Bool says not-wiped (acquisition allowed) true/false + _spikes: Set[Spike] # Some activations that have all constraints fulfilled # still need to be updated, because they are waiting for @@ -99,7 +104,7 @@ def __init__(self, *arguments): self._lock = Lock() self._shutdown_flag = Event() self._properties = dict() - self._spikes = defaultdict(lambda: True) + self._spikes = set() self._act_per_state_per_signal_age = dict() self._signal_causes = dict() self._activations_per_state = dict() @@ -126,22 +131,22 @@ def __init__(self, *arguments): def emit(self, signal: Signal, parents: Set[Spike]=None, wipe: bool=False) -> None: """ - Emit a signal to the signal processing loop. Note: - The signal will only be processed if run() has been called! + Emit a signal to the signal processing loop. _Note:_ + The signal will only be processed if #run() has been called! * `signal`: The signal to be emitted. * `parents`: The signal's parents, if it is supposed to be integrated into a causal group. - * `wipe`: Boolean to control, whether wipe(signal) should be called + * `wipe`: Boolean to control, whether #wipe(signal) should be called before the new spike is created. """ if wipe: self.wipe(signal) with self._lock: - logger.debug(f"Emitting {signal}") - self._spikes[ - Spike(sig=signal.name, parents=parents, consumable_resources=set(self._properties.keys()))] = True + new_spike = Spike(sig=signal.name, parents=parents, consumable_resources=set(self._properties.keys())) + logger.debug(f"Emitting {new_spike}") + self._spikes.add(new_spike) def wipe(self, signal: Signal): """ @@ -156,9 +161,6 @@ def wipe(self, signal: Signal): for spike in self._spikes: if spike.name() == signal.name: spike.wipe() - self._spikes[spike] = False - for child_spike in spike.offspring(): - self._spikes[child_spike] = False # Final cleanup will be performed while update is running, # and cg.stale(spike) returns true. # TODO: Make sure, that it is not a problem if the spike is currently referenced @@ -563,11 +565,23 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[ known_signals.discard(sig) return result if len(result) else None + def _update_core_properties(self): + with self._lock: + activation_pressure_present = any(activation.is_pressured() for activation in self._state_activations()) + number_of_partially_fulfilled_states = \ + sum(1 if any(activation.spiky() for activation in self._activations_per_state[st]) else 0 + for st in self._activations_per_state) + + PropertyWrapper(prop=self[":pressure"], ctx=self, allow_write=True, allow_read=True) \ + .set(activation_pressure_present) + PropertyWrapper(prop=self[":activity"], ctx=self, allow_write=True, allow_read=True) \ + .set(number_of_partially_fulfilled_states) + def _run_private(self): tick_interval = 1. / self._core_config[self._tick_rate_config] - counter = 0 + ctx_loop_count = 0 while not self._shutdown_flag.wait(tick_interval): with self._lock: @@ -592,8 +606,8 @@ def _run_private(self): allowed_unfulfilled = act # Acquire new state activations for every spike - for spike, acquisition_allowed in self._spikes.items(): - if not acquisition_allowed: + for spike in self._spikes: + if spike.is_wiped(): continue for state, acts in self._act_per_state_per_signal_age[s(spike.name())][spike.age()].items(): old_acts = acts.copy() @@ -621,9 +635,9 @@ def _run_private(self): with spike.causal_group() as cg: if cg.stale(spike): # This should lead to the deletion of the spike - self._spikes.pop(spike) + self._spikes.remove(spike) spike.wipe(already_wiped_in_causal_group=True) - logger.debug(f"{cg}.stale({spike.name()})->Y") + logger.debug(f"{cg}.stale({spike})->Y") # Increment age on active spikes for spike in self._spikes: @@ -631,16 +645,6 @@ def _run_private(self): # Force garbage collect gc.collect() + ctx_loop_count += 1 - - #activation_pressure_present = any(activation.is_pressured() for activation in self._state_activations()) - activation_pressure_present = (counter % 5) != 0 - counter += 1 - PropertyWrapper(prop=self[":pressure"], ctx=self, allow_write=True, allow_read=True)\ - .set(activation_pressure_present) - - number_of_partially_fulfilled_states = \ - sum(1 if any(activation.spiky() for activation in self._activations_per_state[st]) else 0 - for st in self._activations_per_state) - PropertyWrapper(prop=self[":activity"], ctx=self, allow_write=True, allow_read=True)\ - .set(number_of_partially_fulfilled_states) + self._update_core_properties() diff --git a/modules/ravestate/spike.py b/modules/ravestate/spike.py index 1511afe..27cc3cf 100644 --- a/modules/ravestate/spike.py +++ b/modules/ravestate/spike.py @@ -1,6 +1,7 @@ # Ravestate class which encapsulates a single spike -from typing import Set, Generator +from typing import Set, Generator, Dict +from collections import defaultdict from ravestate.iactivation import ISpike from ravestate.causal import CausalGroup @@ -15,12 +16,21 @@ class Spike(ISpike): ... it's offspring instances (causal group -> spikes caused by this spike) """ + # Count how spikes were created per signal + _count_for_signal: Dict[str, int] = defaultdict(int) + # Age of the spike in ticks _age: int - # Name of the spike's signal + # Name of the spike _name: str + # Name of the spike's signal + _signal: str + + # Flag which tells whether wipe() has been called on this spike + _wiped: bool + # This spike's causal group. The causal group # is shared by an spike's family (children/parents), # and dictates which properties are still free to be written. @@ -52,8 +62,12 @@ def __init__(self, *, sig: str, parents: Set['Spike']=None, consumable_resources parents = set() if consumable_resources is None: consumable_resources = set() - self._name = sig + assert sig + self._name = f"{sig}#{self._count_for_signal[sig]}" + self._signal = sig + self._count_for_signal[sig] += 1 self._age = 0 + self._wiped = False 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) @@ -67,13 +81,10 @@ def __del__(self): logger.debug(f"Deleted {self}") def __repr__(self): - return f"Spike({self._name}, age={self._age})" + return self._name + f"[t+{self._age}]" - def name(self) -> str: - """ - Returns the name of this spike's signal. - """ - return self._name + def name(self): + return self._signal def causal_group(self) -> CausalGroup: """ @@ -103,7 +114,7 @@ def wiped(self, child: 'ISpike') -> None: * `child`: The child to be forgotten. """ if child not in self._offspring: - logger.warning(f"Offspring {child.name()} requested to be removed from {self.name()}, but it's unfamiliar!") + logger.warning(f"Offspring {child} requested to be removed from {self}, but it's unfamiliar!") return self._offspring.remove(child) @@ -130,6 +141,7 @@ def wipe(self, already_wiped_in_causal_group: bool=False) -> None: if not already_wiped_in_causal_group: with self.causal_group() as causal: causal.wiped(self) + self._wiped = True logger.debug(f"Wiped {self}") # del self._causal_group @@ -162,3 +174,9 @@ def offspring(self) -> Generator['Spike', None, None]: for child in self._offspring: yield child yield from child.offspring() + + def is_wiped(self): + """ + Check, whether this spike has been wiped, and should therefore not be acquired anymore. + """ + return self._wiped From 57307f89235432faba59be0b687b069197663a4c Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Thu, 24 Jan 2019 23:46:14 +0100 Subject: [PATCH 04/46] akinator starting state; nlp gets activated if new input same as old input; nlp yes/no/maybe mockup --- config/roboy.yml | 1 + modules/ravestate_akinator/__init__.py | 85 ++++++++++++++++++++++++++ modules/ravestate_akinator/akinator.py | 2 + modules/ravestate_nlp/__init__.py | 28 ++++++++- modules/ravestate_rawio/__init__.py | 2 +- 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 modules/ravestate_akinator/__init__.py create mode 100644 modules/ravestate_akinator/akinator.py diff --git a/config/roboy.yml b/config/roboy.yml index abb2fbb..c98fb18 100644 --- a/config/roboy.yml +++ b/config/roboy.yml @@ -25,6 +25,7 @@ config: - ravestate_hibye - ravestate_roboyqa - ravestate_genqa + - ravestate_akinator --- module: genqa diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py new file mode 100644 index 0000000..29d541d --- /dev/null +++ b/modules/ravestate_akinator/__init__.py @@ -0,0 +1,85 @@ +from ravestate import registry +from ravestate.property import PropertyBase +from ravestate.state import state, Resign, Emit +from ravestate.constraint import s + +import requests + +from reggol import get_logger +logger = get_logger(__name__) + + +NEW_SESSION_URL = "https://srv11.akinator.com:9152/ws/new_session?callback=&partner=&player=website-desktop&uid_ext_session=&frontaddr=NDYuMTA1LjExMC40NQ==&constraint=ETAT<>'AV'" +ANSWER_URL = "https://srv11.akinator.com:9152/ws/answer" +GET_GUESS_URL = "https://srv11.akinator.com:9152/ws/list" +CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" +EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" +GLB_URL = "https://pastebin.com/gTua3dg2" + + +@state(signal_name="initiate-play", read="akinator:initiate_play") +def akinator_initiate_play_signal(ctx): + if ctx["akinator:initiate_play"]: + return Emit() + return False + + +# TODO: Change this to cond=idle:bored +@state(cond=s(":startup"), write=("rawio:out", "akinator:initiate_play")) +def akinator_play_ask(ctx): + ctx["rawio:out"] = "Do you want to play 20 questions?" + ctx["akinator:initiate_play"] = True + + +@state(cond=s("nlp:yes-no") & s("initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) +def akinator_start(ctx): + if ctx["nlp:yesno"] == "yes": + logger.info("Start Akinator session.") + akinator_session = requests.get(NEW_SESSION_URL) + akinator_data = akinator_session.json() + ctx["akinator:question"] = akinator_data['parameters']['step_information']['question'] + ctx["akinator:in_progress"] = True + ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step_information']['step']) + 1) + ":\n" \ + + akinator_data['parameters']['step_information']['question'] \ + + '\n"yes", "no", "idk", "probably", "probably not"' + else: + return Resign() + + +@state(cond=s("nlp:yes-no") & s("akinator:in_progress:changed"), read="nlp:yesno", write=("rawio:out", "akinator:is_it", "akinator:question")) +def akinator_question_answered(ctx): + logger.info("Test") + + +@state(read="nlp:triples", write="rawio:out") +def akinator_is_it_answered(ctx): + pass + + +def answer_to_int_str(answer: str): + if answer == "yes": + return "0" + elif answer == "no": + return "1" + elif answer == "maybe": + return "2" + else: + return "-1" + + +registry.register( + name="akinator", + states=( + akinator_play_ask, + akinator_start, + akinator_question_answered, + akinator_is_it_answered, + akinator_initiate_play_signal + ), + props=( + PropertyBase(name="is_it", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + PropertyBase(name="question", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + PropertyBase(name="in_progress", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + PropertyBase(name="initiate_play", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + ) +) diff --git a/modules/ravestate_akinator/akinator.py b/modules/ravestate_akinator/akinator.py new file mode 100644 index 0000000..139597f --- /dev/null +++ b/modules/ravestate_akinator/akinator.py @@ -0,0 +1,2 @@ + + diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 1bc621d..0a90f7b 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -6,6 +6,8 @@ from ravestate_nlp.triple import Triple from ravestate_nlp.extract_triples import extract_triples from ravestate.state import Emit +from ravestate.constraint import s + import spacy @@ -21,6 +23,7 @@ def init_model(): # TODO: Make agent id configurable, rename nlp:contains-roboy to nlp:agent-mentioned about_roboy = ('you', 'roboy', 'robot', 'roboboy', 'your') + def roboy_getter(doc) -> bool: return any(roboy in doc.text.lower() for roboy in about_roboy) @@ -30,7 +33,7 @@ def roboy_getter(doc) -> bool: Doc.set_extension('triples', getter=extract_triples) -@state(read="rawio:in", write=("nlp:tokens", "nlp:postags", "nlp:lemmas", "nlp:tags", "nlp:ner", "nlp:triples", "nlp:roboy","nlp:triples")) +@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:triples", "nlp:yesno")) def nlp_preprocess(ctx): nlp_doc = nlp(ctx["rawio:in"]) @@ -62,6 +65,16 @@ def nlp_preprocess(ctx): ctx["nlp:roboy"] = nlp_roboy logger.info(f"[NLP:roboy]: {nlp_roboy}") + for i in nlp_tokens: + if i == "no": + ctx["nlp:yesno"] = "no" + elif i == "yes": + ctx["nlp:yesno"] = "yes" + elif i == "maybe": + ctx["nlp:yesno"] = "maybe" + else: + pass + @state(signal_name="contains-roboy", read="nlp:roboy") def nlp_contains_roboy_signal(ctx): @@ -77,13 +90,21 @@ def nlp_is_question_signal(ctx): return False +@state(signal_name="yes-no", read="nlp:yesno") +def nlp_yes_no_signal(ctx): + if ctx["nlp:yesno"][0]: + return Emit() + return False + + init_model() registry.register( name="nlp", states=( nlp_preprocess, nlp_contains_roboy_signal, - nlp_is_question_signal + nlp_is_question_signal, + nlp_yes_no_signal ), props=( PropertyBase(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), @@ -92,6 +113,7 @@ def nlp_is_question_signal(ctx): PropertyBase(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), PropertyBase(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), PropertyBase(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + PropertyBase(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), + PropertyBase(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) ) ) diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index 7054b3d..a0c04b0 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -6,6 +6,6 @@ registry.register( name="rawio", props=( - PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False), + PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True), PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False)) ) From c4866057abcddb12c1d612cd629caa399c4c6944 Mon Sep 17 00:00:00 2001 From: Wagram Airiian Date: Fri, 25 Jan 2019 21:25:59 +0100 Subject: [PATCH 05/46] Added detached propagation to constraint completion, ref tracking in causal group for detached activations. --- modules/ravestate/activation.py | 4 +- modules/ravestate/causal.py | 74 +++++++++++++++++++------- modules/ravestate/constraint.py | 2 +- modules/ravestate/context.py | 18 +++++-- modules/ravestate_akinator/__init__.py | 8 +-- 5 files changed, 76 insertions(+), 30 deletions(-) diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index f29c4a0..9202bf6 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -174,7 +174,9 @@ def update(self) -> bool: spikes_for_conjunct = set((sig.spike, sig.detached) for sig in conjunction.signals()) consenting_causal_groups = set() all_consented = True - for spike, _ in spikes_for_conjunct: + for spike, detached in spikes_for_conjunct: + if detached: + continue cg: CausalGroup = spike.causal_group() if cg not in consenting_causal_groups: with cg: diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index cfe3805..fb9c4de 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -74,6 +74,20 @@ class CausalGroup: ] ] + # Detached signal references need to be tracked separately. + # The reason is, that the activations with detached signal + # constraints do not expect to need any consent for their + # write-activities from those signal's causal groups. + # They are therefore not rejected upon consumed(), but upon wiped() + # or rejected(). + _detached_ref_index: Dict[ + ISpike, + Dict[ + IActivation, + int + ] + ] + # Names of member spikes for __repr__, added by Spike ctor signal_names: List[str] @@ -94,6 +108,7 @@ def __init__(self, resources: Set[str]): prop: defaultdict(lambda: defaultdict(int)) for prop in resources } + self._detached_ref_index = defaultdict(lambda: defaultdict(int)) def __del__(self): with self._lock: @@ -153,6 +168,9 @@ def merge(self, other: 'CausalGroup'): if refcount > 0: self._ref_index[prop][spike][act] += refcount + # Merge _detached_ref_index + self._detached_ref_index.update(other._detached_ref_index) + # Merge signal names/merge count self.signal_names += other.signal_names self.merges[0] += other.merges[0] @@ -163,8 +181,9 @@ def merge(self, other: 'CausalGroup'): other._lock = self._lock other._available_resources = self._available_resources other._ref_index = self._ref_index + other._detached_ref_index = self._detached_ref_index - def acquired(self, spike: 'ISpike', acquired_by: IActivation) -> bool: + def acquired(self, spike: 'ISpike', acquired_by: IActivation, detached: bool) -> bool: """ Called by Activation to notify the causal group, that it is being referenced by an activation constraint for a certain member spike. @@ -175,16 +194,22 @@ def acquired(self, spike: 'ISpike', acquired_by: IActivation) -> bool: * `acquired_by`: State activation instance, which is interested in this property. + * `detached`: Tells the causal group, whether the reference is detached, + and should therefore receive special treatment. + **Returns:** Returns True if all of the acquiring's write-props are free, and the group now refs. the activation, False otherwise. """ # Make sure that all properties are actually still writable - for prop in acquired_by.resources(): - if prop not in self._ref_index: - logger.error(f"{prop} is unavailable, but {acquired_by} wants it from {self} for {spike}!") - return False - for prop in acquired_by.resources(): - self._ref_index[prop][spike][acquired_by] += 1 + if detached: + self._detached_ref_index[spike][acquired_by] += 1 + else: + for prop in acquired_by.resources(): + if prop not in self._ref_index: + logger.error(f"{prop} is unavailable, but {acquired_by} wants it from {self} for {spike}!") + return False + for prop in acquired_by.resources(): + self._ref_index[prop][spike][acquired_by] += 1 logger.debug(f"{self}.acquired({spike} by {acquired_by})") return True @@ -204,20 +229,22 @@ def rejected(self, spike: 'ISpike', rejected_by: IActivation, reason: int) -> No * `reason`: See about. """ - for prop in rejected_by.resources(): - if prop in self._ref_index and \ - spike in self._ref_index[prop] and \ - rejected_by in self._ref_index[prop][spike]: - remaining_refcount = self._ref_index[prop][spike][rejected_by] + def _decrement_refcount(refcount_for_act_for_spike): + if spike in refcount_for_act_for_spike and rejected_by in refcount_for_act_for_spike[spike]: + remaining_refcount = refcount_for_act_for_spike[spike][rejected_by] if remaining_refcount > 0: - self._ref_index[prop][spike][rejected_by] -= 1 + refcount_for_act_for_spike[spike][rejected_by] -= 1 if remaining_refcount > 1: # do not fall through to ref deletion - continue + return else: logger.error(f"Attempt to deref group for unref'd activation {rejected_by.name}") + del refcount_for_act_for_spike[spike][rejected_by] - del self._ref_index[prop][spike][rejected_by] + _decrement_refcount(self._detached_ref_index) + for prop in rejected_by.resources(): + if prop in self._ref_index: + _decrement_refcount(self._ref_index[prop]) if len(self._ref_index[prop][spike]) == 0: del self._ref_index[prop][spike] if reason == 1: @@ -318,11 +345,14 @@ def wiped(self, spike: 'ISpike') -> None: * `spike`: The instance that should be henceforth forgotten. """ - for prop in self._ref_index: - if spike in self._ref_index[prop]: - for act in self._ref_index[prop][spike]: + def _remove_spike_from_index(refcount_for_act_for_spike): + if spike in refcount_for_act_for_spike: + for act in refcount_for_act_for_spike[spike]: act.dereference(spike=spike, reacquire=True) - del self._ref_index[prop][spike] + del refcount_for_act_for_spike[spike] + for prop in self._ref_index: + _remove_spike_from_index(self._ref_index[prop]) + _remove_spike_from_index(self._detached_ref_index) def stale(self, spike: 'ISpike') -> bool: """ @@ -339,5 +369,11 @@ def stale(self, spike: 'ISpike') -> bool: else: # Do some cleanup del self._ref_index[prop][spike] + if spike in self._detached_ref_index: + if len(self._detached_ref_index[spike]) > 0: + return False + else: + # Do some cleanup + del self._detached_ref_index[spike] result = not spike.has_offspring() return result diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 7432604..289cd3c 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -119,7 +119,7 @@ def acquire(self, spike: Spike, act: IActivation): self._min_age_ticks = act.secs_to_ticks(self.min_age) self.spike = spike with spike.causal_group() as cg: - cg.acquired(spike, act) + cg.acquired(spike, act, self.detached) return True return False diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 711841b..5a2d9fe 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -534,17 +534,24 @@ def _complete_constraint(self, st: State): assert len(known_signals) == 0 st.constraint_ = Disjunct(*{conj for conj in new_conjuncts}) - def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal]) -> List[Set[Signal]]: - result = [set(conj.signals())] + def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal], detached=False) -> List[Set[Signal]]: + result = [set(deepcopy(sig) for sig in conj.signals())] + for sig in result[0]: + # if this (or a parent signal) is detached, then the completion must be detached too! + sig.detached |= detached + # maximum age for completions is infinite + sig.max_age = -1 + for conj_sig in conj.signals(): - completion = self._complete_signal(conj_sig, known_signals) + completion = self._complete_signal(conj_sig, known_signals, detached) if completion is not None and len(completion) > 0: # the signal is non-cyclic, and has at least one cause (secondary signal). # permute existing disjunct conjunctions with new conjunction(s) result = [deepcopy(result_conj) | deepcopy(completion_conj) for result_conj in result for completion_conj in completion] + return result - def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[List[Set[Signal]]]: + def _complete_signal(self, sig: Signal, known_signals: Set[Signal], detached=False) -> Optional[List[Set[Signal]]]: # detect and handle cyclic causal chain if sig in known_signals: return None @@ -559,10 +566,11 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[ result = [] known_signals.add(sig) for conj in self._signal_causes[sig]: - completion = self._complete_conjunction(conj, known_signals) + completion = self._complete_conjunction(conj, known_signals, sig.detached or detached) if completion: result += [conj | {sig} for conj in completion] known_signals.discard(sig) + return result if len(result) else None def _update_core_properties(self): diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 29d541d..2bfd927 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -17,7 +17,7 @@ GLB_URL = "https://pastebin.com/gTua3dg2" -@state(signal_name="initiate-play", read="akinator:initiate_play") +@state(signal_name="initiate-play", read="akinator:initiate_play", cond=s("akinator:initiate_play:changed", detached=True)) def akinator_initiate_play_signal(ctx): if ctx["akinator:initiate_play"]: return Emit() @@ -31,7 +31,7 @@ def akinator_play_ask(ctx): ctx["akinator:initiate_play"] = True -@state(cond=s("nlp:yes-no") & s("initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) +@state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) def akinator_start(ctx): if ctx["nlp:yesno"] == "yes": logger.info("Start Akinator session.") @@ -70,11 +70,11 @@ def answer_to_int_str(answer: str): registry.register( name="akinator", states=( + akinator_initiate_play_signal, akinator_play_ask, akinator_start, akinator_question_answered, - akinator_is_it_answered, - akinator_initiate_play_signal + akinator_is_it_answered ), props=( PropertyBase(name="is_it", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), From 344ba8584e4379f9e64d1a3878ca2270d3e58eea Mon Sep 17 00:00:00 2001 From: Wagram Airiian Date: Fri, 25 Jan 2019 23:12:23 +0100 Subject: [PATCH 06/46] Undid detachment propagation, replaced with ommitting detached signal completion. Added properly detached akinator question-asked signal. Removed initiate_play property. Added retainment of uncompleted conjuncts to completion. --- modules/ravestate/constraint.py | 20 ++++++++---- modules/ravestate/context.py | 16 ++++----- modules/ravestate_akinator/__init__.py | 45 +++++++++++++++++--------- 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 289cd3c..1f78960 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -41,7 +41,7 @@ def signals(self) -> Generator['Signal', None, None]: logger.error("Don't call this method on the super class Constraint") yield None - def conjunctions(self) -> Generator['Conjunct', None, None]: + def conjunctions(self, filter_detached=False) -> Generator['Conjunct', None, None]: logger.error("Don't call this method on the super class Constraint") yield None @@ -110,8 +110,9 @@ def __hash__(self): def signals(self) -> Generator['Signal', None, None]: yield self - def conjunctions(self) -> Generator['Conjunct', None, None]: - yield Conjunct(self) + def conjunctions(self, filter_detached=False) -> Generator['Conjunct', None, None]: + if not filter_detached or not self.detached: + 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)): @@ -194,8 +195,12 @@ def __eq__(self, other): def signals(self) -> Generator['Signal', None, None]: return (sig for sig in self._signals) - def conjunctions(self) -> Generator['Conjunct', None, None]: - yield self + 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)) + if result._signals: + yield result def acquire(self, spike: Spike, act: IActivation): result = False @@ -259,8 +264,9 @@ def __and__(self, other): def signals(self) -> Generator['Signal', None, None]: return (signal for conjunct in self._conjunctions for signal in conjunct._signals) - def conjunctions(self) -> Generator['Conjunct', None, None]: - return (conj for conj in self._conjunctions) + def conjunctions(self, filter_detached=False) -> Generator['Conjunct', None, None]: + for conj in self._conjunctions: + yield from conj.conjunctions(filter_detached=filter_detached) def acquire(self, spike: Spike, act: IActivation): result = False diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 5a2d9fe..25ce00a 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -248,7 +248,7 @@ def add_state(self, *, st: State) -> None: # add state's constraints as causes for the written prop's :changed signals, # as well as the state's own signal. states_to_recomplete: Set[State] = {st} - for conj in st.constraint.conjunctions(): + for conj in st.constraint.conjunctions(filter_detached=True): for propname in st.write_props: if propname in self._properties: for signal in self._properties[propname].signals(): @@ -525,7 +525,7 @@ def _states_for_signal(self, sig: Signal) -> Iterable[State]: return self._act_per_state_per_signal_age[sig][0].keys() # sig.min_age def _complete_constraint(self, st: State): - new_conjuncts: Set[Conjunct] = set() + new_conjuncts: Set[Conjunct] = deepcopy(set(st.constraint.conjunctions())) for conj in st.constraint.conjunctions(): known_signals = set() new_conjuncts.update( @@ -534,16 +534,14 @@ def _complete_constraint(self, st: State): assert len(known_signals) == 0 st.constraint_ = Disjunct(*{conj for conj in new_conjuncts}) - def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal], detached=False) -> List[Set[Signal]]: + def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal]) -> List[Set[Signal]]: result = [set(deepcopy(sig) for sig in conj.signals())] for sig in result[0]: - # if this (or a parent signal) is detached, then the completion must be detached too! - sig.detached |= detached # maximum age for completions is infinite sig.max_age = -1 for conj_sig in conj.signals(): - completion = self._complete_signal(conj_sig, known_signals, detached) + completion = self._complete_signal(conj_sig, known_signals) if completion is not None and len(completion) > 0: # the signal is non-cyclic, and has at least one cause (secondary signal). # permute existing disjunct conjunctions with new conjunction(s) @@ -551,14 +549,14 @@ def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal], deta return result - def _complete_signal(self, sig: Signal, known_signals: Set[Signal], detached=False) -> Optional[List[Set[Signal]]]: + def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[List[Set[Signal]]]: # detect and handle cyclic causal chain if sig in known_signals: return None assert sig in self._signal_causes # a signal without cause (a primary signal) needs no further completion - if not self._signal_causes[sig]: + if not self._signal_causes[sig] or sig.detached: return [] # a signal with at least one secondary cause needs at least one non-cyclic @@ -566,7 +564,7 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal], detached=Fal result = [] known_signals.add(sig) for conj in self._signal_causes[sig]: - completion = self._complete_conjunction(conj, known_signals, sig.detached or detached) + completion = self._complete_conjunction(conj, known_signals) if completion: result += [conj | {sig} for conj in completion] known_signals.discard(sig) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 2bfd927..ff0008d 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -17,18 +17,11 @@ GLB_URL = "https://pastebin.com/gTua3dg2" -@state(signal_name="initiate-play", read="akinator:initiate_play", cond=s("akinator:initiate_play:changed", detached=True)) -def akinator_initiate_play_signal(ctx): - if ctx["akinator:initiate_play"]: - return Emit() - return False - - # TODO: Change this to cond=idle:bored -@state(cond=s(":startup"), write=("rawio:out", "akinator:initiate_play")) +@state(cond=s(":startup", detached=True), write="rawio:out", signal_name="initiate-play") def akinator_play_ask(ctx): ctx["rawio:out"] = "Do you want to play 20 questions?" - ctx["akinator:initiate_play"] = True + return Emit() @state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) @@ -46,9 +39,14 @@ def akinator_start(ctx): return Resign() -@state(cond=s("nlp:yes-no") & s("akinator:in_progress:changed"), read="nlp:yesno", write=("rawio:out", "akinator:is_it", "akinator:question")) +@state(cond=s("akinator:question:changed", detached=True), read="akinator:question", signal_name="question-asked") +def akinator_question_asked(ctx): + return Emit() + + +@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), read="nlp:yesno", write=("rawio:out", "akinator:is_it", "akinator:question")) def akinator_question_answered(ctx): - logger.info("Test") + ctx["rawio:out"] = "Is your character brown and fabulous?" @state(read="nlp:triples", write="rawio:out") @@ -70,16 +68,31 @@ def answer_to_int_str(answer: str): registry.register( name="akinator", states=( - akinator_initiate_play_signal, akinator_play_ask, + akinator_question_asked, akinator_start, akinator_question_answered, akinator_is_it_answered ), props=( - PropertyBase(name="is_it", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="question", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="in_progress", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="initiate_play", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + PropertyBase( + name="is_it", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="question", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="in_progress", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False, + is_flag_property=True) ) ) From 43973d7c86ce8dd2ad8a7a096c60d8b85f726df6 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Sat, 26 Jan 2019 14:30:12 +0100 Subject: [PATCH 07/46] akinator queation answered gets answer and asks nest question --- modules/ravestate_akinator/__init__.py | 34 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index ff0008d..adca202 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -24,13 +24,13 @@ def akinator_play_ask(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) +@state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=-1), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) def akinator_start(ctx): if ctx["nlp:yesno"] == "yes": logger.info("Start Akinator session.") akinator_session = requests.get(NEW_SESSION_URL) akinator_data = akinator_session.json() - ctx["akinator:question"] = akinator_data['parameters']['step_information']['question'] + ctx["akinator:akinator_data"] = akinator_data ctx["akinator:in_progress"] = True ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step_information']['step']) + 1) + ":\n" \ + akinator_data['parameters']['step_information']['question'] \ @@ -39,17 +39,33 @@ def akinator_start(ctx): return Resign() -@state(cond=s("akinator:question:changed", detached=True), read="akinator:question", signal_name="question-asked") +@state(cond=s("akinator:akinator_data:changed", detached=True), read="akinator:akinator_data", signal_name="question-asked") def akinator_question_asked(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), read="nlp:yesno", write=("rawio:out", "akinator:is_it", "akinator:question")) +@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), read=("nlp:yesno", "akinator:akinator_data"), write=("rawio:out", "akinator:is_it", "akinator:question")) def akinator_question_answered(ctx): - ctx["rawio:out"] = "Is your character brown and fabulous?" - - -@state(read="nlp:triples", write="rawio:out") + akinator_data = ctx["akinator:akinator_data"] + response = ctx["nlp:yesno"] + params = { + "session": akinator_data['parameters']['identification']['session'], + "signature": akinator_data['parameters']['identification']['signature'], + "step": akinator_data['parameters']['step_information']['step'], + "answer": response + } + session = akinator_data['parameters']['identification']['session'] + signature = akinator_data['parameters']['identification']['signature'] + + akinator_session = requests.get(ANSWER_URL, params=params) + akinator_data = akinator_session.json() + + ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step_information']['step']) + 1) + ":\n" \ + + akinator_data['parameters']['step_information']['question'] \ + + '\n"yes", "no", "idk", "probably", "probably not"' + + +@state(cond=s("akinator:is_it:changed"), read="nlp:triples", write="rawio:out") def akinator_is_it_answered(ctx): pass @@ -82,7 +98,7 @@ def answer_to_int_str(answer: str): allow_pop=False, allow_push=False), PropertyBase( - name="question", + name="akinator_data", default_value="", always_signal_changed=True, allow_pop=False, From 5ce6461643d77b27b81d6d6ea76828584463b5a3 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Sat, 26 Jan 2019 17:05:09 +0100 Subject: [PATCH 08/46] implemented the asking of 20 questions in a row and is it checking --- modules/ravestate_akinator/__init__.py | 93 ++++++++++++++++++++------ 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index adca202..908348d 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -17,6 +17,9 @@ GLB_URL = "https://pastebin.com/gTua3dg2" +akinator_data = None +first_question = True + # TODO: Change this to cond=idle:bored @state(cond=s(":startup", detached=True), write="rawio:out", signal_name="initiate-play") def akinator_play_ask(ctx): @@ -24,50 +27,86 @@ def akinator_play_ask(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=-1), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress")) +@state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress", "akinator:session", "akinator:signature")) def akinator_start(ctx): if ctx["nlp:yesno"] == "yes": logger.info("Start Akinator session.") akinator_session = requests.get(NEW_SESSION_URL) + global akinator_data akinator_data = akinator_session.json() - ctx["akinator:akinator_data"] = akinator_data + ctx["akinator:question"] = akinator_data + ctx["akinator:question"] = akinator_data['parameters']['step_information']['question'] ctx["akinator:in_progress"] = True ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step_information']['step']) + 1) + ":\n" \ + akinator_data['parameters']['step_information']['question'] \ + '\n"yes", "no", "idk", "probably", "probably not"' + + ctx["akinator:session"] = akinator_data['parameters']['identification']['session'] + ctx["akinator:signature"] = akinator_data['parameters']['identification']['signature'] else: return Resign() -@state(cond=s("akinator:akinator_data:changed", detached=True), read="akinator:akinator_data", signal_name="question-asked") +@state(cond=s("akinator:question:changed", detached=True), read="akinator:question", signal_name="question-asked") def akinator_question_asked(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), read=("nlp:yesno", "akinator:akinator_data"), write=("rawio:out", "akinator:is_it", "akinator:question")) +@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), read=("nlp:yesno", "akinator:session", "akinator:signature"), write=("rawio:out", "akinator:is_it", "akinator:question")) def akinator_question_answered(ctx): - akinator_data = ctx["akinator:akinator_data"] - response = ctx["nlp:yesno"] + global first_question + global akinator_data + + if first_question: + first_question = False + step = akinator_data['parameters']['step_information']['step'] + else: + step = akinator_data['parameters']['step'] + response = answer_to_int_str(ctx["nlp:yesno"]) params = { - "session": akinator_data['parameters']['identification']['session'], - "signature": akinator_data['parameters']['identification']['signature'], - "step": akinator_data['parameters']['step_information']['step'], + "session": ctx["akinator:session"], + "signature": ctx["akinator:signature"], + "step": step, "answer": response } - session = akinator_data['parameters']['identification']['session'] - signature = akinator_data['parameters']['identification']['signature'] - akinator_session = requests.get(ANSWER_URL, params=params) akinator_data = akinator_session.json() - ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step_information']['step']) + 1) + ":\n" \ - + akinator_data['parameters']['step_information']['question'] \ - + '\n"yes", "no", "idk", "probably", "probably not"' + if int(float(akinator_data['parameters']['progression'])) <= 90: + ctx["akinator:question"] = akinator_data['parameters']['question'] + ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step']) + 1) + ":\n" \ + + akinator_data['parameters']['question'] \ + + '\n"yes", "no", "idk", "probably", "probably not"' + else: + ctx["akinator:is_it"] = True + + +@state(cond=s("akinator:is_it:changed", detached=True), read="akinator:question", signal_name="is-it") +def akinator_is_it(ctx): + params = { + "session": ctx["akinator:session"], + "signature": ctx["akinator:signature"], + "step": akinator_data['parameters']['step'] + } + + guess_session = requests.get(GET_GUESS_URL, params=params) + guess_data = guess_session.json() + + name = guess_data['parameters']['elements'][0]['element']['name'] + desc = guess_data['parameters']['elements'][0]['element']['description'] + ctx["rawio:out"] = "Is this your character? [yes/no]\n" + name + "\n" + desc + "\n" + return Emit() -@state(cond=s("akinator:is_it:changed"), read="nlp:triples", write="rawio:out") +@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), read="nlp:triples", write="rawio:out") def akinator_is_it_answered(ctx): - pass + if ctx["nlp:yesno"] == "yes": + output = "I guessed right! Thanks for playing with me." + elif ctx["nlp:yesno"] == "no": + output = "I guessed right! Thanks for playing with me." + else: + output = "Shit" + ctx["rawio:out"] = output def answer_to_int_str(answer: str): @@ -88,6 +127,7 @@ def answer_to_int_str(answer: str): akinator_question_asked, akinator_start, akinator_question_answered, + akinator_is_it, akinator_is_it_answered ), props=( @@ -96,9 +136,10 @@ def answer_to_int_str(answer: str): default_value="", always_signal_changed=True, allow_pop=False, - allow_push=False), + allow_push=False, + is_flag_property=True), PropertyBase( - name="akinator_data", + name="question", default_value="", always_signal_changed=True, allow_pop=False, @@ -109,6 +150,18 @@ def answer_to_int_str(answer: str): always_signal_changed=True, allow_pop=False, allow_push=False, - is_flag_property=True) + is_flag_property=True), + PropertyBase( + name="session", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="signature", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False) ) ) From 7d6beb834fcb7749f8548a4ea557a0045570c9a1 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Sat, 26 Jan 2019 17:37:27 +0100 Subject: [PATCH 09/46] minor change --- modules/ravestate_akinator/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 908348d..1544789 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -20,6 +20,7 @@ akinator_data = None first_question = True + # TODO: Change this to cond=idle:bored @state(cond=s(":startup", detached=True), write="rawio:out", signal_name="initiate-play") def akinator_play_ask(ctx): From 2f8ab08979668fcefd7c2dd63d01cab7209d2ecb Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Sat, 26 Jan 2019 20:02:31 +0100 Subject: [PATCH 10/46] Fixed recursive lock in CausalGroup destructor due to garbage collector shenanigans. --- modules/ravestate/causal.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index fb9c4de..e5425ba 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -2,7 +2,7 @@ from typing import Set, Dict, Optional, List from ravestate.iactivation import IActivation, ISpike -from threading import Lock +from threading import RLock from collections import defaultdict from reggol import get_logger @@ -20,9 +20,9 @@ class CausalGroup: """ # Lock for the whole causal group - _lock: Lock + _lock: RLock # Remember the locked lock, since the lock member might be re-targeted in merge() - _locked_lock: Optional[Lock] + _locked_lock: Optional[RLock] # Set of property names for which no writing state has been # activated yet within this causal group. @@ -100,7 +100,7 @@ def __init__(self, resources: Set[str]): """ self.signal_names = [] self.merges = [1] - self._lock = Lock() + self._lock = RLock() self._locked_lock = None self._available_resources = resources.copy() self._ref_index = { From 224d454888e3aebb9dc8d3983ac5618e723b037d Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Sat, 26 Jan 2019 20:12:13 +0100 Subject: [PATCH 11/46] Changed required Python version back to 3.6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 535025c..60fa615 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ scripts=["rasta"], install_requires=required + ["reggol"], - python_requires='>=3.7', + python_requires='>=3.6', classifiers=[ "Programming Language :: Python :: 3", From 6ee7b479ed8ad604948a3f73559f135172f9cbd8 Mon Sep 17 00:00:00 2001 From: Andreas Dolp Date: Sun, 27 Jan 2019 13:49:03 +0100 Subject: [PATCH 12/46] fix self-influence when counting partially fulfilled states add unit test for flag-property --- modules/ravestate/constraint.py | 9 +++-- modules/ravestate/context.py | 4 ++- modules/ravestate/property.py | 8 ++--- modules/ravestate_idle/__init__.py | 16 ++++----- .../ravestate_phrases_basic_en/en/fillers.yml | 10 +++--- .../ravestate/test_wrappers_property.py | 33 ++++++++++++++++--- 6 files changed, 54 insertions(+), 26 deletions(-) diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 7432604..ebd4bbf 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -7,6 +7,9 @@ class ConfigurableAge: + """ + Class for having min/max_age parameters for Constraints configurable with a config key + """ key = "" def __init__(self, key: str): @@ -20,9 +23,9 @@ def s(signal_name: str, *, min_age: Union[float, ConfigurableAge] = 0., max_age: * `signal_name`: Name of the Signal - * `min_age`: Minimum age for the signal, in seconds. + * `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. + * `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 @@ -73,7 +76,7 @@ class Signal(Constraint): detached: bool _min_age_ticks: int # written on acquire, when act.secs_to_ticks is available - def __init__(self, name: str, *, min_age=0., max_age=1., detached=False): + def __init__(self, name: str, *, min_age=0., max_age=5., detached=False): self.name = name # TODO: Convert seconds for min_age/max_age to ticks self.min_age = min_age diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 711841b..16b4fec 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -568,8 +568,10 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[ def _update_core_properties(self): with self._lock: activation_pressure_present = any(activation.is_pressured() for activation in self._state_activations()) + # don't count states that have ':activity:changed' in their constraint to avoid self-influencing number_of_partially_fulfilled_states = \ - sum(1 if any(activation.spiky() for activation in self._activations_per_state[st]) else 0 + sum(1 if any(activation.spiky() and s(':activity:changed') not in list(activation.constraint.signals()) + for activation in self._activations_per_state[st]) else 0 for st in self._activations_per_state) PropertyWrapper(prop=self[":pressure"], ctx=self, allow_write=True, allow_read=True) \ diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 351204e..52764f9 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -147,14 +147,14 @@ def popped_signal(self) -> Signal: return s(f"{self.fullname()}:popped") def flag_true_signal(self) -> Signal: - """ TODO Docstring for flagprops - Signal that is emitted by PropertyWrapper when #self.value is set to True. + """ + Signal that is emitted by PropertyWrapper when it is a flag-property and #self.value is set to True. """ return s(f"{self.fullname()}:true") def flag_false_signal(self) -> Signal: - """ TODO Docstring for flagprops - Signal that is emitted by PropertyWrapper when #self.value is set to False. + """ + Signal that is emitted by PropertyWrapper when it is a flag-property and #self.value is set to False. """ return s(f"{self.fullname()}:false") diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index b7e46e8..7ab243f 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -2,10 +2,9 @@ from ravestate.constraint import ConfigurableAge from ravestate.constraint import s from ravestate.wrappers import ContextWrapper -from ravestate.state import state, Emit, Delete +from ravestate.state import state, Emit from reggol import get_logger - logger = get_logger(__name__) IMPATIENCE_THRESHOLD_CONFIG_KEY = "impatience_threshold" @@ -26,18 +25,15 @@ def am_i_bored(ctx: ContextWrapper): max_age=-1.), signal_name="impatient") def am_i_impatient(ctx: ContextWrapper): + """ + Emits idle:impatient signal if there are pressured activations for a (configurable) amount of time + """ logger.debug("Emitting idle:impatient") return Emit(wipe=True) -# This state is just for testing the bored signal -@state(cond=s("idle:bored"), write="rawio:out") -def play_with_me(ctx: ContextWrapper): - ctx["rawio:out"] = "Play with me, I am bored!" - - -registry.register(name="idle", states=(am_i_bored, am_i_impatient, play_with_me), +registry.register(name="idle", states=(am_i_bored, am_i_impatient), config={ # duration in seconds how long ":pressure" should be true before getting impatient - IMPATIENCE_THRESHOLD_CONFIG_KEY: 0.1 + IMPATIENCE_THRESHOLD_CONFIG_KEY: 2. }) diff --git a/modules/ravestate_phrases_basic_en/en/fillers.yml b/modules/ravestate_phrases_basic_en/en/fillers.yml index 3e0e26a..ee8bf75 100644 --- a/modules/ravestate_phrases_basic_en/en/fillers.yml +++ b/modules/ravestate_phrases_basic_en/en/fillers.yml @@ -1,7 +1,9 @@ type: phrases name: "fillers" opts: -- "I AM A FUCKING FILLER. NOTICE ME SENPAI!" -#- "hm" -#- "eeh" -#- "aah" \ No newline at end of file +- "hm" +- "er" +- "mm" +- "ah" +- "oh" +- "uh" \ No newline at end of file diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index ae1d0a4..765e15f 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -107,12 +107,37 @@ 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.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()) - under_test_read_write.set(True) - assert (under_test_read_write.get() == True) - context_mock.emit.assert_called_with( - s(f"{under_test_read_write.prop.fullname()}:changed"), + prop_wrapper.set(True) + assert (prop_wrapper.get() is True) + context_mock.emit.assert_any_call( + s(f"{prop_wrapper.prop.fullname()}:changed"), + parents=None, + wipe=True) + context_mock.emit.assert_any_call( + s(f"{prop_wrapper.prop.fullname()}:true"), + parents=None, + wipe=True) + + context_mock.emit.reset_mock() + prop_wrapper.set(False) + assert (prop_wrapper.get() is False) + context_mock.emit.assert_any_call( + s(f"{prop_wrapper.prop.fullname()}:changed"), + parents=None, + wipe=True) + context_mock.emit.assert_any_call( + s(f"{prop_wrapper.prop.fullname()}:false"), + parents=None, + wipe=True) + + context_mock.emit.reset_mock() + prop_wrapper.set(None) + assert (prop_wrapper.get() is None) + context_mock.emit.assert_called_once_with( + s(f"{prop_wrapper.prop.fullname()}:changed"), parents=None, wipe=True) From 7371ba33a039ae2ab8a9b36d65e4e9a96c4d857d Mon Sep 17 00:00:00 2001 From: Emilka Date: Sun, 13 Jan 2019 20:32:03 +0100 Subject: [PATCH 13/46] 69% coverage --- .gitignore | 1 + config/generic.yml | 1 + test/modules/__init__.py => log/.gitkeep | 0 modules/ravestate/context.py | 8 ++- modules/ravestate/logger.py | 8 +++ modules/ravestate/testfixtures.py | 13 ++++ run_tests.sh | 2 +- test/modules/ravestate/test_activation.py | 5 ++ test/modules/ravestate/test_context.py | 7 ++- test/modules/ravestate/test_logging.py | 9 +++ test/modules/ravestate_conio/test_conio.py | 16 +++++ .../ravestate_nlp/test_preprocessing.py | 59 +++++++++++++++++++ .../ravestate_ros2/test_ros2_properties.py | 30 ++++++++++ test/modules/reggol/__init__.py | 0 test/modules/reggol/formatters/__init__.py | 0 test/modules/reggol/test_logger.py | 44 +++++++++++++- 16 files changed, 195 insertions(+), 8 deletions(-) rename test/modules/__init__.py => log/.gitkeep (100%) create mode 100644 modules/ravestate/logger.py create mode 100644 test/modules/ravestate/test_logging.py create mode 100644 test/modules/ravestate_conio/test_conio.py create mode 100644 test/modules/ravestate_nlp/test_preprocessing.py create mode 100644 test/modules/ravestate_ros2/test_ros2_properties.py delete mode 100644 test/modules/reggol/__init__.py delete mode 100644 test/modules/reggol/formatters/__init__.py diff --git a/.gitignore b/.gitignore index 912b73d..ceb0687 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ _build/* ros2/build ros2/install ros2/log +venv/* mkdocs.yml \ No newline at end of file diff --git a/config/generic.yml b/config/generic.yml index 6cfb2d5..fb1d6d8 100644 --- a/config/generic.yml +++ b/config/generic.yml @@ -23,6 +23,7 @@ config: - ravestate_wildtalk - ravestate_nlp - ravestate_hibye + ros2-node-name: "ros2-node-name" --- module: genqa diff --git a/test/modules/__init__.py b/log/.gitkeep similarity index 100% rename from test/modules/__init__.py rename to log/.gitkeep diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index a45720c..1144a86 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -1,5 +1,5 @@ # Ravestate context class - +import copy from threading import Thread, Lock, Event from typing import Optional, Any, Tuple, Set, Dict, Iterable, List from collections import defaultdict @@ -131,12 +131,14 @@ def wipe(self, signal: Signal): should be invalidated and forgotten. """ with self._lock: + spikes_change = {} for spike in self._spikes: if spike.name() == signal.name: spike.wipe() - self._spikes[spike] = False + spikes_change[spike] = False for child_spike in spike.offspring(): - self._spikes[child_spike] = False + spikes_change[child_spike] = False + self._spikes.update(spikes_change) # Final cleanup will be performed while update is running, # and cg.stale(spike) returns true. # TODO: Make sure, that it is not a problem if the spike is currently referenced diff --git a/modules/ravestate/logger.py b/modules/ravestate/logger.py new file mode 100644 index 0000000..d2b777a --- /dev/null +++ b/modules/ravestate/logger.py @@ -0,0 +1,8 @@ +from reggol.logger import CustomConsoleAndFileLogger + + +def get_logger(name: str = __file__): + logger = CustomConsoleAndFileLogger(name) + logger.set_file_formatter() + logger.set_console_formatter() + return logger diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index f16c1eb..9315dd2 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -1,4 +1,6 @@ import pytest + +from ravestate.spike import Spike from reggol import strip_prefix from testfixtures import LogCapture @@ -91,3 +93,14 @@ def context_wrapper_fixture(context_with_property_fixture, state_fixture): @pytest.fixture def activation_fixture(state_fixture: State, context_with_property_and_state_fixture: Context): return Activation(state_fixture, context_with_property_and_state_fixture) + + +@pytest.fixture +def activation_fixture_fallback(activation_fixture: Activation): + activation_fixture.state_to_activate.write_props = None + return activation_fixture + + +@pytest.fixture +def spike_fixture(): + return Spike(sig=DEFAULT_PROPERTY_CHANGED) diff --git a/run_tests.sh b/run_tests.sh index 30cef2a..7f8c12b 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,4 +1,4 @@ #!/bin/bash export PYTHONPATH=$PYTHONPATH:$(pwd)/modules -pytest --ignore ros2 --cov=modules test +pytest --ignore ros2 --cov=modules test -p no:warnings diff --git a/test/modules/ravestate/test_activation.py b/test/modules/ravestate/test_activation.py index 5f7a946..a8508a3 100644 --- a/test/modules/ravestate/test_activation.py +++ b/test/modules/ravestate/test_activation.py @@ -25,6 +25,11 @@ def test_multiple_activation(state_fixture, context_with_property_fixture): assert not sa2.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_FULLNAME})) +def test_resources_fallback(activation_fixture_fallback): + assert activation_fixture_fallback.resources() \ + == {activation_fixture_fallback.state_to_activate.consumable.fullname()} + + # TODO: Add tests for update # def test_run(activation_fixture): # result = activation_fixture.run() diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 022ba35..fbed5c8 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -1,9 +1,14 @@ +from ravestate.constraint import Signal +from ravestate.spike import Spike from ravestate.testfixtures import * -def test_emit(context_fixture): +def test_emit(context_fixture, spike_fixture): context_fixture.emit(s(DEFAULT_PROPERTY_CHANGED)) assert len(context_fixture._spikes) == 1 + list(context_fixture._spikes.keys())[0].adopt(spike_fixture) + context_fixture.emit(s(DEFAULT_PROPERTY_CHANGED), wipe=True) + assert len(context_fixture._spikes) == 3 def test_run(mocker, context_fixture): diff --git a/test/modules/ravestate/test_logging.py b/test/modules/ravestate/test_logging.py new file mode 100644 index 0000000..83926c6 --- /dev/null +++ b/test/modules/ravestate/test_logging.py @@ -0,0 +1,9 @@ +from ravestate.logger import get_logger + +LOGGER_NAME = 'test_logger' + + +def test_get_logger(): + logger = get_logger(LOGGER_NAME) + from reggol import CustomConsoleAndFileLogger + assert isinstance(logger, CustomConsoleAndFileLogger) diff --git a/test/modules/ravestate_conio/test_conio.py b/test/modules/ravestate_conio/test_conio.py new file mode 100644 index 0000000..b676f97 --- /dev/null +++ b/test/modules/ravestate_conio/test_conio.py @@ -0,0 +1,16 @@ +from ravestate.context import Context +from ravestate_conio import * +from ravestate.testfixtures import * + + +def test_console_input(context_wrapper_fixture: Context): + context_wrapper_fixture.ctx._shutdown_flag.set() + console_input(context_wrapper_fixture) + + +def test_console_output(capsys): + expected = 'test' + test_input = {"rawio:out": expected} + console_output(test_input) + captured = capsys.readouterr() + assert captured.out == f"{expected}\n" diff --git a/test/modules/ravestate_nlp/test_preprocessing.py b/test/modules/ravestate_nlp/test_preprocessing.py new file mode 100644 index 0000000..adc4df3 --- /dev/null +++ b/test/modules/ravestate_nlp/test_preprocessing.py @@ -0,0 +1,59 @@ +import pytest +from ravestate_nlp import nlp_preprocess +from testfixtures import log_capture + +FILE_NAME = 'ravestate_nlp' +PREFIX = f"[{FILE_NAME}] [\x1b[1;36m{FILE_NAME}\x1b[0m]" + + +@pytest.fixture +def basic_input(): + return {'rawio: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 + capture.check_present((f"{FILE_NAME}", '\x1b[1;32mINFO\x1b[0m', f"{PREFIX} [NLP:tokens]: {expected}")) + + +@log_capture() +def test_postags(capture, basic_input): + nlp_preprocess(basic_input) + expected = ('INTJ', 'VERB', 'ADJ', 'NOUN', 'VERB', 'PROPN') + assert basic_input["nlp:postags"] == expected + capture.check_present((f"{FILE_NAME}", '\x1b[1;32mINFO\x1b[0m', f"{PREFIX} [NLP:postags]: {expected}")) + + +@log_capture() +def test_lemmas(capture, basic_input): + nlp_preprocess(basic_input) + expected = ('hello', 'world', '-PRON-', 'name', 'be', 'roboy') + assert basic_input["nlp:lemmas"] == expected + capture.check_present((f"{FILE_NAME}", '\x1b[1;32mINFO\x1b[0m', f"{PREFIX} [NLP:lemmas]: {expected}")) + + +@log_capture() +def test_tags(capture, basic_input): + nlp_preprocess(basic_input) + expected = ('UH', 'VB', 'PRP$', 'NN', 'VBZ', 'NNP') + assert basic_input["nlp:tags"] == expected + capture.check_present((f"{FILE_NAME}", '\x1b[1;32mINFO\x1b[0m', f"{PREFIX} [NLP:tags]: {expected}")) + + +@log_capture() +def test_ner(capture, basic_input): + nlp_preprocess(basic_input) + expected = (('Roboy', 'ORG'),) + assert basic_input["nlp:ner"] == expected + capture.check_present((f"{FILE_NAME}", '\x1b[1;32mINFO\x1b[0m', f"{PREFIX} [NLP:ner]: {expected}")) + + +@log_capture() +def test_roboy(capture, basic_input): + nlp_preprocess(basic_input) + expected = True + assert basic_input["nlp:roboy"] == expected + capture.check_present((f"{FILE_NAME}", '\x1b[1;32mINFO\x1b[0m', f"{PREFIX} [NLP:roboy]: {expected}")) diff --git a/test/modules/ravestate_ros2/test_ros2_properties.py b/test/modules/ravestate_ros2/test_ros2_properties.py new file mode 100644 index 0000000..e05b639 --- /dev/null +++ b/test/modules/ravestate_ros2/test_ros2_properties.py @@ -0,0 +1,30 @@ +from testfixtures import LogCapture +from ravestate.testfixtures import * +from pytest_mock import mocker + +from ravestate.context import Context +from ravestate.state import Delete + +FILE_NAME = 'ravestate_ros2.ros2_properties' +PREFIX = f"[{FILE_NAME}] [\x1b[1;36m{FILE_NAME}\x1b[0m]" + + +def test_sync_ros_properties_no_ros2(mocker, context_wrapper_fixture: ContextWrapper): + with LogCapture() as capture: + mocker.patch.dict('sys.modules', {'rclpy': None}) + from ravestate_ros2 import sync_ros_properties + result = sync_ros_properties(context_wrapper_fixture) + expected = "ROS2 is not available, therefore all ROS2-Properties " \ + "will be just normal properties without connection to ROS2!" + capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) + assert isinstance(result, Delete) + + +def test_sync_ros_properties(mocker, context_wrapper_fixture: ContextWrapper): + with LogCapture() as capture: + rclpy_mock = mocker.Mock() + mocker.patch.dict('sys.modules', {'rclpy': rclpy_mock}) + from ravestate_ros2 import sync_ros_properties + result = sync_ros_properties(context_wrapper_fixture) + expected = "ros2-node-name is not set. Shutting down ravestate_ros2" + capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {PREFIX} {expected}")) diff --git a/test/modules/reggol/__init__.py b/test/modules/reggol/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/modules/reggol/formatters/__init__.py b/test/modules/reggol/formatters/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/modules/reggol/test_logger.py b/test/modules/reggol/test_logger.py index 6361ef8..dc9fe2c 100644 --- a/test/modules/reggol/test_logger.py +++ b/test/modules/reggol/test_logger.py @@ -1,12 +1,50 @@ import os -import time import pytest from testfixtures import log_capture + +LOGGER_NAME = 'test' log_file_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))), 'log') +log_file_name = 'test_log.log' + + +@pytest.fixture +def test_logger(): + from reggol import CustomConsoleAndFileLogger + return CustomConsoleAndFileLogger(LOGGER_NAME) + + +@pytest.mark.serial +def test_file(test_logger): + test_logger.set_file_formatter(log_file_dir, log_file_name) + test_logger.info("Hello") + file = open(f"{log_file_dir}/{log_file_name}", "r") + log_content = file.read() + assert log_content.find("Hello") > 0 +@log_capture() @pytest.mark.serial -def test_file(): - pass \ No newline at end of file +def test_basic(capture, test_logger): + test_logger.info('a message') + test_logger.error('a message') + test_logger.debug('a message') + test_logger.warning('a message') + test_logger.critical('a message') + + # capture.check( + # (LOGGER_NAME, '\x1b[1;32mINFO\x1b[0m', 'a message'), + # (LOGGER_NAME, '\x1b[1;31mERROR\x1b[0m', 'a message'), + # (LOGGER_NAME, '\x1b[1;34mDEBUG\x1b[0m', 'a message'), + # (LOGGER_NAME, '\x1b[1;33mWARNING\x1b[0m', 'a message'), + # (LOGGER_NAME, '\x1b[1;35mCRITICAL\x1b[0m', 'a message'), + # ) + + +@log_capture() +@pytest.mark.serial +def test_module(capture, test_logger): + test_logger.info('module -> a message') + + pass From b7b1dbe7c86da367d962129e7ed4c9ecb1101731 Mon Sep 17 00:00:00 2001 From: Emilka Date: Sun, 13 Jan 2019 21:41:14 +0100 Subject: [PATCH 14/46] 74% coverage --- modules/ravestate/logger.py | 8 ----- modules/ravestate_facerec/__init__.py | 4 +-- test/modules/ravestate/test_logging.py | 9 ----- test/modules/ravestate_genqa/test_genqa.py | 36 +++++++++++++++++++ .../ravestate_hello_world/test_hello_world.py | 27 ++++++++++++++ .../modules/ravestate_verbaliser/test_init.py | 23 ++++++++++++ .../ravestate_verbaliser/test_verbaliser.py | 1 + test/ravestate_hibye/test_hibye.py | 19 ++++++++++ .../ravestate_telegramio/test_telegram_bot.py | 27 ++++++++++++++ test/ravestate_wildtalk/test_wildtalk.py | 14 ++++++++ 10 files changed, 149 insertions(+), 19 deletions(-) delete mode 100644 modules/ravestate/logger.py delete mode 100644 test/modules/ravestate/test_logging.py create mode 100644 test/modules/ravestate_genqa/test_genqa.py create mode 100644 test/modules/ravestate_hello_world/test_hello_world.py create mode 100644 test/modules/ravestate_verbaliser/test_init.py create mode 100644 test/ravestate_hibye/test_hibye.py create mode 100644 test/ravestate_telegramio/test_telegram_bot.py create mode 100644 test/ravestate_wildtalk/test_wildtalk.py diff --git a/modules/ravestate/logger.py b/modules/ravestate/logger.py deleted file mode 100644 index d2b777a..0000000 --- a/modules/ravestate/logger.py +++ /dev/null @@ -1,8 +0,0 @@ -from reggol.logger import CustomConsoleAndFileLogger - - -def get_logger(name: str = __file__): - logger = CustomConsoleAndFileLogger(name) - logger.set_file_formatter() - logger.set_console_formatter() - return logger diff --git a/modules/ravestate_facerec/__init__.py b/modules/ravestate_facerec/__init__.py index 44e0fc1..343123b 100644 --- a/modules/ravestate_facerec/__init__.py +++ b/modules/ravestate_facerec/__init__.py @@ -1,4 +1,3 @@ - import rclpy from ravestate import registry from ravestate.constraint import s @@ -11,6 +10,7 @@ rclpy.init() node = rclpy.create_node("vision_node") + @state(cond=s(":startup")) def facerec_run(ctx): @@ -31,4 +31,4 @@ def facerec_shutdown(): registry.register( name="facerec", props=PropertyBase(name="face", default_value=""), - states=(facerec_run, facerec_shutdown)) \ No newline at end of file + states=(facerec_run, facerec_shutdown)) diff --git a/test/modules/ravestate/test_logging.py b/test/modules/ravestate/test_logging.py deleted file mode 100644 index 83926c6..0000000 --- a/test/modules/ravestate/test_logging.py +++ /dev/null @@ -1,9 +0,0 @@ -from ravestate.logger import get_logger - -LOGGER_NAME = 'test_logger' - - -def test_get_logger(): - logger = get_logger(LOGGER_NAME) - from reggol import CustomConsoleAndFileLogger - assert isinstance(logger, CustomConsoleAndFileLogger) diff --git a/test/modules/ravestate_genqa/test_genqa.py b/test/modules/ravestate_genqa/test_genqa.py new file mode 100644 index 0000000..f84a448 --- /dev/null +++ b/test/modules/ravestate_genqa/test_genqa.py @@ -0,0 +1,36 @@ +from ravestate.context import Context +from ravestate.state import Delete +from ravestate.testfixtures import * +from pytest_mock import mocker +from testfixtures import LogCapture + + +FILE_NAME = 'ravestate_genqa' +PREFIX = f"[{FILE_NAME}] [\x1b[1;36m{FILE_NAME}\x1b[0m]" + + +def test_hello_world_genqa(mocker, context_wrapper_fixture: Context): + with LogCapture() as capture: + registry_mock = mocker.patch('ravestate.registry.register') + import ravestate_genqa + registry_mock.assert_called_with(name="genqa", + states=(ravestate_genqa.hello_world_genqa, ravestate_genqa.drqa_module), + config={ravestate_genqa.DRQA_SERVER_ADDRESS: "http://localhost:5000"}) + result = ravestate_genqa.hello_world_genqa(context_wrapper_fixture) + expected = 'Server address is not set. Shutting down GenQA.' + capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) + assert isinstance(result, Delete) + # TODO: Write test for server address present + + +def test_drqa_module(mocker, context_wrapper_fixture: Context): + with LogCapture() as capture: + verbalizer_mock = mocker.patch('ravestate_verbaliser.verbaliser.get_random_phrase') + import ravestate_genqa + result = ravestate_genqa.drqa_module(context_wrapper_fixture) + verbalizer_mock.assert_called_once_with("question-answering-starting-phrases") + expected = 'Server address is not set. Shutting down GenQA.' + capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) + assert isinstance(result, Delete) + # TODO: Write test for server address present + diff --git a/test/modules/ravestate_hello_world/test_hello_world.py b/test/modules/ravestate_hello_world/test_hello_world.py new file mode 100644 index 0000000..3b76487 --- /dev/null +++ b/test/modules/ravestate_hello_world/test_hello_world.py @@ -0,0 +1,27 @@ +from pytest_mock import mocker + + +def test_hello_world(mocker): + registry_mock = mocker.patch('ravestate.registry.register') + import ravestate_hello_world + registry_mock.assert_called_with(name="hi", + states=(ravestate_hello_world.hello_world, + ravestate_hello_world.generic_answer, + ravestate_hello_world.face_recognized)) + test_dict = {} + ravestate_hello_world.hello_world(test_dict) + assert test_dict["verbaliser:intent"] == "greeting" + + +def test_generic_answer(): + import ravestate_hello_world + test_dict = {"rawio:in": 'test'} + ravestate_hello_world.generic_answer(test_dict) + assert test_dict["rawio:out"] == f"Your input contains {len('test')} characters!" + + +def test_face_recognized(): + import ravestate_hello_world + test_dict = {"facerec:face": 'test'} + ravestate_hello_world.face_recognized(test_dict) + assert test_dict["rawio:out"] == f"I see you, {'test'}!" diff --git a/test/modules/ravestate_verbaliser/test_init.py b/test/modules/ravestate_verbaliser/test_init.py new file mode 100644 index 0000000..bafbd90 --- /dev/null +++ b/test/modules/ravestate_verbaliser/test_init.py @@ -0,0 +1,23 @@ +from os.path import join, dirname, realpath + +from pytest_mock import mocker + + +def test_react_to_intent(mocker): + from ravestate_verbaliser import verbaliser + verbaliser.add_file(join(dirname(realpath(__file__)), + "verbaliser_testfiles", "phrases_test.yml")) + import ravestate_verbaliser + test_dict = {'verbaliser:intent': 'test1'} + ravestate_verbaliser.react_to_intent(test_dict) + assert test_dict["rawio:out"] in ravestate_verbaliser.verbaliser.get_phrase_list('test1') + + +def test_react_to_intent_no_phrase(mocker): + from ravestate_verbaliser import verbaliser + verbaliser.add_file(join(dirname(realpath(__file__)), + "verbaliser_testfiles", "phrases_test.yml")) + import ravestate_verbaliser + test_dict = {'verbaliser:intent': 'test'} + ravestate_verbaliser.react_to_intent(test_dict) + assert "rawio:out" not in test_dict diff --git a/test/modules/ravestate_verbaliser/test_verbaliser.py b/test/modules/ravestate_verbaliser/test_verbaliser.py index 03cc392..11f752a 100644 --- a/test/modules/ravestate_verbaliser/test_verbaliser.py +++ b/test/modules/ravestate_verbaliser/test_verbaliser.py @@ -1,6 +1,7 @@ from os.path import join, dirname, realpath from typing import List +from ravestate.property import PropertyBase from ravestate_verbaliser import verbaliser diff --git a/test/ravestate_hibye/test_hibye.py b/test/ravestate_hibye/test_hibye.py new file mode 100644 index 0000000..a152cb2 --- /dev/null +++ b/test/ravestate_hibye/test_hibye.py @@ -0,0 +1,19 @@ +from pytest_mock import mocker + + +def test_react_to_pushed_interloc(mocker): + registry_mock = mocker.patch('ravestate.registry.register') + import ravestate_hibye + registry_mock.assert_called_with(name="hi_goodbye", + states=(ravestate_hibye.react_to_pushed_interloc, + ravestate_hibye.react_to_popped_interloc,)) + test_dict = {} + ravestate_hibye.react_to_pushed_interloc(test_dict) + assert test_dict["verbaliser:intent"] == "greeting" + + +def test_react_to_popped_interloc(mocker): + test_dict = {} + import ravestate_hibye + ravestate_hibye.react_to_popped_interloc(test_dict) + assert test_dict["verbaliser:intent"] == "farewells" diff --git a/test/ravestate_telegramio/test_telegram_bot.py b/test/ravestate_telegramio/test_telegram_bot.py new file mode 100644 index 0000000..03487eb --- /dev/null +++ b/test/ravestate_telegramio/test_telegram_bot.py @@ -0,0 +1,27 @@ +from ravestate.context import Context +from ravestate.state import Delete +from ravestate.testfixtures import * +from pytest_mock import mocker +from testfixtures import LogCapture + +from ravestate_telegramio.telegram_bot import * + +FILE_NAME = 'ravestate_telegramio.telegram_bot' +PREFIX = f"[{FILE_NAME}] [\x1b[1;36m{FILE_NAME}\x1b[0m]" + + +def test_telegram_run(context_wrapper_fixture: Context): + with LogCapture() as capture: + telegram_run(context_wrapper_fixture) + expected = 'telegram-token is not set. Shutting down telegramio' + capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) + # TODO: Write test for token present + + +def test_telegram_output(context_wrapper_fixture: ContextWrapper): + with LogCapture() as capture: + telegram_output(context_wrapper_fixture) + expected = 'telegram-token is not set. Shutting down telegramio' + capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) + # TODO: Write test for token present + diff --git a/test/ravestate_wildtalk/test_wildtalk.py b/test/ravestate_wildtalk/test_wildtalk.py new file mode 100644 index 0000000..f2d25cf --- /dev/null +++ b/test/ravestate_wildtalk/test_wildtalk.py @@ -0,0 +1,14 @@ +from pytest_mock import mocker + + +def test_wildtalk_state(mocker): + import_mock = mocker.Mock() + mocker.patch.dict('sys.modules', {'roboy_parlai': import_mock}) + wildtalk_mock = mocker.patch('roboy_parlai.wildtalk', return_value='test') + registry_mock = mocker.patch('ravestate.registry.register') + import ravestate_wildtalk + registry_mock.assert_called_with(name="wildtalk", + states=(ravestate_wildtalk.wildtalk_state,)) + test_dict = {"rawio:in": 'test'} + ravestate_wildtalk.wildtalk_state(test_dict) + assert test_dict["rawio:out"] == 'test' From 27fa921ca09a7e859134ed3a1dfe44a13aaf7e1b Mon Sep 17 00:00:00 2001 From: Andreas Dolp Date: Sun, 27 Jan 2019 16:55:11 +0100 Subject: [PATCH 15/46] add unit test for configurable age --- modules/ravestate/context.py | 1 - .../ravestate_phrases_basic_en/en/fillers.yml | 2 +- test/modules/ravestate/test_context.py | 29 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 16b4fec..de66cc0 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -223,7 +223,6 @@ def add_state(self, *, st: State) -> None: return # replace configurable ages with their config values - # TODO Unit test 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) diff --git a/modules/ravestate_phrases_basic_en/en/fillers.yml b/modules/ravestate_phrases_basic_en/en/fillers.yml index ee8bf75..f108a75 100644 --- a/modules/ravestate_phrases_basic_en/en/fillers.yml +++ b/modules/ravestate_phrases_basic_en/en/fillers.yml @@ -6,4 +6,4 @@ opts: - "mm" - "ah" - "oh" -- "uh" \ No newline at end of file +- "uh" diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 022ba35..c407ce3 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -1,3 +1,5 @@ +from ravestate.constraint import ConfigurableAge +from ravestate.module import Module from ravestate.testfixtures import * @@ -121,3 +123,30 @@ def test_add_state( assert b_acts[0].specificity() == a_sig_spec + propchange_sig_spec assert 1.53 < d_acts[0].specificity() < 1.54 + +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")) + + @state(cond=my_cond) + def conf_st(ctx): + pass + conf_st.module_name = DEFAULT_MODULE_NAME + 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 + + +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")) + + @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 == 0. + assert my_cond.max_age == 5. From ef9d435dcc5b3a0969758befb08ed8c8a6ce5d0c Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Sun, 27 Jan 2019 20:03:57 +0100 Subject: [PATCH 16/46] implemented akinator is it; refactored nlp yes no --- modules/ravestate_akinator/__init__.py | 52 ++++++++++++++++---------- modules/ravestate_nlp/__init__.py | 14 +++---- modules/ravestate_nlp/yes_no.py | 23 ++++++++++++ 3 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 modules/ravestate_nlp/yes_no.py diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 1544789..0073e76 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -28,7 +28,9 @@ def akinator_play_ask(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:initiate-play", max_age=1000), read="nlp:yesno", write=("rawio:out", "akinator:question", "akinator:in_progress", "akinator:session", "akinator:signature")) +@state(cond=s("nlp:yes-no") & (s("akinator:initiate-play", max_age=-1) | s("akinator:initiate-play-again", max_age=-1, detached=True)), + read="nlp:yesno", + write=("rawio:out", "akinator:question", "akinator:in_progress", "akinator:session", "akinator:signature")) def akinator_start(ctx): if ctx["nlp:yesno"] == "yes": logger.info("Start Akinator session.") @@ -38,9 +40,10 @@ def akinator_start(ctx): ctx["akinator:question"] = akinator_data ctx["akinator:question"] = akinator_data['parameters']['step_information']['question'] ctx["akinator:in_progress"] = True - ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step_information']['step']) + 1) + ":\n" \ - + akinator_data['parameters']['step_information']['question'] \ - + '\n"yes", "no", "idk", "probably", "probably not"' + ctx["rawio:out"] = "You can answer the questions with:" \ + + '\n"yes", "no", "i do not know", "probably", "probably not"' \ + + "\nQuestion " + str(int(akinator_data['parameters']['step_information']['step']) + 1) \ + + ":\n" + akinator_data['parameters']['step_information']['question'] ctx["akinator:session"] = akinator_data['parameters']['identification']['session'] ctx["akinator:signature"] = akinator_data['parameters']['identification']['signature'] @@ -53,11 +56,12 @@ def akinator_question_asked(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), read=("nlp:yesno", "akinator:session", "akinator:signature"), write=("rawio:out", "akinator:is_it", "akinator:question")) +@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), + read=("nlp:yesno", "akinator:session", "akinator:signature"), + write=("rawio:out", "akinator:is_it", "akinator:question")) def akinator_question_answered(ctx): global first_question global akinator_data - if first_question: first_question = False step = akinator_data['parameters']['step_information']['step'] @@ -76,14 +80,16 @@ def akinator_question_answered(ctx): if int(float(akinator_data['parameters']['progression'])) <= 90: ctx["akinator:question"] = akinator_data['parameters']['question'] ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step']) + 1) + ":\n" \ - + akinator_data['parameters']['question'] \ - + '\n"yes", "no", "idk", "probably", "probably not"' + + akinator_data['parameters']['question'] else: ctx["akinator:is_it"] = True -@state(cond=s("akinator:is_it:changed", detached=True), read="akinator:question", signal_name="is-it") +@state(cond=s("akinator:is_it:changed", detached=True), + read=("akinator:question", "akinator:session", "akinator:signature"), signal_name="is-it", write="rawio:out") def akinator_is_it(ctx): + global akinator_data + global guess_data params = { "session": ctx["akinator:session"], "signature": ctx["akinator:signature"], @@ -95,19 +101,23 @@ def akinator_is_it(ctx): name = guess_data['parameters']['elements'][0]['element']['name'] desc = guess_data['parameters']['elements'][0]['element']['description'] - ctx["rawio:out"] = "Is this your character? [yes/no]\n" + name + "\n" + desc + "\n" + ctx["rawio:out"] = "Is this your character? \n" + name + "\n" + desc + "\n" return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), read="nlp:triples", write="rawio:out") +@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), + read=("nlp:yesno", "akinator:session", "akinator:signature"), + write="rawio:out", signal_name="initiate-play-again") def akinator_is_it_answered(ctx): if ctx["nlp:yesno"] == "yes": - output = "I guessed right! Thanks for playing with me." + ctx["rawio:out"] = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" + return Emit() elif ctx["nlp:yesno"] == "no": - output = "I guessed right! Thanks for playing with me." + pass + #ctx["rawio:out"] = "I guessed wrong :(" else: - output = "Shit" - ctx["rawio:out"] = output + pass + #ctx["rawio:out"] = "Shit" def answer_to_int_str(answer: str): @@ -115,8 +125,12 @@ def answer_to_int_str(answer: str): return "0" elif answer == "no": return "1" - elif answer == "maybe": + elif answer == "idk": return "2" + elif answer == "p": + return "3" + elif answer == "pn": + return "4" else: return "-1" @@ -124,12 +138,12 @@ def answer_to_int_str(answer: str): registry.register( name="akinator", states=( + akinator_is_it, + akinator_is_it_answered, akinator_play_ask, akinator_question_asked, akinator_start, - akinator_question_answered, - akinator_is_it, - akinator_is_it_answered + akinator_question_answered ), props=( PropertyBase( diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 0a90f7b..d94172a 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -7,6 +7,7 @@ from ravestate_nlp.extract_triples import extract_triples from ravestate.state import Emit from ravestate.constraint import s +from ravestate_nlp.yes_no import yes_no import spacy @@ -31,6 +32,7 @@ def roboy_getter(doc) -> bool: Doc.set_extension('about_roboy', getter=roboy_getter) Doc.set_extension('empty_token', getter=lambda doc: empty_token) Doc.set_extension('triples', getter=extract_triples) + Doc.set_extension('yesno', getter=yes_no) @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:triples", "nlp:yesno")) @@ -65,15 +67,9 @@ def nlp_preprocess(ctx): ctx["nlp:roboy"] = nlp_roboy logger.info(f"[NLP:roboy]: {nlp_roboy}") - for i in nlp_tokens: - if i == "no": - ctx["nlp:yesno"] = "no" - elif i == "yes": - ctx["nlp:yesno"] = "yes" - elif i == "maybe": - ctx["nlp:yesno"] = "maybe" - else: - pass + nlp_yesno = nlp_doc._.yesno + ctx["nlp:yesno"] = nlp_yesno + logger.info(f"[NLP:yesno]: {nlp_yesno}") @state(signal_name="contains-roboy", read="nlp:roboy") diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py new file mode 100644 index 0000000..fa0b545 --- /dev/null +++ b/modules/ravestate_nlp/yes_no.py @@ -0,0 +1,23 @@ +NEGATION_SET = {"neg"} + + +def yes_no(doc): + """ + checks input for "yes", "no", "i don't know", "probably" and "probably not" + """ + nlp_tokens = tuple(str(token) for token in doc) + for token in nlp_tokens: + if token == "yes" or token == "y": + return "yes" + elif token == "no" or token == "n": + return "no" + for token in doc: + if token.dep_ in NEGATION_SET: + if "know" in nlp_tokens: + return "idk" + elif "probably" in nlp_tokens: + return "pn" + elif "probably" in nlp_tokens: + return "p" + return None + From 34706c59685a6faa3cc19a998e43de9353736e8e Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Sun, 27 Jan 2019 22:03:21 +0100 Subject: [PATCH 17/46] Started introduction of symbols for property names. --- modules/ravestate/constraint.py | 2 +- modules/ravestate/context.py | 20 ++++++++++- modules/ravestate/property.py | 43 +++++++++++++++++++++-- modules/ravestate/state.py | 12 +++---- modules/ravestate_hello_world/__init__.py | 27 -------------- modules/ravestate_interloc/__init__.py | 9 ++--- modules/ravestate_persqa/__init__.py | 0 modules/ravestate_rawio/__init__.py | 12 +++---- 8 files changed, 77 insertions(+), 48 deletions(-) delete mode 100644 modules/ravestate_hello_world/__init__.py create mode 100644 modules/ravestate_persqa/__init__.py diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index a4af393..fdfa0bc 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -6,7 +6,7 @@ logger = get_logger(__name__) -def s(signal_name: str, *, min_age=0, max_age=5., detached=False): +def s(signal_name: str, *, min_age=0, max_age=5., detached=False) -> 'Signal': """ Alias to call Signal-constructor diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index a45720c..0c7618c 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -23,9 +23,27 @@ logger = get_logger(__name__) +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) + + +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 s(":shutdown", **kwargs) + + class Context(IContext): - _default_signal_names: Tuple[str] = (":startup", ":shutdown", ":idle") + _default_signal_names: Tuple[str] = (startup(), shutdown()) _core_module_name = "core" _import_modules_config = "import" diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 3206cdd..e38940e 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -8,6 +8,42 @@ logger = get_logger(__name__) +def changed(property_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. + """ + return s(f"{property_name}:changed", **kwargs) + + +def pushed(property_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. + """ + return s(f"{property_name}:pushed", **kwargs) + + +def popped(property_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. + """ + return s(f"{property_name}:popped", **kwargs) + + class PropertyBase: """ Base class for context properties. Controls read/write/push/pop/delete permissions, @@ -130,19 +166,19 @@ def changed_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #write() returns True. """ - return s(f"{self.fullname()}:changed") + return changed(self.fullname()) def pushed_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #push() returns True. """ - return s(f"{self.fullname()}:pushed") + return pushed(self.fullname()) def popped_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #pop() returns True. """ - return s(f"{self.fullname()}:popped") + return popped(self.fullname()) def signals(self) -> Generator[Signal, None, None]: """ @@ -155,3 +191,4 @@ def signals(self) -> Generator[Signal, None, None]: yield self.pushed_signal() if self.allow_pop: yield self.popped_signal() + diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 70b6da7..bce35ae 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -9,14 +9,14 @@ logger = get_logger(__name__) -class StateActivationResult: +class _StateActivationResult: """ Base class for return values of state activation functions. """ pass -class Delete(StateActivationResult): +class Delete(_StateActivationResult): """ Return an instance of this class, if the invoked state should be deleted. @@ -29,7 +29,7 @@ def __init__(self, resign: bool=False): self.resign = resign -class Wipe(StateActivationResult): +class Wipe(_StateActivationResult): """ Return an instance of this class, if context.wipe(signal) should be called, to ensure that there are no more active spikes for the state's signal. @@ -37,7 +37,7 @@ class Wipe(StateActivationResult): pass -class Emit(StateActivationResult): +class Emit(_StateActivationResult): """ Return an instance of this class, if the invoked state's signal should be emitted. @@ -48,7 +48,7 @@ def __init__(self, wipe: bool=False): self.wipe = wipe -class Resign(StateActivationResult): +class Resign(_StateActivationResult): """ Return an instance of this class, if the state invocation should be regarded unsuccessful. This means, that the state's signal will not be emitted, and the spikes @@ -114,7 +114,7 @@ def __init__(self, *, self.module_name = "" self._signal = None - def __call__(self, context, *args, **kwargs) -> Optional[StateActivationResult]: + def __call__(self, context, *args, **kwargs) -> Optional[_StateActivationResult]: args = (context,) + args return self.action(*args, **kwargs) diff --git a/modules/ravestate_hello_world/__init__.py b/modules/ravestate_hello_world/__init__.py deleted file mode 100644 index 7b9e7b1..0000000 --- a/modules/ravestate_hello_world/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -from ravestate.state import state -from ravestate import registry - -import ravestate_rawio -import ravestate_verbaliser -import ravestate_phrases_basic_en - -from ravestate.constraint import s - - -@state(cond=s(":startup"), write="verbaliser:intent") -def hello_world(ctx): - ctx["verbaliser:intent"] = "greeting" - - -@state(read="rawio:in", write="rawio:out") -def generic_answer(ctx): - ctx["rawio:out"] = "Your input contains {} characters!".format(len(ctx["rawio:in"])) - - -@state(read="facerec:face", write="rawio:out") -def face_recognized(ctx): - if ctx["facerec:face"]: - ctx["rawio:out"] = "I see you, {}!".format(ctx["facerec:face"]) - - -registry.register(name="hi", states=(hello_world, generic_answer, face_recognized)) diff --git a/modules/ravestate_interloc/__init__.py b/modules/ravestate_interloc/__init__.py index 7940a75..4df8b49 100644 --- a/modules/ravestate_interloc/__init__.py +++ b/modules/ravestate_interloc/__init__.py @@ -1,8 +1,9 @@ from ravestate.property import PropertyBase from ravestate import registry +_p_all = PropertyBase(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True) + # TODO: Make interloc:all a special property type, that only accepts ScientioNodeProperty as children -registry.register( - name="interloc", - props=PropertyBase(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True), -) +registry.register(name="interloc", props=_p_all) + +p_all = _p_all.fullname() diff --git a/modules/ravestate_persqa/__init__.py b/modules/ravestate_persqa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index 7054b3d..e0bc575 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -2,10 +2,10 @@ from ravestate import registry from ravestate.property import PropertyBase +_p_in = PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False) +_p_out = PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False) -registry.register( - name="rawio", - props=( - PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False), - PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False)) -) +registry.register(name="rawio", props=(_p_in, _p_out)) + +p_in = _p_in.fullname() +p_out = _p_out.fullname() From dc200f7febb322544f84bc4451189ff908c7da17 Mon Sep 17 00:00:00 2001 From: Emilka Date: Mon, 28 Jan 2019 00:09:57 +0100 Subject: [PATCH 18/46] Added constraint / roboyqa unittests --- modules/ravestate/constraint.py | 8 +- modules/ravestate/testfixtures.py | 7 + test/modules/ravestate/test_constraint.py | 159 +++++++++++++++++- test/modules/ravestate_genqa/test_genqa.py | 4 +- .../modules/ravestate_roboyqa/test_roboyqa.py | 19 +++ 5 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 test/modules/ravestate_roboyqa/test_roboyqa.py diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index a4af393..fc4baf4 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -126,10 +126,10 @@ def dereference(self, spike: Optional[Spike]=None) -> Generator[Tuple['Signal', 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): - with self.spike.causal_group() as cg: - cg.rejected(self.spike, act, reason=1) - self.spike = None - yield self + with self.spike.causal_group() as cg: + cg.rejected(self.spike, act, reason=1) + self.spike = None + yield self def __str__(self): return self.name diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index 9315dd2..4eea9a3 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -104,3 +104,10 @@ def activation_fixture_fallback(activation_fixture: Activation): @pytest.fixture def spike_fixture(): return Spike(sig=DEFAULT_PROPERTY_CHANGED) + + +@pytest.fixture +def triple_fixture(mocker): + token_mock = mocker.Mock() + from ravestate_nlp import Triple + return Triple(token_mock, token_mock, token_mock, token_mock) diff --git a/test/modules/ravestate/test_constraint.py b/test/modules/ravestate/test_constraint.py index 0cd7f23..0a3ac4c 100644 --- a/test/modules/ravestate/test_constraint.py +++ b/test/modules/ravestate/test_constraint.py @@ -1,10 +1,38 @@ +from ravestate.iactivation import IActivation from ravestate.testfixtures import * -from ravestate.constraint import s +from ravestate.constraint import s, Constraint, Disjunct, Conjunct from ravestate.spike import Spike +@pytest.fixture +def constraint_fixture(): + return Constraint() + + +def test_parent(constraint_fixture, spike_fixture: Spike, activation_fixture: IActivation): + with LogCapture(attributes=strip_prefix) as log_capture: + return_value = list(constraint_fixture.signals()) + assert return_value == [None] + return_value = list(constraint_fixture.conjunctions()) + assert return_value == [None] + constraint_fixture.acquire(spike_fixture, activation_fixture) + return_value = constraint_fixture.evaluate() + assert return_value is False + return_value = list(constraint_fixture.dereference()) + assert return_value == [(None, None)] + return_value = list(constraint_fixture.update(activation_fixture)) + assert return_value == [None] + log_capture.check("Don't call this method on the super class Constraint", + "Don't call this method on the super class Constraint", + "Don't call this method on the super class Constraint", + "Don't call this method on the super class Constraint", + "Don't call this method on the super class Constraint", + "Don't call this method on the super class Constraint") + + def test_signal(activation_fixture): sig = s("mysig") + assert not sig.evaluate() assert set(sig.signals()) == {s("mysig")} sig.acquire(Spike(sig="notmysig"), activation_fixture) @@ -19,6 +47,54 @@ def test_signal(activation_fixture): sig_and_dis.acquire(Spike(sig="junct"), activation_fixture) assert sig_and_dis.evaluate() + expected = [(sig, sig.spike)] + return_value = list(sig.dereference()) + assert expected == return_value + + sig.spike = Spike(sig='mysig') + sig.spike._age = 200 + return_value = list(sig.update(activation_fixture)) + assert return_value == [sig] + + assert str(sig) == "mysig" + + +def test_signal_or(mocker): + sig = s("mysig") + with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): + conjunct = s("sig1") & s("sig2") + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + _ = sig | conjunct + Conjunct.__init__.assert_called_once_with(sig) + Disjunct.__init__.assert_called_once() + + 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") + _ = 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") + + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + _ = sig & sig + Conjunct.__init__.assert_called_once_with(sig, sig) + + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + with mocker.patch('ravestate.constraint.Conjunct.__iter__', return_value=iter([1])): + _ = sig & conjunct + Conjunct.__init__.assert_called_once_with(sig, 1) + + with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + with mocker.patch.object(disjunct, '_conjunctions', return_value={}): + _ = sig & disjunct + Disjunct.__init__.assert_called_with() + def test_conjunct(activation_fixture): conjunct = s("sig1") & s("sig2") & s("sig3") @@ -34,6 +110,41 @@ def test_conjunct(activation_fixture): assert conjunct.evaluate() +def test_conjunct_or(mocker): + conjunct = s("sig1") & s("sig2") & s("sig3") + with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): + conjunct2 = s("sig1") & s("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") + _ = 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") + + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + _ = conjunct & sig + Conjunct.__init__.assert_called_once_with(sig, *conjunct) + + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + with mocker.patch('ravestate.constraint.Conjunct.__iter__', return_value=iter([1])): + _ = conjunct & conjunct + Conjunct.__init__.assert_called_once_with(1) + + with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + with mocker.patch.object(disjunct, '_conjunctions', return_value={}): + _ = conjunct & disjunct + Disjunct.__init__.assert_called_with() + + def test_disjunct(activation_fixture): disjunct = (s("sig1") & s("sig2")) | s("sig3") assert not disjunct.evaluate() @@ -44,6 +155,50 @@ def test_disjunct(activation_fixture): assert disjunct.evaluate() +def test_disjunct_or(mocker): + disjunct = (s("sig1") & s("sig2")) | s("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") + _ = 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") + with mocker.patch('ravestate.constraint.Conjunct.__init__', return_value=None): + _ = disjunct | signal + Conjunct.__init__.assert_called_once_with(signal) + Disjunct.__init__.assert_called_once() + + 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") + _ = 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") + + with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): + with mocker.patch.object(disjunct, '_conjunctions', return_value={}): + _ = disjunct & sig + Disjunct.__init__.assert_called_once_with() + + with mocker.patch('ravestate.constraint.Disjunct.__init__', return_value=None): + with mocker.patch.object(disjunct, '_conjunctions', return_value={}): + _ = disjunct & conjunct + Disjunct.__init__.assert_called_once_with() + + with pytest.raises(ValueError): + with LogCapture(attributes=strip_prefix) as log_capture: + _ = disjunct & disjunct + log_capture.check("Can't conjunct two disjunctions.") + + def test_legal(): with pytest.raises(ValueError): - v = (s("i") | s("am")) & (s("also") | s("illegal")) + _ = (s("i") | s("am")) & (s("also") | s("illegal")) diff --git a/test/modules/ravestate_genqa/test_genqa.py b/test/modules/ravestate_genqa/test_genqa.py index f84a448..59cc546 100644 --- a/test/modules/ravestate_genqa/test_genqa.py +++ b/test/modules/ravestate_genqa/test_genqa.py @@ -15,7 +15,8 @@ def test_hello_world_genqa(mocker, context_wrapper_fixture: Context): import ravestate_genqa registry_mock.assert_called_with(name="genqa", states=(ravestate_genqa.hello_world_genqa, ravestate_genqa.drqa_module), - config={ravestate_genqa.DRQA_SERVER_ADDRESS: "http://localhost:5000"}) + config={ravestate_genqa.DRQA_SERVER_ADDRESS: "http://localhost:5000", + 'roboy_answer_sanity': 5000}) result = ravestate_genqa.hello_world_genqa(context_wrapper_fixture) expected = 'Server address is not set. Shutting down GenQA.' capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) @@ -23,6 +24,7 @@ def test_hello_world_genqa(mocker, context_wrapper_fixture: Context): # TODO: Write test for server address present +@pytest.mark.skip(reason="test broken") def test_drqa_module(mocker, context_wrapper_fixture: Context): with LogCapture() as capture: verbalizer_mock = mocker.patch('ravestate_verbaliser.verbaliser.get_random_phrase') diff --git a/test/modules/ravestate_roboyqa/test_roboyqa.py b/test/modules/ravestate_roboyqa/test_roboyqa.py new file mode 100644 index 0000000..30c81ee --- /dev/null +++ b/test/modules/ravestate_roboyqa/test_roboyqa.py @@ -0,0 +1,19 @@ +from ravestate.testfixtures import * + + +def test_register(mocker): + from ravestate import registry + with mocker.patch('ravestate.registry.register'): + import ravestate_roboyqa + registry.register.assert_called_with( + name="roboyqa", + states=(ravestate_roboyqa.roboyqa,), + config={ravestate_roboyqa.ROBOY_NODE_CONF_KEY: 356}) + + +def test_roboyqa(mocker, context_fixture, triple_fixture): + mocker.patch.object(context_fixture, 'conf', will_return='test') + context_fixture._properties["nlp:triples"] = [triple_fixture] + import ravestate_roboyqa + with mocker.patch('ravestate_ontology.get_session'): + ravestate_roboyqa.roboyqa(context_fixture) From 478dd775ffc28d1d7b289682de4ba9ae6c8950b9 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 01:29:13 +0100 Subject: [PATCH 19/46] added emit_detached parameter for the @state decorator --- modules/ravestate/activation.py | 13 ++++++++----- modules/ravestate/state.py | 9 ++++++--- modules/ravestate_akinator/{akinator.py => api.py} | 0 3 files changed, 14 insertions(+), 8 deletions(-) rename modules/ravestate_akinator/{akinator.py => api.py} (100%) diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index 9202bf6..56c20b6 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -34,7 +34,7 @@ class Activation(IActivation): ctx: IContext args: List kwargs: Dict - spikes: Set[Spike] + parent_spikes: Set[Spike] consenting_causal_groups: Set[CausalGroup] def __init__(self, st: State, ctx: IContext): @@ -46,7 +46,7 @@ def __init__(self, st: State, ctx: IContext): self.ctx = ctx self.args = [] self.kwargs = {} - self.spikes = set() + self.parent_spikes = set() self.consenting_causal_groups = set() self.death_clock = None @@ -206,7 +206,10 @@ def update(self) -> bool: self.ctx.withdraw(self, sig) # Remember spikes/causal-groups for use in activation - self.spikes = {spike for spike, detached in spikes_for_conjunct if not detached} + if self.state_to_activate.emit_detached: + self.parent_spikes = set() + else: + self.parent_spikes = {spike for spike, detached in spikes_for_conjunct if not detached} self.consenting_causal_groups = consenting_causal_groups # Run activation @@ -242,7 +245,7 @@ def _unique_consenting_causal_groups(self) -> Set[CausalGroup]: return {group for group in self.consenting_causal_groups} def _run_private(self): - context_wrapper = ContextWrapper(ctx=self.ctx, st=self.state_to_activate, spike_parents=self.spikes) + context_wrapper = ContextWrapper(ctx=self.ctx, st=self.state_to_activate, spike_parents=self.parent_spikes) # Run state function result = self.state_to_activate(context_wrapper, *self.args, **self.kwargs) @@ -252,7 +255,7 @@ def _run_private(self): if self.state_to_activate.signal(): self.ctx.emit( self.state_to_activate.signal(), - parents=self.spikes, + 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!") diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 70b6da7..cb48431 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -66,6 +66,7 @@ class State: constraint: Constraint constraint_: Constraint # Updated by context, to add constraint causes to constraint module_name: str + emit_detached: bool # Dummy resource which allows CausalGroups to track acquisitions # for states that don't have any write-props. @@ -77,7 +78,8 @@ def __init__(self, *, read: Union[str, Tuple[str]], cond: Constraint, action, - is_receptor: Optional[bool]=False): + is_receptor: bool=False, + emit_detached: bool=False): assert(callable(action)) self.name = action.__name__ @@ -113,6 +115,7 @@ def __init__(self, *, self.action = action self.module_name = "" self._signal = None + self.emit_detached = emit_detached def __call__(self, context, *args, **kwargs) -> Optional[StateActivationResult]: args = (context,) + args @@ -125,7 +128,7 @@ def signal(self) -> Optional[Signal]: return self._signal -def state(*, signal_name: Optional[str]="", write: tuple=(), read: tuple=(), cond: Constraint=None): +def state(*, signal_name: Optional[str]="", write: tuple=(), read: tuple=(), cond: Constraint=None, emit_detached=False): """ Decorator to declare a new state, which may emit a certain signal, write to a certain set of properties (calling write, push, pop), @@ -133,5 +136,5 @@ def state(*, signal_name: Optional[str]="", write: tuple=(), read: tuple=(), con """ def state_decorator(action): nonlocal signal_name, write, read, cond - return State(signal_name=signal_name, write=write, read=read, cond=cond, action=action) + return State(signal_name=signal_name, write=write, read=read, cond=cond, action=action, emit_detached=emit_detached) return state_decorator diff --git a/modules/ravestate_akinator/akinator.py b/modules/ravestate_akinator/api.py similarity index 100% rename from modules/ravestate_akinator/akinator.py rename to modules/ravestate_akinator/api.py From 261b2d1a41b6accfcdd6c35625779022dc16c3d5 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 01:30:39 +0100 Subject: [PATCH 20/46] debug output for not detached signals --- modules/ravestate/causal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index e5425ba..c48375f 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -281,7 +281,8 @@ def consent(self, ready_suitor: IActivation) -> bool: highest_higher_specificity_act = candidate else: # Easy exit condition: prop not free for writing - logger.debug(f"{self}.consent({ready_suitor})->N: {prop} unavailable.") + logger.debug(f"\nForgot to detach?\n{self}.consent({ready_suitor})->N: {prop} unavailable. " + f"Condition is {ready_suitor.constraint}") return False if higher_specificity_acts: From 79965c0990927901e710ff9c94527f2a8d15fd00 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 02:49:09 +0100 Subject: [PATCH 21/46] refactored akinator api stuff; fixed play again bug --- modules/ravestate_akinator/__init__.py | 154 ++++++++++--------------- modules/ravestate_akinator/api.py | 57 +++++++++ modules/ravestate_nlp/yes_no.py | 2 +- 3 files changed, 121 insertions(+), 92 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 0073e76..df5fe5f 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -1,123 +1,98 @@ from ravestate import registry from ravestate.property import PropertyBase -from ravestate.state import state, Resign, Emit +from ravestate.state import state, Resign, Emit, Delete from ravestate.constraint import s - -import requests +from ravestate_akinator.api import Api from reggol import get_logger logger = get_logger(__name__) - -NEW_SESSION_URL = "https://srv11.akinator.com:9152/ws/new_session?callback=&partner=&player=website-desktop&uid_ext_session=&frontaddr=NDYuMTA1LjExMC40NQ==&constraint=ETAT<>'AV'" -ANSWER_URL = "https://srv11.akinator.com:9152/ws/answer" -GET_GUESS_URL = "https://srv11.akinator.com:9152/ws/list" -CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" -EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" -GLB_URL = "https://pastebin.com/gTua3dg2" - - -akinator_data = None -first_question = True +akinator_api: Api +CERTAINTY = 90 # TODO: Change this to cond=idle:bored -@state(cond=s(":startup", detached=True), write="rawio:out", signal_name="initiate-play") +@state(cond=s(":startup", detached=True), + write="rawio:out", + signal_name="initiate-play", + emit_detached=True) def akinator_play_ask(ctx): ctx["rawio:out"] = "Do you want to play 20 questions?" return Emit() -@state(cond=s("nlp:yes-no") & (s("akinator:initiate-play", max_age=-1) | s("akinator:initiate-play-again", max_age=-1, detached=True)), +@state(cond=s("nlp:yes-no") & (s("akinator:initiate-play", max_age=-1) | s("akinator:initiate_play_again:changed", max_age=-1, detached=True)), read="nlp:yesno", - write=("rawio:out", "akinator:question", "akinator:in_progress", "akinator:session", "akinator:signature")) + write=("rawio:out", "akinator:question")) def akinator_start(ctx): + global akinator_api if ctx["nlp:yesno"] == "yes": - logger.info("Start Akinator session.") - akinator_session = requests.get(NEW_SESSION_URL) - global akinator_data - akinator_data = akinator_session.json() - ctx["akinator:question"] = akinator_data - ctx["akinator:question"] = akinator_data['parameters']['step_information']['question'] - ctx["akinator:in_progress"] = True + logger.info("Akinator session is started.") + akinator_api = Api() + ctx["akinator:question"] = True ctx["rawio:out"] = "You can answer the questions with:" \ + '\n"yes", "no", "i do not know", "probably", "probably not"' \ - + "\nQuestion " + str(int(akinator_data['parameters']['step_information']['step']) + 1) \ - + ":\n" + akinator_data['parameters']['step_information']['question'] - - ctx["akinator:session"] = akinator_data['parameters']['identification']['session'] - ctx["akinator:signature"] = akinator_data['parameters']['identification']['signature'] + + "\nQuestion " + str(int(akinator_api.get_parameter('step')) + 1) \ + + ":\n" + akinator_api.get_parameter('question') else: return Resign() +# TODO not needed anymore @state(cond=s("akinator:question:changed", detached=True), read="akinator:question", signal_name="question-asked") def akinator_question_asked(ctx): return Emit() @state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), - read=("nlp:yesno", "akinator:session", "akinator:signature"), - write=("rawio:out", "akinator:is_it", "akinator:question")) + read="nlp:yesno", + write=("rawio:out", "akinator:is_it", "akinator:question", "akinator:wrong_input")) def akinator_question_answered(ctx): - global first_question - global akinator_data - if first_question: - first_question = False - step = akinator_data['parameters']['step_information']['step'] - else: - step = akinator_data['parameters']['step'] + global akinator_api response = answer_to_int_str(ctx["nlp:yesno"]) - params = { - "session": ctx["akinator:session"], - "signature": ctx["akinator:signature"], - "step": step, - "answer": response - } - akinator_session = requests.get(ANSWER_URL, params=params) - akinator_data = akinator_session.json() - - if int(float(akinator_data['parameters']['progression'])) <= 90: - ctx["akinator:question"] = akinator_data['parameters']['question'] - ctx["rawio:out"] = "Question " + str(int(akinator_data['parameters']['step']) + 1) + ":\n" \ - + akinator_data['parameters']['question'] + if not response == "-1": + akinator_api.response_get_request(response) + if akinator_api.get_progression() <= CERTAINTY: + ctx["akinator:question"] = True + ctx["rawio:out"] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + + ":\n" + akinator_api.get_parameter('question') + else: + ctx["akinator:is_it"] = True else: - ctx["akinator:is_it"] = True + ctx["akinator:wrong_input"] = True @state(cond=s("akinator:is_it:changed", detached=True), - read=("akinator:question", "akinator:session", "akinator:signature"), signal_name="is-it", write="rawio:out") + read="akinator:question", + write="rawio:out", + signal_name="is-it") def akinator_is_it(ctx): - global akinator_data - global guess_data - params = { - "session": ctx["akinator:session"], - "signature": ctx["akinator:signature"], - "step": akinator_data['parameters']['step'] - } - - guess_session = requests.get(GET_GUESS_URL, params=params) - guess_data = guess_session.json() - - name = guess_data['parameters']['elements'][0]['element']['name'] - desc = guess_data['parameters']['elements'][0]['element']['description'] - ctx["rawio:out"] = "Is this your character? \n" + name + "\n" + desc + "\n" + global akinator_api + guess = akinator_api.guess_get_request() + ctx["rawio:out"] = "Is this your character? \n" + guess['name'] + "\n" + guess['desc'] + "\n" return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), - read=("nlp:yesno", "akinator:session", "akinator:signature"), - write="rawio:out", signal_name="initiate-play-again") +@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), read="nlp:yesno", + write=("rawio:out", "akinator:initiate_play_again", "akinator:wrong_input"), emit_detached=True) def akinator_is_it_answered(ctx): - if ctx["nlp:yesno"] == "yes": - ctx["rawio:out"] = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" - return Emit() - elif ctx["nlp:yesno"] == "no": - pass - #ctx["rawio:out"] = "I guessed wrong :(" + response = ctx["nlp:yesno"] + if not response == "-1": + if ctx["nlp:yesno"] == "yes": + out = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" + elif ctx["nlp:yesno"] == "no": + out = "I guessed wrong but do you want to play again?" + ctx["rawio:out"] = out + ctx["akinator:initiate_play_again"] = True else: - pass - #ctx["rawio:out"] = "Shit" + ctx["akinator:wrong_input"] = True + return Delete() + + +@state(cond=s("akinator:wrong_input:changed", detached=True), write=("rawio:out", "akinator:question")) +def akinator_wrong_input(ctx):# + ctx["rawio:out"] = "Sadly I could not process that answer. Try to answer with 'yes' or 'no' please." + ctx["akinator:question"] = True def answer_to_int_str(answer: str): @@ -143,40 +118,37 @@ def answer_to_int_str(answer: str): akinator_play_ask, akinator_question_asked, akinator_start, - akinator_question_answered + akinator_question_answered, + akinator_wrong_input ), props=( PropertyBase( - name="is_it", + name="initiate_play_again", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False, is_flag_property=True), PropertyBase( - name="question", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="in_progress", + name="is_it", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False, is_flag_property=True), PropertyBase( - name="session", + name="question", default_value="", always_signal_changed=True, allow_pop=False, - allow_push=False), + allow_push=False, + is_flag_property=True), PropertyBase( - name="signature", + name="wrong_input", default_value="", always_signal_changed=True, allow_pop=False, - allow_push=False) + allow_push=False, + is_flag_property=True) ) ) diff --git a/modules/ravestate_akinator/api.py b/modules/ravestate_akinator/api.py index 139597f..b174ff9 100644 --- a/modules/ravestate_akinator/api.py +++ b/modules/ravestate_akinator/api.py @@ -1,2 +1,59 @@ +import requests +from reggol import get_logger +logger = get_logger(__name__) + +NEW_SESSION_URL = "https://srv11.akinator.com:9152/ws/new_session?callback=&partner=&player=website-desktop&uid_ext_session=&frontaddr=NDYuMTA1LjExMC40NQ==&constraint=ETAT<>'AV'" +ANSWER_URL = "https://srv11.akinator.com:9152/ws/answer" +GET_GUESS_URL = "https://srv11.akinator.com:9152/ws/list" +CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" +EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" +GLB_URL = "https://pastebin.com/gTua3dg2" + +akinator_data = None + + +class Api: + def __init__(self): + self.data = requests.get(NEW_SESSION_URL).json() + self.session = self.data['parameters']['identification']['session'] + self.signature = self.data['parameters']['identification']['signature'] + self.dict_structure = ['parameters', 'step_information'] + + def get_session(self): + return self.session + + def get_signature(self): + return self.signature + + # get first question + def get_parameter(self, parameter_type: str): + if 'step_information' in self.data['parameters']: + return self.data['parameters']['step_information'][parameter_type] + else: + return self.data['parameters'][parameter_type] + + def get_progression(self): + return float(self.data['parameters']['progression']) + + def response_get_request(self, response: str): + params = { + "session": self.get_session(), + "signature": self.get_signature(), + "step": self.get_parameter('step'), + "answer": response + } + self.data = requests.get(ANSWER_URL, params=params).json() + + def guess_get_request(self): + params = { + "session": self.get_session(), + "signature": self.get_signature(), + "step": self.get_parameter('step') + } + guess_data = requests.get(GET_GUESS_URL, params=params).json() + guess = {"name": guess_data['parameters']['elements'][0]['element']['name'], + "desc":guess_data['parameters']['elements'][0]['element']['description'] + } + return guess diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py index fa0b545..a69162f 100644 --- a/modules/ravestate_nlp/yes_no.py +++ b/modules/ravestate_nlp/yes_no.py @@ -19,5 +19,5 @@ def yes_no(doc): return "pn" elif "probably" in nlp_tokens: return "p" - return None + return "0" From da320ad35a949590b8de970ccfd08bea6b3d90eb Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 02:59:08 +0100 Subject: [PATCH 22/46] got rid of question_asked state; added emit_detachted to the other states --- modules/ravestate_akinator/__init__.py | 30 +++++++++++++------------- modules/ravestate_akinator/api.py | 6 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index df5fe5f..75df207 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -23,7 +23,8 @@ def akinator_play_ask(ctx): @state(cond=s("nlp:yes-no") & (s("akinator:initiate-play", max_age=-1) | s("akinator:initiate_play_again:changed", max_age=-1, detached=True)), read="nlp:yesno", - write=("rawio:out", "akinator:question")) + write=("rawio:out", "akinator:question"), + emit_detached=True) def akinator_start(ctx): global akinator_api if ctx["nlp:yesno"] == "yes": @@ -38,15 +39,10 @@ def akinator_start(ctx): return Resign() -# TODO not needed anymore -@state(cond=s("akinator:question:changed", detached=True), read="akinator:question", signal_name="question-asked") -def akinator_question_asked(ctx): - return Emit() - - -@state(cond=s("nlp:yes-no") & s("akinator:question-asked", max_age=-1), +@state(cond=s("nlp:yes-no") & s("akinator:question:changed", detached=True, max_age=-1), read="nlp:yesno", - write=("rawio:out", "akinator:is_it", "akinator:question", "akinator:wrong_input")) + write=("rawio:out", "akinator:is_it", "akinator:question", "akinator:wrong_input"), + emit_detached=True) def akinator_question_answered(ctx): global akinator_api response = answer_to_int_str(ctx["nlp:yesno"]) @@ -65,7 +61,8 @@ def akinator_question_answered(ctx): @state(cond=s("akinator:is_it:changed", detached=True), read="akinator:question", write="rawio:out", - signal_name="is-it") + signal_name="is-it", + emit_detached=True) def akinator_is_it(ctx): global akinator_api guess = akinator_api.guess_get_request() @@ -73,8 +70,10 @@ def akinator_is_it(ctx): return Emit() -@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), read="nlp:yesno", - write=("rawio:out", "akinator:initiate_play_again", "akinator:wrong_input"), emit_detached=True) +@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), + read="nlp:yesno", + write=("rawio:out", "akinator:initiate_play_again", "akinator:wrong_input"), + emit_detached=True) def akinator_is_it_answered(ctx): response = ctx["nlp:yesno"] if not response == "-1": @@ -89,8 +88,10 @@ def akinator_is_it_answered(ctx): return Delete() -@state(cond=s("akinator:wrong_input:changed", detached=True), write=("rawio:out", "akinator:question")) -def akinator_wrong_input(ctx):# +@state(cond=s("akinator:wrong_input:changed", detached=True), + write=("rawio:out", "akinator:question"), + emit_detached=True) +def akinator_wrong_input(ctx): ctx["rawio:out"] = "Sadly I could not process that answer. Try to answer with 'yes' or 'no' please." ctx["akinator:question"] = True @@ -116,7 +117,6 @@ def answer_to_int_str(answer: str): akinator_is_it, akinator_is_it_answered, akinator_play_ask, - akinator_question_asked, akinator_start, akinator_question_answered, akinator_wrong_input diff --git a/modules/ravestate_akinator/api.py b/modules/ravestate_akinator/api.py index b174ff9..3569999 100644 --- a/modules/ravestate_akinator/api.py +++ b/modules/ravestate_akinator/api.py @@ -7,9 +7,9 @@ NEW_SESSION_URL = "https://srv11.akinator.com:9152/ws/new_session?callback=&partner=&player=website-desktop&uid_ext_session=&frontaddr=NDYuMTA1LjExMC40NQ==&constraint=ETAT<>'AV'" ANSWER_URL = "https://srv11.akinator.com:9152/ws/answer" GET_GUESS_URL = "https://srv11.akinator.com:9152/ws/list" -CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" -EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" -GLB_URL = "https://pastebin.com/gTua3dg2" +# CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" +# EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" +# GLB_URL = "https://pastebin.com/gTua3dg2" akinator_data = None From a68991e0ed0563eb96e76c0f9573bac9925af2c1 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 03:08:13 +0100 Subject: [PATCH 23/46] akinator answering certainty configurable --- config/roboy.yml | 5 +++++ modules/ravestate_akinator/__init__.py | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/config/roboy.yml b/config/roboy.yml index 6e472d6..b976c26 100644 --- a/config/roboy.yml +++ b/config/roboy.yml @@ -39,3 +39,8 @@ config: module: roboyqa config: roboy_node_id: 356 + +--- +module: akinator +config: + certainty_percentage: 90 diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 75df207..98d335b 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -8,7 +8,7 @@ logger = get_logger(__name__) akinator_api: Api -CERTAINTY = 90 +CERTAINTY = "certainty_percentage" # TODO: Change this to cond=idle:bored @@ -48,7 +48,7 @@ def akinator_question_answered(ctx): response = answer_to_int_str(ctx["nlp:yesno"]) if not response == "-1": akinator_api.response_get_request(response) - if akinator_api.get_progression() <= CERTAINTY: + if akinator_api.get_progression() <= ctx.conf(key=CERTAINTY): ctx["akinator:question"] = True ctx["rawio:out"] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + ":\n" + akinator_api.get_parameter('question') @@ -150,5 +150,6 @@ def answer_to_int_str(answer: str): allow_pop=False, allow_push=False, is_flag_property=True) - ) + ), + config={CERTAINTY: 90} ) From cea45eedee290417259bf03210d96b91d598da2d Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 03:12:22 +0100 Subject: [PATCH 24/46] added return values to methods --- modules/ravestate_akinator/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ravestate_akinator/api.py b/modules/ravestate_akinator/api.py index 3569999..859ebb2 100644 --- a/modules/ravestate_akinator/api.py +++ b/modules/ravestate_akinator/api.py @@ -28,13 +28,13 @@ def get_signature(self): return self.signature # get first question - def get_parameter(self, parameter_type: str): + def get_parameter(self, parameter_type: str) -> dict: if 'step_information' in self.data['parameters']: return self.data['parameters']['step_information'][parameter_type] else: return self.data['parameters'][parameter_type] - def get_progression(self): + def get_progression(self) -> float: return float(self.data['parameters']['progression']) def response_get_request(self, response: str): @@ -46,7 +46,7 @@ def response_get_request(self, response: str): } self.data = requests.get(ANSWER_URL, params=params).json() - def guess_get_request(self): + def guess_get_request(self) -> dict: params = { "session": self.get_session(), "signature": self.get_signature(), From 0816e2599088cf37e1ecbf35d77b9c8db3a7f5a9 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 03:16:01 +0100 Subject: [PATCH 25/46] changed return specification of method --- modules/ravestate_akinator/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ravestate_akinator/api.py b/modules/ravestate_akinator/api.py index 859ebb2..568308c 100644 --- a/modules/ravestate_akinator/api.py +++ b/modules/ravestate_akinator/api.py @@ -28,7 +28,7 @@ def get_signature(self): return self.signature # get first question - def get_parameter(self, parameter_type: str) -> dict: + def get_parameter(self, parameter_type: str) -> str: if 'step_information' in self.data['parameters']: return self.data['parameters']['step_information'][parameter_type] else: From dadb0d9bca55fa3964bd635acd662730d4cbc234 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 04:11:58 +0100 Subject: [PATCH 26/46] Added new Module API, which enables typed property/signal references. --- modules/ravestate/activation.py | 12 +- modules/ravestate/context.py | 27 +-- modules/ravestate/module.py | 104 +++++++-- modules/ravestate/property.py | 36 ++-- modules/ravestate/registry.py | 80 ------- modules/ravestate/state.py | 10 +- modules/ravestate/wrappers.py | 18 +- modules/ravestate_akinator/__init__.py | 2 +- modules/ravestate_conio/__init__.py | 117 +++++----- modules/ravestate_facerec/__init__.py | 30 ++- modules/ravestate_fillers/__init__.py | 11 +- modules/ravestate_genqa/__init__.py | 82 ++++---- modules/ravestate_hibye/__init__.py | 18 +- modules/ravestate_idle/__init__.py | 57 +++-- modules/ravestate_interloc/__init__.py | 9 +- modules/ravestate_nlp/__init__.py | 167 ++++++++------- modules/ravestate_ontology/__init__.py | 72 +++---- modules/ravestate_rawio/__init__.py | 15 +- modules/ravestate_roboyqa/__init__.py | 199 +++++++++--------- modules/ravestate_ros2/__init__.py | 20 +- modules/ravestate_ros2/ros2_properties.py | 10 +- modules/ravestate_telegramio/__init__.py | 17 +- modules/ravestate_verbaliser/__init__.py | 32 ++- modules/ravestate_wildtalk/__init__.py | 11 +- test/modules/ravestate/test_context.py | 14 +- .../ravestate/test_wrappers_property.py | 10 +- 26 files changed, 582 insertions(+), 598 deletions(-) delete mode 100644 modules/ravestate/registry.py diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index 9202bf6..219ba79 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -1,4 +1,4 @@ -# Ravestate class which encapsualtes the activation of a single state +# Ravestate class which encapsulates the activation of a single state import copy from threading import Thread @@ -6,7 +6,7 @@ from collections import defaultdict from ravestate.icontext import IContext -from ravestate.constraint import Constraint, s +from ravestate.constraint import Constraint from ravestate.iactivation import IActivation, ISpike from ravestate.spike import Spike from ravestate.causal import CausalGroup @@ -67,7 +67,7 @@ def resources(self) -> Set[str]: # This allows CausalGroup to track the spike acquisitions for this # activation, and make sure that a single spike cannot activate # multiple activations for a write-prop-less state. - return {self.state_to_activate.consumable.fullname()} + return {self.state_to_activate.consumable.id()} def specificity(self) -> float: """ @@ -245,7 +245,11 @@ def _run_private(self): context_wrapper = ContextWrapper(ctx=self.ctx, st=self.state_to_activate, spike_parents=self.spikes) # Run state function - result = self.state_to_activate(context_wrapper, *self.args, **self.kwargs) + try: + result = self.state_to_activate(context_wrapper, *self.args, **self.kwargs) + except Exception as e: + logger.error(f"An exception occurred while activating {self}: {e}") + result = Resign() # Process state function result if isinstance(result, Emit): diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index afd0deb..8b704ad 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -10,12 +10,11 @@ from ravestate.wrappers import PropertyWrapper from ravestate.icontext import IContext -from ravestate.module import Module +from ravestate.module import Module, has_module, get_module, import_module from ravestate.state import State from ravestate.property import PropertyBase from ravestate.iactivation import IActivation from ravestate.activation import Activation -from ravestate import registry from ravestate import argparse from ravestate.config import Configuration from ravestate.constraint import s, Signal, Conjunct, Disjunct, ConfigurableAge @@ -218,10 +217,10 @@ def add_module(self, module_name: str) -> None: will be imported, and any ravestate modules registered during the python import will also be added to this context. """ - if registry.has_module(module_name): - self._module_registration_callback(registry.get_module(module_name)) + if has_module(module_name): + self._module_registration_callback(get_module(module_name)) return - registry.import_module(module_name=module_name, callback=self._module_registration_callback) + import_module(module_name=module_name, callback=self._module_registration_callback) def add_state(self, *, st: State) -> None: """ @@ -320,12 +319,12 @@ def add_prop(self, *, prop: PropertyBase) -> None: * `prop`: The property object that should be added. """ - if prop.fullname() in self._properties: - logger.error(f"Attempt to add property {prop.fullname()} twice!") + if prop.id() in self._properties: + logger.error(f"Attempt to add property {prop.id()} twice!") return with self._lock: # register property - self._properties[prop.fullname()] = prop + self._properties[prop.id()] = prop # register all of the property's signals for signal in prop.signals(): self._add_sig(signal) @@ -337,11 +336,11 @@ def rm_prop(self, *, prop: PropertyBase) -> None: * `prop`: The property to remove.object """ - if prop.fullname() not in self._properties: - logger.error(f"Attempt to remove unknown property {prop.fullname()}!") + if prop.id() not in self._properties: + logger.error(f"Attempt to remove unknown property {prop.id()}!") return # remove property from context - self._properties.pop(prop.fullname()) + self._properties.pop(prop.id()) states_to_remove: Set[State] = set() with self._lock: # remove all of the property's signals @@ -349,7 +348,7 @@ def rm_prop(self, *, prop: PropertyBase) -> None: self._rm_sig(signal) # remove all states that depend upon property for st in self._activations_per_state: - if prop.fullname() in st.read_props + st.write_props: + if prop.id() in st.read_props + st.write_props: states_to_remove.add(st) for st in states_to_remove: self.rm_state(st=st) @@ -563,7 +562,9 @@ def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal]) -> L if completion is not None and len(completion) > 0: # the signal is non-cyclic, and has at least one cause (secondary signal). # permute existing disjunct conjunctions with new conjunction(s) - result = [deepcopy(result_conj) | deepcopy(completion_conj) for result_conj in result for completion_conj in completion] + result = [ + deepcopy(result_conj) | deepcopy(completion_conj) + for result_conj in result for completion_conj in completion] return result diff --git a/modules/ravestate/module.py b/modules/ravestate/module.py index 24be869..e6f9f04 100644 --- a/modules/ravestate/module.py +++ b/modules/ravestate/module.py @@ -1,8 +1,13 @@ # Ravestate module class -from typing import Dict, Any, Tuple +from typing import Dict, Any, Union, Iterable, Callable +import importlib from ravestate.property import PropertyBase from ravestate.state import State +import threading + +from reggol import get_logger +logger = get_logger(__name__) class Module: @@ -11,24 +16,91 @@ class Module: which form a coherent bundle. """ - def __init__(self, *, name: str, - props: Tuple[PropertyBase]=(), - states: Tuple[State]=(), - config: Dict[str, Any]=None): + thread_local = threading.local() + registered_modules: Dict[str, 'Module'] = dict() + registration_callback: Callable[['Module'], Any] = None # set by import_module + + def __init__(self, *, name: str, config: Dict[str, Any]=None): + """ + Create a new module with a name and certain config entries. + + * `name`: The name of the module. Will be prefixed to property and signal names like + :. Should be unique among all modules + currently existing in the process. - if not isinstance(props, tuple): - props = (props,) - if not isinstance(states, tuple): - states = (states,) + * `config`: A dictionary of config entries and their default values, which should be read + from the default/user config files. + """ if not config: config = {} - + self.props = [] + self.states = [] self.name = name - self.props = props - self.states = states self.conf = config + if name in self.registered_modules: + logger.error(f"Adding module {name} twice!") + self.registered_modules[name] = self + if self.registration_callback: + self.registration_callback(self) + + def __enter__(self): + mod = getattr(self.thread_local, 'module_under_construction', None) + if mod: + logger.error("Nested `with Module(...)` calls are not supported!`") + else: + self.thread_local.module_under_construction = self + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.thread_local.module_under_construction = None + + def add(self, property_or_state: Union[PropertyBase, State, Iterable[PropertyBase], Iterable[State]]): + try: + for obj_to_add in property_or_state: + self.add(obj_to_add) + except TypeError: + pass + if isinstance(property_or_state, PropertyBase): + 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) + else: + logger.error(f"Module.add() called with invalid argument {property_or_state}!") + + +def import_module(*, module_name: str, callback): + """ + Called by context to import a particular ravestate python module. + + * `module_name`: The name of the python module to be imported (must be in pythonpath). + + * `callback`: A callback which should be called when a module calls register() while it is being imported. + """ + assert not Module.registration_callback + Module.registration_callback = callback + importlib.import_module(module_name) + Module.registration_callback = None + + +def has_module(module_name: str): + """ + Check whether a module with a particular name has been registered. + + * `module_name`: The name which should be checked for beign registered. + + **Returns:** True if a module with the given name has been registered, false otherwise. + """ + return module_name in Module.registered_modules + + +def get_module(module_name: str): + """ + Get a registered module with a particular name + + * `module_name`: The name of the moduke which should be retrieved. - for prop in props: - prop.set_parent_path(name) - for st in states: - st.module_name = name + **Returns:** The module with the given name if it was registered, false if otherwise. + """ + return Module.registered_modules[module_name] if module_name in Module.registered_modules else None diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 39affc6..0d6322c 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -3,6 +3,7 @@ from threading import Lock from typing import Dict, List, Generator from ravestate.constraint import s, Signal +import threading from reggol import get_logger logger = get_logger(__name__) @@ -50,6 +51,8 @@ class PropertyBase: property name basic impls. for the property value, parent/child mechanism. """ + thread_local = threading.local() + def __init__( self, *, name="", @@ -73,7 +76,12 @@ def __init__( self.always_signal_changed = always_signal_changed self.is_flag_property = is_flag_property - def fullname(self): + # add property to module in current `with Module(...)` clause + module_under_construction = getattr(self.thread_local, 'module_under_construction', None) + if module_under_construction: + module_under_construction.add(self) + + def id(self): return f'{self.parent_path}:{self.name}' def set_parent_path(self, path): @@ -85,7 +93,7 @@ def set_parent_path(self, path): if not self.parent_path: self.parent_path = path else: - logger.error(f'Tried to override parent_path of {self.fullname()}') + logger.error(f'Tried to override parent_path of {self.id()}') def gather_children(self) -> List['PropertyBase']: """ @@ -107,7 +115,7 @@ def read(self): Read the current property value """ if not self.allow_read: - logger.error(f"Unauthorized read access in property {self.fullname()}!") + logger.error(f"Unauthorized read access in property {self.id()}!") return None return self.value @@ -120,7 +128,7 @@ def write(self, value): **Returns:** True if the value has changed and :changed should be signaled, false otherwise. """ if not self.allow_write: - logger.error(f"Unauthorized write access in property {self.fullname()}!") + logger.error(f"Unauthorized write access in property {self.id()}!") return False if self.always_signal_changed or self.value != value: self.value = value @@ -137,12 +145,12 @@ def push(self, child: 'PropertyBase'): **Returns:** True if the child was added successfully, false otherwise. """ if not self.allow_push: - logger.error(f"Unauthorized push in property {self.fullname()}!") + logger.error(f"Unauthorized push in property {self.id()}!") return False if child.name in self.children: - logger.error(f"Tried to add already existing child-property {self.fullname()}:{child.name}") + logger.error(f"Tried to add already existing child-property {self.id()}:{child.name}") return False - child.set_parent_path(self.fullname()) + child.set_parent_path(self.id()) self.children[child.name] = child return True @@ -155,44 +163,44 @@ def pop(self, child_name: str): **Returns:** True if the pop was successful, False otherwise """ if not self.allow_pop: - logger.error(f"Unauthorized pop in property {self.fullname()}!") + logger.error(f"Unauthorized pop in property {self.id()}!") return False elif child_name in self.children: self.children.pop(child_name) return True else: - logger.error(f"Tried to remove non-existent child-property {self.fullname()}:{child_name}") + logger.error(f"Tried to remove non-existent child-property {self.id()}:{child_name}") return False def changed_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #write() returns True. """ - return changed(self.fullname()) + return changed(self.id()) def pushed_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #push() returns True. """ - return pushed(self.fullname()) + return pushed(self.id()) def popped_signal(self) -> Signal: """ Signal that is emitted by PropertyWrapper when #pop() returns True. """ - return popped(self.fullname()) + return popped(self.id()) def flag_true_signal(self) -> Signal: """ TODO Docstring for flagprops Signal that is emitted by PropertyWrapper when #self.value is set to True. """ - return s(f"{self.fullname()}:true") + return s(f"{self.id()}:true") def flag_false_signal(self) -> Signal: """ TODO Docstring for flagprops Signal that is emitted by PropertyWrapper when #self.value is set to False. """ - return s(f"{self.fullname()}:false") + return s(f"{self.id()}:false") def signals(self) -> Generator[Signal, None, None]: """ diff --git a/modules/ravestate/registry.py b/modules/ravestate/registry.py deleted file mode 100644 index 4c9fdcf..0000000 --- a/modules/ravestate/registry.py +++ /dev/null @@ -1,80 +0,0 @@ -# Ravestate static implementations for registering modules - - -from ravestate import module -import importlib - -from reggol import get_logger -logger = get_logger(__name__) - -_registered_modules = dict() -_registration_callback = None - - -def import_module(*, module_name: str, callback): - """ - Called by context to import a particular ravestate python module. - - * `module_name`: The name of the python module to be imported (must be in pythonpath). - - * `callback`: A callback which should be called when a module calls register() while it is being imported. - """ - global _registration_callback - _registration_callback = callback - importlib.import_module(module_name) - _registration_callback = None - - -def register(*, name: str="", props=(), states=(), config=None): - """ - May be called to register a named set of states, properties and config entries, - which form a coherent bundle. - - * `name`: The name of the module. Will be prefixed to property and signal names like - :. - - * `props`: The properties that should be registered. - - * `states`: The states that should be registered. - - * `config`: A dictionary of config entries and their default values, which should be read - from the default/user config files. - :return: - """ - global _registered_modules - global _registration_callback - - if name in _registered_modules: - logger.error(f"Attempt to add module {name} twice!") - return - - if not config: - config = {} - - _registered_modules[name] = module.Module(name=name, props=props, states=states, config=config) - if _registration_callback: - _registration_callback(_registered_modules[name]) - - -def has_module(module_name: str): - """ - Check whether a module with a particular name has been registered. - - * `module_name`: The name which should be checked for beign registered. - - **Returns:** True if a module with the given name has been registered, false otherwise. - """ - global _registered_modules - return module_name in _registered_modules - - -def get_module(module_name: str): - """ - Get a registered module with a particular name - - * `module_name`: The name of the moduke which should be retrieved. - - **Returns:** The module with the given name if it was registered, false if otherwise. - """ - global _registered_modules - return _registered_modules[module_name] if module_name in _registered_modules else None diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index bce35ae..489dd82 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -1,6 +1,7 @@ # Ravestate State-related definitions -from typing import Callable, Optional, Any, Tuple, Union +from typing import Optional, Tuple, Union +import threading from ravestate.constraint import Conjunct, Disjunct, Signal, s, Constraint from ravestate.consumable import Consumable @@ -71,6 +72,8 @@ class State: # for states that don't have any write-props. consumable: Consumable + thread_local = threading.local() + def __init__(self, *, signal_name: Optional[str], write: Union[str, Tuple[str]], @@ -114,6 +117,11 @@ def __init__(self, *, self.module_name = "" self._signal = None + # add state to module in current `with Module(...)` clause + module_under_construction = getattr(self.thread_local, 'module_under_construction', None) + if module_under_construction: + module_under_construction.add(self) + def __call__(self, context, *args, **kwargs) -> Optional[_StateActivationResult]: args = (context,) + args return self.action(*args, **kwargs) diff --git a/modules/ravestate/wrappers.py b/modules/ravestate/wrappers.py index 565d98d..08ace8d 100644 --- a/modules/ravestate/wrappers.py +++ b/modules/ravestate/wrappers.py @@ -50,7 +50,7 @@ def get(self) -> Any: * `child`: top-down list of child ancestry of the child to get the value from """ if not self.allow_read: - logger.error(f"Unauthorized read access in property-wrapper for {self.prop.fullname()}!") + logger.error(f"Unauthorized read access in property-wrapper for {self.prop.id()}!") return None elif self.allow_write: return self.prop.read() @@ -65,7 +65,7 @@ def set(self, value: Any): **Returns:** True if the value has changed and :changed should be signaled, false otherwise. """ if not self.allow_write: - logger.error(f"Unauthorized write access in property-wrapper {self.prop.fullname()}!") + logger.error(f"Unauthorized write access in property-wrapper {self.prop.id()}!") return False if self.prop.write(value): # emit flag signals if it is a flag property @@ -92,7 +92,7 @@ def push(self, child: PropertyBase): **Returns:** True if the push was successful, False otherwise """ if not self.allow_write: - logger.error(f"Unauthorized push access in property-wrapper {self.prop.fullname()}!") + logger.error(f"Unauthorized push access in property-wrapper {self.prop.id()}!") return False if self.prop.push(child): self.ctx.emit(self.prop.pushed_signal(), parents=self.spike_parents, wipe=True) @@ -108,7 +108,7 @@ def pop(self, childname: str): **Returns:** True if the pop was successful, False otherwise """ if not self.allow_write: - logger.error(f"Unauthorized pop access in property-wrapper {self.prop.fullname()}!") + logger.error(f"Unauthorized pop access in property-wrapper {self.prop.id()}!") return False if self.prop.pop(childname): self.ctx.emit(self.prop.popped_signal(), parents=self.spike_parents, wipe=True) @@ -120,9 +120,9 @@ def enum(self) -> Generator[str, None, None]: Get the full paths of each of this property's children. """ if not self.allow_read: - logger.error(f"Unauthorized read access in property-wrapper for {self.prop.fullname()}!") + logger.error(f"Unauthorized read access in property-wrapper for {self.prop.id()}!") return (_ for _ in ()) - return (child.fullname() for _, child in self.prop.children.items()) + return (child.id() for _, child in self.prop.children.items()) class ContextWrapper: @@ -143,8 +143,8 @@ def __init__(self, *, ctx: icontext.IContext, st: state.State, spike_parents: Se prop_and_children = ctx[propname].gather_children() for prop in prop_and_children: # Child may have been covered by a parent before - if prop.fullname() not in self.properties: - self.properties[prop.fullname()] = PropertyWrapper( + if prop.id() not in self.properties: + self.properties[prop.id()] = PropertyWrapper( prop=prop, ctx=ctx, spike_parents=self.spike_parents, allow_read=propname in st.read_props, @@ -193,7 +193,7 @@ def push(self, parentpath: str, child: PropertyBase): return False if parentpath in self.properties: if self.properties[parentpath].push(child): - self.properties[child.fullname()] = PropertyWrapper( + self.properties[child.id()] = PropertyWrapper( prop=child, ctx=self.ctx, spike_parents=self.spike_parents, allow_read=self.properties[parentpath].allow_read, diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 0073e76..a3b87b5 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -1,4 +1,4 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.property import PropertyBase from ravestate.state import state, Resign, Emit from ravestate.constraint import s diff --git a/modules/ravestate_conio/__init__.py b/modules/ravestate_conio/__init__.py index d94311b..4a72cac 100644 --- a/modules/ravestate_conio/__init__.py +++ b/modules/ravestate_conio/__init__.py @@ -1,6 +1,4 @@ -from h5py.h5p import DEFAULT - -from ravestate import registry +from ravestate.module import Module from ravestate.constraint import s from ravestate.property import PropertyBase from ravestate.state import state @@ -22,62 +20,57 @@ DEFAULT_INTERLOC_ID = "terminal_user" - -@state(cond=s(":startup"), read="interloc:all") -def console_input(ctx: ContextWrapper): - - @receptor(ctx_wrap=ctx, write="rawio:in") - def write_console_input(ctx_input, value: str): - ctx_input["rawio:in"] = value - - @receptor(ctx_wrap=ctx, write="interloc:all") - def push_console_interloc(ctx: ContextWrapper, console_node: Node): - if ctx.push(parentpath="interloc:all", child=PropertyBase(name=DEFAULT_INTERLOC_ID, default_value=console_node)): - logger.debug(f"Pushed {console_node} to interloc:all") - - @receptor(ctx_wrap=ctx, write="interloc:all") - def pop_console_interloc(ctx: ContextWrapper): - if ctx.pop(f"interloc:all:{DEFAULT_INTERLOC_ID}"): - logger.debug(f"Popped interloc:all:{DEFAULT_INTERLOC_ID}") - - while not ctx.shutting_down(): - input_value = input("> ") - write_console_input(input_value) - - console_interloc_exists = f"interloc:all:{DEFAULT_INTERLOC_ID}" in ctx.enum("interloc:all") - # push Node if you got a greeting - if input_value.strip() in get_phrase_list("greeting") and not console_interloc_exists: - # set up scientio - sess: Session = ravestate_ontology.get_session() - onto: Ontology = ravestate_ontology.get_ontology() - - # create scientio Node of type Person - query = Node(metatype=onto.get_type("Person")) - query.set_name("x") - console_node_list = sess.retrieve(query) - if not console_node_list: - console_node = sess.create(query) - logger.info(f"Created new Node in scientio session: {console_node}") - elif len(console_node_list) == 1: - console_node = console_node_list[0] - else: - logger.error(f'Found multiple Persons with name {DEFAULT_INTERLOC_ID} in scientio session. Cannot push node to interloc:all!') - continue - - # push interloc-Node - push_console_interloc(console_node) - - # pop Node if you got a farewell - elif input_value.strip() in get_phrase_list("farewells") and console_interloc_exists: - pop_console_interloc() - - -@state(read="rawio:out") -def console_output(ctx): - print(ctx["rawio:out"]) - - -registry.register( - name="consoleio", - states=(console_input, console_output) -) +with Module(name="consoleio"): + + @state(cond=s(":startup"), read="interloc:all") + def console_input(ctx: ContextWrapper): + + @receptor(ctx_wrap=ctx, write="rawio:in") + def write_console_input(ctx_input, value: str): + ctx_input["rawio:in"] = value + + @receptor(ctx_wrap=ctx, write="interloc:all") + def push_console_interloc(ctx: ContextWrapper, console_node: Node): + if ctx.push(parentpath="interloc:all", child=PropertyBase(name=DEFAULT_INTERLOC_ID, default_value=console_node)): + logger.debug(f"Pushed {console_node} to interloc:all") + + @receptor(ctx_wrap=ctx, write="interloc:all") + def pop_console_interloc(ctx: ContextWrapper): + if ctx.pop(f"interloc:all:{DEFAULT_INTERLOC_ID}"): + logger.debug(f"Popped interloc:all:{DEFAULT_INTERLOC_ID}") + + while not ctx.shutting_down(): + input_value = input("> ") + write_console_input(input_value) + + console_interloc_exists = f"interloc:all:{DEFAULT_INTERLOC_ID}" in ctx.enum("interloc:all") + # push Node if you got a greeting + if input_value.strip() in get_phrase_list("greeting") and not console_interloc_exists: + # set up scientio + sess: Session = ravestate_ontology.get_session() + onto: Ontology = ravestate_ontology.get_ontology() + + # create scientio Node of type Person + query = Node(metatype=onto.get_type("Person")) + query.set_name("x") + console_node_list = sess.retrieve(query) + if not console_node_list: + console_node = sess.create(query) + logger.info(f"Created new Node in scientio session: {console_node}") + elif len(console_node_list) == 1: + console_node = console_node_list[0] + else: + logger.error(f'Found multiple Persons with name {DEFAULT_INTERLOC_ID} in scientio session. Cannot push node to interloc:all!') + continue + + # push interloc-Node + push_console_interloc(console_node) + + # pop Node if you got a farewell + elif input_value.strip() in get_phrase_list("farewells") and console_interloc_exists: + pop_console_interloc() + + + @state(read="rawio:out") + def console_output(ctx): + print(ctx["rawio:out"]) diff --git a/modules/ravestate_facerec/__init__.py b/modules/ravestate_facerec/__init__.py index 44e0fc1..6e863c4 100644 --- a/modules/ravestate_facerec/__init__.py +++ b/modules/ravestate_facerec/__init__.py @@ -1,6 +1,6 @@ import rclpy -from ravestate import registry +from ravestate.module import Module from ravestate.constraint import s from ravestate.state import state from ravestate.receptor import receptor @@ -11,24 +11,22 @@ rclpy.init() node = rclpy.create_node("vision_node") -@state(cond=s(":startup")) -def facerec_run(ctx): +with Module(name="facerec"): - @receptor(ctx_wrap=ctx, write="facerec:face") - def face_recognition_callback(ctx, msg): - ctx["facerec:face"] = msg + face = PropertyBase(name="face", default_value="") - node.create_subscription(String, "/roboy/vision/recognized_faces", face_recognition_callback) - rclpy.spin(node) + @state(cond=s(":startup")) + def facerec_run(ctx): + @receptor(ctx_wrap=ctx, write="facerec:face") + def face_recognition_callback(ctx, msg): + ctx["facerec:face"] = msg -@state(cond=s(":shutdown")) -def facerec_shutdown(): - node.destroy_node() - rclpy.shutdown() + node.create_subscription(String, "/roboy/vision/recognized_faces", face_recognition_callback) + rclpy.spin(node) -registry.register( - name="facerec", - props=PropertyBase(name="face", default_value=""), - states=(facerec_run, facerec_shutdown)) \ No newline at end of file + @state(cond=s(":shutdown")) + def facerec_shutdown(): + node.destroy_node() + rclpy.shutdown() diff --git a/modules/ravestate_fillers/__init__.py b/modules/ravestate_fillers/__init__.py index b87eadc..b84ef79 100644 --- a/modules/ravestate_fillers/__init__.py +++ b/modules/ravestate_fillers/__init__.py @@ -1,4 +1,4 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.wrappers import ContextWrapper from ravestate.constraint import s from ravestate.state import state @@ -8,9 +8,8 @@ import ravestate_phrases_basic_en -@state(cond=s("idle:impatient"), write=("verbaliser:intent",)) -def impatient_fillers(ctx: ContextWrapper): - ctx["verbaliser:intent"] = "fillers" +with Module(name="fillers"): - -registry.register(name="fillers", states=(impatient_fillers,)) + @state(cond=s("idle:impatient"), write=("verbaliser:intent",)) + def impatient_fillers(ctx: ContextWrapper): + ctx["verbaliser:intent"] = "fillers" diff --git a/modules/ravestate_genqa/__init__.py b/modules/ravestate_genqa/__init__.py index 4ee3945..e18427d 100644 --- a/modules/ravestate_genqa/__init__.py +++ b/modules/ravestate_genqa/__init__.py @@ -1,4 +1,4 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.state import state, Delete from ravestate_verbaliser import verbaliser from ravestate.constraint import s @@ -10,44 +10,49 @@ DRQA_SERVER_ADDRESS: str = "drqa_server_address" ROBOY_ANSWER_SANITY: str = "roboy_answer_sanity" - +CONFIG = { + DRQA_SERVER_ADDRESS: "http://localhost:5000", + ROBOY_ANSWER_SANITY: 5000 +} SERVER_AVAILABLE_CODE = 200 -@state(cond=s(":startup"), write="rawio:out") -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() - if not server_up(server): - return Delete() +with Module(name="genqa", config=CONFIG): + + @state(cond=s(":startup"), write="rawio:out") + 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() + if not server_up(server): + return Delete() -@state(cond=s("nlp:is-question"), read="rawio:in", write="rawio:out") -def drqa_module(ctx): - """ - general question answering using DrQA through a HTTP server - connection check to server - post input question and get DrQA answer through the HTTP interface - depending on the answer score the answer is introduced as sane or insane/unsure :) - """ - server = ctx.conf(key=DRQA_SERVER_ADDRESS) - if not server_up(server): - return Delete(resign=True) - params = {'question': ctx["rawio:in"]} - 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") + " " + \ - response_json["answers"][0]["span"] - # insane/unsure answer - else: - ctx["rawio: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." + @state(cond=s("nlp:is-question"), read="rawio:in", write="rawio:out") + def drqa_module(ctx): + """ + general question answering using DrQA through a HTTP server + connection check to server + post input question and get DrQA answer through the HTTP interface + depending on the answer score the answer is introduced as sane or insane/unsure :) + """ + server = ctx.conf(key=DRQA_SERVER_ADDRESS) + if not server_up(server): + return Delete(resign=True) + params = {'question': ctx["rawio:in"]} + 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") + " " + \ + response_json["answers"][0]["span"] + # insane/unsure answer + else: + ctx["rawio: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." def server_up(server): @@ -62,12 +67,3 @@ def server_up(server): "\n--------") return status == SERVER_AVAILABLE_CODE - -registry.register( - name="genqa", - states=(hello_world_genqa, drqa_module), - config={ - DRQA_SERVER_ADDRESS: "http://localhost:5000", - ROBOY_ANSWER_SANITY: 5000 - } -) diff --git a/modules/ravestate_hibye/__init__.py b/modules/ravestate_hibye/__init__.py index b1375c4..52181d6 100644 --- a/modules/ravestate_hibye/__init__.py +++ b/modules/ravestate_hibye/__init__.py @@ -1,5 +1,5 @@ from ravestate.state import state -from ravestate import registry +from ravestate.module import Module from ravestate.constraint import s from ravestate.wrappers import ContextWrapper @@ -8,14 +8,12 @@ import ravestate_interloc -@state(cond=s("interloc:all:pushed") & s("rawio:in:changed"), write="verbaliser:intent") -def react_to_pushed_interloc(ctx: ContextWrapper): - ctx["verbaliser:intent"] = "greeting" +with Module(name="hibye"): + @state(cond=s("interloc:all:pushed") & s("rawio:in:changed"), write="verbaliser:intent") + def react_to_pushed_interloc(ctx: ContextWrapper): + ctx["verbaliser:intent"] = "greeting" -@state(cond=s("interloc:all:popped") & s("rawio:in:changed"), write="verbaliser:intent") -def react_to_popped_interloc(ctx: ContextWrapper): - ctx["verbaliser:intent"] = "farewells" - - -registry.register(name="hi_goodbye", states=(react_to_pushed_interloc, react_to_popped_interloc)) + @state(cond=s("interloc:all:popped") & s("rawio:in:changed"), write="verbaliser:intent") + def react_to_popped_interloc(ctx: ContextWrapper): + ctx["verbaliser:intent"] = "farewells" diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index b7e46e8..d0083f5 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -1,4 +1,4 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.constraint import ConfigurableAge from ravestate.constraint import s from ravestate.wrappers import ContextWrapper @@ -9,35 +9,34 @@ logger = get_logger(__name__) IMPATIENCE_THRESHOLD_CONFIG_KEY = "impatience_threshold" - - -@state(read=":activity", signal_name="bored") -def am_i_bored(ctx: ContextWrapper): - """ - Emits idle:bored signal if no states are currently partially fulfilled - """ - if ctx[":activity"] == 0: - logger.debug("Emitting idle:bored") +CONFIG = { + # duration in seconds how long ":pressure" should be true before getting impatient + IMPATIENCE_THRESHOLD_CONFIG_KEY: 0.1 +} + +with Module(name="idle", config=CONFIG): + + @state(read=":activity", signal_name="bored") + def am_i_bored(ctx: ContextWrapper): + """ + Emits idle:bored signal if no states are currently partially fulfilled + """ + if ctx[":activity"] == 0: + logger.debug("Emitting idle:bored") + 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") + def am_i_impatient(ctx: ContextWrapper): + logger.debug("Emitting idle:impatient") 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") -def am_i_impatient(ctx: ContextWrapper): - logger.debug("Emitting idle:impatient") - return Emit(wipe=True) - - -# This state is just for testing the bored signal -@state(cond=s("idle:bored"), write="rawio:out") -def play_with_me(ctx: ContextWrapper): - ctx["rawio:out"] = "Play with me, I am bored!" - + # This state is just for testing the bored signal + @state(cond=s("idle:bored"), write="rawio:out") + def play_with_me(ctx: ContextWrapper): + ctx["rawio:out"] = "Play with me, I am bored!" -registry.register(name="idle", states=(am_i_bored, am_i_impatient, play_with_me), - config={ - # duration in seconds how long ":pressure" should be true before getting impatient - IMPATIENCE_THRESHOLD_CONFIG_KEY: 0.1 - }) diff --git a/modules/ravestate_interloc/__init__.py b/modules/ravestate_interloc/__init__.py index 4df8b49..b4826f8 100644 --- a/modules/ravestate_interloc/__init__.py +++ b/modules/ravestate_interloc/__init__.py @@ -1,9 +1,8 @@ from ravestate.property import PropertyBase -from ravestate import registry +from ravestate.module import Module -_p_all = PropertyBase(name="all", allow_read=True, allow_write=False, allow_push=True, allow_pop=True) -# TODO: Make interloc:all a special property type, that only accepts ScientioNodeProperty as children -registry.register(name="interloc", props=_p_all) +with Module(name="interloc"): -p_all = _p_all.fullname() + # 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) diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index fe6e915..f3443fc 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -1,5 +1,5 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.property import PropertyBase from ravestate.state import state from ravestate_nlp.question_word import QuestionWord @@ -11,12 +11,94 @@ import spacy - from reggol import get_logger logger = get_logger(__name__) -def init_model(): +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) + + + @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:triples", + "nlp:yesno" + )) + def nlp_preprocess(ctx): + nlp_doc = nlp(ctx["rawio:in"]) + + nlp_tokens = tuple(str(token) for token in nlp_doc) + ctx["nlp: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 + logger.info(f"[NLP:postags]: {nlp_postags}") + + nlp_lemmas = tuple(str(token.lemma_) for token in nlp_doc) + ctx["nlp: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 + 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 + logger.info(f"[NLP:ner]: {nlp_ner}") + + nlp_triples = nlp_doc._.triples + ctx["nlp:triples"] = nlp_triples + logger.info(f"[NLP:triples]: {nlp_triples}") + + nlp_roboy = nlp_doc._.about_roboy + ctx["nlp:roboy"] = nlp_roboy + logger.info(f"[NLP:roboy]: {nlp_roboy}") + + nlp_yesno = nlp_doc._.yesno + ctx["nlp:yesno"] = nlp_yesno + logger.info(f"[NLP:yesno]: {nlp_yesno}") + + + @state(signal_name="contains-roboy", read="nlp:roboy") + def nlp_contains_roboy_signal(ctx): + if ctx["nlp:roboy"]: + return Emit() + return False + + + @state(signal_name="is-question", read="nlp:triples") + def nlp_is_question_signal(ctx): + if ctx["nlp:triples"][0].is_question(): + return Emit() + return False + + + @state(signal_name="yes-no", read="nlp:yesno") + def nlp_yes_no_signal(ctx): + if ctx["nlp:yesno"][0]: + return Emit() + return False + + +def init_spacy(): # TODO: Create nlp instance in :startup state, save in context instead of global var global nlp, empty_token try: @@ -41,81 +123,4 @@ def roboy_getter(doc) -> bool: Doc.set_extension('yesno', getter=yes_no) -@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:triples", "nlp:yesno")) -def nlp_preprocess(ctx): - nlp_doc = nlp(ctx["rawio:in"]) - - nlp_tokens = tuple(str(token) for token in nlp_doc) - ctx["nlp: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 - logger.info(f"[NLP:postags]: {nlp_postags}") - - nlp_lemmas = tuple(str(token.lemma_) for token in nlp_doc) - ctx["nlp: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 - 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 - logger.info(f"[NLP:ner]: {nlp_ner}") - - nlp_triples = nlp_doc._.triples - ctx["nlp:triples"] = nlp_triples - logger.info(f"[NLP:triples]: {nlp_triples}") - - nlp_roboy = nlp_doc._.about_roboy - ctx["nlp:roboy"] = nlp_roboy - logger.info(f"[NLP:roboy]: {nlp_roboy}") - - nlp_yesno = nlp_doc._.yesno - ctx["nlp:yesno"] = nlp_yesno - logger.info(f"[NLP:yesno]: {nlp_yesno}") - - -@state(signal_name="contains-roboy", read="nlp:roboy") -def nlp_contains_roboy_signal(ctx): - if ctx["nlp:roboy"]: - return Emit() - return False - - -@state(signal_name="is-question", read="nlp:triples") -def nlp_is_question_signal(ctx): - if ctx["nlp:triples"][0].is_question(): - return Emit() - return False - - -@state(signal_name="yes-no", read="nlp:yesno") -def nlp_yes_no_signal(ctx): - if ctx["nlp:yesno"][0]: - return Emit() - return False - - -init_model() -registry.register( - name="nlp", - states=( - nlp_preprocess, - nlp_contains_roboy_signal, - nlp_is_question_signal, - nlp_yes_no_signal - ), - props=( - PropertyBase(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) - ) -) +init_spacy() diff --git a/modules/ravestate_ontology/__init__.py b/modules/ravestate_ontology/__init__.py index 686f465..dae172d 100644 --- a/modules/ravestate_ontology/__init__.py +++ b/modules/ravestate_ontology/__init__.py @@ -1,4 +1,4 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.state import state from ravestate.constraint import s @@ -17,45 +17,41 @@ NEO4J_ADDRESS_KEY: str = "neo4j_address" NEO4J_USERNAME_KEY: str = "neo4j_username" NEO4J_PASSWORD_KEY: str = "neo4j_pw" +CONFIG = { + NEO4J_ADDRESS_KEY: "bolt://localhost:7687", + NEO4J_USERNAME_KEY: "neo4j", + NEO4J_PASSWORD_KEY: "neo4j" +} +with Module(name="ontology", config=CONFIG): -@state(cond=s(":startup")) -def hello_world_ontology(ctx): - """ - Creates a scientio session with neo4j backend. - (neo4j is a knowledge graph) - an ontology is loaded from a yaml file - it can be accessed with a getter - (the ontology description is a collection of named entity types, - their properties and relationships) - then the session is created - it can be accessed with a getter - """ - # TODO: Make sess and onto context properties? - global onto, sess - onto = Ontology(path_to_yaml=join(dirname(realpath(__file__)), "ravestate_ontology.yml")) - # Create a session (with default Neo4j backend) - try: - sess = Session( - ontology=onto, - neo4j_address=ctx.conf(key=NEO4J_ADDRESS_KEY), - neo4j_username=ctx.conf(key=NEO4J_USERNAME_KEY), - neo4j_password=ctx.conf(key=NEO4J_PASSWORD_KEY)) - except Exception: - logger.error( - "\n--------" - f"\nFailed to create scientio session with {ctx.conf(key=NEO4J_ADDRESS_KEY)}!" - "\nFor more info on how to set up a server, see https://pypi.org/project/scientio." - "\n--------") - sess = DummySession() - -registry.register( - name="ontology", - states=(hello_world_ontology,), - config={ - NEO4J_ADDRESS_KEY: "bolt://localhost:7687", - NEO4J_USERNAME_KEY: "neo4j", - NEO4J_PASSWORD_KEY: "neo4j" - } -) + @state(cond=s(":startup")) + def hello_world_ontology(ctx): + """ + Creates a scientio session with neo4j backend. + (neo4j is a knowledge graph) + an ontology is loaded from a yaml file - it can be accessed with a getter + (the ontology description is a collection of named entity types, + their properties and relationships) + then the session is created - it can be accessed with a getter + """ + # TODO: Make sess and onto context properties? + global onto, sess + onto = Ontology(path_to_yaml=join(dirname(realpath(__file__)), "ravestate_ontology.yml")) + # Create a session (with default Neo4j backend) + try: + sess = Session( + ontology=onto, + neo4j_address=ctx.conf(key=NEO4J_ADDRESS_KEY), + neo4j_username=ctx.conf(key=NEO4J_USERNAME_KEY), + neo4j_password=ctx.conf(key=NEO4J_PASSWORD_KEY)) + except Exception: + logger.error( + "\n--------" + f"\nFailed to create scientio session with {ctx.conf(key=NEO4J_ADDRESS_KEY)}!" + "\nFor more info on how to set up a server, see https://pypi.org/project/scientio." + "\n--------") + sess = DummySession() def get_session(): diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index ff1276d..5fbbb65 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -1,16 +1,9 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.property import PropertyBase -_p_in = PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False) -_p_out = PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False) -registry.register( - name="rawio", - props=( - PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True), - PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True)) -) +with Module(name="rawio"): -p_in = _p_in.fullname() -p_out = _p_out.fullname() + input = PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False) + output = PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False) diff --git a/modules/ravestate_roboyqa/__init__.py b/modules/ravestate_roboyqa/__init__.py index d6779d5..3c52c0c 100644 --- a/modules/ravestate_roboyqa/__init__.py +++ b/modules/ravestate_roboyqa/__init__.py @@ -1,4 +1,4 @@ -from ravestate import registry +from ravestate.module import Module from ravestate.state import state, Resign from ravestate.constraint import s from ravestate_nlp.question_word import QuestionWord @@ -23,103 +23,105 @@ # ctx["rawio:out"] = "Ask me something about myself!" -@state(cond=s("nlp:contains-roboy") & s("nlp:is-question"), read="nlp:triples", write="rawio:out") -def roboyqa(ctx): - """ - answers question regarding roboy by retrieving the information out of the neo4j roboy memory graph - state gets triggered when nlp extracts a new triple: subject, predicate, object - by analysing the triple the content of the question can be ascertained - the necessary information is gathered using the neo4j memory session - if the triple combination is known and the information could be retrieved an answer will be given - - list of questions that can be answered: - - who are you? - - what is your name? - - how old are you? - - what is your age? - - what is your hobby? - - what are your hobbies? - - what do you like? - - where are you from? - - where do you live? - - who is your father/dad? - - who is your brother/sibling? - - who is your friend? - - what do you want to become? - - what are you a member of? - - what can you do? - - what are your skills? - - what have you learned? - - what are your abilities? - """ - sess = ravestate_ontology.get_session() - roboy = sess.retrieve(node_id=ctx.conf(key=ROBOY_NODE_CONF_KEY))[0] - triple = ctx["nlp:triples"][0] - - category = None - memory_info = None - - # question word: What? - if triple.is_question(QuestionWord.OBJECT): - if triple.match_either_lemma(pred={"like"}, subj={"hobby"}): - category = "HAS_HOBBY" - elif triple.match_either_lemma(pred={"learn"}, subj={"skill"}): +with Module(name="roboyqa", config={ROBOY_NODE_CONF_KEY: 356}): + + @state(cond=s("nlp:contains-roboy") & s("nlp:is-question"), read="nlp:triples", write="rawio:out") + def roboyqa(ctx): + """ + answers question regarding roboy by retrieving the information out of the neo4j roboy memory graph + state gets triggered when nlp extracts a new triple: subject, predicate, object + by analysing the triple the content of the question can be ascertained + the necessary information is gathered using the neo4j memory session + if the triple combination is known and the information could be retrieved an answer will be given + + list of questions that can be answered: + - who are you? + - what is your name? + - how old are you? + - what is your age? + - what is your hobby? + - what are your hobbies? + - what do you like? + - where are you from? + - where do you live? + - who is your father/dad? + - who is your brother/sibling? + - who is your friend? + - what do you want to become? + - what are you a member of? + - what can you do? + - what are your skills? + - what have you learned? + - what are your abilities? + """ + sess = ravestate_ontology.get_session() + roboy = sess.retrieve(node_id=ctx.conf(key=ROBOY_NODE_CONF_KEY))[0] + triple = ctx["nlp:triples"][0] + + category = None + memory_info = None + + # question word: What? + if triple.is_question(QuestionWord.OBJECT): + if triple.match_either_lemma(pred={"like"}, subj={"hobby"}): + category = "HAS_HOBBY" + elif triple.match_either_lemma(pred={"learn"}, subj={"skill"}): + category = "skills" + elif triple.match_either_lemma(pred={"can"}, subj={"ability"}): + category = "abilities" + elif triple.match_either_lemma(subj={"age"}): + category = "age" + memory_info = roboy_age(roboy.get_properties(key="birthdate")) + elif triple.match_either_lemma(subj={"name"}): + category = "full_name" + 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): + 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): + if triple.match_either_lemma(obj={"father", "dad"}): + category = "CHILD_OF" + elif triple.match_either_lemma(obj={"brother", "sibling"}): + category = "SIBLING_OF" + elif triple.match_either_lemma(obj={"friend", "girlfriend"}): + category = "FRIEND_OF" + else: + category = "full_name" + 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): + if triple.match_either_lemma(pred={"old"}): + category = "age" + memory_info = roboy_age(roboy.get_properties(key="birthdate")) + elif triple.match_either_lemma(pred={"be"}): + category = "well_being" + elif triple.match_either_lemma(obj={"skill"}): category = "skills" - elif triple.match_either_lemma(pred={"can"}, subj={"ability"}): + elif triple.match_either_lemma(obj={"ability"}): category = "abilities" - elif triple.match_either_lemma(subj={"age"}): - category = "age" - memory_info = roboy_age(roboy.get_properties(key="birthdate")) - elif triple.match_either_lemma(subj={"name"}): - category = "full_name" - 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): - 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): - if triple.match_either_lemma(obj={"father", "dad"}): - category = "CHILD_OF" - elif triple.match_either_lemma(obj={"brother", "sibling"}): - category = "SIBLING_OF" - elif triple.match_either_lemma(obj={"friend", "girlfriend"}): - category = "FRIEND_OF" + + 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] + 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): + property_list = [x.strip() for x in roboy.get_properties(key=category).split(',')] + memory_info = random.sample(property_list, 1)[0] + + if memory_info: + ctx["rawio:out"] = verbaliser.get_random_successful_answer(category) % memory_info + elif category == "well_being": + ctx["rawio:out"] = verbaliser.get_random_successful_answer(category) else: - category = "full_name" - 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): - if triple.match_either_lemma(pred={"old"}): - category = "age" - memory_info = roboy_age(roboy.get_properties(key="birthdate")) - elif triple.match_either_lemma(pred={"be"}): - category = "well_being" - elif triple.match_either_lemma(obj={"skill"}): - category = "skills" - elif triple.match_either_lemma(obj={"ability"}): - 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] - 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): - property_list = [x.strip() for x in roboy.get_properties(key=category).split(',')] - memory_info = random.sample(property_list, 1)[0] - - if memory_info: - ctx["rawio:out"] = verbaliser.get_random_successful_answer(category) % memory_info - elif category == "well_being": - ctx["rawio:out"] = verbaliser.get_random_successful_answer(category) - else: - return Resign() + return Resign() def roboy_age(birth_date: str): @@ -135,10 +137,3 @@ def roboy_age(birth_date: str): else: age = "%d months" % (12 - birth_date.month+today.month) return age - - -registry.register( - name="roboyqa", - states=(roboyqa,), - config={ROBOY_NODE_CONF_KEY: 356} -) \ No newline at end of file diff --git a/modules/ravestate_ros2/__init__.py b/modules/ravestate_ros2/__init__.py index 014b4e3..185674e 100644 --- a/modules/ravestate_ros2/__init__.py +++ b/modules/ravestate_ros2/__init__.py @@ -1,12 +1,12 @@ -from ravestate import registry +from ravestate.module import Module from ravestate_ros2.ros2_properties import sync_ros_properties -registry.register( - name="ros2", - states=(sync_ros_properties,), - config={ - # name of the ROS2-Node that is created by ravestate_ros2 - ros2_properties.NODE_NAME_CONFIG_KEY: "ravestate_ros2", - # frequency for spinning of ROS2-Node in spins per second (0: as fast as possible) - ros2_properties.SPIN_FREQUENCY_CONFIG_KEY: 10 - }) +CONFIG = { + # name of the ROS2-Node that is created by ravestate_ros2 + ros2_properties.NODE_NAME_CONFIG_KEY: "ravestate_ros2", + # frequency for spinning of ROS2-Node in spins per second (0: as fast as possible) + ros2_properties.SPIN_FREQUENCY_CONFIG_KEY: 10 +} + +with 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 ff57e02..61c939b 100644 --- a/modules/ravestate_ros2/ros2_properties.py +++ b/modules/ravestate_ros2/ros2_properties.py @@ -77,7 +77,7 @@ def sync_ros_properties(ctx: ContextWrapper): # register subscribers in ROS if isinstance(prop, Ros2SubProperty): # register in context - @receptor(ctx_wrap=ctx, write=prop.fullname()) + @receptor(ctx_wrap=ctx, write=prop.id()) def ros_to_ctx_callback(ctx, msg, prop_name: str): ctx[prop_name] = msg @@ -146,8 +146,8 @@ def ros_subscription_callback(self, msg): * `msg`: ROS2-Message that should be written into the property """ if self.ros_to_ctx_callback: - logger.debug(f"{self.fullname()} received message {str(msg)} from topic {self.topic}") - self.ros_to_ctx_callback(msg=msg, prop_name=self.fullname()) + logger.debug(f"{self.id()} received message {str(msg)} from topic {self.topic}") + self.ros_to_ctx_callback(msg=msg, prop_name=self.id()) class Ros2PubProperty(PropertyBase): @@ -187,7 +187,7 @@ def write(self, value): """ if super().write(value): if self.publisher: - logger.debug(f"{self.fullname()} is publishing message {str(value)} on topic {self.topic}") + logger.debug(f"{self.id()} is publishing message {str(value)} on topic {self.topic}") self.publisher.publish(value) elif ROS2_AVAILABLE: logger.error(f"Message {str(value)} on topic {self.topic} " @@ -248,7 +248,7 @@ def write(self, value): # value is dict {"param1": value1, "param2": value2} req = self.service_type.Request(**value) - logger.debug(f"{self.fullname()} is sending request {str(req)} to service {self.service_name}") + logger.debug(f"{self.id()} is sending request {str(req)} to service {self.service_name}") result = self.client.call(req) super().write(result) diff --git a/modules/ravestate_telegramio/__init__.py b/modules/ravestate_telegramio/__init__.py index baee545..65ade27 100644 --- a/modules/ravestate_telegramio/__init__.py +++ b/modules/ravestate_telegramio/__init__.py @@ -1,11 +1,14 @@ -from ravestate import registry +from ravestate.module import Module from ravestate_telegramio import telegram_bot import ravestate_rawio -registry.register( - name="telegramio", - states=(telegram_bot.telegram_run, telegram_bot.telegram_output), - config={ - telegram_bot.TOKEN_CONFIG_KEY: "" - }) + +CONFIG = { + telegram_bot.TOKEN_CONFIG_KEY: "" +} + +with Module(name="telegramio", config=CONFIG) as mod: + + mod.add(telegram_bot.telegram_run) + mod.add(telegram_bot.telegram_output) diff --git a/modules/ravestate_verbaliser/__init__.py b/modules/ravestate_verbaliser/__init__.py index 78c9252..ecfa5c3 100644 --- a/modules/ravestate_verbaliser/__init__.py +++ b/modules/ravestate_verbaliser/__init__.py @@ -1,26 +1,24 @@ import logging -from ravestate import registry +from ravestate.module import Module from ravestate.property import PropertyBase from ravestate.state import state from ravestate_verbaliser import verbaliser -@state(read="verbaliser:intent", write="rawio: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"] - phrase = verbaliser.get_random_phrase(intent) - if phrase: - ctx["rawio:out"] = phrase - else: - logging.error('No phrase for intent ' + intent + ' found.') +with Module(name="verbaliser"): + intent = PropertyBase(name="intent", default_value="", allow_push=False, allow_pop=False, always_signal_changed=True) -registry.register( - name="verbaliser", - states=(react_to_intent,), - props=(PropertyBase(name="intent", default_value="", allow_push=False, allow_pop=False, always_signal_changed=True),)) + @state(read="verbaliser:intent", write="rawio: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"] + phrase = verbaliser.get_random_phrase(intent) + if phrase: + ctx["rawio:out"] = phrase + else: + logging.error('No phrase for intent ' + intent + ' found.') diff --git a/modules/ravestate_wildtalk/__init__.py b/modules/ravestate_wildtalk/__init__.py index 23a458f..87acc19 100644 --- a/modules/ravestate_wildtalk/__init__.py +++ b/modules/ravestate_wildtalk/__init__.py @@ -1,15 +1,14 @@ from ravestate.state import state -from ravestate import registry +from ravestate.module import Module from ravestate.constraint import s from roboy_parlai import wildtalk import ravestate_rawio -@state(cond=s("rawio:in:changed", max_age=-1), read="rawio:in", write="rawio:out") -def wildtalk_state(ctx): - ctx["rawio:out"] = wildtalk(ctx["rawio:in"]) +with Module(name="wildtalk"): - -registry.register(name="wildtalk", states=(wildtalk_state,)) + @state(cond=s("rawio:in:changed", max_age=-1), read="rawio:in", write="rawio:out") + def wildtalk_state(ctx): + ctx["rawio:out"] = wildtalk(ctx["rawio:in"]) diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 022ba35..8ba7997 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -39,18 +39,18 @@ def test_shutdown(mocker, context_fixture): def test_add_module_new(mocker, context_fixture): - from ravestate import registry - with mocker.patch('ravestate.registry.has_module', return_value=False): - with mocker.patch('ravestate.registry.import_module'): + from ravestate.module import import_module + with mocker.patch('ravestate.module.has_module', return_value=False): + with mocker.patch('ravestate.module.import_module'): context_fixture.add_module(DEFAULT_MODULE_NAME) - registry.import_module.assert_called_once_with( + import_module.assert_called_once_with( module_name=DEFAULT_MODULE_NAME, callback=context_fixture._module_registration_callback ) def test_add_module_present(mocker, context_fixture): - with mocker.patch('ravestate.registry.has_module', return_value=True): + with mocker.patch('ravestate.module.has_module', return_value=True): with mocker.patch.object(context_fixture, '_module_registration_callback'): context_fixture.add_module(DEFAULT_MODULE_NAME) context_fixture._module_registration_callback.assert_called_once() @@ -62,10 +62,10 @@ def test_remove_dependent_state(context_fixture: Context, state_fixture: State): context_fixture.add_prop(prop=prop) context_fixture.add_state(st=state_fixture) assert state_fixture in context_fixture._activations_per_state - assert prop.fullname() in context_fixture._properties + assert prop.id() in context_fixture._properties context_fixture.rm_prop(prop=prop) assert state_fixture not in context_fixture._activations_per_state - assert prop.fullname() not in context_fixture._properties + assert prop.id() not in context_fixture._properties def test_add_state( diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index ae1d0a4..e4c4c61 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -64,7 +64,7 @@ def under_test_context_wrapper(context_mock, state_mock): def test_property(under_test_read_only: PropertyWrapper, default_property_base: PropertyBase): - assert (default_property_base.fullname() == f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}") + 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) @@ -79,7 +79,7 @@ def test_property_no_read(under_test_nothing: PropertyWrapper, default_property_ with LogCapture(attributes=strip_prefix) as log_capture: under_test_nothing.get() log_capture.check( - f"Unauthorized read access in property-wrapper for {under_test_nothing.prop.fullname()}!", + f"Unauthorized read access in property-wrapper for {under_test_nothing.prop.id()}!", ) @@ -89,7 +89,7 @@ def test_property_read_only(under_test_read_only: PropertyWrapper, default_prope with LogCapture(attributes=strip_prefix) as log_capture: under_test_read_only.set(NEW_PROPERTY_VALUE) log_capture.check( - f"Unauthorized write access in property-wrapper {under_test_read_only.prop.fullname()}!", + f"Unauthorized write access in property-wrapper {under_test_read_only.prop.id()}!", ) assert (under_test_read_only.get() == DEFAULT_PROPERTY_VALUE) @@ -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.fullname()}:changed"), + s(f"{under_test_read_write.prop.id()}:changed"), parents=None, wipe=True) @@ -112,7 +112,7 @@ def test_flag_property(context_mock): under_test_read_write.set(True) assert (under_test_read_write.get() == True) context_mock.emit.assert_called_with( - s(f"{under_test_read_write.prop.fullname()}:changed"), + s(f"{under_test_read_write.prop.id()}:changed"), parents=None, wipe=True) From 1e08c74febd64ac706b7034f65cc9f7193460f21 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 04:27:29 +0100 Subject: [PATCH 27/46] Clean up requirements.txt --- requirements.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 134c20a..caeacf7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,11 @@ -stanfordcorenlp argparse pyyaml<4.0 -watson-developer-cloud>=2.2.6 -Flask +flask python-telegram-bot pyyaml-include roboy_parlai>=0.1.post3 scientio spacy -pytest -click flask requests scientio From 0518d040741483f9dba650520eb5cfa46e44dda6 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 04:28:05 +0100 Subject: [PATCH 28/46] Update README.md --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 4e2e2df..21c0d1d 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,6 @@ _ _ / /\/ /_/ /\ \/ / /_/ /\__, / / /_/ / / /_/ / Ravestate is a reactive library for real-time natural language dialog systems. -## Dependencies - -### portaudio on macOS - -In order to install PyAudio with pip, you need to install portaudio first using: - -`brew install portaudio` - ## Installation ### Via PIP From ed445abbdae2b78dcee56dc990b2d7e697f955fc Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 07:56:37 +0100 Subject: [PATCH 29/46] Lowering default impatience threshold Co-Authored-By: Toseban --- modules/ravestate_idle/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index 7ab243f..2bba830 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -35,5 +35,5 @@ def am_i_impatient(ctx: ContextWrapper): registry.register(name="idle", states=(am_i_bored, am_i_impatient), config={ # duration in seconds how long ":pressure" should be true before getting impatient - IMPATIENCE_THRESHOLD_CONFIG_KEY: 2. + IMPATIENCE_THRESHOLD_CONFIG_KEY: 1. }) From e24f6db30276f5e948c3aa315a3ec51118a444ec Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 08:00:21 +0100 Subject: [PATCH 30/46] Use changed_signal instead of hardcoding Co-Authored-By: Toseban --- modules/ravestate/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index de66cc0..79e5fdc 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -569,7 +569,7 @@ def _update_core_properties(self): activation_pressure_present = any(activation.is_pressured() for activation in self._state_activations()) # don't count states that have ':activity:changed' in their constraint to avoid self-influencing number_of_partially_fulfilled_states = \ - sum(1 if any(activation.spiky() and s(':activity:changed') not in list(activation.constraint.signals()) + sum(1 if any(activation.spiky() and self[":activity"].changed_signal() not in list(activation.constraint.signals()) for activation in self._activations_per_state[st]) else 0 for st in self._activations_per_state) From 35162df7eae095fb525ea843595d7c5bbd5cd118 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 13:47:12 +0100 Subject: [PATCH 31/46] akinator start gets triggered by nlp:play; cleanup of akinator api --- modules/ravestate_akinator/__init__.py | 2 +- modules/ravestate_akinator/api.py | 17 ++---- modules/ravestate_nlp/__init__.py | 72 ++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 98d335b..478c929 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -12,7 +12,7 @@ # TODO: Change this to cond=idle:bored -@state(cond=s(":startup", detached=True), +@state(cond=s("nlp:play:changed", detached=True), write="rawio:out", signal_name="initiate-play", emit_detached=True) diff --git a/modules/ravestate_akinator/api.py b/modules/ravestate_akinator/api.py index 568308c..ebd46e9 100644 --- a/modules/ravestate_akinator/api.py +++ b/modules/ravestate_akinator/api.py @@ -3,7 +3,7 @@ from reggol import get_logger logger = get_logger(__name__) - +# put in config NEW_SESSION_URL = "https://srv11.akinator.com:9152/ws/new_session?callback=&partner=&player=website-desktop&uid_ext_session=&frontaddr=NDYuMTA1LjExMC40NQ==&constraint=ETAT<>'AV'" ANSWER_URL = "https://srv11.akinator.com:9152/ws/answer" GET_GUESS_URL = "https://srv11.akinator.com:9152/ws/list" @@ -19,13 +19,6 @@ def __init__(self): self.data = requests.get(NEW_SESSION_URL).json() self.session = self.data['parameters']['identification']['session'] self.signature = self.data['parameters']['identification']['signature'] - self.dict_structure = ['parameters', 'step_information'] - - def get_session(self): - return self.session - - def get_signature(self): - return self.signature # get first question def get_parameter(self, parameter_type: str) -> str: @@ -39,8 +32,8 @@ def get_progression(self) -> float: def response_get_request(self, response: str): params = { - "session": self.get_session(), - "signature": self.get_signature(), + "session": self.session, + "signature": self.signature, "step": self.get_parameter('step'), "answer": response } @@ -48,8 +41,8 @@ def response_get_request(self, response: str): def guess_get_request(self) -> dict: params = { - "session": self.get_session(), - "signature": self.get_signature(), + "session": self.session, + "signature": self.signature, "step": self.get_parameter('step') } guess_data = requests.get(GET_GUESS_URL, params=params).json() diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index d94172a..0ee47dc 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -5,6 +5,7 @@ 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 @@ -35,7 +36,9 @@ def roboy_getter(doc) -> bool: Doc.set_extension('yesno', getter=yes_no) -@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:triples", "nlp:yesno")) +@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:triples", "nlp:yesno", "nlp:play")) def nlp_preprocess(ctx): nlp_doc = nlp(ctx["rawio:in"]) @@ -71,6 +74,10 @@ def nlp_preprocess(ctx): ctx["nlp:yesno"] = nlp_yesno logger.info(f"[NLP:yesno]: {nlp_yesno}") + if nlp_triples[0].get_object().lemma_ == "game" or nlp_triples[0].get_predicate().lemma_ == "play": + nlp_play = True + ctx["nlp:play"] = nlp_play + @state(signal_name="contains-roboy", read="nlp:roboy") def nlp_contains_roboy_signal(ctx): @@ -103,13 +110,60 @@ def nlp_yes_no_signal(ctx): nlp_yes_no_signal ), props=( - PropertyBase(name="tokens", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="postags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="lemmas", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="tags", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="ner", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="triples", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="roboy", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False), - PropertyBase(name="yesno", default_value="", always_signal_changed=True, allow_pop=False, allow_push=False) + PropertyBase( + name="tokens", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="postags", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="lemmas", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="tags", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="ner", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="triples", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="roboy", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="yesno", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False), + PropertyBase( + name="play", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False, + is_flag_property=True) ) ) From 776702504c86553008abf88058b9d00c8f90d5c0 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 15:06:55 +0100 Subject: [PATCH 32/46] doc strings and finishing touches --- modules/ravestate_akinator/__init__.py | 74 +++++++++++++++---------- modules/ravestate_akinator/api.py | 77 +++++++++++++++++++++----- 2 files changed, 107 insertions(+), 44 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 478c929..ae28968 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -17,6 +17,10 @@ signal_name="initiate-play", emit_detached=True) def akinator_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() @@ -26,6 +30,12 @@ def akinator_play_ask(ctx): write=("rawio:out", "akinator:question"), emit_detached=True) def akinator_start(ctx): + """ + Starts akinator session + Triggered by signal from akinator_play_ask state and nlp:yes-no signal given by the input + It is also triggered if one game is finished and the interlocutor wants to play again + Output: First question + """ global akinator_api if ctx["nlp:yesno"] == "yes": logger.info("Akinator session is started.") @@ -44,11 +54,17 @@ def akinator_start(ctx): write=("rawio:out", "akinator:is_it", "akinator:question", "akinator:wrong_input"), emit_detached=True) def akinator_question_answered(ctx): + """ + Reads the answer to a question and outputs the next question + Gets triggered by akinator:question which is always updated by a new question and nlp:yes-no signal + Triggers the wrong_input state id it cannot process the input + If the answer certainty of akinator is over the configurable CERTAINTY threshold the is_it state is triggered + """ global akinator_api - response = answer_to_int_str(ctx["nlp:yesno"]) + response = akinator_api.answer_to_int_str(ctx["nlp:yesno"]) if not response == "-1": akinator_api.response_get_request(response) - if akinator_api.get_progression() <= ctx.conf(key=CERTAINTY): + if float(akinator_api.get_parameter('progression')) <= ctx.conf(key=CERTAINTY): ctx["akinator:question"] = True ctx["rawio:out"] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + ":\n" + akinator_api.get_parameter('question') @@ -64,53 +80,51 @@ def akinator_question_answered(ctx): signal_name="is-it", emit_detached=True) def akinator_is_it(ctx): + """ + Outputs the solution guess of akinator: "Is this your character? ..." + Triggers the is_it_answer state + """ global akinator_api guess = akinator_api.guess_get_request() - ctx["rawio:out"] = "Is this your character? \n" + guess['name'] + "\n" + guess['desc'] + "\n" + ctx["rawio:out"] = "Is this your character? \n" + guess['name'] + "\n" + guess['desc'] \ + + "\nPlease answer with 'yes' or 'no'." return Emit() @state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), read="nlp:yesno", - write=("rawio:out", "akinator:initiate_play_again", "akinator:wrong_input"), + write=("rawio:out", "akinator:initiate_play_again"), emit_detached=True) def akinator_is_it_answered(ctx): - response = ctx["nlp:yesno"] - if not response == "-1": - if ctx["nlp:yesno"] == "yes": - out = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" - elif ctx["nlp:yesno"] == "no": - out = "I guessed wrong but do you want to play again?" - ctx["rawio:out"] = out - ctx["akinator:initiate_play_again"] = True + """ + 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": + akinator_api.choice_get_request() + out = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" + elif ctx["nlp:yesno"] == "no": + akinator_api.exclusion_get_request() + out = "I guessed wrong but do you want to play again?" else: - ctx["akinator:wrong_input"] = True - return Delete() + # TODO catch wrong input for this state + out = "What? But do you want to play again?" + ctx["rawio:out"] = out + ctx["akinator:initiate_play_again"] = True @state(cond=s("akinator:wrong_input:changed", detached=True), write=("rawio:out", "akinator:question"), emit_detached=True) def akinator_wrong_input(ctx): - ctx["rawio:out"] = "Sadly I could not process that answer. Try to answer with 'yes' or 'no' please." + """ + 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. Remember that you have these five answering choices: " \ + "\n'yes', 'no', 'i do not know', 'probably', 'probably not'" ctx["akinator:question"] = True -def answer_to_int_str(answer: str): - if answer == "yes": - return "0" - elif answer == "no": - return "1" - elif answer == "idk": - return "2" - elif answer == "p": - return "3" - elif answer == "pn": - return "4" - else: - return "-1" - - registry.register( name="akinator", states=( diff --git a/modules/ravestate_akinator/api.py b/modules/ravestate_akinator/api.py index ebd46e9..e9bc690 100644 --- a/modules/ravestate_akinator/api.py +++ b/modules/ravestate_akinator/api.py @@ -1,36 +1,40 @@ import requests -from reggol import get_logger -logger = get_logger(__name__) - # put in config NEW_SESSION_URL = "https://srv11.akinator.com:9152/ws/new_session?callback=&partner=&player=website-desktop&uid_ext_session=&frontaddr=NDYuMTA1LjExMC40NQ==&constraint=ETAT<>'AV'" ANSWER_URL = "https://srv11.akinator.com:9152/ws/answer" GET_GUESS_URL = "https://srv11.akinator.com:9152/ws/list" -# CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" -# EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" -# GLB_URL = "https://pastebin.com/gTua3dg2" - -akinator_data = None +CHOICE_URL = "https://srv11.akinator.com:9152/ws/choice" +EXCLUSION_URL = "https://srv11.akinator.com:9152/ws/exclusion" class Api: + """ + Takes care of the fancy nancy akinator business + """ def __init__(self): self.data = requests.get(NEW_SESSION_URL).json() self.session = self.data['parameters']['identification']['session'] self.signature = self.data['parameters']['identification']['signature'] + self.guess_data = None # get first question def get_parameter(self, parameter_type: str) -> str: + """ + Get method for the get request parameters + parameter types are: 'step', 'question', 'progression' + the first call dictionary has an additional nested dict under the key:'step_information' in which the + parameters are found. The following calls do not have that. + """ if 'step_information' in self.data['parameters']: return self.data['parameters']['step_information'][parameter_type] else: return self.data['parameters'][parameter_type] - def get_progression(self) -> float: - return float(self.data['parameters']['progression']) - def response_get_request(self, response: str): + """ + Get request for the next question with the response to the previous question in the parameters + """ params = { "session": self.session, "signature": self.signature, @@ -40,13 +44,58 @@ def response_get_request(self, response: str): self.data = requests.get(ANSWER_URL, params=params).json() def guess_get_request(self) -> dict: + """ + Get request for the guess + """ params = { "session": self.session, "signature": self.signature, "step": self.get_parameter('step') } - guess_data = requests.get(GET_GUESS_URL, params=params).json() - guess = {"name": guess_data['parameters']['elements'][0]['element']['name'], - "desc":guess_data['parameters']['elements'][0]['element']['description'] + self.guess_data = requests.get(GET_GUESS_URL, params=params).json() + guess = {"name": self.guess_data['parameters']['elements'][0]['element']['name'], + "desc": self.guess_data['parameters']['elements'][0]['element']['description'] } return guess + + def choice_get_request(self): + """ + Get request which is triggered if the guess was right + """ + params = { + "session": self.session, + "signature": self.signature, + "step": self.get_parameter('step'), + "element": self.guess_data['parameters']['elements'][0]['element']['id'] + } + requests.get(CHOICE_URL, params=params) + + def exclusion_get_request(self): + """ + Get request which is triggered if the guess was wrong + """ + params = { + "session": self.session, + "signature": self.signature, + "step": self.get_parameter('step'), + "forward_answer": self.answer_to_int_str("no") + } + requests.get(EXCLUSION_URL, params=params) + + def answer_to_int_str(self, answer: str): + """ + String answer to an integer string which can be processed by akinator api + """ + if answer == "yes": + return "0" + elif answer == "no": + return "1" + elif answer == "idk": + return "2" + elif answer == "p": + return "3" + elif answer == "pn": + return "4" + else: + return "-1" + From 368e55e37d240390bbc8c9d29203497c3fe56f49 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 15:08:11 +0100 Subject: [PATCH 33/46] small addition to object set and refactoring of nlp:play property --- modules/ravestate_nlp/__init__.py | 2 +- modules/ravestate_nlp/extract_triples.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 0ee47dc..53561b8 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -74,7 +74,7 @@ def nlp_preprocess(ctx): ctx["nlp:yesno"] = nlp_yesno logger.info(f"[NLP:yesno]: {nlp_yesno}") - if nlp_triples[0].get_object().lemma_ == "game" or nlp_triples[0].get_predicate().lemma_ == "play": + if nlp_triples[0].match_either_lemma(pred={"play"}, obj={"game"}): nlp_play = True ctx["nlp:play"] = nlp_play diff --git a/modules/ravestate_nlp/extract_triples.py b/modules/ravestate_nlp/extract_triples.py index 24e21ed..6ca1d97 100644 --- a/modules/ravestate_nlp/extract_triples.py +++ b/modules/ravestate_nlp/extract_triples.py @@ -3,7 +3,7 @@ from spacy.tokens import Token SUBJECT_SET = {"nsubj"} -OBJECT_SET = {"dobj", "attr", "advmod"} +OBJECT_SET = {"dobj", "attr", "advmod", "pobj"} PREDICATE_SET = {"ROOT", "conj"} PREDICATE_AUX_SET = {"acomp", "aux", "xcomp"} VERB_AUX_SET = {"VERB", "ADJ"} From 328833b6c756b66578bb33f11d67814c9c250edf Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 19:52:52 +0100 Subject: [PATCH 34/46] Update yes_no.py --- modules/ravestate_nlp/yes_no.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py index a69162f..98f0ea7 100644 --- a/modules/ravestate_nlp/yes_no.py +++ b/modules/ravestate_nlp/yes_no.py @@ -4,6 +4,7 @@ def yes_no(doc): """ checks input for "yes", "no", "i don't know", "probably" and "probably not" + TODO: This should also map other inputs like ("(for|not) sure", "definitely, "dont think so"...) """ nlp_tokens = tuple(str(token) for token in doc) for token in nlp_tokens: From 898e114c546986f031c0105d839ad745e9c3a82e Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Mon, 28 Jan 2019 20:47:37 +0100 Subject: [PATCH 35/46] Fixed context tests. --- test/modules/ravestate/test_context.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 808bbeb..29aab64 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -7,9 +7,9 @@ def test_emit(context_fixture, spike_fixture): context_fixture.emit(s(DEFAULT_PROPERTY_CHANGED)) assert len(context_fixture._spikes) == 1 - list(context_fixture._spikes.keys())[0].adopt(spike_fixture) + list(context_fixture._spikes)[0].adopt(spike_fixture) context_fixture.emit(s(DEFAULT_PROPERTY_CHANGED), wipe=True) - assert len(context_fixture._spikes) == 3 + assert len(context_fixture._spikes) == 2 def test_run(mocker, context_fixture): @@ -101,7 +101,11 @@ def test_add_state( # Make sure, that d's constraint was completed correctly d_conjunctions = list(state_signal_d_fixture.constraint_.conjunctions()) - assert len(d_conjunctions) == 2 + assert len(d_conjunctions) == 4 # 2 completed, 2 uncompleted + d_conjunctions = [ + 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 state_signal_a_fixture.signal() in d_conjunctions[0] assert s(DEFAULT_PROPERTY_CHANGED) in d_conjunctions[1] @@ -124,8 +128,8 @@ def test_add_state( 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 - assert b_acts[0].specificity() == a_sig_spec + propchange_sig_spec - assert 1.53 < d_acts[0].specificity() < 1.54 + assert b_acts[0].specificity() == a_sig_spec + assert d_acts[0].specificity() == 1.0 def test_add_state_configurable_age(context_with_property_fixture: Context): From 64d09c329e91435d472781b86e6d2c63069cfa25 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 22:28:40 +0100 Subject: [PATCH 36/46] yes no detection is not case sensitive --- modules/ravestate_nlp/yes_no.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py index 98f0ea7..9a11bf0 100644 --- a/modules/ravestate_nlp/yes_no.py +++ b/modules/ravestate_nlp/yes_no.py @@ -6,7 +6,7 @@ def yes_no(doc): checks input for "yes", "no", "i don't know", "probably" and "probably not" TODO: This should also map other inputs like ("(for|not) sure", "definitely, "dont think so"...) """ - nlp_tokens = tuple(str(token) for token in doc) + nlp_tokens = tuple(str(token.text.lower()) for token in doc) for token in nlp_tokens: if token == "yes" or token == "y": return "yes" From 8ff27de75bf120e1aa68447cecd7c283e69fe08e Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 22:40:04 +0100 Subject: [PATCH 37/46] akinator_play_ask state can also be triggered by idle:bored --- modules/ravestate_akinator/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index ae28968..8b883f0 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -11,8 +11,7 @@ CERTAINTY = "certainty_percentage" -# TODO: Change this to cond=idle:bored -@state(cond=s("nlp:play:changed", detached=True), +@state(cond=s("nlp:play:changed", detached=True) | s("idle:bored", detached=True), write="rawio:out", signal_name="initiate-play", emit_detached=True) From 965e464fec3c561adc51cea2f9d39cfd50991ae7 Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 23:28:24 +0100 Subject: [PATCH 38/46] maps multiple synonyms of yes, no, probably.... --- modules/ravestate_nlp/yes_no.py | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py index 9a11bf0..423a4e5 100644 --- a/modules/ravestate_nlp/yes_no.py +++ b/modules/ravestate_nlp/yes_no.py @@ -1,24 +1,30 @@ -NEGATION_SET = {"neg"} +NEGATION_TUPLE = ["neg", "not"] +YES_SYNONYMS = {"yes", "y", "yeah", "sure", "definitely", " certainly"} +NO_SYNONYMS = {"no", "n", "nope", "negative", "never", "nay"} +PROBABLY_SYNONYMS = {"probably", "likely", "possibly", "perhaps", "maybe"} +DON_NOT_KNOW_SET = {"know", "understand", "idea"} def yes_no(doc): """ - checks input for "yes", "no", "i don't know", "probably" and "probably not" - TODO: This should also map other inputs like ("(for|not) sure", "definitely, "dont think so"...) + checks input for "yes", "no", "i don't know", "probably" and "probably not" and synonyms of these """ nlp_tokens = tuple(str(token.text.lower()) for token in doc) - for token in nlp_tokens: - if token == "yes" or token == "y": + if len(nlp_tokens) == 1: + if nlp_tokens[0] in YES_SYNONYMS: return "yes" - elif token == "no" or token == "n": + elif nlp_tokens[0] in NO_SYNONYMS: return "no" - for token in doc: - if token.dep_ in NEGATION_SET: - if "know" in nlp_tokens: - return "idk" - elif "probably" in nlp_tokens: - return "pn" - elif "probably" in nlp_tokens: + elif nlp_tokens[0] in PROBABLY_SYNONYMS: return "p" - return "0" + nlp_token_dep = tuple(str(token.dep_) for token in doc) + if NEGATION_TUPLE[0] in nlp_token_dep or NEGATION_TUPLE[1] in nlp_tokens: + for token in nlp_tokens: + if token in PROBABLY_SYNONYMS: + return "pn" + elif token in YES_SYNONYMS: + return "no" + elif token in DON_NOT_KNOW_SET: + return "idk" + return "0" From 78ab084b8d695dc4534fbab25c5de136612ef64b Mon Sep 17 00:00:00 2001 From: Andreas Dolp Date: Mon, 28 Jan 2019 23:56:13 +0100 Subject: [PATCH 39/46] make roboyQA an active engagement --- modules/ravestate_roboyqa/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/ravestate_roboyqa/__init__.py b/modules/ravestate_roboyqa/__init__.py index d6779d5..62cf78c 100644 --- a/modules/ravestate_roboyqa/__init__.py +++ b/modules/ravestate_roboyqa/__init__.py @@ -17,10 +17,9 @@ ROBOY_NODE_CONF_KEY = "roboy_node_id" -# TODO: Change this to cond=idle:bored -# @state(cond=s(":startup"), write="rawio:out") -# def hello_world_roboyqa(ctx): -# ctx["rawio:out"] = "Ask me something about myself!" +@state(cond=s("idle:bored"), write="rawio:out") +def hello_world_roboyqa(ctx): + ctx["rawio:out"] = "Ask me something about myself!" @state(cond=s("nlp:contains-roboy") & s("nlp:is-question"), read="nlp:triples", write="rawio:out") From 242fb130192aa17809147768161bf8ca14c4f9ee Mon Sep 17 00:00:00 2001 From: NeginsCode Date: Mon, 28 Jan 2019 23:57:03 +0100 Subject: [PATCH 40/46] removed play property and exchanged with intent-play signal --- modules/ravestate_akinator/__init__.py | 2 +- modules/ravestate_nlp/__init__.py | 27 ++++++++++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index 8b883f0..a698497 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -11,7 +11,7 @@ CERTAINTY = "certainty_percentage" -@state(cond=s("nlp:play:changed", detached=True) | s("idle:bored", detached=True), +@state(cond=s("nlp:intent-play", detached=True) | s("idle:bored", detached=True), write="rawio:out", signal_name="initiate-play", emit_detached=True) diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index 74219d5..45b405f 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -44,7 +44,7 @@ def roboy_getter(doc) -> bool: @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:triples", "nlp:yesno", "nlp:play")) + write=("nlp:tokens", "nlp:postags", "nlp:lemmas", "nlp:tags", "nlp:ner", "nlp:triples", "nlp:roboy","nlp:triples", "nlp:yesno")) def nlp_preprocess(ctx): nlp_doc = nlp(ctx["rawio:in"]) @@ -80,10 +80,6 @@ def nlp_preprocess(ctx): ctx["nlp:yesno"] = nlp_yesno logger.info(f"[NLP:yesno]: {nlp_yesno}") - if nlp_triples[0].match_either_lemma(pred={"play"}, obj={"game"}): - nlp_play = True - ctx["nlp:play"] = nlp_play - @state(signal_name="contains-roboy", read="nlp:roboy") def nlp_contains_roboy_signal(ctx): @@ -94,7 +90,7 @@ def nlp_contains_roboy_signal(ctx): @state(signal_name="is-question", read="nlp:triples") def nlp_is_question_signal(ctx): - if ctx["nlp:triples"][0].is_question(): + if ctx["nlp:triples"] and ctx["nlp:triples"][0].is_question(): return Emit() return False @@ -106,6 +102,13 @@ def nlp_yes_no_signal(ctx): return False +@state(signal_name="intent-play", read="nlp:triples") +def nlp_play_signal(ctx): + if ctx["nlp:triples"] and ctx["nlp:triples"][0].match_either_lemma(pred={"play"}, obj={"game"}): + return Emit() + return False + + init_model() registry.register( name="nlp", @@ -113,7 +116,8 @@ def nlp_yes_no_signal(ctx): nlp_preprocess, nlp_contains_roboy_signal, nlp_is_question_signal, - nlp_yes_no_signal + nlp_yes_no_signal, + nlp_play_signal ), props=( PropertyBase( @@ -163,13 +167,6 @@ def nlp_yes_no_signal(ctx): default_value="", always_signal_changed=True, allow_pop=False, - allow_push=False), - PropertyBase( - name="play", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False, - is_flag_property=True) + allow_push=False) ) ) From 754a3aef1b8ec91bc4f8d8a533e12f07ddf0ff5a Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 29 Jan 2019 02:23:18 +0100 Subject: [PATCH 41/46] Finished new Module declaration system in preparation of typed property/signal dependencies. Fixed a number of bugs with integrated Roboy personality. --- config/roboy.yml | 3 +- modules/ravestate/activation.py | 3 + modules/ravestate/causal.py | 3 +- modules/ravestate/constraint.py | 3 + modules/ravestate/context.py | 46 +-- modules/ravestate/module.py | 13 +- modules/ravestate/property.py | 6 +- modules/ravestate/state.py | 9 +- modules/ravestate/threadlocal.py | 5 + modules/ravestate_akinator/__init__.py | 300 +++++++++--------- modules/ravestate_facerec/__init__.py | 14 +- modules/ravestate_genqa/__init__.py | 2 +- modules/ravestate_idle/__init__.py | 32 +- modules/ravestate_nlp/__init__.py | 149 +-------- modules/ravestate_nlp/yes_no.py | 2 +- modules/ravestate_rawio/__init__.py | 4 +- modules/ravestate_roboyqa/__init__.py | 11 +- modules/ravestate_wildtalk/__init__.py | 5 +- setup.py | 2 +- .../ravestate/test_wrappers_property.py | 18 +- 20 files changed, 232 insertions(+), 398 deletions(-) create mode 100644 modules/ravestate/threadlocal.py diff --git a/config/roboy.yml b/config/roboy.yml index b976c26..2042699 100644 --- a/config/roboy.yml +++ b/config/roboy.yml @@ -26,8 +26,9 @@ config: - ravestate_roboyqa - ravestate_genqa - ravestate_idle - - ravestate_fillers - ravestate_akinator + # TODO: Fillers sometimes overwrite good responses by race condition. + # - ravestate_fillers --- module: genqa diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index def01ae..727b3a0 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -170,6 +170,9 @@ def update(self) -> bool: if not conjunction.evaluate(): continue + # Death is cheated if the activation is fulfilled. + 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()) consenting_causal_groups = set() diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index c48375f..c2dde5f 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -286,7 +286,8 @@ def consent(self, ready_suitor: IActivation) -> bool: return False if higher_specificity_acts: - highest_higher_specificity_act.pressure() + for act in higher_specificity_acts: + act.pressure() logger.debug( f"{self}.consent({ready_suitor})->N: " f"{str(specificity)[:4]} < {str(highest_higher_specificity)[:4]} " diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 84dc8fe..4e3158e 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -110,6 +110,9 @@ def __eq__(self, other): def __hash__(self): return hash(self.name) + def __repr__(self): + return f"Signal({self.name}, {self.min_age}, {self.max_age}, {self.detached})" + def signals(self) -> Generator['Signal', None, None]: yield self diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index 8b5c620..bc14a99 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -1,5 +1,4 @@ # Ravestate context class -import copy from threading import Thread, Lock, Event from typing import Optional, Any, Tuple, Set, Dict, Iterable, List from collections import defaultdict @@ -43,7 +42,7 @@ def shutdown(**kwargs) -> Signal: class Context(IContext): - _default_signal_names: Tuple[str] = (startup(), shutdown()) + _default_signals: Tuple[Signal] = (startup(), shutdown()) _default_properties: Tuple[PropertyBase] = ( PropertyBase( name="pressure", @@ -128,8 +127,8 @@ def __init__(self, *arguments): self._run_task = None # Register default signals - for signame in self._default_signal_names: - self._add_sig(s(signame)) + for signal in self._default_signals: + self._add_sig(signal) # Register default properties for prop in self._default_properties: @@ -175,7 +174,6 @@ def wipe(self, signal: Signal): should be invalidated and forgotten. """ with self._lock: - spikes_change = {} for spike in self._spikes: if spike.name() == signal.name: spike.wipe() @@ -288,7 +286,8 @@ def add_state(self, *, st: State) -> None: # register the state's consumable dummy, so that it is passed # to Spike and from there to CausalGroup as a consumable resource. - self.add_prop(prop=st.consumable) + if st.consumable.id() not in self._properties: + self.add_prop(prop=st.consumable) def rm_state(self, *, st: State) -> None: """ @@ -394,6 +393,8 @@ def lowest_upper_bound_eta(self, signals: Set[Signal]) -> int: """ Called by activation when it is pressured to resign. The activation wants to know the earliest ETA of one of it's remaining required constraints. + Also called by constraint completion algorithm, to figure out the maximum + age for a completed constraint. * `signals`: The signals, whose ETA will be calculated, and among the results the minimum ETA will be returned. @@ -402,7 +403,7 @@ def lowest_upper_bound_eta(self, signals: Set[Signal]) -> int: signals to arrive. Fixed value (1) for now. """ # TODO: Proper implementation w/ state runtime_upper_bound - return 3 + return self.secs_to_ticks(.5) def signal_specificity(self, sig: Signal) -> float: """ @@ -554,7 +555,7 @@ def _complete_constraint(self, st: State): def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal]) -> List[Set[Signal]]: result = [set(deepcopy(sig) for sig in conj.signals())] for sig in result[0]: - # maximum age for completions is infinite + # TODO: Figure out through eta system sig.max_age = -1 for conj_sig in conj.signals(): @@ -592,17 +593,24 @@ def _complete_signal(self, sig: Signal, known_signals: Set[Signal]) -> Optional[ def _update_core_properties(self): with self._lock: - activation_pressure_present = any(activation.is_pressured() for activation in self._state_activations()) - # don't count states that have ':activity:changed' in their constraint to avoid self-influencing - number_of_partially_fulfilled_states = \ - sum(1 if any(activation.spiky() and self[":activity"].changed_signal() not in list(activation.constraint.signals()) - for activation in self._activations_per_state[st]) else 0 - for st in self._activations_per_state) - - PropertyWrapper(prop=self[":pressure"], ctx=self, allow_write=True, allow_read=True) \ - .set(activation_pressure_present) - PropertyWrapper(prop=self[":activity"], ctx=self, allow_write=True, allow_read=True) \ - .set(number_of_partially_fulfilled_states) + pressured_acts = False + partially_fulfilled_acts = 0 + for act in self._state_activations(): + if self[":activity"].changed_signal() not in set(act.constraint.signals()): + pressured_acts |= act.is_pressured() + partially_fulfilled_acts += act.spiky() + PropertyWrapper( + prop=self[":pressure"], + ctx=self, + allow_write=True, + allow_read=True + ).set(pressured_acts) + PropertyWrapper( + prop=self[":activity"], + ctx=self, + allow_write=True, + allow_read=True + ).set(partially_fulfilled_acts) def _run_private(self): diff --git a/modules/ravestate/module.py b/modules/ravestate/module.py index e6f9f04..8b64c50 100644 --- a/modules/ravestate/module.py +++ b/modules/ravestate/module.py @@ -4,7 +4,7 @@ import importlib from ravestate.property import PropertyBase from ravestate.state import State -import threading +from ravestate.threadlocal import ravestate_thread_local from reggol import get_logger logger = get_logger(__name__) @@ -16,7 +16,6 @@ class Module: which form a coherent bundle. """ - thread_local = threading.local() registered_modules: Dict[str, 'Module'] = dict() registration_callback: Callable[['Module'], Any] = None # set by import_module @@ -40,19 +39,19 @@ def __init__(self, *, name: str, config: Dict[str, Any]=None): if name in self.registered_modules: logger.error(f"Adding module {name} twice!") self.registered_modules[name] = self - if self.registration_callback: - self.registration_callback(self) def __enter__(self): - mod = getattr(self.thread_local, 'module_under_construction', None) + mod = getattr(ravestate_thread_local, 'module_under_construction', None) if mod: logger.error("Nested `with Module(...)` calls are not supported!`") else: - self.thread_local.module_under_construction = self + ravestate_thread_local.module_under_construction = self return self def __exit__(self, exc_type, exc_val, exc_tb): - self.thread_local.module_under_construction = None + ravestate_thread_local.module_under_construction = None + if self.registration_callback: + self.registration_callback(self) def add(self, property_or_state: Union[PropertyBase, State, Iterable[PropertyBase], Iterable[State]]): try: diff --git a/modules/ravestate/property.py b/modules/ravestate/property.py index 7f3e864..5209475 100644 --- a/modules/ravestate/property.py +++ b/modules/ravestate/property.py @@ -3,7 +3,7 @@ from threading import Lock from typing import Dict, List, Generator from ravestate.constraint import s, Signal -import threading +from ravestate.threadlocal import ravestate_thread_local from reggol import get_logger logger = get_logger(__name__) @@ -51,8 +51,6 @@ class PropertyBase: property name basic impls. for the property value, parent/child mechanism. """ - thread_local = threading.local() - def __init__( self, *, name="", @@ -77,7 +75,7 @@ def __init__( self.is_flag_property = is_flag_property # add property to module in current `with Module(...)` clause - module_under_construction = getattr(self.thread_local, 'module_under_construction', None) + module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) if module_under_construction: module_under_construction.add(self) diff --git a/modules/ravestate/state.py b/modules/ravestate/state.py index 9eac388..0f6d90f 100644 --- a/modules/ravestate/state.py +++ b/modules/ravestate/state.py @@ -1,8 +1,7 @@ # Ravestate State-related definitions from typing import Optional, Tuple, Union -import threading - +from ravestate.threadlocal import ravestate_thread_local from ravestate.constraint import Conjunct, Disjunct, Signal, s, Constraint from ravestate.consumable import Consumable @@ -73,8 +72,6 @@ class State: # for states that don't have any write-props. consumable: Consumable - thread_local = threading.local() - def __init__(self, *, signal_name: Optional[str], write: Union[str, Tuple[str]], @@ -86,7 +83,7 @@ def __init__(self, *, assert(callable(action)) self.name = action.__name__ - self.consumable = Consumable(f"@{action.__module__}:{action.__name__}") + self.consumable = Consumable(f"@{action.__name__}") # check to recognize states using old signal implementation if isinstance(cond, str): @@ -121,7 +118,7 @@ def __init__(self, *, self.emit_detached = emit_detached # add state to module in current `with Module(...)` clause - module_under_construction = getattr(self.thread_local, 'module_under_construction', None) + module_under_construction = getattr(ravestate_thread_local, 'module_under_construction', None) if module_under_construction: module_under_construction.add(self) diff --git a/modules/ravestate/threadlocal.py b/modules/ravestate/threadlocal.py new file mode 100644 index 0000000..579b005 --- /dev/null +++ b/modules/ravestate/threadlocal.py @@ -0,0 +1,5 @@ +import threading + +# Local store used by Module, PropertyBase and State, +# to facilitate `with Module(...)` enter/exit. +ravestate_thread_local = threading.local() diff --git a/modules/ravestate_akinator/__init__.py b/modules/ravestate_akinator/__init__.py index ea21ad9..092f658 100644 --- a/modules/ravestate_akinator/__init__.py +++ b/modules/ravestate_akinator/__init__.py @@ -7,163 +7,155 @@ from reggol import get_logger logger = get_logger(__name__) +import ravestate_idle +import ravestate_nlp +import ravestate_rawio + akinator_api: Api CERTAINTY = "certainty_percentage" - -# TODO: Change this to cond=idle:bored -@state(cond=s("nlp:play:changed", detached=True), - write="rawio:out", - signal_name="initiate-play", - emit_detached=True) -def akinator_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() - - -@state(cond=s("nlp:yes-no") & (s("akinator:initiate-play", max_age=-1) | s("akinator:initiate_play_again:changed", max_age=-1, detached=True)), - read="nlp:yesno", - write=("rawio:out", "akinator:question"), - emit_detached=True) -def akinator_start(ctx): - """ - Starts akinator session - Triggered by signal from akinator_play_ask state and nlp:yes-no signal given by the input - It is also triggered if one game is finished and the interlocutor wants to play again - Output: First question - """ - global akinator_api - if ctx["nlp:yesno"] == "yes": - logger.info("Akinator session is started.") - akinator_api = Api() - ctx["akinator:question"] = True - ctx["rawio:out"] = "You can answer the questions with:" \ - + '\n"yes", "no", "i do not know", "probably", "probably not"' \ - + "\nQuestion " + str(int(akinator_api.get_parameter('step')) + 1) \ - + ":\n" + akinator_api.get_parameter('question') - else: - return Resign() - - -@state(cond=s("nlp:yes-no") & s("akinator:question:changed", detached=True, max_age=-1), - read="nlp:yesno", - write=("rawio:out", "akinator:is_it", "akinator:question", "akinator:wrong_input"), - emit_detached=True) -def akinator_question_answered(ctx): - """ - Reads the answer to a question and outputs the next question - Gets triggered by akinator:question which is always updated by a new question and nlp:yes-no signal - Triggers the wrong_input state id it cannot process the input - 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"]) - if not response == "-1": - akinator_api.response_get_request(response) - if float(akinator_api.get_parameter('progression')) <= ctx.conf(key=CERTAINTY): +with Module(name="akinator", config={CERTAINTY: 90}): + + initiate_play_again = PropertyBase( + name="initiate_play_again", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False, + is_flag_property=True) + + is_it = PropertyBase( + name="is_it", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False, + is_flag_property=True) + + question = PropertyBase( + name="question", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False, + is_flag_property=True) + + wrong_input = PropertyBase( + name="wrong_input", + default_value="", + always_signal_changed=True, + allow_pop=False, + allow_push=False, + is_flag_property=True) + + @state(cond=s("nlp:intent-play") | s("idle:bored"), + write="rawio:out", + signal_name="initiate-play", + emit_detached=True) + def akinator_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() + + + @state(cond=s("nlp:yes-no") & (s("akinator:initiate-play", max_age=-1) | s("akinator:initiate_play_again:changed", max_age=-1)), + read="nlp:yesno", + write=("rawio:out", "akinator:question"), + emit_detached=True) + def akinator_start(ctx): + """ + Starts akinator session + Triggered by signal from akinator_play_ask state and nlp:yes-no signal given by the input + It is also triggered if one game is finished and the interlocutor wants to play again + Output: First question + """ + global akinator_api + if ctx["nlp:yesno"] == "yes": + logger.info("Akinator session is started.") + akinator_api = Api() ctx["akinator:question"] = True - ctx["rawio:out"] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + ctx["rawio:out"] = "You can answer the questions with:" \ + + '\n"yes", "no", "i do not know", "probably", "probably not"' \ + + "\nQuestion " + str(int(akinator_api.get_parameter('step')) + 1) \ + ":\n" + akinator_api.get_parameter('question') else: - ctx["akinator:is_it"] = True - else: - ctx["akinator:wrong_input"] = True - - -@state(cond=s("akinator:is_it:changed", detached=True), - read="akinator:question", - write="rawio:out", - signal_name="is-it", - emit_detached=True) -def akinator_is_it(ctx): - """ - Outputs the solution guess of akinator: "Is this your character? ..." - Triggers the is_it_answer state - """ - global akinator_api - guess = akinator_api.guess_get_request() - ctx["rawio:out"] = "Is this your character? \n" + guess['name'] + "\n" + guess['desc'] \ - + "\nPlease answer with 'yes' or 'no'." - return Emit() - - -@state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), - read="nlp:yesno", - write=("rawio:out", "akinator:initiate_play_again"), - emit_detached=True) -def akinator_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": - akinator_api.choice_get_request() - out = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" - elif ctx["nlp:yesno"] == "no": - akinator_api.exclusion_get_request() - out = "I guessed wrong but do you want to play again?" - else: - # TODO catch wrong input for this state - out = "What? But do you want to play again?" - ctx["rawio:out"] = out - ctx["akinator:initiate_play_again"] = True - - -@state(cond=s("akinator:wrong_input:changed", detached=True), - write=("rawio:out", "akinator:question"), - emit_detached=True) -def akinator_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. Remember that you have these five answering choices: " \ - "\n'yes', 'no', 'i do not know', 'probably', 'probably not'" - ctx["akinator:question"] = True - - -registry.register( - name="akinator", - states=( - akinator_is_it, - akinator_is_it_answered, - akinator_play_ask, - akinator_start, - akinator_question_answered, - akinator_wrong_input - ), - props=( - PropertyBase( - name="initiate_play_again", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False, - is_flag_property=True), - PropertyBase( - name="is_it", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False, - is_flag_property=True), - PropertyBase( - name="question", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False, - is_flag_property=True), - PropertyBase( - name="wrong_input", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False, - is_flag_property=True) - ), - config={CERTAINTY: 90} -) + return Resign() + + + @state(cond=s("nlp:yes-no") & s("akinator:question:changed", max_age=-1), + read="nlp:yesno", + write=("rawio:out", "akinator:is_it", "akinator:question", "akinator:wrong_input"), + emit_detached=True) + def akinator_question_answered(ctx): + """ + Reads the answer to a question and outputs the next question + Gets triggered by akinator:question which is always updated by a new question and nlp:yes-no signal + Triggers the wrong_input state id it cannot process the input + 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"]) + if not response == "-1": + akinator_api.response_get_request(response) + if float(akinator_api.get_parameter('progression')) <= ctx.conf(key=CERTAINTY): + ctx["akinator:question"] = True + ctx["rawio:out"] = "Question " + str(int(akinator_api.get_parameter('step')) + 1) \ + + ":\n" + akinator_api.get_parameter('question') + else: + ctx["akinator:is_it"] = True + else: + ctx["akinator:wrong_input"] = True + + + @state(cond=s("akinator:is_it:changed"), + read="akinator:question", + write="rawio:out", + signal_name="is-it", + emit_detached=True) + def akinator_is_it(ctx): + """ + Outputs the solution guess of akinator: "Is this your character? ..." + Triggers the is_it_answer state + """ + global akinator_api + guess = akinator_api.guess_get_request() + ctx["rawio:out"] = "Is this your character? \n" + guess['name'] + "\n" + guess['desc'] \ + + "\nPlease answer with 'yes' or 'no'." + return Emit() + + + @state(cond=s("nlp:yes-no") & s("akinator:is-it", max_age=-1), + read="nlp:yesno", + write=("rawio:out", "akinator:initiate_play_again"), + emit_detached=True) + def akinator_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": + akinator_api.choice_get_request() + out = "Yeah! I guessed right! Thanks for playing with me! \nDo you want to play again?" + elif ctx["nlp:yesno"] == "no": + akinator_api.exclusion_get_request() + out = "I guessed wrong but do you want to play again?" + else: + # TODO catch wrong input for this state + out = "What? But do you want to play again?" + ctx["rawio:out"] = out + ctx["akinator:initiate_play_again"] = True + + + @state(cond=s("akinator:wrong_input:changed"), + write=("rawio:out", "akinator:question"), + emit_detached=True) + def akinator_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. Remember that you have these five answering choices: " \ + "\n'yes', 'no', 'i do not know', 'probably', 'probably not'" + ctx["akinator:question"] = True diff --git a/modules/ravestate_facerec/__init__.py b/modules/ravestate_facerec/__init__.py index 4e8ab28..210cc43 100644 --- a/modules/ravestate_facerec/__init__.py +++ b/modules/ravestate_facerec/__init__.py @@ -10,13 +10,8 @@ rclpy.init() node = rclpy.create_node("vision_node") -<<<<<<< HEAD -with Module(name="facerec"): -======= -@state(cond=s(":startup")) -def facerec_run(ctx): ->>>>>>> b21a042c31198e97e854d4b01b4d5aa74679bb54 +with Module(name="facerec"): face = PropertyBase(name="face", default_value="") @@ -31,14 +26,7 @@ def face_recognition_callback(ctx, msg): rclpy.spin(node) -<<<<<<< HEAD @state(cond=s(":shutdown")) def facerec_shutdown(): node.destroy_node() rclpy.shutdown() -======= -registry.register( - name="facerec", - props=PropertyBase(name="face", default_value=""), - states=(facerec_run, facerec_shutdown)) ->>>>>>> b21a042c31198e97e854d4b01b4d5aa74679bb54 diff --git a/modules/ravestate_genqa/__init__.py b/modules/ravestate_genqa/__init__.py index e18427d..3bea9e3 100644 --- a/modules/ravestate_genqa/__init__.py +++ b/modules/ravestate_genqa/__init__.py @@ -19,7 +19,7 @@ with Module(name="genqa", config=CONFIG): - @state(cond=s(":startup"), write="rawio:out") + @state(cond=s(":startup")) def hello_world_genqa(ctx): server = ctx.conf(key=DRQA_SERVER_ADDRESS) if not server: diff --git a/modules/ravestate_idle/__init__.py b/modules/ravestate_idle/__init__.py index bd710c9..956032c 100644 --- a/modules/ravestate_idle/__init__.py +++ b/modules/ravestate_idle/__init__.py @@ -10,7 +10,7 @@ IMPATIENCE_THRESHOLD_CONFIG_KEY = "impatience_threshold" CONFIG = { # duration in seconds how long ":pressure" should be true before getting impatient - IMPATIENCE_THRESHOLD_CONFIG_KEY: 0.1 + IMPATIENCE_THRESHOLD_CONFIG_KEY: 1.0 } with Module(name="idle", config=CONFIG): @@ -21,41 +21,11 @@ def am_i_bored(ctx: ContextWrapper): Emits idle:bored signal if no states are currently partially fulfilled """ if ctx[":activity"] == 0: - logger.debug("Emitting idle:bored") 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") def am_i_impatient(ctx: ContextWrapper): - logger.debug("Emitting idle:impatient") return Emit(wipe=True) - - -<<<<<<< HEAD - # This state is just for testing the bored signal - @state(cond=s("idle:bored"), write="rawio:out") - def play_with_me(ctx: ContextWrapper): - ctx["rawio:out"] = "Play with me, I am bored!" - -======= -@state(cond=s(signal_name=":pressure:true", - min_age=ConfigurableAge(key=IMPATIENCE_THRESHOLD_CONFIG_KEY), - max_age=-1.), - signal_name="impatient") -def am_i_impatient(ctx: ContextWrapper): - """ - Emits idle:impatient signal if there are pressured activations for a (configurable) amount of time - """ - logger.debug("Emitting idle:impatient") - return Emit(wipe=True) - - -registry.register(name="idle", states=(am_i_bored, am_i_impatient), - config={ - # duration in seconds how long ":pressure" should be true before getting impatient - IMPATIENCE_THRESHOLD_CONFIG_KEY: 1. - }) ->>>>>>> b21a042c31198e97e854d4b01b4d5aa74679bb54 diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index e475ea3..aa7d29d 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -10,8 +10,6 @@ from ravestate.constraint import s from ravestate_nlp.yes_no import yes_no -import spacy - from reggol import get_logger logger = get_logger(__name__) @@ -74,7 +72,8 @@ def nlp_preprocess(ctx): logger.info(f"[NLP:roboy]: {nlp_roboy}") nlp_yesno = nlp_doc._.yesno - ctx["nlp:yesno"] = nlp_yesno + if nlp_yesno != "_": + ctx["nlp:yesno"] = nlp_yesno logger.info(f"[NLP:yesno]: {nlp_yesno}") @@ -91,10 +90,16 @@ def nlp_is_question_signal(ctx): return Emit() return False - @state(signal_name="yes-no", read="nlp:yesno") def nlp_yes_no_signal(ctx): - if ctx["nlp:yesno"][0]: + if ctx["nlp:yesno"] != "_": + return Emit() + return False + + @state(signal_name="intent-play", read="nlp:triples") + def nlp_intent_play_signal(ctx): + nlp_triples = ctx["nlp:triples"] + if nlp_triples[0].match_either_lemma(pred={"play"}, obj={"game"}): return Emit() return False @@ -124,138 +129,4 @@ def roboy_getter(doc) -> bool: Doc.set_extension('yesno', getter=yes_no) -<<<<<<< HEAD init_spacy() -======= -@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:triples", "nlp:yesno", "nlp:play")) -def nlp_preprocess(ctx): - nlp_doc = nlp(ctx["rawio:in"]) - - nlp_tokens = tuple(str(token) for token in nlp_doc) - ctx["nlp: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 - logger.info(f"[NLP:postags]: {nlp_postags}") - - nlp_lemmas = tuple(str(token.lemma_) for token in nlp_doc) - ctx["nlp: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 - 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 - logger.info(f"[NLP:ner]: {nlp_ner}") - - nlp_triples = nlp_doc._.triples - ctx["nlp:triples"] = nlp_triples - logger.info(f"[NLP:triples]: {nlp_triples}") - - nlp_roboy = nlp_doc._.about_roboy - ctx["nlp:roboy"] = nlp_roboy - logger.info(f"[NLP:roboy]: {nlp_roboy}") - - nlp_yesno = nlp_doc._.yesno - ctx["nlp:yesno"] = nlp_yesno - logger.info(f"[NLP:yesno]: {nlp_yesno}") - - if nlp_triples[0].match_either_lemma(pred={"play"}, obj={"game"}): - nlp_play = True - ctx["nlp:play"] = nlp_play - - -@state(signal_name="contains-roboy", read="nlp:roboy") -def nlp_contains_roboy_signal(ctx): - if ctx["nlp:roboy"]: - return Emit() - return False - - -@state(signal_name="is-question", read="nlp:triples") -def nlp_is_question_signal(ctx): - if ctx["nlp:triples"][0].is_question(): - return Emit() - return False - - -@state(signal_name="yes-no", read="nlp:yesno") -def nlp_yes_no_signal(ctx): - if ctx["nlp:yesno"][0]: - return Emit() - return False - - -init_model() -registry.register( - name="nlp", - states=( - nlp_preprocess, - nlp_contains_roboy_signal, - nlp_is_question_signal, - nlp_yes_no_signal - ), - props=( - PropertyBase( - name="tokens", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="postags", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="lemmas", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="tags", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="ner", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="triples", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="roboy", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="yesno", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False), - PropertyBase( - name="play", - default_value="", - always_signal_changed=True, - allow_pop=False, - allow_push=False, - is_flag_property=True) - ) -) ->>>>>>> b21a042c31198e97e854d4b01b4d5aa74679bb54 diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py index 98f0ea7..7d51627 100644 --- a/modules/ravestate_nlp/yes_no.py +++ b/modules/ravestate_nlp/yes_no.py @@ -20,5 +20,5 @@ def yes_no(doc): return "pn" elif "probably" in nlp_tokens: return "p" - return "0" + return "_" diff --git a/modules/ravestate_rawio/__init__.py b/modules/ravestate_rawio/__init__.py index 5fbbb65..a0df0ec 100644 --- a/modules/ravestate_rawio/__init__.py +++ b/modules/ravestate_rawio/__init__.py @@ -5,5 +5,5 @@ with Module(name="rawio"): - input = PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False) - output = PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False) + input = PropertyBase(name="in", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True) + output = PropertyBase(name="out", default_value="", allow_pop=False, allow_push=False, always_signal_changed=True) diff --git a/modules/ravestate_roboyqa/__init__.py b/modules/ravestate_roboyqa/__init__.py index ee57a23..ebe7c33 100644 --- a/modules/ravestate_roboyqa/__init__.py +++ b/modules/ravestate_roboyqa/__init__.py @@ -9,6 +9,8 @@ import random import datetime +import ravestate_idle + from reggol import get_logger logger = get_logger(__name__) @@ -17,13 +19,12 @@ ROBOY_NODE_CONF_KEY = "roboy_node_id" -@state(cond=s("idle:bored"), write="rawio:out") -def hello_world_roboyqa(ctx): - ctx["rawio:out"] = "Ask me something about myself!" - - with Module(name="roboyqa", config={ROBOY_NODE_CONF_KEY: 356}): + @state(cond=s("idle:bored"), write="rawio:out") + def hello_world_roboyqa(ctx): + ctx["rawio:out"] = "Ask me something about myself!" + @state(cond=s("nlp:contains-roboy") & s("nlp:is-question"), read="nlp:triples", write="rawio:out") def roboyqa(ctx): """ diff --git a/modules/ravestate_wildtalk/__init__.py b/modules/ravestate_wildtalk/__init__.py index 87acc19..bed9a07 100644 --- a/modules/ravestate_wildtalk/__init__.py +++ b/modules/ravestate_wildtalk/__init__.py @@ -11,4 +11,7 @@ @state(cond=s("rawio:in:changed", max_age=-1), read="rawio:in", write="rawio:out") def wildtalk_state(ctx): - ctx["rawio:out"] = wildtalk(ctx["rawio:in"]) + text = ctx["rawio:in"] + if not text: # make sure that text is not empty + text = " " + ctx["rawio:out"] = wildtalk(text) diff --git a/setup.py b/setup.py index 60fa615..c3244e0 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setuptools.setup( name="ravestate", - version="0.3.post1", + version="0.4.0", url="https://github.com/roboy/ravestate", author="Roboy", author_email="info@roboy.org", diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index 0dda942..3bdff7d 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -110,20 +110,15 @@ def test_flag_property(context_mock): 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()) -<<<<<<< HEAD - under_test_read_write.set(True) - assert (under_test_read_write.get() == True) - context_mock.emit.assert_called_with( - s(f"{under_test_read_write.prop.id()}:changed"), -======= + prop_wrapper.set(True) assert (prop_wrapper.get() is True) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.fullname()}:changed"), + s(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.fullname()}:true"), + s(f"{prop_wrapper.prop.id()}:true"), parents=None, wipe=True) @@ -131,11 +126,11 @@ 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.fullname()}:changed"), + s(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True) context_mock.emit.assert_any_call( - s(f"{prop_wrapper.prop.fullname()}:false"), + s(f"{prop_wrapper.prop.id()}:false"), parents=None, wipe=True) @@ -143,8 +138,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.fullname()}:changed"), ->>>>>>> b21a042c31198e97e854d4b01b4d5aa74679bb54 + s(f"{prop_wrapper.prop.id()}:changed"), parents=None, wipe=True) From 1883f97104b311a5c0ba8f2570f2b916cccab9b0 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 29 Jan 2019 02:30:46 +0100 Subject: [PATCH 42/46] Merged salt-march --- modules/ravestate_nlp/yes_no.py | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/modules/ravestate_nlp/yes_no.py b/modules/ravestate_nlp/yes_no.py index 7d51627..573d1cd 100644 --- a/modules/ravestate_nlp/yes_no.py +++ b/modules/ravestate_nlp/yes_no.py @@ -1,24 +1,29 @@ -NEGATION_SET = {"neg"} +NEGATION_TUPLE = ["neg", "not"] +YES_SYNONYMS = {"yes", "y", "yeah", "sure", "definitely", " certainly"} +NO_SYNONYMS = {"no", "n", "nope", "negative", "never", "nay"} +PROBABLY_SYNONYMS = {"probably", "likely", "possibly", "perhaps", "maybe"} +DON_NOT_KNOW_SET = {"know", "understand", "idea"} def yes_no(doc): """ - checks input for "yes", "no", "i don't know", "probably" and "probably not" - TODO: This should also map other inputs like ("(for|not) sure", "definitely, "dont think so"...) + checks input for "yes", "no", "i don't know", "probably" and "probably not" and synonyms of these """ - nlp_tokens = tuple(str(token) for token in doc) - for token in nlp_tokens: - if token == "yes" or token == "y": + nlp_tokens = tuple(str(token.text.lower()) for token in doc) + if len(nlp_tokens) == 1: + if nlp_tokens[0] in YES_SYNONYMS: return "yes" - elif token == "no" or token == "n": + elif nlp_tokens[0] in NO_SYNONYMS: return "no" - for token in doc: - if token.dep_ in NEGATION_SET: - if "know" in nlp_tokens: - return "idk" - elif "probably" in nlp_tokens: - return "pn" - elif "probably" in nlp_tokens: + elif nlp_tokens[0] in PROBABLY_SYNONYMS: return "p" + nlp_token_dep = tuple(str(token.dep_) for token in doc) + if NEGATION_TUPLE[0] in nlp_token_dep or NEGATION_TUPLE[1] in nlp_tokens: + for token in nlp_tokens: + if token in PROBABLY_SYNONYMS: + return "pn" + elif token in YES_SYNONYMS: + return "no" + elif token in DON_NOT_KNOW_SET: + return "idk" return "_" - From da14baff61b3ac43fa0f0b252f4e76b7411beefa Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 29 Jan 2019 03:13:16 +0100 Subject: [PATCH 43/46] Fixed tests (again) --- modules/ravestate/testfixtures.py | 8 +-- test/modules/ravestate/test_activation.py | 14 ++-- test/modules/ravestate/test_context.py | 32 ++++----- .../ravestate/test_wrappers_context.py | 70 +++++++++---------- .../ravestate/test_wrappers_property.py | 6 +- test/modules/ravestate_genqa/test_genqa.py | 5 -- .../ravestate_hello_world/test_hello_world.py | 27 ------- .../modules/ravestate_roboyqa/test_roboyqa.py | 10 --- test/ravestate_hibye/test_hibye.py | 4 -- test/ravestate_wildtalk/test_wildtalk.py | 3 - 10 files changed, 65 insertions(+), 114 deletions(-) delete mode 100644 test/modules/ravestate_hello_world/test_hello_world.py diff --git a/modules/ravestate/testfixtures.py b/modules/ravestate/testfixtures.py index 4eea9a3..64717a0 100644 --- a/modules/ravestate/testfixtures.py +++ b/modules/ravestate/testfixtures.py @@ -13,15 +13,15 @@ DEFAULT_MODULE_NAME = 'module' DEFAULT_PROPERTY_NAME = 'property' -DEFAULT_PROPERTY_FULLNAME = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}" +DEFAULT_PROPERTY_ID = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}" DEFAULT_PROPERTY_VALUE = 'Kruder' -DEFAULT_PROPERTY_CHANGED = f"{DEFAULT_PROPERTY_FULLNAME}:changed" +DEFAULT_PROPERTY_CHANGED = f"{DEFAULT_PROPERTY_ID}:changed" NEW_PROPERTY_VALUE = 'Dorfmeister' @pytest.fixture def state_fixture(mocker): - @state(write=(DEFAULT_PROPERTY_FULLNAME,), read=(DEFAULT_PROPERTY_FULLNAME,)) + @state(write=(DEFAULT_PROPERTY_ID,), read=(DEFAULT_PROPERTY_ID,)) def state_mock_fn(ctx): pass state_mock_fn.module_name = DEFAULT_MODULE_NAME @@ -30,7 +30,7 @@ def state_mock_fn(ctx): @pytest.fixture def state_signal_a_fixture(mocker): - @state(read=(DEFAULT_PROPERTY_FULLNAME,), signal_name="a") + @state(read=(DEFAULT_PROPERTY_ID,), signal_name="a") def state_mock_a_fn(ctx): pass state_mock_a_fn.module_name = DEFAULT_MODULE_NAME diff --git a/test/modules/ravestate/test_activation.py b/test/modules/ravestate/test_activation.py index a8508a3..7fd259f 100644 --- a/test/modules/ravestate/test_activation.py +++ b/test/modules/ravestate/test_activation.py @@ -7,27 +7,27 @@ def test_specificity(activation_fixture): def test_acquire_spike(activation_fixture): - assert activation_fixture.acquire(Spike(sig=DEFAULT_PROPERTY_CHANGED, consumable_resources={DEFAULT_PROPERTY_FULLNAME})) + assert activation_fixture.acquire(Spike(sig=DEFAULT_PROPERTY_CHANGED, consumable_resources={DEFAULT_PROPERTY_ID})) def test_acquire_spike_mismatches(activation_fixture): - assert not activation_fixture.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_FULLNAME})) + assert not activation_fixture.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_ID})) def test_multiple_activation(state_fixture, context_with_property_fixture): sa1 = Activation(state_fixture, context_with_property_fixture) assert sa1.acquire( - Spike(sig=DEFAULT_PROPERTY_CHANGED, consumable_resources={DEFAULT_PROPERTY_FULLNAME})) - assert not sa1.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_FULLNAME})) + Spike(sig=DEFAULT_PROPERTY_CHANGED, consumable_resources={DEFAULT_PROPERTY_ID})) + assert not sa1.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_ID})) sa2 = Activation(state_fixture, context_with_property_fixture) assert sa2.acquire( - Spike(sig=DEFAULT_PROPERTY_CHANGED, consumable_resources={DEFAULT_PROPERTY_FULLNAME})) - assert not sa2.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_FULLNAME})) + Spike(sig=DEFAULT_PROPERTY_CHANGED, consumable_resources={DEFAULT_PROPERTY_ID})) + assert not sa2.acquire(Spike(sig='x', consumable_resources={DEFAULT_PROPERTY_ID})) def test_resources_fallback(activation_fixture_fallback): assert activation_fixture_fallback.resources() \ - == {activation_fixture_fallback.state_to_activate.consumable.fullname()} + == {activation_fixture_fallback.state_to_activate.consumable.id()} # TODO: Add tests for update diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index 91288ab..c63d0b3 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -44,22 +44,22 @@ def test_shutdown(mocker, context_fixture): context_fixture._run_task.join.assert_called_once() -def test_add_module_new(mocker, context_fixture): - from ravestate.module import import_module - with mocker.patch('ravestate.module.has_module', return_value=False): - with mocker.patch('ravestate.module.import_module'): - context_fixture.add_module(DEFAULT_MODULE_NAME) - import_module.assert_called_once_with( - module_name=DEFAULT_MODULE_NAME, - callback=context_fixture._module_registration_callback - ) - - -def test_add_module_present(mocker, context_fixture): - with mocker.patch('ravestate.module.has_module', return_value=True): - with mocker.patch.object(context_fixture, '_module_registration_callback'): - context_fixture.add_module(DEFAULT_MODULE_NAME) - context_fixture._module_registration_callback.assert_called_once() +# TODO: broken +# def test_add_module_new(mocker, context_fixture): +# with mocker.patch('ravestate.module.has_module', return_value=False): +# with mocker.patch('ravestate.module.import_module') as import_module: +# context_fixture.add_module(DEFAULT_MODULE_NAME) +# import_module.assert_called_once_with( +# module_name=DEFAULT_MODULE_NAME, +# callback=context_fixture._module_registration_callback +# ) +# +# +# def test_add_module_present(mocker, context_fixture): +# with mocker.patch('ravestate.module.has_module', return_value=True): +# with mocker.patch.object(context_fixture, '_module_registration_callback'): +# context_fixture.add_module(DEFAULT_MODULE_NAME) +# context_fixture._module_registration_callback.assert_called_once() def test_remove_dependent_state(context_fixture: Context, state_fixture: State): diff --git a/test/modules/ravestate/test_wrappers_context.py b/test/modules/ravestate/test_wrappers_context.py index d66a884..ea5a056 100644 --- a/test/modules/ravestate/test_wrappers_context.py +++ b/test/modules/ravestate/test_wrappers_context.py @@ -5,8 +5,8 @@ CHILD_PROPERTY_VALUE = 'I am a child' GRANDCHILD_PROPERTY_NAME = 'grandchild' GRANDCHILD_PROPERTY_VALUE = 'I am a grandchild' -CHILD_PROPERTY_FULLNAME = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}" -GRANDCHILD_PROPERTY_FULLNAME = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}:{GRANDCHILD_PROPERTY_NAME}" +CHILD_PROPERTY_ID = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}" +GRANDCHILD_PROPERTY_ID = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}:{GRANDCHILD_PROPERTY_NAME}" def test_add_state(mocker, context_fixture, context_with_property_fixture, state_fixture, context_wrapper_fixture): @@ -30,97 +30,97 @@ 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_FULLNAME, + assert context_wrapper_fixture.push(parentpath=DEFAULT_PROPERTY_ID, child=PropertyBase(name=CHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_FULLNAME}:pushed"), + s(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=True) # test child - assert CHILD_PROPERTY_FULLNAME in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_FULLNAME)) - assert context_wrapper_fixture[CHILD_PROPERTY_FULLNAME] == DEFAULT_PROPERTY_VALUE - context_wrapper_fixture[CHILD_PROPERTY_FULLNAME] = CHILD_PROPERTY_VALUE + assert CHILD_PROPERTY_ID in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_ID)) + 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_FULLNAME}:changed"), + s(f"{CHILD_PROPERTY_ID}:changed"), parents=None, wipe=True) - assert context_wrapper_fixture[CHILD_PROPERTY_FULLNAME] == CHILD_PROPERTY_VALUE + assert context_wrapper_fixture[CHILD_PROPERTY_ID] == CHILD_PROPERTY_VALUE # pop child - assert context_wrapper_fixture.pop(CHILD_PROPERTY_FULLNAME) + assert context_wrapper_fixture.pop(CHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_FULLNAME}:popped"), + s(f"{DEFAULT_PROPERTY_ID}:popped"), parents=None, wipe=True) - assert [] == list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_FULLNAME)) + assert [] == list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_ID)) 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_FULLNAME, + assert context_wrapper_fixture.push(parentpath=DEFAULT_PROPERTY_ID, child=PropertyBase(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_FULLNAME}:pushed"), + s(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=True) # push grandchild - assert context_wrapper_fixture.push(parentpath=CHILD_PROPERTY_FULLNAME, + assert context_wrapper_fixture.push(parentpath=CHILD_PROPERTY_ID, child=PropertyBase(name=GRANDCHILD_PROPERTY_NAME, default_value=DEFAULT_PROPERTY_VALUE)) context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_FULLNAME}:pushed"), + s(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, wipe=True) # test children - assert CHILD_PROPERTY_FULLNAME in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_FULLNAME)) - assert GRANDCHILD_PROPERTY_FULLNAME in list(context_wrapper_fixture.enum(CHILD_PROPERTY_FULLNAME)) - assert context_wrapper_fixture[GRANDCHILD_PROPERTY_FULLNAME] == DEFAULT_PROPERTY_VALUE - context_wrapper_fixture[GRANDCHILD_PROPERTY_FULLNAME] = GRANDCHILD_PROPERTY_VALUE + assert CHILD_PROPERTY_ID in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_ID)) + assert GRANDCHILD_PROPERTY_ID in list(context_wrapper_fixture.enum(CHILD_PROPERTY_ID)) + 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_FULLNAME}:changed"), + s(f"{GRANDCHILD_PROPERTY_ID}:changed"), parents=None, wipe=True) - assert context_wrapper_fixture[GRANDCHILD_PROPERTY_FULLNAME] == GRANDCHILD_PROPERTY_VALUE + assert context_wrapper_fixture[GRANDCHILD_PROPERTY_ID] == GRANDCHILD_PROPERTY_VALUE # pop child - assert context_wrapper_fixture.pop(CHILD_PROPERTY_FULLNAME) + assert context_wrapper_fixture.pop(CHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_FULLNAME}:popped"), + s(f"{DEFAULT_PROPERTY_ID}:popped"), parents=None, wipe=True) - assert [] == list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_FULLNAME)) + assert [] == list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_ID)) 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_FULLNAME, + assert context_wrapper_fixture.push(parentpath=DEFAULT_PROPERTY_ID, child=PropertyBase(name=CHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - s(f"{DEFAULT_PROPERTY_FULLNAME}:pushed"), + s(f"{DEFAULT_PROPERTY_ID}:pushed"), parents=None, wipe=True) # push grandchild - assert context_wrapper_fixture.push(parentpath=CHILD_PROPERTY_FULLNAME, + assert context_wrapper_fixture.push(parentpath=CHILD_PROPERTY_ID, child=PropertyBase(name=GRANDCHILD_PROPERTY_NAME)) context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_FULLNAME}:pushed"), + s(f"{CHILD_PROPERTY_ID}:pushed"), parents=None, wipe=True) # test children - assert CHILD_PROPERTY_FULLNAME in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_FULLNAME)) - assert GRANDCHILD_PROPERTY_FULLNAME in list(context_wrapper_fixture.enum(CHILD_PROPERTY_FULLNAME)) + assert CHILD_PROPERTY_ID in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_ID)) + assert GRANDCHILD_PROPERTY_ID in list(context_wrapper_fixture.enum(CHILD_PROPERTY_ID)) # pop grandchild - assert context_wrapper_fixture.pop(GRANDCHILD_PROPERTY_FULLNAME) + assert context_wrapper_fixture.pop(GRANDCHILD_PROPERTY_ID) context_with_property_fixture.emit.assert_called_with( - s(f"{CHILD_PROPERTY_FULLNAME}:popped"), + s(f"{CHILD_PROPERTY_ID}:popped"), parents=None, wipe=True) - assert [] == list(context_wrapper_fixture.enum(CHILD_PROPERTY_FULLNAME)) - assert CHILD_PROPERTY_FULLNAME in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_FULLNAME)) + assert [] == list(context_wrapper_fixture.enum(CHILD_PROPERTY_ID)) + assert CHILD_PROPERTY_ID in list(context_wrapper_fixture.enum(DEFAULT_PROPERTY_ID)) diff --git a/test/modules/ravestate/test_wrappers_property.py b/test/modules/ravestate/test_wrappers_property.py index 3bdff7d..1eeab33 100644 --- a/test/modules/ravestate/test_wrappers_property.py +++ b/test/modules/ravestate/test_wrappers_property.py @@ -13,8 +13,8 @@ CHILD_PROPERTY_VALUE = 'I am a child' GRANDCHILD_PROPERTY_NAME = 'grandchild' GRANDCHILD_PROPERTY_VALUE = 'I am a grandchild' -CHILD_PROPERTY_FULLNAME = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}" -GRANDCHILD_PROPERTY_FULLNAME = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}:{GRANDCHILD_PROPERTY_NAME}" +CHILD_PROPERTY_ID = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}" +GRANDCHILD_PROPERTY_ID = f"{DEFAULT_MODULE_NAME}:{DEFAULT_PROPERTY_NAME}:{CHILD_PROPERTY_NAME}:{GRANDCHILD_PROPERTY_NAME}" @pytest.fixture @@ -145,7 +145,7 @@ 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 list(under_test_read_write.enum())[0] == CHILD_PROPERTY_FULLNAME + 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 diff --git a/test/modules/ravestate_genqa/test_genqa.py b/test/modules/ravestate_genqa/test_genqa.py index 59cc546..53e4da9 100644 --- a/test/modules/ravestate_genqa/test_genqa.py +++ b/test/modules/ravestate_genqa/test_genqa.py @@ -11,12 +11,7 @@ def test_hello_world_genqa(mocker, context_wrapper_fixture: Context): with LogCapture() as capture: - registry_mock = mocker.patch('ravestate.registry.register') import ravestate_genqa - registry_mock.assert_called_with(name="genqa", - states=(ravestate_genqa.hello_world_genqa, ravestate_genqa.drqa_module), - config={ravestate_genqa.DRQA_SERVER_ADDRESS: "http://localhost:5000", - 'roboy_answer_sanity': 5000}) result = ravestate_genqa.hello_world_genqa(context_wrapper_fixture) expected = 'Server address is not set. Shutting down GenQA.' capture.check_present((f"{FILE_NAME}", '\x1b[1;31mERROR\x1b[0m', f"{PREFIX} {expected}")) diff --git a/test/modules/ravestate_hello_world/test_hello_world.py b/test/modules/ravestate_hello_world/test_hello_world.py deleted file mode 100644 index 3b76487..0000000 --- a/test/modules/ravestate_hello_world/test_hello_world.py +++ /dev/null @@ -1,27 +0,0 @@ -from pytest_mock import mocker - - -def test_hello_world(mocker): - registry_mock = mocker.patch('ravestate.registry.register') - import ravestate_hello_world - registry_mock.assert_called_with(name="hi", - states=(ravestate_hello_world.hello_world, - ravestate_hello_world.generic_answer, - ravestate_hello_world.face_recognized)) - test_dict = {} - ravestate_hello_world.hello_world(test_dict) - assert test_dict["verbaliser:intent"] == "greeting" - - -def test_generic_answer(): - import ravestate_hello_world - test_dict = {"rawio:in": 'test'} - ravestate_hello_world.generic_answer(test_dict) - assert test_dict["rawio:out"] == f"Your input contains {len('test')} characters!" - - -def test_face_recognized(): - import ravestate_hello_world - test_dict = {"facerec:face": 'test'} - ravestate_hello_world.face_recognized(test_dict) - assert test_dict["rawio:out"] == f"I see you, {'test'}!" diff --git a/test/modules/ravestate_roboyqa/test_roboyqa.py b/test/modules/ravestate_roboyqa/test_roboyqa.py index 30c81ee..a5d54d5 100644 --- a/test/modules/ravestate_roboyqa/test_roboyqa.py +++ b/test/modules/ravestate_roboyqa/test_roboyqa.py @@ -1,16 +1,6 @@ from ravestate.testfixtures import * -def test_register(mocker): - from ravestate import registry - with mocker.patch('ravestate.registry.register'): - import ravestate_roboyqa - registry.register.assert_called_with( - name="roboyqa", - states=(ravestate_roboyqa.roboyqa,), - config={ravestate_roboyqa.ROBOY_NODE_CONF_KEY: 356}) - - def test_roboyqa(mocker, context_fixture, triple_fixture): mocker.patch.object(context_fixture, 'conf', will_return='test') context_fixture._properties["nlp:triples"] = [triple_fixture] diff --git a/test/ravestate_hibye/test_hibye.py b/test/ravestate_hibye/test_hibye.py index a152cb2..b647e6c 100644 --- a/test/ravestate_hibye/test_hibye.py +++ b/test/ravestate_hibye/test_hibye.py @@ -2,11 +2,7 @@ def test_react_to_pushed_interloc(mocker): - registry_mock = mocker.patch('ravestate.registry.register') import ravestate_hibye - registry_mock.assert_called_with(name="hi_goodbye", - states=(ravestate_hibye.react_to_pushed_interloc, - ravestate_hibye.react_to_popped_interloc,)) test_dict = {} ravestate_hibye.react_to_pushed_interloc(test_dict) assert test_dict["verbaliser:intent"] == "greeting" diff --git a/test/ravestate_wildtalk/test_wildtalk.py b/test/ravestate_wildtalk/test_wildtalk.py index f2d25cf..80ccb43 100644 --- a/test/ravestate_wildtalk/test_wildtalk.py +++ b/test/ravestate_wildtalk/test_wildtalk.py @@ -5,10 +5,7 @@ def test_wildtalk_state(mocker): import_mock = mocker.Mock() mocker.patch.dict('sys.modules', {'roboy_parlai': import_mock}) wildtalk_mock = mocker.patch('roboy_parlai.wildtalk', return_value='test') - registry_mock = mocker.patch('ravestate.registry.register') import ravestate_wildtalk - registry_mock.assert_called_with(name="wildtalk", - states=(ravestate_wildtalk.wildtalk_state,)) test_dict = {"rawio:in": 'test'} ravestate_wildtalk.wildtalk_state(test_dict) assert test_dict["rawio:out"] == 'test' From f42dfd39fec1bb27cdb3944182837308cbd7a754 Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 29 Jan 2019 14:19:05 +0100 Subject: [PATCH 44/46] Fixed causal mapping for emit_detached, pressure now processed for selective causal groups. --- modules/ravestate/activation.py | 24 +++++++++++++++------- modules/ravestate/causal.py | 24 +++++++++++++--------- modules/ravestate/constraint.py | 34 +++++++++++++++++++++++-------- modules/ravestate/context.py | 23 +++++++++++---------- modules/ravestate/iactivation.py | 24 ++++++++++++++++------ modules/ravestate_nlp/__init__.py | 5 ++++- pydocmd.yml | 1 - 7 files changed, 91 insertions(+), 44 deletions(-) diff --git a/modules/ravestate/activation.py b/modules/ravestate/activation.py index 727b3a0..3c81fd6 100644 --- a/modules/ravestate/activation.py +++ b/modules/ravestate/activation.py @@ -7,7 +7,7 @@ from ravestate.icontext import IContext from ravestate.constraint import Constraint -from ravestate.iactivation import IActivation, ISpike +from ravestate.iactivation import IActivation, ISpike, ICausalGroup from ravestate.spike import Spike from ravestate.causal import CausalGroup from ravestate.state import State, Emit, Delete, Resign, Wipe @@ -36,6 +36,7 @@ class Activation(IActivation): kwargs: Dict parent_spikes: Set[Spike] consenting_causal_groups: Set[CausalGroup] + pressuring_causal_groups: Set[ICausalGroup] def __init__(self, st: State, ctx: IContext): self.id = f"{st.name}#{Activation._count_for_state[st]}" @@ -49,6 +50,7 @@ def __init__(self, st: State, ctx: IContext): self.parent_spikes = set() self.consenting_causal_groups = set() self.death_clock = None + self.pressuring_causal_groups = set() def __del__(self): logger.debug(f"Deleted {self}") @@ -80,7 +82,7 @@ def specificity(self) -> float: sum(self.ctx.signal_specificity(sig) for sig in conj.signals()) for conj in self.constraint.conjunctions()) - def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, reject: bool=False) -> None: + def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, reject: bool=False, pressured: bool=False) -> None: """ Notify the activation, that a single or all spike(s) are not available anymore, and should therefore not be referenced anymore by the activation. @@ -88,7 +90,7 @@ def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, re ... context when a state is deleted.
... causal group, when a referenced signal was consumed for a required property.
... causal group, when a referenced signal was wiped.
- ... this activation (with reacquire=True), if it gives in to activation pressure. + ... this activation (with reacquire=True and pressured=True), if it gives in to activation pressure. * `spike`: The spike that should be forgotten by the activation, or none, if all referenced spikes should be forgotten. @@ -99,8 +101,12 @@ def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, re * `reject`: Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups. + + * `pressured`: Flag which controls, whether de-referencing should only occur + for spikes of causal groups in the pressuring_causal_groups set. """ - for sig_to_reacquire, dereferenced_instance in self.constraint.dereference(spike): + unreferenced = self.constraint.dereference(spike=spike, causal_groups=self.pressuring_causal_groups) + for sig_to_reacquire, dereferenced_instance in unreferenced: if reacquire: self.ctx.reacquire(self, sig_to_reacquire) if reject and dereferenced_instance: @@ -131,15 +137,18 @@ def secs_to_ticks(self, seconds: float) -> int: """ return self.ctx.secs_to_ticks(seconds) - def pressure(self): + def pressure(self, give_me_up: ICausalGroup): """ Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run. + + * `give_me_up`: Causal group that wishes to be de-referenced by this activation. """ if self.death_clock is None: self._reset_death_clock() + self.pressuring_causal_groups = {causal for causal in self.pressuring_causal_groups} | {give_me_up} def is_pressured(self): return self.death_clock is not None @@ -225,8 +234,9 @@ def update(self) -> bool: if self.death_clock is not None: if self.death_clock <= 0: self.death_clock = None - self.dereference(reacquire=True, reject=True) - logger.info(f"Eliminated {self}.") + self.dereference(reacquire=True, reject=True, pressured=True) + logger.info(f"Eliminated {self} from {self.pressuring_causal_groups}.") + self.pressuring_causal_groups = set() else: self.death_clock -= 1 diff --git a/modules/ravestate/causal.py b/modules/ravestate/causal.py index c2dde5f..5fb9e3e 100644 --- a/modules/ravestate/causal.py +++ b/modules/ravestate/causal.py @@ -1,7 +1,7 @@ # Ravestate class which encapsulates a graph of signal parent/offspring instances from typing import Set, Dict, Optional, List -from ravestate.iactivation import IActivation, ISpike +from ravestate.iactivation import IActivation, ISpike, ICausalGroup from threading import RLock from collections import defaultdict @@ -9,7 +9,7 @@ logger = get_logger(__name__) -class CausalGroup: +class CausalGroup(ICausalGroup): """ Class which represents a causal group graph of spike parent/offspring spikes (a "superspike"). These must synchronize wrt/ their (un)written @@ -119,20 +119,24 @@ def __enter__(self) -> 'CausalGroup': # Remember current lock, since it might change, # if the lock object is switched in merge() lock = self._lock - lock.__enter__() + lock.acquire() if lock != self._lock: - lock.__exit__(None, None, None) + lock.release() return self.__enter__() # Remember the locked lock, since the lock member might be re-targeted in merge() - assert not self._locked_lock self._locked_lock = self._lock return self - def __exit__(self, exc_type, exc_value, traceback) -> bool: + def __exit__(self, exc_type, exc_value, traceback): assert self._locked_lock - result = self._locked_lock.__exit__(exc_type, exc_value, traceback) - self._locked_lock = None - return result + self._locked_lock.release() + if self._locked_lock.acquire(False): + # Test whether lock is still owned (recursively). + self._locked_lock.release() + else: + # If the lock is not owned anymore, it isn't locked by this thread anymore + # -> acquire would block, returns False. + self._locked_lock = None def __eq__(self, other) -> bool: return isinstance(other, CausalGroup) and other._lock == self._lock @@ -287,7 +291,7 @@ def consent(self, ready_suitor: IActivation) -> bool: if higher_specificity_acts: for act in higher_specificity_acts: - act.pressure() + act.pressure(self) logger.debug( f"{self}.consent({ready_suitor})->N: " f"{str(specificity)[:4]} < {str(highest_higher_specificity)[:4]} " diff --git a/modules/ravestate/constraint.py b/modules/ravestate/constraint.py index 4e3158e..cc79c3e 100644 --- a/modules/ravestate/constraint.py +++ b/modules/ravestate/constraint.py @@ -1,6 +1,6 @@ from typing import List, Set, Generator, Optional, Tuple, Union, Callable, Any from ravestate.spike import Spike -from ravestate.iactivation import IActivation +from ravestate.iactivation import IActivation, ICausalGroup from reggol import get_logger logger = get_logger(__name__) @@ -56,7 +56,9 @@ def evaluate(self) -> bool: logger.error("Don't call this method on the super class Constraint") return False - def dereference(self, spike: Optional[Spike]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: + def dereference(self, *, + spike: Optional[Spike]=None, + causal_groups: Optional[Set[ICausalGroup]]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: logger.error("Don't call this method on the super class Constraint") yield None, None @@ -133,8 +135,14 @@ def acquire(self, spike: Spike, act: IActivation): def evaluate(self) -> bool: return self.spike and self._min_age_ticks <= self.spike.age() - def dereference(self, spike: Optional[Spike]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: + def dereference(self, *, + spike: Optional[Spike]=None, + causal_groups: Optional[Set[ICausalGroup]]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: if (not spike and self.spike) or (spike and self.spike is spike): + if causal_groups: + with self.spike.causal_group() as cg: + if cg not in list(causal_groups): + return former_signal_instance = self.spike self.spike = None yield self, former_signal_instance @@ -217,8 +225,13 @@ def acquire(self, spike: Spike, act: IActivation): def evaluate(self) -> bool: return all(map(lambda si: si.evaluate(), self._signals)) - def dereference(self, spike: Optional[Spike]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: - return (result for child in self._signals for result in child.dereference(spike)) + def dereference(self, *, + spike: Optional[Spike]=None, + causal_groups: Optional[Set[ICausalGroup]]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: + return ( + result + for child in self._signals + for result in child.dereference(spike=spike, causal_groups=causal_groups)) def update(self, act: IActivation) -> Generator['Signal', None, None]: return (result for child in self._signals for result in child.update(act)) @@ -283,11 +296,16 @@ def acquire(self, spike: Spike, act: IActivation): def evaluate(self) -> bool: return any(map(lambda si: si.evaluate(), self._conjunctions)) - def dereference(self, spike: Optional[Spike]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: - return (result for child in self._conjunctions for result in child.dereference(spike)) + def dereference(self, *, + spike: Optional[Spike]=None, + causal_groups: Optional[Set[ICausalGroup]]=None) -> Generator[Tuple['Signal', 'Spike'], None, None]: + return (result for child in self._conjunctions for result in child.dereference(spike=spike, causal_groups=causal_groups)) def update(self, act: IActivation) -> Generator['Signal', None, None]: - return (result for child in self._conjunctions for result in child.update(act)) + return ( + result + for child in self._conjunctions + for result in child.update(act)) def __str__(self): return " | ".join(map(lambda conjunct: conjunct.__str__(), self._conjunctions)) diff --git a/modules/ravestate/context.py b/modules/ravestate/context.py index bc14a99..ab2e356 100644 --- a/modules/ravestate/context.py +++ b/modules/ravestate/context.py @@ -263,16 +263,17 @@ def add_state(self, *, st: State) -> None: # add state's constraints as causes for the written prop's :changed signals, # as well as the state's own signal. states_to_recomplete: Set[State] = {st} - for conj in st.constraint.conjunctions(filter_detached=True): - for propname in st.write_props: - if propname in self._properties: - for signal in self._properties[propname].signals(): - self._signal_causes[signal].append(conj) - # Since a new cause for the property's signal is added, - # it must be added to all states depending on that signal. - states_to_recomplete.update(self._states_for_signal(signal)) - if st.signal(): - self._signal_causes[st.signal()].append(conj) + if not st.emit_detached: + for conj in st.constraint.conjunctions(filter_detached=True): + for propname in st.write_props: + if propname in self._properties: + for signal in self._properties[propname].signals(): + self._signal_causes[signal].append(conj) + # Since a new cause for the property's signal is added, + # it must be added to all states depending on that signal. + states_to_recomplete.update(self._states_for_signal(signal)) + if st.signal(): + self._signal_causes[st.signal()].append(conj) # add state to state activation map self._activations_per_state[st] = set() @@ -556,7 +557,7 @@ def _complete_conjunction(self, conj: Conjunct, known_signals: Set[Signal]) -> L result = [set(deepcopy(sig) for sig in conj.signals())] for sig in result[0]: # TODO: Figure out through eta system - sig.max_age = -1 + sig.max_age = 4. for conj_sig in conj.signals(): completion = self._complete_signal(conj_sig, known_signals) diff --git a/modules/ravestate/iactivation.py b/modules/ravestate/iactivation.py index cc8504e..a4a939c 100644 --- a/modules/ravestate/iactivation.py +++ b/modules/ravestate/iactivation.py @@ -45,6 +45,13 @@ def offspring(self) -> Generator['Spike', None, None]: yield None +class ICausalGroup: + """ + Base class for causal group + """ + pass + + class IActivation: """ Base interface class for state activations. @@ -67,15 +74,15 @@ def specificity(self) -> float: """ pass - def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, reject: bool=False) -> None: + def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, reject: bool=False, pressured: bool=False) -> None: """ Notify the activation, that a single or all spike(s) are not available anymore, and should therefore not be referenced anymore by the activation. This is called by ...
- ... context when a state is deleted.
- ... causal group, when a referenced signal was consumed for a required property.
- ... causal group, when a referenced signal was wiped.
- ... this activation (with reacquire=True), if it gives in to activation pressure. + ... context when a state is deleted.
+ ... causal group, when a referenced signal was consumed for a required property.
+ ... causal group, when a referenced signal was wiped.
+ ... this activation (with reacquire=True), if it gives in to activation pressure. * `spike`: The spike that should be forgotten by the activation, or none, if all referenced spikes should be forgotten. @@ -86,15 +93,20 @@ def dereference(self, *, spike: Optional[ISpike]=None, reacquire: bool=False, re * `reject`: Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups. + + * `pressured`: Flag which controls, whether de-referencing should only occur + for spikes of causal groups in the pressuring_causal_groups set. """ pass - def pressure(self): + def pressure(self, give_me_up: ICausalGroup): """ Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run. + + * `give_me_up`: Causal group that wishes to be de-referenced by this activation. """ pass diff --git a/modules/ravestate_nlp/__init__.py b/modules/ravestate_nlp/__init__.py index aa7d29d..f10659e 100644 --- a/modules/ravestate_nlp/__init__.py +++ b/modules/ravestate_nlp/__init__.py @@ -41,7 +41,10 @@ "nlp:yesno" )) def nlp_preprocess(ctx): - nlp_doc = nlp(ctx["rawio:in"]) + text = ctx["rawio:in"] + if not text: + return False + nlp_doc = nlp(text) nlp_tokens = tuple(str(token) for token in nlp_doc) ctx["nlp:tokens"] = nlp_tokens diff --git a/pydocmd.yml b/pydocmd.yml index 13a48d3..22d9ea6 100644 --- a/pydocmd.yml +++ b/pydocmd.yml @@ -13,7 +13,6 @@ generate: - ravestate.property++ - modules.md: - ravestate.module++ - - ravestate.registry++ - context.md: - ravestate.context++ - ravestate.spike++ From 75f79f723638353836c9916ba4ceed72fe62c2af Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 29 Jan 2019 14:57:01 +0100 Subject: [PATCH 45/46] Fixed new_module context unit test. --- test/modules/__init__.py | 0 test/modules/ravestate/test_context.py | 32 +++++++++---------- test/modules/ravestate_hibye/__init__.py | 0 .../ravestate_hibye/test_hibye.py | 0 test/modules/ravestate_telegramio/__init__.py | 0 .../ravestate_telegramio/test_telegram_bot.py | 0 test/modules/ravestate_wildtalk/__init__.py | 0 .../ravestate_wildtalk/test_wildtalk.py | 0 8 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 test/modules/__init__.py create mode 100644 test/modules/ravestate_hibye/__init__.py rename test/{ => modules}/ravestate_hibye/test_hibye.py (100%) create mode 100644 test/modules/ravestate_telegramio/__init__.py rename test/{ => modules}/ravestate_telegramio/test_telegram_bot.py (100%) create mode 100644 test/modules/ravestate_wildtalk/__init__.py rename test/{ => modules}/ravestate_wildtalk/test_wildtalk.py (100%) diff --git a/test/modules/__init__.py b/test/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/modules/ravestate/test_context.py b/test/modules/ravestate/test_context.py index c63d0b3..59274c8 100644 --- a/test/modules/ravestate/test_context.py +++ b/test/modules/ravestate/test_context.py @@ -44,22 +44,22 @@ def test_shutdown(mocker, context_fixture): context_fixture._run_task.join.assert_called_once() -# TODO: broken -# def test_add_module_new(mocker, context_fixture): -# with mocker.patch('ravestate.module.has_module', return_value=False): -# with mocker.patch('ravestate.module.import_module') as import_module: -# context_fixture.add_module(DEFAULT_MODULE_NAME) -# import_module.assert_called_once_with( -# module_name=DEFAULT_MODULE_NAME, -# callback=context_fixture._module_registration_callback -# ) -# -# -# def test_add_module_present(mocker, context_fixture): -# with mocker.patch('ravestate.module.has_module', return_value=True): -# with mocker.patch.object(context_fixture, '_module_registration_callback'): -# context_fixture.add_module(DEFAULT_MODULE_NAME) -# context_fixture._module_registration_callback.assert_called_once() +def test_add_module_new(mocker, context_fixture): + with mocker.patch('ravestate.context.has_module', return_value=False): + with mocker.patch('ravestate.context.import_module'): + from ravestate.context import import_module + context_fixture.add_module(DEFAULT_MODULE_NAME) + import_module.assert_called_once_with( + module_name=DEFAULT_MODULE_NAME, + callback=context_fixture._module_registration_callback + ) + + +def test_add_module_present(mocker, context_fixture): + with mocker.patch('ravestate.context.has_module', return_value=True): + with mocker.patch.object(context_fixture, '_module_registration_callback'): + context_fixture.add_module(DEFAULT_MODULE_NAME) + context_fixture._module_registration_callback.assert_called_once() def test_remove_dependent_state(context_fixture: Context, state_fixture: State): diff --git a/test/modules/ravestate_hibye/__init__.py b/test/modules/ravestate_hibye/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/ravestate_hibye/test_hibye.py b/test/modules/ravestate_hibye/test_hibye.py similarity index 100% rename from test/ravestate_hibye/test_hibye.py rename to test/modules/ravestate_hibye/test_hibye.py diff --git a/test/modules/ravestate_telegramio/__init__.py b/test/modules/ravestate_telegramio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/ravestate_telegramio/test_telegram_bot.py b/test/modules/ravestate_telegramio/test_telegram_bot.py similarity index 100% rename from test/ravestate_telegramio/test_telegram_bot.py rename to test/modules/ravestate_telegramio/test_telegram_bot.py diff --git a/test/modules/ravestate_wildtalk/__init__.py b/test/modules/ravestate_wildtalk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/ravestate_wildtalk/test_wildtalk.py b/test/modules/ravestate_wildtalk/test_wildtalk.py similarity index 100% rename from test/ravestate_wildtalk/test_wildtalk.py rename to test/modules/ravestate_wildtalk/test_wildtalk.py From 34cd4bfc8f92e4565596b9be8dcd2267d98c999c Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Tue, 29 Jan 2019 14:59:53 +0100 Subject: [PATCH 46/46] Updated docs. --- README.md | 2 ++ docs/context/index.html | 59 +++++++++++++++++++++++++--------- docs/index.html | 21 ++++-------- docs/modules/index.html | 46 +++++++++----------------- docs/properties/index.html | 47 ++++++++++++++++++++++++++- docs/search/search_index.json | 2 +- docs/sitemap.xml | 12 +++---- docs/sitemap.xml.gz | Bin 199 -> 198 bytes docs/states/index.html | 34 +++++++++++++------- 9 files changed, 142 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 21c0d1d..c9eb786 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,11 @@ If you have installed the dependencies from ``requirements-dev.txt``, generate the docs by running this command at project root: ```bash +export PYTHONPATH=$PYTHONPATH:$(pwd)/modules git rm -rf docs rm -rf _build docs pydocmd build +git add docs/* ``` The structure and content of the docs are defined in the file ``pydocmd.yml``. diff --git a/docs/context/index.html b/docs/context/index.html index b980ae1..35a6a0d 100644 --- a/docs/context/index.html +++ b/docs/context/index.html @@ -121,6 +121,22 @@

ravestate.context

+

startup

+ +
startup(**kwargs) -> ravestate.constraint.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.

+

shutdown

+ +
shutdown(**kwargs) -> ravestate.constraint.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.

Context

Context(self, *arguments)
@@ -131,8 +147,8 @@ 

emit

Context.emit(self, signal: ravestate.constraint.Signal, parents: Set[ravestate.spike.Spike] = None, wipe: bool = False) -> None
 
-

Emit a signal to the signal processing loop. Note: - The signal will only be processed if run() has been called!

+

Emit a signal to the signal processing loop. Note: + The signal will only be processed if run() has been called!

  • signal: The signal to be emitted.

    @@ -141,7 +157,7 @@

    emit

    parents: The signal's parents, if it is supposed to be integrated into a causal group.

  • -

    wipe: Boolean to control, whether wipe(signal) should be called +

    wipe: Boolean to control, whether wipe(signal) should be called before the new spike is created.

@@ -252,7 +268,9 @@

lowest_upper_bound_eta

Called by activation when it is pressured to resign. The activation wants - to know the earliest ETA of one of it's remaining required constraints.

+ to know the earliest ETA of one of it's remaining required constraints. + Also called by constraint completion algorithm, to figure out the maximum + age for a completed constraint.

  • signals: The signals, whose ETA will be calculated, and among the results the minimum ETA will be returned.
  • @@ -326,12 +344,6 @@

    Spike

    This class encapsulates a single spike, to track ...
    ... it's consumption for different output properties (through CausalGroup).
    ... it's offspring instances (causal group -> spikes caused by this spike)

    -

    name

    - -
    Spike.name(self) -> str
    -
    - -

    Returns the name of this spike's signal.

    causal_group

    Spike.causal_group(self) -> ravestate.causal.CausalGroup
    @@ -399,6 +411,12 @@ 

    offspring

    Recursively yields this spike's offspring and it's children's offspring.

    Returns: All of this spike's offspring spikes.

    +

    is_wiped

    + +
    Spike.is_wiped(self)
    +
    + +

    Check, whether this spike has been wiped, and should therefore not be acquired anymore.

    ravestate.activation

    Activation

    @@ -425,7 +443,7 @@

    specificity

    which in turn is calculated as one over the signal's subscriber count.

    dereference

    -
    Activation.dereference(self, *, spike: Union[ravestate.iactivation.ISpike, NoneType] = None, reacquire: bool = False, reject: bool = False) -> None
    +
    Activation.dereference(self, *, spike: Union[ravestate.iactivation.ISpike, NoneType] = None, reacquire: bool = False, reject: bool = False, pressured: bool = False) -> None
     

    Notify the activation, that a single or all spike(s) are not available @@ -434,7 +452,7 @@

    dereference

    ... context when a state is deleted.
    ... causal group, when a referenced signal was consumed for a required property.
    ... causal group, when a referenced signal was wiped.
    -... this activation (with reacquire=True), if it gives in to activation pressure.

    +... this activation (with reacquire=True and pressured=True), if it gives in to activation pressure.

    • spike: The spike that should be forgotten by the activation, or @@ -449,6 +467,10 @@

      dereference

      reject: Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups.

    • +
    • +

      pressured: Flag which controls, whether de-referencing should only occur + for spikes of causal groups in the pressuring_causal_groups set.

      +

    acquire

    @@ -474,13 +496,16 @@

    secs_to_ticks

    Returns: An integer tick count.

    pressure

    -
    Activation.pressure(self)
    +
    Activation.pressure(self, give_me_up: ravestate.iactivation.ICausalGroup)
     

    Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run.

    +
      +
    • give_me_up: Causal group that wishes to be de-referenced by this activation.
    • +

    spiky

    Activation.spiky(self) -> bool
    @@ -522,7 +547,7 @@ 

    merge

    Afterwards, other's member objects will be set to this's.

    acquired

    -
    CausalGroup.acquired(self, spike: 'ISpike', acquired_by: ravestate.iactivation.IActivation) -> bool
    +
    CausalGroup.acquired(self, spike: 'ISpike', acquired_by: ravestate.iactivation.IActivation, detached: bool) -> bool
     

    Called by Activation to notify the causal group, that @@ -536,6 +561,10 @@

    acquired

    acquired_by: State activation instance, which is interested in this property.

    +
  • +

    detached: Tells the causal group, whether the reference is detached, + and should therefore receive special treatment.

    +

Returns: Returns True if all of the acquiring's write-props are free, and the group now refs. the activation, False otherwise.

@@ -547,7 +576,7 @@

rejected

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 auto-eliminated. (reason=0)
+... the state 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)

    diff --git a/docs/index.html b/docs/index.html index b19185c..71d8e1d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -56,15 +56,6 @@
  • About
  • -
  • Dependencies
  • - - - -
  • Installation
    • @@ -169,10 +160,6 @@

      About

      Ravestate is a reactive library for real-time natural language dialog systems.

      -

      Dependencies

      -

      portaudio on macOS

      -

      In order to install PyAudio with pip, you need to install portaudio first using:

      -

      brew install portaudio

      Installation

      Via PIP

      The easiest way to install ravestate is through pip:

      @@ -222,7 +209,11 @@

      Running tests

      Building/maintaining the docs

      If you have installed the dependencies from requirements-dev.txt, generate the docs by running this command at project root:

      -

      pydocmd build

      +
      git rm -rf docs
      +rm -rf _build docs
      +pydocmd build
      +
      +

      The structure and content of the docs are defined in the file pydocmd.yml.

      @@ -272,5 +263,5 @@

      Building/maintaining the docs

      diff --git a/docs/modules/index.html b/docs/modules/index.html index e84664f..ad5fa58 100644 --- a/docs/modules/index.html +++ b/docs/modules/index.html @@ -123,15 +123,23 @@

      ravestate.module

      Module

      -
      Module(self, *, name: str, props: Tuple[ravestate.property.PropertyBase] = (), states: Tuple[ravestate.state.State] = (), config: Dict[str, Any] = None)
      +
      Module(self, *, name: str, config: Dict[str, Any] = None)
       

      Atomic class, which encapsulates a named set of states, properties and config entries, which form a coherent bundle.

      -

      ravestate.registry

      - -

      import_module

      - +

      registered_modules

      + +

      dict() -> new empty dictionary +dict(mapping) -> new dictionary initialized from a mapping object's + (key, value) pairs +dict(iterable) -> new dictionary initialized as if via: + d = {} + for k, v in iterable: + d[k] = v +dict(**kwargs) -> new dictionary initialized with the name=value pairs + in the keyword argument list. For example: dict(one=1, two=2) +

      import_module

      import_module(*, module_name: str, callback)
       
      @@ -144,31 +152,7 @@

      import_module

      callback: A callback which should be called when a module calls register() while it is being imported.

    -

    register

    - -
    register(*, name: str = '', props=(), states=(), config=None)
    -
    - -

    May be called to register a named set of states, properties and config entries, -which form a coherent bundle.

    -
      -
    • -

      name: The name of the module. Will be prefixed to property and signal names like - :.

      -
    • -
    • -

      props: The properties that should be registered.

      -
    • -
    • -

      states: The states that should be registered.

      -
    • -
    • -

      config: A dictionary of config entries and their default values, which should be read - from the default/user config files. -:return:

      -
    • -
    -

    has_module

    +

    has_module

    has_module(module_name: str)
     
    @@ -178,7 +162,7 @@

    has_module

  • module_name: The name which should be checked for beign registered.

Returns: True if a module with the given name has been registered, false otherwise.

-

get_module

+

get_module

get_module(module_name: str)
 
diff --git a/docs/properties/index.html b/docs/properties/index.html index 1e5474b..9c3ed27 100644 --- a/docs/properties/index.html +++ b/docs/properties/index.html @@ -121,9 +121,42 @@

ravestate.property

+

changed

+ +
changed(property_name, **kwargs) -> ravestate.constraint.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.

+

pushed

+ +
pushed(property_name, **kwargs) -> ravestate.constraint.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.

+

popped

+ +
popped(property_name, **kwargs) -> ravestate.constraint.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.

PropertyBase

-
PropertyBase(self, *, name='', allow_read=True, allow_write=True, allow_push=True, allow_pop=True, default_value=None, always_signal_changed=False)
+
PropertyBase(self, *, name='', allow_read=True, allow_write=True, allow_push=True, allow_pop=True, default_value=None, always_signal_changed=False, is_flag_property=False)
 

Base class for context properties. Controls read/write/push/pop/delete permissions, @@ -197,6 +230,18 @@

popped_signal

Signal that is emitted by PropertyWrapper when pop() returns True.

+

flag_true_signal

+ +
PropertyBase.flag_true_signal(self) -> ravestate.constraint.Signal
+
+ +

Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to True.

+

flag_false_signal

+ +
PropertyBase.flag_false_signal(self) -> ravestate.constraint.Signal
+
+ +

Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to False.

signals

PropertyBase.signals(self) -> Generator[ravestate.constraint.Signal, NoneType, NoneType]
diff --git a/docs/search/search_index.json b/docs/search/search_index.json
index e6c7573..200a6e4 100644
--- a/docs/search/search_index.json
+++ b/docs/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"_ __ _ __ _ ___ ____ __ ______ ______/ /_____/ /___ _ _ / \\/ __ \\/ / / / __ \\/ ___\\, / __ \\, /__ \\ _ _ / /\\/ /_/ /\\ \\/ / /_/ /\\__, / / /_/ / / /_/ / _ \\/ _\\__/\\/ _\\__/ ,___/\\____/\\/\\__/\\/\\/ ,___/ _____ _ _\\____/ _ _\\____/ /_ _\\ 0> 0> \\__\u22bd__/ (C) Roboy 2019 \u22c2 About Ravestate is a reactive library for real-time natural language dialog systems. Dependencies portaudio on macOS In order to install PyAudio with pip, you need to install portaudio first using: brew install portaudio Installation Via PIP The easiest way to install ravestate is through pip: pip install ravestate For developers First, install dependencies: pip install -r requirements.txt # To run tests, install pytest, mocking, fixtures... pip install -r requirements-dev.txt Then, you may open the repository in any IDE, and mark the modules folder as a sources root. Running Hello World Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments: Running with command line spec You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to the rasta command, which is installed with ravestate: rasta ravestate_conio ravestate_hello_world Run rasta -h to see more options! Running with config file(s) You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module: core config: import: - ravestate_conio - ravestate_hello_world Then, run rasta with this config file: rasta -f hello_world.yml Running tests If you have installed the dependencies from requirements-dev.txt you may run the ravestate test suite as follows: ./run_tests.sh Building/maintaining the docs If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: pydocmd build The structure and content of the docs are defined in the file pydocmd.yml .","title":"Home"},{"location":"#about","text":"Ravestate is a reactive library for real-time natural language dialog systems.","title":"About"},{"location":"#dependencies","text":"","title":"Dependencies"},{"location":"#portaudio-on-macos","text":"In order to install PyAudio with pip, you need to install portaudio first using: brew install portaudio","title":"portaudio on macOS"},{"location":"#installation","text":"","title":"Installation"},{"location":"#via-pip","text":"The easiest way to install ravestate is through pip: pip install ravestate","title":"Via PIP"},{"location":"#for-developers","text":"First, install dependencies: pip install -r requirements.txt # To run tests, install pytest, mocking, fixtures... pip install -r requirements-dev.txt Then, you may open the repository in any IDE, and mark the modules folder as a sources root.","title":"For developers"},{"location":"#running-hello-world","text":"Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments:","title":"Running Hello World"},{"location":"#running-with-command-line-spec","text":"You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to the rasta command, which is installed with ravestate: rasta ravestate_conio ravestate_hello_world Run rasta -h to see more options!","title":"Running with command line spec"},{"location":"#running-with-config-files","text":"You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module: core config: import: - ravestate_conio - ravestate_hello_world Then, run rasta with this config file: rasta -f hello_world.yml","title":"Running with config file(s)"},{"location":"#running-tests","text":"If you have installed the dependencies from requirements-dev.txt you may run the ravestate test suite as follows: ./run_tests.sh","title":"Running tests"},{"location":"#buildingmaintaining-the-docs","text":"If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: pydocmd build The structure and content of the docs are defined in the file pydocmd.yml .","title":"Building/maintaining the docs"},{"location":"config/","text":"ravestate.argparse handle_args handle_args(*args) -> Tuple[List[str], List[Tuple[str, str, Any]], List[str]] Runs an argument parser for the given args. Returns modules-to-load, config value-overrides and config file-pathes. Note: If the arguments are ill-formatted, or the -h argument is passed, help will be printed to the console and the program will abort. args : Argument list which will be fed into argparse.parse_args. Returns: A Tuple with three items: 1.) A list of module names which should be imported. 2.) A list of tuples, where each tuple is a module name, a config key name, and a value. 3.) A list of yaml file paths. ravestate.config Configuration Configuration(self, paths: List[str]) The Configuration class maintains a dictionary of key-value stores, which represent configuration entries for specific named modules. The key-value stores may be successively updated with consecutive yaml files, where each yaml document has the following content: module: module-name config: key-a: value-a key-b: value-b # etc add_conf Configuration.add_conf(self, mod: ravestate.module.Module) Register a set of allowed config entries for a specific module. Correctly typed values for allowed keys, that were previously parsed during construction from the yaml files, will be applied immediately. mod : A module object with a name and a conf dict. get_conf Configuration.get_conf(self, module_name: str) Retrieve updated config values for a module that was previously registered with add_conf. module_name : The module name for which configuration should be retrieved. Returns: A dictionary which contains exactly the keys that were contained in the module configuration dictionary during add_conf, or an empty dictionary if the module name is unknown. get Configuration.get(self, module_name: str, key: str) -> Any Gte the current value of a config entry. module_name : The module that provides the config entry. key : A config key for the module that was previously added through add_conf. Returns: The current value, or None, if the entry does not exist. set Configuration.set(self, module_name: str, key: str, value: Any) Set the current value of a config entry. module_name : The module of the config entry. key : A config key for the module that was previously added through add_conf. value : The new value for the config entry. An error will be raised, if the type of the new value does not match the type of the old value. write Configuration.write(self, path: str) Write all current config entries to a yaml file. path : The file path to write. Will be overwritten! read Configuration.read(self, path: str) Loads all documents from a yaml file and tries to interpret them as configuration objects as described above. path : The yaml file path from which to load config documents.","title":"Configuration"},{"location":"context/","text":"ravestate.context Context Context(self, *arguments) emit Context.emit(self, signal: ravestate.constraint.Signal, parents: Set[ravestate.spike.Spike] = None, wipe: bool = False) -> None Emit a signal to the signal processing loop. Note: The signal will only be processed if run() has been called! signal : The signal to be emitted. parents : The signal's parents, if it is supposed to be integrated into a causal group. wipe : Boolean to control, whether wipe(signal) should be called before the new spike is created. wipe Context.wipe(self, signal: ravestate.constraint.Signal) Delete all spikes for the given signal. Partially fulfilled states that have acquired an affected spike will be forced to reject it. Wiping a parent spike will also wipe all child spikes. signal : The signal for which all existing spikes (and their children) should be invalidated and forgotten. run Context.run(self) -> None Creates a signal processing thread, starts it, and emits the :startup signal. shutting_down Context.shutting_down(self) -> bool Retrieve the shutdown flag value, which indicates whether shutdown() has been called. shutdown Context.shutdown(self) -> None Sets the shutdown flag and waits for the signal processing thread to join. add_module Context.add_module(self, module_name: str) -> None Add a module by python module folder name, or by ravestate module name. module_name : The name of the module to be added. If it is the name of a python module that has not been imported yet, the python module will be imported, and any ravestate modules registered during the python import will also be added to this context. add_state Context.add_state(self, *, st: ravestate.state.State) -> None Add a state to this context. It will be indexed wrt/ the properties/signals it depends on. Error messages will be generated for unknown signals/properties. st : The state which should be added to this context. rm_state Context.rm_state(self, *, st: ravestate.state.State) -> None Remove a state from this context. Note, that any state which is constrained on the signal that is emitted by the deleted state will also be deleted. st : The state to remove. An error message will be generated, if the state was not previously added to this context with add_state(). add_prop Context.add_prop(self, *, prop: ravestate.property.PropertyBase) -> 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. prop : The property object that should be added. rm_prop Context.rm_prop(self, *, prop: ravestate.property.PropertyBase) -> None Remove a property from this context. Generates error message, if the property was not added with add_prop() to the context previously prop : The property to remove.object conf Context.conf(self, *, mod: str, key: Union[str, NoneType] = None) -> Any Get a single config value, or all config values for a particular module. mod : The module whose configuration should be retrieved. key : A specific config key of the given module, if only a single config value should be retrieved. Returns: The value of a single config entry if key and module are both specified and valid, or a dictionary of config entries if only the module name is specified (and valid). lowest_upper_bound_eta Context.lowest_upper_bound_eta(self, signals: Set[ravestate.constraint.Signal]) -> int Called by activation when it is pressured to resign. The activation wants to know the earliest ETA of one of it's remaining required constraints. signals : The signals, whose ETA will be calculated, and among the results the minimum ETA will be returned. Returns: Lowest upper bound number of ticks it should take for at least one of the required signals to arrive. Fixed value (1) for now. signal_specificity Context.signal_specificity(self, sig: ravestate.constraint.Signal) -> float Called by state activation to determine it's constraint's specificity. sig : The signal whose specificity should be returned. Returns: The given signal's specificity. reacquire Context.reacquire(self, act: ravestate.iactivation.IActivation, sig: ravestate.constraint.Signal) Called by activation, to indicate, that it needs a new Spike for the specified signal, and should for this purpose be referenced by context. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that needs a new spike of the specified nature. sig : Signal type for which a new spike is needed. withdraw Context.withdraw(self, act: ravestate.iactivation.IActivation, sig: ravestate.constraint.Signal) Called by activation to make sure that it isn't referenced anymore as looking for the specified signal. This might be, because the activation chose to eliminate itself due to activation pressure, or because one of the activations conjunctions was fulfilled, so it is no longer looking for signals to fulfill the remaining conjunctions. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that has lost interest in the specified signal. sig : Signal type for which interest is lost. secs_to_ticks Context.secs_to_ticks(self, seconds: float) -> int Convert seconds to an equivalent integer number of ticks, given this context's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. ravestate.spike Spike Spike(self, *, sig: str, parents: Set[ForwardRef('Spike')] = None, consumable_resources: Set[str] = None) This class encapsulates a single spike, to track ... ... it's consumption for different output properties (through CausalGroup ). ... it's offspring instances (causal group -> spikes caused by this spike) name Spike.name(self) -> str Returns the name of this spike's signal. causal_group Spike.causal_group(self) -> ravestate.causal.CausalGroup Get this spike's causal group. Returns: This instances causal group. Should never be None. adopt Spike.adopt(self, child: 'Spike') -> None Called in spike constructor, for instances which claim to be caused by this spike. child : The child to add to this spike's causal group. wiped Spike.wiped(self, child: 'ISpike') -> None Called by an offspring signal, to notify the spike that it was wiped, and should therefore be removed from the children set. child : The child to be forgotten. wipe Spike.wipe(self, already_wiped_in_causal_group: bool = False) -> None Called either in Context run loop when the spike is found to be stale (with wiped_in_causal_group=True), or in Context.wipe(spike), or by parent (recursively). After this function is called, the spike should be cleaned up by GC. already_wiped_in_causal_group : Boolean which indicates, whether wiped(spike) must still be called on the group to make sure sure that no dangling references to the spike are maintained by any state activations. has_offspring Spike.has_offspring(self) Called by CausalGroup.stale(spike). Returns: True if the spike has active offspring, false otherwise. tick Spike.tick(self) -> None Increment this spike's age by 1. age Spike.age(self) -> int Obtain this spike's age (in ticks). offspring Spike.offspring(self) -> Generator[ForwardRef('Spike'), NoneType, NoneType] Recursively yields this spike's offspring and it's children's offspring. Returns: All of this spike's offspring spikes. ravestate.activation Activation Activation(self, st: ravestate.state.State, ctx: ravestate.icontext.IContext) Encapsulates the potential activation of a state. Tracks the collection of Spikes to fulfill of the state-defined activation constraints. resources Activation.resources(self) -> Set[str] Return's the set of the activation's write-access property names. specificity Activation.specificity(self) -> float Returns the lowest specificity among the specificity values of the activation's conjunct constraints. The specificity for a single conjunction is calculated as the sum of it's component signal's specificities, which in turn is calculated as one over the signal's subscriber count. dereference Activation.dereference(self, *, spike: Union[ravestate.iactivation.ISpike, NoneType] = None, reacquire: bool = False, reject: bool = False) -> None Notify the activation, that a single or all spike(s) are not available anymore, and should therefore not be referenced anymore by the activation. This is called by ... ... context when a state is deleted. ... causal group, when a referenced signal was consumed for a required property. ... causal group, when a referenced signal was wiped. ... this activation (with reacquire=True), if it gives in to activation pressure. spike : The spike that should be forgotten by the activation, or none, if all referenced spikes should be forgotten. reacquire : Flag which tells the function, whether for every rejected spike, the activation should hook into context for reacquisition of a replacement spike. reject : Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups. acquire Activation.acquire(self, spike: ravestate.spike.Spike) -> bool Let the activation acquire a signal it is registered to be interested in. spike : The signal which should fulfill at least one of this activation's signal constraints. Returns: Should return True. secs_to_ticks Activation.secs_to_ticks(self, seconds: float) -> int Convert seconds to an equivalent integer number of ticks, given this activation's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. pressure Activation.pressure(self) Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run. spiky Activation.spiky(self) -> bool Returns true, if the activation has acquired any spikes at all. Returns: True, if any of this activation's constraint's signal is referencing a spike. update Activation.update(self) -> bool Called once per tick on this activation, to give it a chance to activate itself, or auto-eliminate, or reject spikes which have become too old. Returns: True, if the target state is activated and teh activation be forgotten, false if needs further attention in the form of updates() by context in the future. ravestate.causal CausalGroup CausalGroup(self, resources: Set[str]) Class which represents a causal group graph of spike parent/offspring spikes (a \"superspike\"). These must synchronize wrt/ their (un)written properties and state activation candidates, such that they don't cause output races. Note: Always use a with ... construct to interact with a causal group. Otherwise, undefined behavior may occur due to race conditions. merge CausalGroup.merge(self, other: 'CausalGroup') Merge this causal group with another. Unwritten props will become the set intersection of this group's unwritten props and other's unwritten props. consumed() will be called with all properties that are consumed by other, but not this. Afterwards, other's member objects will be set to this's. acquired CausalGroup.acquired(self, spike: 'ISpike', acquired_by: ravestate.iactivation.IActivation) -> bool Called by Activation to notify the causal group, that it is being referenced by an activation constraint for a certain member spike. spike : State activation instance, which is now being referenced by the specified causal group. acquired_by : State activation instance, which is interested in this property. Returns: Returns True if all of the acquiring's write-props are free, and the group now refs. the activation, False otherwise. rejected CausalGroup.rejected(self, spike: 'ISpike', rejected_by: ravestate.iactivation.IActivation, reason: int) -> None 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 auto-eliminated. (reason=0) ... the spike got too old. (reason=1) ... the activation is happening and dereferencing it's spikes. (reason=2) spike : The member spike whose ref-set should be reduced. rejected_by : State activation instance, which is no longer interested in this property. reason : See about. consent CausalGroup.consent(self, ready_suitor: ravestate.iactivation.IActivation) -> bool Called by constraint, to inquire whether this causal group would happily be consumed for the given state activation's properties. This will be called periodically on the group by state activations that are ready to go. Therefore, a False return value from this function is never a final judgement (more like a \"maybe later\"). ready_suitor : The state activation which would like to consume this instance for it's write props. Returns: True if this instance agrees to proceeding with the given consumer for the consumer's write props, False otherwise. activated CausalGroup.activated(self, act: ravestate.iactivation.IActivation) Called by activation which previously received a go-ahead from consent(), when it is truly proceeding with running (after it got the go-ahead from all it's depended=on causal groups). act : The activation that is now running. resigned CausalGroup.resigned(self, act: ravestate.iactivation.IActivation) -> None Called by activation, to let the causal group know that it failed, and a less specific activation may now be considered for the resigned state's write props. act : The activation that is unexpectedly not consuming it's resources, because it's state resigned/failed. consumed CausalGroup.consumed(self, resources: Set[str]) -> None Called by activation to notify the group, that it has been consumed for the given set of properties. resources : The properties which have been consumed. wiped CausalGroup.wiped(self, spike: 'ISpike') -> None Called by a spike, to notify the causal group that the instance was wiped and should no longer be remembered. spike : The instance that should be henceforth forgotten. stale CausalGroup.stale(self, spike: 'ISpike') -> bool Determine, whether a spike is stale (has no remaining interested activations and no children). Returns: True, if no activations reference the given spike for any unwritten property. False otherwise.","title":"Context"},{"location":"modules/","text":"ravestate.module Module Module(self, *, name: str, props: Tuple[ravestate.property.PropertyBase] = (), states: Tuple[ravestate.state.State] = (), config: Dict[str, Any] = None) Atomic class, which encapsulates a named set of states, properties and config entries, which form a coherent bundle. ravestate.registry import_module import_module(*, module_name: str, callback) Called by context to import a particular ravestate python module. module_name : The name of the python module to be imported (must be in pythonpath). callback : A callback which should be called when a module calls register() while it is being imported. register register(*, name: str = '', props=(), states=(), config=None) May be called to register a named set of states, properties and config entries, which form a coherent bundle. name : The name of the module. Will be prefixed to property and signal names like : . props : The properties that should be registered. states : The states that should be registered. config : A dictionary of config entries and their default values, which should be read from the default/user config files. :return: has_module has_module(module_name: str) Check whether a module with a particular name has been registered. module_name : The name which should be checked for beign registered. Returns: True if a module with the given name has been registered, false otherwise. get_module get_module(module_name: str) Get a registered module with a particular name module_name : The name of the moduke which should be retrieved. Returns: The module with the given name if it was registered, false if otherwise.","title":"Modules"},{"location":"properties/","text":"ravestate.property PropertyBase PropertyBase(self, *, name='', allow_read=True, allow_write=True, allow_push=True, allow_pop=True, default_value=None, always_signal_changed=False) Base class for context properties. Controls read/write/push/pop/delete permissions, property name basic impls. for the property value, parent/child mechanism. set_parent_path PropertyBase.set_parent_path(self, path) Set the ancestors (including modulename) for a property path : ancestry in the form of modulename:parent_prop_name (or simply modulename) gather_children PropertyBase.gather_children(self) -> List[ForwardRef('PropertyBase')] Collect this property, and all of it's children. read PropertyBase.read(self) Read the current property value write PropertyBase.write(self, value) Write a new value to the property value : The new value. Returns: True if the value has changed and :changed should be signaled, false otherwise. push PropertyBase.push(self, child: 'PropertyBase') Add a child to the property child : The child object Returns: True if the child was added successfully, false otherwise. pop PropertyBase.pop(self, child_name: str) Remove a child from the property by it's name. child_name : Name of the child to be removed. Returns: True if the pop was successful, False otherwise changed_signal PropertyBase.changed_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when write() returns True. pushed_signal PropertyBase.pushed_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when push() returns True. popped_signal PropertyBase.popped_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when pop() returns True. signals PropertyBase.signals(self) -> Generator[ravestate.constraint.Signal, NoneType, NoneType] Yields all signals that may be emitted because of this property, given it's write/push/pop permissions.","title":"Properties"},{"location":"states/","text":"ravestate.state StateActivationResult StateActivationResult(self, /, *args, **kwargs) Base class for return values of state activation functions. Delete Delete(self, resign: bool = False) Return an instance of this class, if the invoked state should be deleted. resign : Set to true, if the state being deleted is due to it failing to execute, so a resignation is implied. This means, that the spikes that were allocated for it's activation may be re-used by another state. Wipe Wipe(self, /, *args, **kwargs) Return an instance of this class, if context.wipe(signal) should be called, to ensure that there are no more active spikes for the state's signal. Emit Emit(self, wipe: bool = False) Return an instance of this class, if the invoked state's signal should be emitted. wipe : Set to true, if context.wipe(signal) should be called before emit, to ensure that there is only one free spike for the given signal. Resign Resign(self, /, *args, **kwargs) Return an instance of this class, if the state invocation should be regarded unsuccessful. This means, that the state's signal will not be emitted, and the spikes that were allocated for it's activation may be re-used by another state. state state(*, signal_name: Union[str, NoneType] = '', write: tuple = (), read: tuple = (), cond: ravestate.constraint.Constraint = None) Decorator to declare a new state, which may emit a certain signal, write to a certain set of properties (calling write, push, pop), and read from certain properties (calling read). ravestate.constraint s s(signal_name: str, *, min_age=0, max_age=5.0, detached=False) Alias to call Signal-constructor signal_name : Name of the Signal min_age : Minimum age for the signal, in seconds. max_age : Maximum age for the signal, in seconds. 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. Constraint Constraint(self, /, *args, **kwargs) Superclass for Signal, Conjunct and Disjunct Signal Signal(self, name: str, *, min_age=0.0, max_age=1.0, detached=False) Class that represents a Signal Conjunct Conjunct(self, *args) Class that represents a Conjunction of Signals Disjunct Disjunct(self, *args) Class that represents a Disjunction of Conjunctions ravestate.receptor receptor receptor(*, ctx_wrap: ravestate.wrappers.ContextWrapper, write: Union[str, Tuple[str]]) A receptor is a special state which can be invoked from outside, to push values into the context. ctx_wrap : A context wrapper as is always given into the state functions as their first argument. write : The property, or tuple of properties, which are going to be written.","title":"States"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"_ __ _ __ _ ___ ____ __ ______ ______/ /_____/ /___ _ _ / \\/ __ \\/ / / / __ \\/ ___\\, / __ \\, /__ \\ _ _ / /\\/ /_/ /\\ \\/ / /_/ /\\__, / / /_/ / / /_/ / _ \\/ _\\__/\\/ _\\__/ ,___/\\____/\\/\\__/\\/\\/ ,___/ _____ _ _\\____/ _ _\\____/ /_ _\\ 0> 0> \\__\u22bd__/ (C) Roboy 2019 \u22c2 About Ravestate is a reactive library for real-time natural language dialog systems. Installation Via PIP The easiest way to install ravestate is through pip: pip install ravestate For developers First, install dependencies: pip install -r requirements.txt # To run tests, install pytest, mocking, fixtures... pip install -r requirements-dev.txt Then, you may open the repository in any IDE, and mark the modules folder as a sources root. Running Hello World Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments: Running with command line spec You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to the rasta command, which is installed with ravestate: rasta ravestate_conio ravestate_hello_world Run rasta -h to see more options! Running with config file(s) You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module: core config: import: - ravestate_conio - ravestate_hello_world Then, run rasta with this config file: rasta -f hello_world.yml Running tests If you have installed the dependencies from requirements-dev.txt you may run the ravestate test suite as follows: ./run_tests.sh Building/maintaining the docs If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: git rm -rf docs rm -rf _build docs pydocmd build The structure and content of the docs are defined in the file pydocmd.yml .","title":"Home"},{"location":"#about","text":"Ravestate is a reactive library for real-time natural language dialog systems.","title":"About"},{"location":"#installation","text":"","title":"Installation"},{"location":"#via-pip","text":"The easiest way to install ravestate is through pip: pip install ravestate","title":"Via PIP"},{"location":"#for-developers","text":"First, install dependencies: pip install -r requirements.txt # To run tests, install pytest, mocking, fixtures... pip install -r requirements-dev.txt Then, you may open the repository in any IDE, and mark the modules folder as a sources root.","title":"For developers"},{"location":"#running-hello-world","text":"Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments:","title":"Running Hello World"},{"location":"#running-with-command-line-spec","text":"You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to the rasta command, which is installed with ravestate: rasta ravestate_conio ravestate_hello_world Run rasta -h to see more options!","title":"Running with command line spec"},{"location":"#running-with-config-files","text":"You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module: core config: import: - ravestate_conio - ravestate_hello_world Then, run rasta with this config file: rasta -f hello_world.yml","title":"Running with config file(s)"},{"location":"#running-tests","text":"If you have installed the dependencies from requirements-dev.txt you may run the ravestate test suite as follows: ./run_tests.sh","title":"Running tests"},{"location":"#buildingmaintaining-the-docs","text":"If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: git rm -rf docs rm -rf _build docs pydocmd build The structure and content of the docs are defined in the file pydocmd.yml .","title":"Building/maintaining the docs"},{"location":"config/","text":"ravestate.argparse handle_args handle_args(*args) -> Tuple[List[str], List[Tuple[str, str, Any]], List[str]] Runs an argument parser for the given args. Returns modules-to-load, config value-overrides and config file-pathes. Note: If the arguments are ill-formatted, or the -h argument is passed, help will be printed to the console and the program will abort. args : Argument list which will be fed into argparse.parse_args. Returns: A Tuple with three items: 1.) A list of module names which should be imported. 2.) A list of tuples, where each tuple is a module name, a config key name, and a value. 3.) A list of yaml file paths. ravestate.config Configuration Configuration(self, paths: List[str]) The Configuration class maintains a dictionary of key-value stores, which represent configuration entries for specific named modules. The key-value stores may be successively updated with consecutive yaml files, where each yaml document has the following content: module: module-name config: key-a: value-a key-b: value-b # etc add_conf Configuration.add_conf(self, mod: ravestate.module.Module) Register a set of allowed config entries for a specific module. Correctly typed values for allowed keys, that were previously parsed during construction from the yaml files, will be applied immediately. mod : A module object with a name and a conf dict. get_conf Configuration.get_conf(self, module_name: str) Retrieve updated config values for a module that was previously registered with add_conf. module_name : The module name for which configuration should be retrieved. Returns: A dictionary which contains exactly the keys that were contained in the module configuration dictionary during add_conf, or an empty dictionary if the module name is unknown. get Configuration.get(self, module_name: str, key: str) -> Any Gte the current value of a config entry. module_name : The module that provides the config entry. key : A config key for the module that was previously added through add_conf. Returns: The current value, or None, if the entry does not exist. set Configuration.set(self, module_name: str, key: str, value: Any) Set the current value of a config entry. module_name : The module of the config entry. key : A config key for the module that was previously added through add_conf. value : The new value for the config entry. An error will be raised, if the type of the new value does not match the type of the old value. write Configuration.write(self, path: str) Write all current config entries to a yaml file. path : The file path to write. Will be overwritten! read Configuration.read(self, path: str) Loads all documents from a yaml file and tries to interpret them as configuration objects as described above. path : The yaml file path from which to load config documents.","title":"Configuration"},{"location":"context/","text":"ravestate.context startup startup(**kwargs) -> ravestate.constraint.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. shutdown shutdown(**kwargs) -> ravestate.constraint.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. Context Context(self, *arguments) emit Context.emit(self, signal: ravestate.constraint.Signal, parents: Set[ravestate.spike.Spike] = None, wipe: bool = False) -> None Emit a signal to the signal processing loop. Note: The signal will only be processed if run() has been called! signal : The signal to be emitted. parents : The signal's parents, if it is supposed to be integrated into a causal group. wipe : Boolean to control, whether wipe (signal) should be called before the new spike is created. wipe Context.wipe(self, signal: ravestate.constraint.Signal) Delete all spikes for the given signal. Partially fulfilled states that have acquired an affected spike will be forced to reject it. Wiping a parent spike will also wipe all child spikes. signal : The signal for which all existing spikes (and their children) should be invalidated and forgotten. run Context.run(self) -> None Creates a signal processing thread, starts it, and emits the :startup signal. shutting_down Context.shutting_down(self) -> bool Retrieve the shutdown flag value, which indicates whether shutdown() has been called. shutdown Context.shutdown(self) -> None Sets the shutdown flag and waits for the signal processing thread to join. add_module Context.add_module(self, module_name: str) -> None Add a module by python module folder name, or by ravestate module name. module_name : The name of the module to be added. If it is the name of a python module that has not been imported yet, the python module will be imported, and any ravestate modules registered during the python import will also be added to this context. add_state Context.add_state(self, *, st: ravestate.state.State) -> None Add a state to this context. It will be indexed wrt/ the properties/signals it depends on. Error messages will be generated for unknown signals/properties. st : The state which should be added to this context. rm_state Context.rm_state(self, *, st: ravestate.state.State) -> None Remove a state from this context. Note, that any state which is constrained on the signal that is emitted by the deleted state will also be deleted. st : The state to remove. An error message will be generated, if the state was not previously added to this context with add_state(). add_prop Context.add_prop(self, *, prop: ravestate.property.PropertyBase) -> 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. prop : The property object that should be added. rm_prop Context.rm_prop(self, *, prop: ravestate.property.PropertyBase) -> None Remove a property from this context. Generates error message, if the property was not added with add_prop() to the context previously prop : The property to remove.object conf Context.conf(self, *, mod: str, key: Union[str, NoneType] = None) -> Any Get a single config value, or all config values for a particular module. mod : The module whose configuration should be retrieved. key : A specific config key of the given module, if only a single config value should be retrieved. Returns: The value of a single config entry if key and module are both specified and valid, or a dictionary of config entries if only the module name is specified (and valid). lowest_upper_bound_eta Context.lowest_upper_bound_eta(self, signals: Set[ravestate.constraint.Signal]) -> int Called by activation when it is pressured to resign. The activation wants to know the earliest ETA of one of it's remaining required constraints. Also called by constraint completion algorithm, to figure out the maximum age for a completed constraint. signals : The signals, whose ETA will be calculated, and among the results the minimum ETA will be returned. Returns: Lowest upper bound number of ticks it should take for at least one of the required signals to arrive. Fixed value (1) for now. signal_specificity Context.signal_specificity(self, sig: ravestate.constraint.Signal) -> float Called by state activation to determine it's constraint's specificity. sig : The signal whose specificity should be returned. Returns: The given signal's specificity. reacquire Context.reacquire(self, act: ravestate.iactivation.IActivation, sig: ravestate.constraint.Signal) Called by activation, to indicate, that it needs a new Spike for the specified signal, and should for this purpose be referenced by context. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that needs a new spike of the specified nature. sig : Signal type for which a new spike is needed. withdraw Context.withdraw(self, act: ravestate.iactivation.IActivation, sig: ravestate.constraint.Signal) Called by activation to make sure that it isn't referenced anymore as looking for the specified signal. This might be, because the activation chose to eliminate itself due to activation pressure, or because one of the activations conjunctions was fulfilled, so it is no longer looking for signals to fulfill the remaining conjunctions. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that has lost interest in the specified signal. sig : Signal type for which interest is lost. secs_to_ticks Context.secs_to_ticks(self, seconds: float) -> int Convert seconds to an equivalent integer number of ticks, given this context's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. ravestate.spike Spike Spike(self, *, sig: str, parents: Set[ForwardRef('Spike')] = None, consumable_resources: Set[str] = None) This class encapsulates a single spike, to track ... ... it's consumption for different output properties (through CausalGroup ). ... it's offspring instances (causal group -> spikes caused by this spike) causal_group Spike.causal_group(self) -> ravestate.causal.CausalGroup Get this spike's causal group. Returns: This instances causal group. Should never be None. adopt Spike.adopt(self, child: 'Spike') -> None Called in spike constructor, for instances which claim to be caused by this spike. child : The child to add to this spike's causal group. wiped Spike.wiped(self, child: 'ISpike') -> None Called by an offspring signal, to notify the spike that it was wiped, and should therefore be removed from the children set. child : The child to be forgotten. wipe Spike.wipe(self, already_wiped_in_causal_group: bool = False) -> None Called either in Context run loop when the spike is found to be stale (with wiped_in_causal_group=True), or in Context.wipe(spike), or by parent (recursively). After this function is called, the spike should be cleaned up by GC. already_wiped_in_causal_group : Boolean which indicates, whether wiped(spike) must still be called on the group to make sure sure that no dangling references to the spike are maintained by any state activations. has_offspring Spike.has_offspring(self) Called by CausalGroup.stale(spike). Returns: True if the spike has active offspring, false otherwise. tick Spike.tick(self) -> None Increment this spike's age by 1. age Spike.age(self) -> int Obtain this spike's age (in ticks). offspring Spike.offspring(self) -> Generator[ForwardRef('Spike'), NoneType, NoneType] Recursively yields this spike's offspring and it's children's offspring. Returns: All of this spike's offspring spikes. is_wiped Spike.is_wiped(self) Check, whether this spike has been wiped, and should therefore not be acquired anymore. ravestate.activation Activation Activation(self, st: ravestate.state.State, ctx: ravestate.icontext.IContext) Encapsulates the potential activation of a state. Tracks the collection of Spikes to fulfill of the state-defined activation constraints. resources Activation.resources(self) -> Set[str] Return's the set of the activation's write-access property names. specificity Activation.specificity(self) -> float Returns the lowest specificity among the specificity values of the activation's conjunct constraints. The specificity for a single conjunction is calculated as the sum of it's component signal's specificities, which in turn is calculated as one over the signal's subscriber count. dereference Activation.dereference(self, *, spike: Union[ravestate.iactivation.ISpike, NoneType] = None, reacquire: bool = False, reject: bool = False, pressured: bool = False) -> None Notify the activation, that a single or all spike(s) are not available anymore, and should therefore not be referenced anymore by the activation. This is called by ... ... context when a state is deleted. ... causal group, when a referenced signal was consumed for a required property. ... causal group, when a referenced signal was wiped. ... this activation (with reacquire=True and pressured=True), if it gives in to activation pressure. spike : The spike that should be forgotten by the activation, or none, if all referenced spikes should be forgotten. reacquire : Flag which tells the function, whether for every rejected spike, the activation should hook into context for reacquisition of a replacement spike. reject : Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups. pressured : Flag which controls, whether de-referencing should only occur for spikes of causal groups in the pressuring_causal_groups set. acquire Activation.acquire(self, spike: ravestate.spike.Spike) -> bool Let the activation acquire a signal it is registered to be interested in. spike : The signal which should fulfill at least one of this activation's signal constraints. Returns: Should return True. secs_to_ticks Activation.secs_to_ticks(self, seconds: float) -> int Convert seconds to an equivalent integer number of ticks, given this activation's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. pressure Activation.pressure(self, give_me_up: ravestate.iactivation.ICausalGroup) Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run. give_me_up : Causal group that wishes to be de-referenced by this activation. spiky Activation.spiky(self) -> bool Returns true, if the activation has acquired any spikes at all. Returns: True, if any of this activation's constraint's signal is referencing a spike. update Activation.update(self) -> bool Called once per tick on this activation, to give it a chance to activate itself, or auto-eliminate, or reject spikes which have become too old. Returns: True, if the target state is activated and teh activation be forgotten, false if needs further attention in the form of updates() by context in the future. ravestate.causal CausalGroup CausalGroup(self, resources: Set[str]) Class which represents a causal group graph of spike parent/offspring spikes (a \"superspike\"). These must synchronize wrt/ their (un)written properties and state activation candidates, such that they don't cause output races. Note: Always use a with ... construct to interact with a causal group. Otherwise, undefined behavior may occur due to race conditions. merge CausalGroup.merge(self, other: 'CausalGroup') Merge this causal group with another. Unwritten props will become the set intersection of this group's unwritten props and other's unwritten props. consumed() will be called with all properties that are consumed by other, but not this. Afterwards, other's member objects will be set to this's. acquired CausalGroup.acquired(self, spike: 'ISpike', acquired_by: ravestate.iactivation.IActivation, detached: bool) -> bool Called by Activation to notify the causal group, that it is being referenced by an activation constraint for a certain member spike. spike : State activation instance, which is now being referenced by the specified causal group. acquired_by : State activation instance, which is interested in this property. detached : Tells the causal group, whether the reference is detached, and should therefore receive special treatment. Returns: Returns True if all of the acquiring's write-props are free, and the group now refs. the activation, False otherwise. rejected CausalGroup.rejected(self, spike: 'ISpike', rejected_by: ravestate.iactivation.IActivation, reason: int) -> None 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 spike got too old. (reason=1) ... the activation is happening and dereferencing it's spikes. (reason=2) spike : The member spike whose ref-set should be reduced. rejected_by : State activation instance, which is no longer interested in this property. reason : See about. consent CausalGroup.consent(self, ready_suitor: ravestate.iactivation.IActivation) -> bool Called by constraint, to inquire whether this causal group would happily be consumed for the given state activation's properties. This will be called periodically on the group by state activations that are ready to go. Therefore, a False return value from this function is never a final judgement (more like a \"maybe later\"). ready_suitor : The state activation which would like to consume this instance for it's write props. Returns: True if this instance agrees to proceeding with the given consumer for the consumer's write props, False otherwise. activated CausalGroup.activated(self, act: ravestate.iactivation.IActivation) Called by activation which previously received a go-ahead from consent(), when it is truly proceeding with running (after it got the go-ahead from all it's depended=on causal groups). act : The activation that is now running. resigned CausalGroup.resigned(self, act: ravestate.iactivation.IActivation) -> None Called by activation, to let the causal group know that it failed, and a less specific activation may now be considered for the resigned state's write props. act : The activation that is unexpectedly not consuming it's resources, because it's state resigned/failed. consumed CausalGroup.consumed(self, resources: Set[str]) -> None Called by activation to notify the group, that it has been consumed for the given set of properties. resources : The properties which have been consumed. wiped CausalGroup.wiped(self, spike: 'ISpike') -> None Called by a spike, to notify the causal group that the instance was wiped and should no longer be remembered. spike : The instance that should be henceforth forgotten. stale CausalGroup.stale(self, spike: 'ISpike') -> bool Determine, whether a spike is stale (has no remaining interested activations and no children). Returns: True, if no activations reference the given spike for any unwritten property. False otherwise.","title":"Context"},{"location":"modules/","text":"ravestate.module Module Module(self, *, name: str, config: Dict[str, Any] = None) Atomic class, which encapsulates a named set of states, properties and config entries, which form a coherent bundle. registered_modules dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2) import_module import_module(*, module_name: str, callback) Called by context to import a particular ravestate python module. module_name : The name of the python module to be imported (must be in pythonpath). callback : A callback which should be called when a module calls register() while it is being imported. has_module has_module(module_name: str) Check whether a module with a particular name has been registered. module_name : The name which should be checked for beign registered. Returns: True if a module with the given name has been registered, false otherwise. get_module get_module(module_name: str) Get a registered module with a particular name module_name : The name of the moduke which should be retrieved. Returns: The module with the given name if it was registered, false if otherwise.","title":"Modules"},{"location":"properties/","text":"ravestate.property changed changed(property_name, **kwargs) -> ravestate.constraint.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. pushed pushed(property_name, **kwargs) -> ravestate.constraint.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. popped popped(property_name, **kwargs) -> ravestate.constraint.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. PropertyBase PropertyBase(self, *, name='', allow_read=True, allow_write=True, allow_push=True, allow_pop=True, default_value=None, always_signal_changed=False, is_flag_property=False) Base class for context properties. Controls read/write/push/pop/delete permissions, property name basic impls. for the property value, parent/child mechanism. set_parent_path PropertyBase.set_parent_path(self, path) Set the ancestors (including modulename) for a property path : ancestry in the form of modulename:parent_prop_name (or simply modulename) gather_children PropertyBase.gather_children(self) -> List[ForwardRef('PropertyBase')] Collect this property, and all of it's children. read PropertyBase.read(self) Read the current property value write PropertyBase.write(self, value) Write a new value to the property value : The new value. Returns: True if the value has changed and :changed should be signaled, false otherwise. push PropertyBase.push(self, child: 'PropertyBase') Add a child to the property child : The child object Returns: True if the child was added successfully, false otherwise. pop PropertyBase.pop(self, child_name: str) Remove a child from the property by it's name. child_name : Name of the child to be removed. Returns: True if the pop was successful, False otherwise changed_signal PropertyBase.changed_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when write() returns True. pushed_signal PropertyBase.pushed_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when push() returns True. popped_signal PropertyBase.popped_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when pop() returns True. flag_true_signal PropertyBase.flag_true_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to True. flag_false_signal PropertyBase.flag_false_signal(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to False. signals PropertyBase.signals(self) -> Generator[ravestate.constraint.Signal, NoneType, NoneType] Yields all signals that may be emitted because of this property, given it's write/push/pop permissions.","title":"Properties"},{"location":"states/","text":"ravestate.state Delete Delete(self, resign: bool = False) Return an instance of this class, if the invoked state should be deleted. resign : Set to true, if the state being deleted is due to it failing to execute, so a resignation is implied. This means, that the spikes that were allocated for it's activation may be re-used by another state. Wipe Wipe(self, /, *args, **kwargs) Return an instance of this class, if context.wipe(signal) should be called, to ensure that there are no more active spikes for the state's signal. Emit Emit(self, wipe: bool = False) Return an instance of this class, if the invoked state's signal should be emitted. wipe : Set to true, if context.wipe(signal) should be called before emit, to ensure that there is only one free spike for the given signal. Resign Resign(self, /, *args, **kwargs) Return an instance of this class, if the state invocation should be regarded unsuccessful. This means, that the state's signal will not be emitted, and the spikes that were allocated for it's activation may be re-used by another state. state state(*, signal_name: Union[str, NoneType] = '', write: tuple = (), read: tuple = (), cond: ravestate.constraint.Constraint = None, emit_detached=False) Decorator to declare a new state, which may emit a certain signal, write to a certain set of properties (calling write, push, pop), and read from certain properties (calling read). ravestate.constraint ConfigurableAge ConfigurableAge(self, key: str) Class for having min/max_age parameters for Constraints configurable with a config key key str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object. str () (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. s s(signal_name: str, *, min_age: Union[float, ravestate.constraint.ConfigurableAge] = 0.0, max_age: Union[float, ravestate.constraint.ConfigurableAge] = 5.0, 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. Constraint Constraint(self, /, *args, **kwargs) Superclass for Signal, Conjunct and Disjunct Signal Signal(self, name: str, *, min_age=0.0, max_age=5.0, detached=False) Class that represents a Signal Conjunct Conjunct(self, *args) Class that represents a Conjunction of Signals Disjunct Disjunct(self, *args) Class that represents a Disjunction of Conjunctions ravestate.receptor receptor receptor(*, ctx_wrap: ravestate.wrappers.ContextWrapper, write: Union[str, Tuple[str]]) A receptor is a special state which can be invoked from outside, to push values into the context. ctx_wrap : A context wrapper as is always given into the state functions as their first argument. write : The property, or tuple of properties, which are going to be written.","title":"States"}]}
\ No newline at end of file
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index b2f5c50..ff0d04b 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -2,32 +2,32 @@
 
     
      None
-     2019-01-17
+     2019-01-29
      daily
     
     
      None
-     2019-01-17
+     2019-01-29
      daily
     
     
      None
-     2019-01-17
+     2019-01-29
      daily
     
     
      None
-     2019-01-17
+     2019-01-29
      daily
     
     
      None
-     2019-01-17
+     2019-01-29
      daily
     
     
      None
-     2019-01-17
+     2019-01-29
      daily
     
 
\ No newline at end of file
diff --git a/docs/sitemap.xml.gz b/docs/sitemap.xml.gz
index 52fda92284170e9cc51f8e618f6de41307593bae..a1f1074210e81e11e201b9c08df7ee5d2fd3c276 100644
GIT binary patch
literal 198
zcmV;%06G63iwFoETu@vB|8r?{Wo=<_E_iKh0PT`75`r)gMSD(>Nl!LVi4#I+X{86q
zgb0}l2_{kW_69-6)}C$l@83WBnq|#nFzBv)(9YJlAQU5IrE9hAYJ5H&@*Qq?#%|sO
z6;TS?P~$$vaSt#}6A3!1L5zVo-$9Ul8Um~)P*P!@p;YsMMImOK4#&5xb
zjwpp4RJec3sPc{p;Xg>Ss`w{5=ZYer<3}a5@PQg
zu_;)Y7c6Jnv|hX=qB`kXeF^$18fom4(;sK1&@W~MuE4*5@0NaCya4z>zgH6j002b5
BR&D?Q

diff --git a/docs/states/index.html b/docs/states/index.html
index ab34269..f87c5a3 100644
--- a/docs/states/index.html
+++ b/docs/states/index.html
@@ -121,12 +121,6 @@
               
                 

ravestate.state

-

StateActivationResult

- -
StateActivationResult(self, /, *args, **kwargs)
-
- -

Base class for return values of state activation functions.

Delete

Delete(self, resign: bool = False)
@@ -166,7 +160,7 @@ 

Resign

that were allocated for it's activation may be re-used by another state.

state

-
state(*, signal_name: Union[str, NoneType] = '', write: tuple = (), read: tuple = (), cond: ravestate.constraint.Constraint = None)
+
state(*, signal_name: Union[str, NoneType] = '', write: tuple = (), read: tuple = (), cond: ravestate.constraint.Constraint = None, emit_detached=False)
 

Decorator to declare a new state, which may emit a certain signal, @@ -174,9 +168,25 @@

state

and read from certain properties (calling read).

ravestate.constraint

-

s

+

ConfigurableAge

+ +
ConfigurableAge(self, key: str)
+
-
s(signal_name: str, *, min_age=0, max_age=5.0, detached=False)
+

Class for having min/max_age parameters for Constraints configurable with a config key

+

key

+ +

str(object='') -> str +str(bytes_or_buffer[, encoding[, errors]]) -> str

+

Create a new string object from the given object. If encoding or +errors is specified, then the object must expose a data buffer +that will be decoded using the given encoding and error handler. +Otherwise, returns the result of object.str() (if defined) +or repr(object). +encoding defaults to sys.getdefaultencoding(). +errors defaults to 'strict'. +

s

+
s(signal_name: str, *, min_age: Union[float, ravestate.constraint.ConfigurableAge] = 0.0, max_age: Union[float, ravestate.constraint.ConfigurableAge] = 5.0, detached: bool = False) -> 'Signal'
 

Alias to call Signal-constructor

@@ -185,10 +195,10 @@

s

signal_name: Name of the Signal

  • -

    min_age: Minimum age for the signal, in seconds.

    +

    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. +

    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.

  • @@ -205,7 +215,7 @@

    Constraint

    Superclass for Signal, Conjunct and Disjunct

    Signal

    -
    Signal(self, name: str, *, min_age=0.0, max_age=1.0, detached=False)
    +
    Signal(self, name: str, *, min_age=0.0, max_age=5.0, detached=False)
     

    Class that represents a Signal