Implemented a first and basic version of a Rete rule engine

This commit is contained in:
2021-02-09 16:06:32 +01:00
parent 821dbed189
commit a2a8d5c5e5
110 changed files with 7301 additions and 1654 deletions
+228 -32
View File
@@ -8,7 +8,9 @@ from cache.IncCache import IncCache
from cache.ListCache import ListCache
from cache.ListIfNeededCache import ListIfNeededCache
from core.concept import Concept
from core.global_symbols import NotFound, Removed
from core.global_symbols import NotFound, Removed, EVENT_CONCEPT_ID_DELETED, \
EVENT_RULE_ID_DELETED
from core.rule import Rule, ACTION_TYPE_EXEC
from core.sheerka.SheerkaOntologyManager import SheerkaOntologyManager, OntologyManagerFrozen, OntologyManagerNotFrozen, \
OntologyManagerCannotPopLatest, OntologyAlreadyExists
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
@@ -168,22 +170,22 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.current_cache_manager().has("cache_name", "key")
def test_i_cannot_pop_ontology_when_not_frozen(self):
sheerka = self.get_sheerka()
sheerka, context = self.init_test().unpack()
manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only)
with pytest.raises(OntologyManagerNotFrozen):
manager.pop_ontology()
manager.pop_ontology(context)
def test_i_cannot_pop_the_latest_cache_manager(self):
sheerka = self.get_sheerka()
sheerka, context = self.init_test().unpack()
manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only)
manager.freeze()
with pytest.raises(OntologyManagerCannotPopLatest):
manager.pop_ontology()
manager.pop_ontology(context)
def test_i_can_pop_ontology(self):
sheerka = self.get_sheerka()
sheerka, context = self.init_test().unpack()
manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only)
manager.freeze()
@@ -191,13 +193,13 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
manager.push_ontology("ontology2")
manager.push_ontology("ontology3")
manager.pop_ontology()
manager.pop_ontology(context)
assert len(manager.ontologies) == 3
manager.pop_ontology()
manager.pop_ontology()
manager.pop_ontology(context)
manager.pop_ontology(context)
with pytest.raises(OntologyManagerCannotPopLatest):
manager.pop_ontology()
manager.pop_ontology(context)
def test_i_can_add_ontology(self):
sheerka, context = self.init_test().unpack()
@@ -217,7 +219,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.get("cache_name", "key3") is NotFound
new_ontology = manager.get_ontology()
manager.pop_ontology()
manager.pop_ontology(context)
# add another ontology, with its own values
manager.push_ontology("another ontology")
@@ -250,7 +252,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.get_ontology("name4")
def test_i_can_access_values_after_push_and_pop_cache_only_true(self):
sheerka = self.get_sheerka(cache_only=True)
sheerka, context = self.init_test(cache_only=True).unpack()
manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only)
manager.register_cache("cache_name", Cache())
@@ -263,7 +265,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
manager.put("cache_name", "key", "value2")
assert manager.get("cache_name", "key") == "value2"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value1"
def test_i_can_access_values_after_push_and_pop_cache_only_false(self):
@@ -296,7 +298,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[1].cache_manager.sdp.state.data == {'cache_name': {'key': 'value1'}}
# remove a layer
manager.pop_ontology()
manager.pop_ontology(context)
assert not manager.current_cache_manager().has("cache_name", "key") # value is no longer in cache
assert manager.get("cache_name", "key") == "value1"
@@ -333,13 +335,13 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.get("cache_name", "key") == "value4"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value3"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value2"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value1"
def test_i_have_access_to_sub_layers_values_cache_only_false(self):
@@ -368,6 +370,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
def test_i_can_get_value_from_all_layers(self):
sheerka = self.get_sheerka(cache_only=False)
context = self.get_context(sheerka)
manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only)
manager.register_cache("cache_name", Cache().auto_configure("cache_name"))
@@ -381,14 +384,15 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.get("cache_name", "key") == "value"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value"
def test_i_can_only_get_top_layer_values_when_dictionary_cache(self):
sheerka = self.get_sheerka()
context = self.get_context(sheerka)
manager = SheerkaOntologyManager(sheerka, sheerka.root_folder, sheerka.cache_only)
manager.register_cache("cache_name", DictionaryCache().auto_configure("cache_name"))
@@ -407,7 +411,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.get_all("cache_name") == {"key": "value", "key1": "value1"}
# I can get back my values after pop
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.copy("cache_name") == {"key": "value"}
def test_dictionary_caches_values_are_copied_when_a_new_ontology_is_pushed(self):
@@ -728,7 +732,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[1].cache_manager.sdp.state.data == {'cache_name': {"key": "value"}}
# The entry still exists in lower ontology
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value"
def test_i_can_remove_when_value_is_in_both_low_and_current_level(self):
@@ -764,7 +768,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[1].cache_manager.sdp.state.data == {'cache_name': {"key": "value"}}
# The entry still exists in lower ontology
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value"
def test_i_can_remove_when_value_is_not_low_level(self):
@@ -830,8 +834,8 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[2].cache_manager.sdp.state.data == {'cache_name': {"key": ["value", "value2"]}}
# The entry still exists in lower ontology
manager.pop_ontology()
manager.pop_ontology()
manager.pop_ontology(context)
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == ["value", "value2"]
def test_i_can_add_concept_default_layer(self):
@@ -941,7 +945,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[1].cache_manager.sdp.get('by_id', foo.id) == foo
# so I can get the old values when I pop ontology
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("by_key", foo.key) == foo
assert manager.get("by_id", foo.id) == foo
@@ -990,7 +994,7 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[1].cache_manager.sdp.get('by_id', foo.id) == foo
# so I can get the old values when I pop ontology
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("by_key", foo.key) == foo
assert manager.get("by_id", foo.id) == foo
@@ -1119,12 +1123,12 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[2].cache_manager.sdp.get("by_key") == {foo.key: foo}
# So I can pop
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("by_id", foo.id) == foo
assert manager.get("by_key", foo.key) == foo
# and pop again
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("by_id", foo.id) == foo
assert manager.get("by_key", foo.key) == foo
@@ -1167,6 +1171,145 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
"key5": "value5"
}
def test_i_can_keep_track_of_created_concepts_by_ontologies(self):
sheerka, context, foo = self.init_concepts("foo", create_new=True)
def from_cache(entry):
return sheerka.om.self_cache_manager.copy(entry)
def from_db(entry):
return sheerka.om.self_cache_manager.sdp.get(entry)
# check that the new concept is tracked
assert from_cache(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {'#unit_test#': {'1001'}}
assert from_cache(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {'1001': '#unit_test#'}
# add a new ontology and make sure the new concepts are tracked
sheerka.push_ontology(context, "new ontology")
sheerka.create_new_concept(context, Concept("bar"))
assert from_cache(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {'#unit_test#': {'1001'},
'new ontology': {'1002'}}
assert from_cache(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {'1001': '#unit_test#',
'1002': 'new ontology'}
# commit the info and check the DB
sheerka.om.commit(context)
assert from_db(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {'#unit_test#': {'1001'},
'new ontology': {'1002'}, }
assert from_db(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {'1001': '#unit_test#',
'1002': 'new ontology', }
# remove a concept a check
sheerka.remove_concept(context, sheerka.get_by_name("foo"))
assert from_cache(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {'new ontology': {'1002'}, }
assert from_cache(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {'1002': 'new ontology', }
sheerka.remove_concept(context, sheerka.get_by_name("bar"))
assert from_cache(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {}
assert from_cache(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {}
# commit again and check
sheerka.om.commit(context)
assert from_db(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {}
assert from_db(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {}
def test_i_can_keep_track_of_created_rules_by_ontologies(self):
sheerka, context, rule1 = self.init_format_rules(("rule1", "id.attr == 'value'", "True"))
def rules_by_ontology_from_cache():
res = sheerka.om.self_cache_manager.copy(SheerkaOntologyManager.RULES_BY_ONTOLOGY_ENTRY)
del res[SheerkaOntologyManager.ROOT_ONTOLOGY_NAME] # discard builtin rules
return res
def ontologies_from_cache():
res = sheerka.om.self_cache_manager.copy(SheerkaOntologyManager.ONTOLOGY_BY_RULE_ENTRY)
return {k: v for k, v in res.items() if v != SheerkaOntologyManager.ROOT_ONTOLOGY_NAME}
def rules_by_ontology_from_db():
res = sheerka.om.self_cache_manager.sdp.get(SheerkaOntologyManager.RULES_BY_ONTOLOGY_ENTRY)
del res[SheerkaOntologyManager.ROOT_ONTOLOGY_NAME] # discard builtin rules
return res
def ontologies_from_db():
res = sheerka.om.self_cache_manager.sdp.get(SheerkaOntologyManager.ONTOLOGY_BY_RULE_ENTRY)
return {k: v for k, v in res.items() if v != SheerkaOntologyManager.ROOT_ONTOLOGY_NAME}
assert rules_by_ontology_from_cache() == {"#unit_test#": {rule1.id}}
assert ontologies_from_cache() == {rule1.id: "#unit_test#"}
# add a new rule from a new ontology and check
sheerka.push_ontology(context, "new ontology")
rule2 = Rule(ACTION_TYPE_EXEC, "rule2", "id2.attr2 == 'value'", "True")
sheerka.create_new_rule(context, rule2)
assert rules_by_ontology_from_cache() == {"#unit_test#": {rule1.id}, "new ontology": {rule2.id}}
assert ontologies_from_cache() == {rule1.id: "#unit_test#", rule2.id: "new ontology"}
# commit and check the result
sheerka.om.commit(context)
assert rules_by_ontology_from_db() == {"#unit_test#": {rule1.id}, "new ontology": {rule2.id}}
assert ontologies_from_db() == {rule1.id: "#unit_test#", rule2.id: "new ontology"}
sheerka.remove_rule(context, rule1)
assert rules_by_ontology_from_cache() == {"new ontology": {rule2.id}}
assert ontologies_from_cache() == {rule2.id: "new ontology"}
# remove the last rule
sheerka.remove_rule(context, rule2)
assert rules_by_ontology_from_cache() == {}
assert ontologies_from_cache() == {}
# commit and check the db
sheerka.om.commit(context)
assert rules_by_ontology_from_db() == {}
assert ontologies_from_db() == {}
def test_i_can_keep_track_of_created_concept_on_ontology_pop(self):
sheerka, context, foo = self.init_concepts("foo", create_new=True)
events_raised = set()
sheerka.subscribe(EVENT_CONCEPT_ID_DELETED, lambda ctx, c: events_raised.add(c))
def from_cache(entry):
return sheerka.om.self_cache_manager.copy(entry)
sheerka.push_ontology(context, "new ontology")
sheerka.create_new_concept(context, Concept("bar"))
sheerka.create_new_concept(context, Concept("baz"))
sheerka.pop_ontology(context)
assert from_cache(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {'#unit_test#': {'1001'}}
assert from_cache(SheerkaOntologyManager.ONTOLOGY_BY_CONCEPT_ENTRY) == {'1001': '#unit_test#'}
# check that the 'concept is deleted' events are raised
assert events_raised == {'1002', '1003'}
def test_i_can_keep_track_of_created_rules_on_ontology_pop(self):
sheerka, context, rule1 = self.init_format_rules(("rule1", "id.attr == 'value'", "True"))
events_raised = set()
sheerka.subscribe(EVENT_RULE_ID_DELETED, lambda ctx, r: events_raised.add(r))
def rules_by_ontology_from_cache():
res = sheerka.om.self_cache_manager.copy(SheerkaOntologyManager.RULES_BY_ONTOLOGY_ENTRY)
del res[SheerkaOntologyManager.ROOT_ONTOLOGY_NAME] # discard builtin rules
return res
def ontologies_from_cache():
res = sheerka.om.self_cache_manager.copy(SheerkaOntologyManager.ONTOLOGY_BY_RULE_ENTRY)
return {k: v for k, v in res.items() if v != SheerkaOntologyManager.ROOT_ONTOLOGY_NAME}
sheerka.push_ontology(context, "new ontology")
sheerka.create_new_rule(context, Rule(ACTION_TYPE_EXEC, "rule2", "id2.attr2 == 'value'", "True"))
sheerka.create_new_rule(context, Rule(ACTION_TYPE_EXEC, "rule3", "id3.attr3 == 'value'", "True"))
sheerka.pop_ontology(context)
assert rules_by_ontology_from_cache() == {'#unit_test#': {'10'}}
assert ontologies_from_cache() == {'10': '#unit_test#'}
# check that the 'rule is deleted' events are raised
assert events_raised == {'11', '12'}
# def test_i_can_list_by_key_when_dictionaries(self):
# sheerka = self.get_sheerka(cache_only=False)
# context = self.get_context(sheerka)
@@ -1268,7 +1411,6 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
# assert manager.list_by_key("cache_name", "key2") == ["e", "f", "g"]
# assert manager.list_by_key("cache_name", "key3") == ["a", "b", "c", "e", "f", "g"]
def test_i_can_get_call_when_a_cache_is_cleared(self):
sheerka = self.get_sheerka(cache_only=False)
context = self.get_context(sheerka)
@@ -1401,11 +1543,11 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
assert manager.ontologies[2].cache_manager.get_cache("cache_name").copy() == {'key1': 'value1',
'key2': 'value2'}
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key1") == "new value1"
assert manager.get("cache_name", "key2") is NotFound
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key1") == "value1"
assert manager.get("cache_name", "key2") == "value2"
@@ -1453,9 +1595,63 @@ class TestSheerkaOntologyWithFileBasedSheerka(TestUsingFileBasedSheerka):
assert manager.get("cache_name", "key") == "value2"
manager.pop_ontology()
manager.pop_ontology(context)
assert manager.get("cache_name", "key") == "value1"
# put back the previous ontology
manager.push_ontology("new ontology")
assert manager.get("cache_name", "key") == "value2"
def test_i_can_remember_concept_and_rules_by_ontology(self):
sheerka, context, foo, r1 = self.init_test().with_concepts(
"foo",
create_new=True
).with_format_rules(
("rule1", "__ret", "True"),
).unpack()
sheerka.om.commit(context)
sheerka = self.new_sheerka_instance(False)
context = self.get_context(sheerka)
sheerka.create_new_concept(context, Concept("bar"))
r2 = sheerka.create_new_rule(context, Rule(ACTION_TYPE_EXEC, "rule2", "__ret.status", "True")).body.body
sheerka.om.commit(context)
sheerka.push_ontology(context, "new ontology")
sheerka.create_new_concept(context, Concept("baz"))
sheerka.create_new_rule(context, Rule(ACTION_TYPE_EXEC, "rule3", "id3.attr3 == 'value'", "True"))
sheerka.om.commit(context)
sheerka = self.new_sheerka_instance(False)
context = self.get_context(sheerka)
sheerka.push_ontology(context, "another ontology")
sheerka.create_new_concept(context, Concept("qux"))
r4 = sheerka.create_new_rule(context, Rule(ACTION_TYPE_EXEC, "rule4", "id4.attr4", "True")).body.body
sheerka.remove_concept(context, foo)
sheerka.remove_rule(context, r2)
sheerka.om.commit(context)
assert sheerka.om.self_cache_manager.copy(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {
'#unit_test#': {'1002'},
'another ontology': {'1004'},
}
assert sheerka.om.self_cache_manager.copy(SheerkaOntologyManager.RULES_BY_ONTOLOGY_ENTRY) == {
'#unit_test#': {r1.id},
'another ontology': {r4.id},
}
# in db
assert sheerka.om.self_cache_manager.sdp.get(SheerkaOntologyManager.CONCEPTS_BY_ONTOLOGY_ENTRY) == {
'#unit_test#': {'1002'},
'another ontology': {'1004'},
'new ontology': {'1003'}}
rules_from_db = sheerka.om.self_cache_manager.sdp.get(SheerkaOntologyManager.RULES_BY_ONTOLOGY_ENTRY)
del rules_from_db["__default__"]
assert rules_from_db == {
'#unit_test#': {'10'},
'another ontology': {'13'},
'new ontology': {'12'}}