diff --git a/.gitignore b/.gitignore index ea29631..d854002 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ requests.egg-info/ *.egg *.egg-info env/ +venv/ +venv_new/ +venv_no_ws_client/ .venv/ .eggs/ .tox/ diff --git a/.pylintrc b/.pylintrc index cc3a040..c97681a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,178 +1,14 @@ -[MASTER] - -# Use multiple processes to speed up Pylint. -jobs=2 - - [MESSAGES CONTROL] disable= - useless-suppression, - suppressed-message, - missing-docstring, - invalid-name, - no-member, - locally-disabled, - fixme, - import-error, - too-many-locals, - no-name-in-module, - too-many-instance-attributes, - logging-fstring-interpolation, - unbalanced-tuple-unpacking, - too-many-public-methods, - useless-super-delegation - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=10 - + import-error, + missing-docstring, + invalid-name, + too-few-public-methods, + too-many-arguments, + duplicate-code, + fixme, [FORMAT] -# Maximum number of characters on a single line. max-line-length=120 - -# Maximum number of lines in a module -max-module-lines=1000 - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=7 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=15 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=10 - -# Maximum number of statements in function / method body -max-statements=30 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=0 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/README.md b/README.md index 45d9aba..49bbaef 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,37 @@ named [py-sc-client](https://github.com/ostis-ai/py-sc-client). This module is compatible with 0.7.0 version of [OSTIS Technology platform](https://github.com/ostis-ai/ostis-web-platform). +## Installation py-sc-kpm + +py-sc-kpm is available on [PyPI](https://pypi.org/project/py-sc-kpm/): + +```sh +$ pip install py-sc-kpm +``` + +py-sc-kpm officially supports Python 3.8+. + # API Reference 1. [Classes](#classes) - + [ScKeynodes](#sckeynodes) - + [ScAgent](#scagent-and-scagentclassic) - + [ScModule](#scmodule) - + [ScServer](#scserver) + + [sc_keynodes](#sc_keynodes) + + [ScAgent](#scagent-and-scagentclassic) + + [ScModule](#scmodule) + + [ScServer](#scserver) 2. [Utils](#utils) - + [Common utils](#common-utils) - + [Creating utils](#creating-utils) - + [Retrieve utils](#retrieve-utils) - + [Action utils](#action-utils) + + [Common utils](#common-utils) + + [Creating utils](#creating-utils) + + [Retrieve utils](#retrieve-utils) + + [Action utils](#action-utils) 3. [Use-cases](#use-cases) +## Asynchrony + +Py-sc-kpm supports sync and async implementations. They are independent of each other: + +- `sc_kpm` - Synchronous +- `asc_kpm` - Asynchronous + ## Classes The library contains the python implementation of useful classes and functions to work with the sc-memory. @@ -29,29 +46,36 @@ The library contains the python implementation of useful classes and functions t ### ScKeynodes Class which provides the ability to cache the identifier and ScAddr of keynodes stored in the KB. +There are sync and async global instances: `asc_keynodes` and `sc_keynodes ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes +from asc_kpm import asc_keynodes # Get the provided identifier -ScKeynodes["identifier_of_keynode"] # Returns an ScAddr of the given identifier +sc_keynodes["identifier_of_keynode"] # Returns an ScAddr of the given identifier +await asc_keynodes.get_valid("identifier_of_keynode") # Get the unprovided identifier -ScKeynodes["not_stored_in_kb"] # Raises InvalidValueError if an identifier doesn't exist in the KB -ScKeynodes.get("not_stored_in_kb") # Returns an invalid ScAddr(0) in the same situation +sc_keynodes["not_stored_in_kb"] # Raises InvalidValueError if an identifier doesn't exist in the KB +sc_keynodes.get("not_stored_in_kb") # Returns an invalid ScAddr(0) in the same situation +await asc_keynodes.get("not_stored_in_kb") # Resolve identifier -ScKeynodes.resolve("my_class_node", sc_types.NODE_CONST_CLASS) # Returns the element if it exists, otherwise creates -ScKeynodes.resolve("some_node", None) # Returns the element if it exists, otherwise returns an invalid ScAddr(0) +sc_keynodes.resolve("my_class_node", sc_types.NODE_CONST_CLASS) # Returns the element if it exists, otherwise creates +sc_keynodes.resolve("some_node", None) # Returns the element if it exists, otherwise returns an invalid ScAddr(0) +await asc_keynodes.resolve("one_more_node", sc_types.NODE_CONST) # Delete identifier -ScKeynodes.delete("identifier_to_delete") # Delete keynode from kb and ScKeynodes cache +sc_keynodes.delete("identifier_to_delete") # Delete keynode from kb and sc_keynodes cache +await asc_keynodes.delete("identifier_to_delete") # Get rrel node -ScKeynodes.rrel_index(1) # Returns valid ScAddr of 'rrel_1' -ScKeynodes.rrel_index(11) # Raises KeyError if index more than 10 -ScKeynodes.rrel_index("some_str") # Raises TypeError if index is not int +sc_keynodes.rrel_index(1) # Returns valid ScAddr of 'rrel_1' +await asc_keynodes.rrel_index(2) +sc_keynodes.rrel_index(11) # Raises KeyError if index more than 10 +sc_keynodes.rrel_index("some_str") # Raises TypeError if index is not int ``` ### ScAgent and ScAgentClassic @@ -61,6 +85,7 @@ A classes for handling a single ScEvent. Define your agents like this: ```python from sc_client.models import ScAddr from sc_kpm import ScAgent, ScAgentClassic, ScResult +from asc_kpm import AScAgent, AScAgentClassic class ScAgentTest(ScAgent): @@ -74,6 +99,19 @@ class ScAgentClassicTest(ScAgentClassic): # ScAgentClassic automatically checks its action ... return ScResult.OK + + +class AScAgentTest(AScAgent): + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + return ScResult.OK + + +class AScAgentClassicTest(AScAgentClassic): + async def on_event(self, class_node: ScAddr, edge: ScAddr, action_node: ScAddr) -> ScResult: + # ScAgentClassic automatically checks its action + ... + return ScResult.OK + ``` For the ScAgent initialization you should define the sc-element and the type of the ScEvent. @@ -85,16 +123,24 @@ you should define the identifier of the action class node and arguments of the S **ClassicScAgent checks its action element automatically and doesn't run `on_event` method if checking fails.** +for async agent you must use async method ainit + ```python from sc_client.constants import sc_types from sc_client.constants.common import ScEventType -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes -action_class = ScKeynodes.resolve("test_class", sc_types.NODE_CONST_CLASS) +action_class = sc_keynodes.resolve("test_class", sc_types.NODE_CONST_CLASS) agent = ScAgentTest(action_class, ScEventType.ADD_OUTGOING_EDGE) classic_agent = ScAgentClassicTest("classic_test_class") classic_agent_ingoing = ScAgentClassicTest("classic_test_class", ScEventType.ADD_INGOING_EDGE) + +aio_action_class = sc_keynodes.resolve("aio_test_class", sc_types.NODE_CONST_CLASS) +agent = await AScAgentTest.ainit(aio_action_class, ScEventType.ADD_OUTGOING_EDGE) + +classic_agent = await AScAgentClassicTest.ainit("aio_classic_test_class") +classic_agent_ingoing = await AScAgentClassicTest.ainit("aio_classic_test_class", ScEventType.ADD_INGOING_EDGE) ``` ### ScModule @@ -115,7 +161,22 @@ module.add_agent(agent3) module.remove_agent(agent3) ``` -_Note: you don't need remove agents in the end of program._ +Async version: + +```python +from asc_kpm import AScModule + +module = await AScModule.ainit( + agent1, + agent2, +) +... +await module.add_agent(agent3) +... +await module.remove_agent(agent3) +``` + +_Note: you don't need remove agents at the end of program._ ### ScServer @@ -133,6 +194,18 @@ server.connect() server.disconnect() ``` +Asyncio + +```python +from asc_kpm import AScServer + +SC_SERVER_URL = "ws://localhost:8090/ws_json" +server = AScServer(SC_SERVER_URL) +await server.connect() +... +await server.disconnect() +``` + Or with-statement. We recommend it because it easier to use, and it's safe: ```python @@ -144,6 +217,15 @@ with server.connect(): ... ``` +```python +from asc_kpm import AScServer + +SC_SERVER_URL = "ws://localhost:8090/ws_json" +server = ScServer(SC_SERVER_URL) +async with server.connect(): + ... +``` + After connection, you can add and remove your modules. Manage your modules like this: ```python @@ -155,6 +237,15 @@ with server.connect(): server.remove_modules(module) ``` +```python +... +async with server.connect(): + module = await ScModule.ainit(...) + await server.add_modules(module) + ... + await server.remove_modules(module) +``` + But the modules are still not registered. For this use register_modules/unregister_modules methods: ```python @@ -166,6 +257,15 @@ with server.connect(): server.unregister_modules() ``` +```python +... +async with server.connect(): + ... + await server.register_modules() + ... + await server.unregister_modules() +``` + Or one mode with-statement. We also recommend to use so because it guarantees a safe agents unregistration if errors occur: @@ -177,6 +277,14 @@ with server.connect(): ... ``` +```python +... +async with server.connect(): + ... + async with server.register_modules(): + ... +``` + If you needn't separate connecting and registration, you can do it all using one command: ```python @@ -188,6 +296,15 @@ server.start() server.stop() ``` +```python +async with server.start(): + ... +# or +await server.start() +... +await server.stop() +``` + There is also method for stopping program until a SIGINT signal (or ^C, or terminate in IDE) is received. So you can leave agents registered for a long time: @@ -200,6 +317,15 @@ with server.connect(): server.serve() # Agents will be active until ^C ``` +```python +... +async with server.connect(): + # Creating some agents + async with server.register_modules(): + # Registration some agents + server.serve() # Agents will be active until ^C +``` + ### ScSets Sc-set is a construction that presents main node called `set_node` and linked elements. @@ -272,7 +398,7 @@ Methods and properties: from sc_client.constants import sc_types from sc_client.models import ScAddr -from sc_kpm.sc_sets import ScSet +from aio_sc_kpm.sc_sets import ScSet from sc_kpm.utils import create_node, create_nodes # Example elements to add @@ -329,7 +455,7 @@ The same logic as in `ScSet`, but *set_node_type* if set NODE_CONST_STRUCT. There are checks that set node has struct sc-type: ```python -from sc_kpm.sc_sets import ScStructure +from aio_sc_kpm.sc_sets import ScStructure sc_struct = ScStructure(..., set_node=..., set_node_type=...) @@ -360,10 +486,9 @@ No access by index. ```python from sc_client.constants import sc_types -from sc_kpm.sc_sets import ScOrientedSet +from aio_sc_kpm.sc_sets import ScOrientedSet from sc_kpm.utils import create_nodes - elements = create_nodes(*[sc_types.NODE_CONST] * 5) numbered_set = ScOrientedSet(*elements) assert numbered_set.elements_list == elements @@ -382,10 +507,9 @@ Easy access to elements by index (index i is marked with rrel(i + 1)) ```python from sc_client.constants import sc_types -from sc_kpm.sc_sets import ScNumberedSet +from aio_sc_kpm.sc_sets import ScNumberedSet from sc_kpm.utils import create_nodes - elements = create_nodes(*[sc_types.NODE_CONST] * 5) numbered_set = ScNumberedSet(*elements) assert numbered_set.elements_list == elements @@ -393,6 +517,21 @@ assert numbered_set[2] == elements[2] numbered_set[5] # raise KeyError ``` +#### Async AScSet, AScStructure, AScNumberedSet and AScOrientedSet + +Async versions of sc-set-classes are idential with some restrictions: + +- Constructor is the other method: + +```python +from asc_kpm.asc_sets import AScSet + +asc_set = await AScSet.create(...) +``` + +- There are no `__len__`, `__bool__`, `__contains__` method, + use async `len()`, `is_empty()` and `contains(...)` instead. + ## Utils There are some functions for working with nodes, edges, links: create them, search, get content, delete, etc. @@ -419,7 +558,7 @@ def create_nodes(*node_types: ScType) -> List[ScAddr]: ... ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.utils.common_utils import create_node, create_nodes lang = create_node(sc_types.NODE_CONST_CLASS) # ScAddr(...) @@ -437,6 +576,7 @@ For creating edge between **src** and **trg** with setting its type use **create ```python def create_edge(edge_type: ScType, src: ScAddr, trg: ScAddr) -> ScAddr: ... + def create_edges(edge_type: ScType, src: ScAddr, *targets: ScAddr) -> List[ScAddr]: ... ``` @@ -502,7 +642,7 @@ def create_norole_relation(src: ScAddr, trg: ScAddr, *nrel_nodes: ScAddr) -> ScA ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.utils import create_node, create_nodes from sc_kpm.utils import create_binary_relation, create_role_relation, create_norole_relation @@ -510,7 +650,7 @@ src, trg = create_nodes(*[sc_types.NODE_CONST] * 2) increase_relation = create_node(sc_types.NODE_CONST_CLASS) brel = create_binary_relation(sc_types.EDGE_ACCESS_CONST_POS_PERM, src, trg, increase_relation) # ScAddr(...) -rrel = create_role_relation(src, trg, ScKeynodes.rrel_index(1)) # ScAddr(...) +rrel = create_role_relation(src, trg, sc_keynodes.rrel_index(1)) # ScAddr(...) nrel = create_norole_relation(src, trg, create_node(sc_types.NODE_CONST_NOROLE)) # ScAddr(...) ``` @@ -575,18 +715,18 @@ def get_element_by_norole_relation(src: ScAddr, nrel_node: ScAddr) -> ScAddr: .. ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.identifiers import CommonIdentifiers from sc_kpm.utils import create_nodes, create_role_relation, create_norole_relation from sc_kpm.utils import get_element_by_role_relation, get_element_by_norole_relation src, trg_rrel, trg_nrel = create_nodes(*[sc_types.NODE_CONST] * 3) -rrel = create_role_relation(src, trg_rrel, ScKeynodes.rrel_index(1)) # ScAddr(...) -nrel = create_norole_relation(src, trg_nrel, ScKeynodes[CommonIdentifiers.NREL_SYSTEM_IDENTIFIER]) # ScAddr(...) +rrel = create_role_relation(src, trg_rrel, sc_keynodes.rrel_index(1)) # ScAddr(...) +nrel = create_norole_relation(src, trg_nrel, sc_keynodes[CommonIdentifiers.NREL_SYSTEM_IDENTIFIER]) # ScAddr(...) -result_rrel = get_element_by_role_relation(src, ScKeynodes.rrel_index(1)) # ScAddr(...) +result_rrel = get_element_by_role_relation(src, sc_keynodes.rrel_index(1)) # ScAddr(...) assert result_rrel == trg_rrel -result_nrel = get_element_by_norole_relation(src, ScKeynodes[CommonIdentifiers.NREL_SYSTEM_IDENTIFIER]) # ScAddr(...) +result_nrel = get_element_by_norole_relation(src, sc_keynodes[CommonIdentifiers.NREL_SYSTEM_IDENTIFIER]) # ScAddr(...) assert result_nrel == trg_nrel ``` @@ -616,13 +756,13 @@ def get_system_idtf(addr: ScAddr) -> str: ... ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.utils import create_node from sc_kpm.utils import get_system_idtf lang_en = create_node(sc_types.NODE_CONST_CLASS) # ScAddr(...) idtf = get_system_idtf(lang_en) # "lang_en" -assert ScKeynodes[idtf] == lang_en +assert sc_keynodes[idtf] == lang_en ``` ## Action utils @@ -645,13 +785,13 @@ This function should not be used in the ScAgentClassic. ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.identifiers import CommonIdentifiers from sc_kpm.utils import create_node, create_edge from sc_kpm.utils.action_utils import check_action_class action_node = create_node(sc_types.NODE_CONST) -create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, ScKeynodes[CommonIdentifiers.QUESTION], action_node) +create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, sc_keynodes[CommonIdentifiers.QUESTION], action_node) action_class = create_node(sc_types.NODE_CONST_CLASS) create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, action_class, action_node) @@ -672,7 +812,7 @@ def get_action_arguments(action_class: Union[ScAddr, Idtf], count: int) -> List[ ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.identifiers import CommonIdentifiers from sc_kpm.utils import create_node, create_edge, create_role_relation from sc_kpm.utils.action_utils import get_action_arguments @@ -681,12 +821,12 @@ action_node = create_node(sc_types.NODE_CONST) # Static argument argument1 = create_node(sc_types.NODE_CONST) -create_role_relation(action_node, argument1, ScKeynodes.rrel_index(1)) +create_role_relation(action_node, argument1, sc_keynodes.rrel_index(1)) # Dynamic argument dynamic_node = create_node(sc_types.NODE_CONST) -rrel_dynamic_arg = ScKeynodes[CommonIdentifiers.RREL_DYNAMIC_ARGUMENT] -create_role_relation(action_node, dynamic_node, rrel_dynamic_arg, ScKeynodes.rrel_index(2)) +rrel_dynamic_arg = sc_keynodes[CommonIdentifiers.RREL_DYNAMIC_ARGUMENT] +create_role_relation(action_node, dynamic_node, rrel_dynamic_arg, sc_keynodes.rrel_index(2)) argument2 = create_node(sc_types.NODE_CONST) create_edge(sc_types.EDGE_ACCESS_CONST_POS_TEMP, dynamic_node, argument2) @@ -711,7 +851,7 @@ Create and get structure with output of action from sc_client.constants import sc_types from sc_kpm.utils import create_node from sc_kpm.utils.action_utils import create_action_answer, get_action_answer -from sc_kpm.sc_sets import ScStructure +from aio_sc_kpm.sc_sets import ScStructure action_node = create_node(sc_types.NODE_CONST_STRUCT) answer_element = create_node(sc_types.NODE_CONST_STRUCT) @@ -723,7 +863,8 @@ assert result_elements == {answer_element} ### Call, execute and wait agent -Agent call function: creates **action node** with some arguments, concepts and connects it to the node with initiation identifier. +Agent call function: creates **action node** with some arguments, concepts and connects it to the node with initiation +identifier. Returns **question node** ```python @@ -741,7 +882,8 @@ Default reaction_node is `question_finished`. def wait_agent(seconds: float, question_node: ScAddr, reaction_node: ScAddr = None) -> None: ... ``` -Agent execute function: combines two previous functions -- calls, waits and returns question node and **True** if success +Agent execute function: combines two previous functions -- calls, waits and returns question node and **True** if +success ```python def execute_agent( @@ -753,12 +895,11 @@ def execute_agent( ) -> Tuple[ScAddr, bool]: ... ``` - ![execute_agent](docs/schemes/png/execute_agent.png) ```python from sc_client.models import ScLinkContentType -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.identifiers import CommonIdentifiers, QuestionStatus from sc_kpm.utils import create_link from sc_kpm.utils.action_utils import execute_agent, call_agent, wait_agent @@ -772,7 +913,7 @@ kwargs = dict( ) question = call_agent(**kwargs) # ScAddr(...) -wait_agent(3, question, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) +wait_agent(3, question, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) # or question, is_successfully = execute_agent(**kwargs, wait_time=3) # ScAddr(...), bool ``` @@ -815,7 +956,7 @@ Example: ```python from sc_client.models import ScLinkContentType -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.identifiers import CommonIdentifiers, QuestionStatus from sc_kpm.utils import create_link from sc_kpm.utils.action_utils import add_action_arguments, call_action, create_action, execute_action, wait_agent @@ -829,7 +970,7 @@ arguments = {arg1: False, arg2: False} add_action_arguments(action_node, arguments) call_action(action_node) -wait_agent(3, action_node, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) +wait_agent(3, action_node, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) # or is_successful = execute_action(action_node, wait_time=3) # bool ``` @@ -842,7 +983,8 @@ Function `finish_action` connects status class to action node: def finish_action(action_node: ScAddr, status: Idtf = QuestionStatus.QUESTION_FINISHED) -> ScAddr: ... ``` -Function `finish_action_with_status` connects `question_finished` and `question_finished_(un)successfully` statuses to it: +Function `finish_action_with_status` connects `question_finished` and `question_finished_(un)successfully` statuses to +it: ```python def finish_action_with_status(action_node: ScAddr, is_success: bool = True) -> None: ... @@ -852,7 +994,7 @@ def finish_action_with_status(action_node: ScAddr, is_success: bool = True) -> N ```python from sc_client.constants import sc_types -from sc_kpm import ScKeynodes +from sc_kpm import sc_keynodes from sc_kpm.identifiers import QuestionStatus from sc_kpm.utils import create_node, check_edge from sc_kpm.utils.action_utils import finish_action, finish_action_with_status @@ -866,18 +1008,24 @@ finish_action_with_status(action_node, True) finish_action(action_node, QuestionStatus.QUESTION_FINISHED) finish_action(action_node, QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY) -question_finished = ScKeynodes[QuestionStatus.QUESTION_FINISHED] -question_finished_successfully = ScKeynodes[QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY] +question_finished = sc_keynodes[QuestionStatus.QUESTION_FINISHED] +question_finished_successfully = sc_keynodes[QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY] assert check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, question_finished, action_node) assert check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, question_finished_successfully, action_node) ``` +### Async utils + +Asyncronous function are the same parameters and return values. + # Use-cases - Script for creating and registration agent until user press ^C: - [based on ScAgentClassic](docs/examples/register_and_wait_for_user.py) + - [based on AScAgentClassic](docs/examples/aio_register_and_wait_for_user.py) - Scripts for creating agent to calculate sum of two arguments and confirm result: - [based on ScAgent](docs/examples/sum_agent.py) - [based on ScAgentClassic](docs/examples/sum_agent_classic.py) + - [based on AScAgentClassic](docs/examples/aio_sum_agent_classic.py) - Logging examples: - [pretty project logging](docs/examples/pretty_logging.py) diff --git a/docs/changelog.md b/docs/changelog.md index 165977c..eb02ef1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.3.0] +### Added +- Async implementation +### Changed +- Py-sc-client version updated to 0.4.0 + ## [v0.2.0] ### Changed - Removed reconnection for py-sc-client 0.3.0 diff --git a/docs/examples/aio_register_and_wait_for_user.py b/docs/examples/aio_register_and_wait_for_user.py new file mode 100644 index 0000000..000ce63 --- /dev/null +++ b/docs/examples/aio_register_and_wait_for_user.py @@ -0,0 +1,32 @@ +""" +This code creates some test agent and registers until the user stops the process. +For this we wait for SIGINT. +""" +import asyncio +import logging + +from sc_client.models import ScAddr + +from asc_kpm import AScAgentClassic, AScModule, AScServer +from sc_kpm import ScResult + +logging.basicConfig(level=logging.INFO) + + +class TestScAgent(AScAgentClassic): + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + return ScResult.OK + + +async def main() -> None: + SC_SERVER_URL = "ws://localhost:8090/ws_json" + server = AScServer(SC_SERVER_URL) + async with await server.connect(): + module = await AScModule.ainit(await TestScAgent.ainit("sum_action_class")) + await server.add_modules(module) + async with await server.register_modules(): + server.serve() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/docs/examples/aio_sum_agent_classic.py b/docs/examples/aio_sum_agent_classic.py new file mode 100644 index 0000000..bae8699 --- /dev/null +++ b/docs/examples/aio_sum_agent_classic.py @@ -0,0 +1,75 @@ +""" +This code creates agent, executes and confirms the result. +Agent is based on ClassicScAgent and calculates sum of two static arguments. + +As you see, SumAgentClassic is more comfortable than ScAgent. +For initialisation, you need only name of action class and event type if it isn't 'add_outgoing_edge'. +""" +import asyncio +import logging + +from sc_client.models import ScAddr, ScLinkContentType + +from asc_kpm import AScAgentClassic, AScModule, AScServer +from asc_kpm.asc_sets import AScStructure +from asc_kpm.utils import create_link, get_link_content_data +from asc_kpm.utils.aio_action_utils import ( + create_action_answer, + execute_agent, + finish_action_with_status, + get_action_answer, + get_action_arguments, +) +from sc_kpm import ScResult +from sc_kpm.identifiers import CommonIdentifiers + +logging.basicConfig( + level=logging.INFO, format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", datefmt="[%d-%b-%y %H:%M:%S]" +) + + +class ASumAgentClassic(AScAgentClassic): + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + result = await self.run(action_element) + is_successful = result == ScResult.OK + await finish_action_with_status(action_element, is_successful) + self.logger.info("Agent finished %s", "successfully" if is_successful else "unsuccessfully") + return result + + async def run(self, action_node: ScAddr) -> ScResult: + self.logger.info("Agent began to run") + arg1_link, arg2_link = await get_action_arguments(action_node, 2) + if not arg1_link or not arg2_link: + return ScResult.ERROR_INVALID_PARAMS + arg1_content = await get_link_content_data(arg1_link) + arg2_content = await get_link_content_data(arg2_link) + if not isinstance(arg1_content, int) or not isinstance(arg2_content, int): + return ScResult.ERROR_INVALID_TYPE + await create_action_answer(action_node, await create_link(arg1_content + arg2_content, ScLinkContentType.INT)) + return ScResult.OK + + +async def main(): + asc_server = AScServer("ws://localhost:8090/ws_json") + async with asc_server.start(): + action_class_name = "sum" + agent = await ASumAgentClassic.ainit(action_class_name) + module = AScModule(agent) + await asc_server.add_modules(module) + question, is_successful = await execute_agent( + arguments={ + await create_link(2, ScLinkContentType.INT): False, + await create_link(3, ScLinkContentType.INT): False, + }, + concepts=[CommonIdentifiers.QUESTION, action_class_name], + wait_time=1, + ) + assert is_successful + answer_struct = await get_action_answer(question) + answer_link = (await AScStructure(set_node=answer_struct).elements_set).pop() + answer_content = await get_link_content_data(answer_link) + logging.info("Answer received: %s", repr(answer_content)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/docs/examples/sum_agent.py b/docs/examples/sum_agent.py index 4afa059..98ecba1 100644 --- a/docs/examples/sum_agent.py +++ b/docs/examples/sum_agent.py @@ -8,8 +8,8 @@ import logging -from sc_client.constants.common import ScEventType -from sc_client.models import ScAddr, ScLinkContentType +from sc_client import ScAddr, ScEventType +from sc_client.models import ScLinkContentType from sc_kpm import ScAgent, ScModule, ScResult, ScServer from sc_kpm.sc_sets import ScStructure @@ -63,7 +63,7 @@ def main(): create_link(3, ScLinkContentType.INT): False, }, concepts=[], - initiation=action_class_name, + initiation="sum", wait_time=1, ) assert is_successful diff --git a/requirements-dev.txt b/requirements-dev.txt index f991148..0f35569 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,8 @@ wheel==0.40.0 tox==4.4.11 pre-commit==3.2.2 pytest==7.3.0 -py-sc-client==0.3.0 +# py-sc-client==0.4.0 +git+https://github.com/ostis-ai/py-sc-client@refactor/full isort==5.12.0 pylint==2.17.2 black==23.3.0 diff --git a/setup.py b/setup.py index 3e6f15c..0c9cf42 100644 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ DIRECTORY_PATH = Path(__file__).parent README = (DIRECTORY_PATH / "README.md").read_text() -VERSION = "0.2.0" -INSTALL_REQUIRES = ["py-sc-client>=0.3.0"] +VERSION = "0.3.0" +INSTALL_REQUIRES = ["py-sc-client>=0.4.0"] CURRENT_PYTHON = sys.version_info[:2] REQUIRED_PYTHON = (3, 8) diff --git a/src/asc_kpm/__init__.py b/src/asc_kpm/__init__.py new file mode 100644 index 0000000..eef43d2 --- /dev/null +++ b/src/asc_kpm/__init__.py @@ -0,0 +1,14 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from asc_kpm import utils +from asc_kpm.asc_agent import AScAgent, AScAgentClassic +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.asc_module import AScModule +from asc_kpm.asc_server import AScServer +from sc_kpm import set_root_config + +set_root_config(__name__) diff --git a/src/asc_kpm/asc_agent.py b/src/asc_kpm/asc_agent.py new file mode 100644 index 0000000..9a37100 --- /dev/null +++ b/src/asc_kpm/asc_agent.py @@ -0,0 +1,98 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" +import logging +from abc import ABC, abstractmethod +from logging import getLogger +from typing import Optional, Union + +from sc_client.constants import sc_types +from sc_client.constants.common import ScEventType +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import AScEventParams, ScAddr, ScEvent +from sc_client.sc_exceptions import InvalidValueError + +from asc_kpm.asc_keynodes_ import Idtf, asc_keynodes +from asc_kpm.utils.aio_action_utils import check_action_class +from sc_kpm.identifiers import QuestionStatus +from sc_kpm.sc_result import ScResult + + +class AScAgentAbstract(ABC): + logger: logging.Logger + + def __init__(self, event_element: ScAddr, event_type: ScEventType) -> None: + self._event_element = event_element + self._event_type = event_type + self._event: Optional[ScEvent] = None + + @abstractmethod + def __repr__(self) -> str: + pass + + async def _register(self) -> None: + if self._event is not None: + self.logger.warning("Almost registered") + return + event_params = AScEventParams(self._event_element, self._event_type, self._callback) + self._event = (await asc_client.events_create(event_params))[0] + self.logger.info("Registered with ScEvent: %s - %s", repr(self._event_element), repr(self._event_type)) + + async def _unregister(self) -> None: + if self._event is None: + self.logger.warning("ScEvent was already destroyed or not registered") + return + await asc_client.events_destroy(self._event) + self._event = None + self.logger.info("Unregistered ScEvent: %s - %s", repr(self._event_element), repr(self._event_type)) + + async def _callback(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr): + result = await self.on_event(event_element, event_edge, action_element) + self.logger.info(result) + + @abstractmethod + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + pass + + +class AScAgent(AScAgentAbstract, ABC): + @classmethod + async def ainit(cls, event_element: Union[Idtf, ScAddr], event_type: ScEventType): + if isinstance(event_element, Idtf): + event_element = await asc_keynodes.resolve(event_element, sc_types.NODE_CONST_CLASS) + cls.logger = getLogger(f"{cls.__module__}.{cls.__name__}") + if not event_element.is_valid(): + cls.logger.error("Failed to initialize ScAgent: event_class is invalid") + raise InvalidValueError(f"event_class of {cls.__name__} is invalid") + return cls(event_element, event_type) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(event_class='{self._event_element}', event_type={repr(self._event_type)})" + + +class AScAgentClassic(AScAgent, ABC): + _action_class_name: Idtf + _action_class: ScAddr + + # pylint: disable=protected-access + # pylint: disable=arguments-differ + # pylint: disable=arguments-renamed + @classmethod + async def ainit( + cls, + action_class_name: Idtf, + event_element: Union[Idtf, ScAddr] = QuestionStatus.QUESTION_INITIATED, + event_type: ScEventType = ScEventType.ADD_OUTGOING_EDGE, + ): + instance = await super().ainit(event_element, event_type) + instance._action_class_name = action_class_name + instance._action_class = await asc_keynodes.resolve(action_class_name, sc_types.NODE_CONST_CLASS) + return instance + + async def _callback(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + if not await check_action_class(self._action_class, action_element): + return ScResult.SKIP + self.logger.info("Confirmed action class") + return await self.on_event(event_element, event_edge, action_element) diff --git a/src/asc_kpm/asc_keynodes_.py b/src/asc_kpm/asc_keynodes_.py new file mode 100644 index 0000000..63ee82d --- /dev/null +++ b/src/asc_kpm/asc_keynodes_.py @@ -0,0 +1,64 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from logging import Logger, getLogger +from typing import Dict, Optional + +from sc_client.constants.sc_types import NODE_CONST_ROLE, ScType +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScIdtfResolveParams +from sc_client.sc_exceptions import InvalidValueError + +Idtf = str + + +class AScKeynodes: + """Class which provides the ability to cache the identifier and ScAddr of keynodes stored in the KB.""" + + def __init__(self) -> None: + self._dict: Dict[Idtf, ScAddr] = {} + self._logger: Logger = getLogger(f"{__name__}.{self.__class__.__name__}") + self._max_rrel_index: int = 10 + + async def get_valid(self, identifier: Idtf) -> ScAddr: + """Get keynode, cannot be invalid ScAddr(0)""" + addr = await self.get(identifier) # pylint: disable=no-value-for-parameter + if not addr.is_valid(): + self._logger.error("Failed to get ScAddr by %s keynode: ScAddr is invalid", identifier) + raise InvalidValueError(f"ScAddr of {identifier} is invalid") + return addr + + async def delete(self, identifier: Idtf) -> bool: + """Delete keynode from the kb and memory and return boolean status""" + addr = await self.get_valid(identifier) + del self._dict[identifier] + return await asc_client.delete_elements(addr) + + async def get(self, identifier: Idtf) -> ScAddr: + """Get keynode, can be ScAddr(0)""" + return await self.resolve(identifier, None) + + async def resolve(self, identifier: Idtf, sc_type: Optional[ScType]) -> ScAddr: + """Get keynode. If sc_type is valid, an element will be created in the KB""" + addr = self._dict.get(identifier) + if addr is None: + params = ScIdtfResolveParams(idtf=identifier, type=sc_type) + addr = (await asc_client.resolve_keynodes(params))[0] + if addr.is_valid(): + self._dict[identifier] = addr + self._logger.debug("Resolved %s identifier with type %s: %s", repr(identifier), repr(sc_type), repr(addr)) + return addr + + async def rrel_index(self, index: int) -> ScAddr: + """Get rrel_i node. Max rrel index is 10""" + if not isinstance(index, int): + raise TypeError("Index of rrel node must be int") + if index > self._max_rrel_index: + raise KeyError(f"You cannot use rrel more than {self._max_rrel_index}") + return await self.resolve(f"rrel_{index}", NODE_CONST_ROLE) # pylint: disable=no-value-for-parameter + + +asc_keynodes = AScKeynodes() diff --git a/src/asc_kpm/asc_module.py b/src/asc_kpm/asc_module.py new file mode 100644 index 0000000..21882ea --- /dev/null +++ b/src/asc_kpm/asc_module.py @@ -0,0 +1,60 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" +from __future__ import annotations + +from logging import getLogger + +from asc_kpm.asc_agent import AScAgentAbstract + + +class AScModule: + """ + ScModule is a container for handling multiple ScAgent objects. + You can add and remove agents while module is registered or unregistered. + """ + + def __init__(self, *agents: AScAgentAbstract) -> None: + self._agents: set[AScAgentAbstract] = {*agents} + self._is_registered: bool = False + self.logger = getLogger(f"{self.__module__}.{self.__class__.__name__}") + + @classmethod + async def ainit(cls, *agents: AScAgentAbstract): + return cls(*agents) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({', '.join(map(repr, self._agents))})" + + async def add_agent(self, agent: AScAgentAbstract) -> None: + """Add an agent to the module and register it if the module is registered""" + if self._is_registered: + await agent._register() # pylint: disable=protected-access + self._agents.add(agent) + + async def remove_agent(self, agent: AScAgentAbstract) -> None: + """Remove agent from the module and unregister it if module is registered""" + if self._is_registered: + await agent._unregister() # pylint: disable=protected-access + self._agents.remove(agent) + + async def _register(self) -> None: + """Register all agents in the module""" + if self._is_registered: + self.logger.warning("Already registered") + return + if not self._agents: + self.logger.warning("No agents to register") + for agent in self._agents: + await agent._register() # pylint: disable=protected-access + self._is_registered = True + self.logger.info("Registered") + + async def _unregister(self) -> None: + """Unregister all agents from the module""" + for agent in self._agents: + await agent._unregister() # pylint: disable=protected-access + self._is_registered = False + self.logger.info("Unregistered") diff --git a/src/asc_kpm/asc_server.py b/src/asc_kpm/asc_server.py new file mode 100644 index 0000000..88d748a --- /dev/null +++ b/src/asc_kpm/asc_server.py @@ -0,0 +1,165 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https:#github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https:#opensource.org/licenses/MIT) +""" + +from __future__ import annotations + +import asyncio +from logging import Logger, getLogger +from typing import Awaitable, Callable + +from sc_client.core.asc_client_instance import asc_client + +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.asc_module import AScModule +from sc_kpm.identifiers import _IdentifiersResolver + + +class AScServer: + """ScServer connects to the server and stores modules""" + + def __init__(self, sc_server_url: str) -> None: + self._url: str = sc_server_url + self._modules: set[AScModule] = set() + self.is_registered = False + self.logger = getLogger(f"{self.__module__}.{self.__class__.__name__}") + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({', '.join(map(repr, self._modules))})" + + def connect(self) -> _AFinisher: + """Connect to server""" + + async def enter(): + await asc_client.connect(self._url) + self.logger.info("Connected by url: %s", repr(self._url)) + asc_client.set_on_reconnect_handler(self._register_after_reconnect) + await self.resolve_identifiers() + + return _AFinisher(enter, self.disconnect, self.logger) + + @staticmethod + async def resolve_identifiers(): + types_map = _IdentifiersResolver.get_types_map() + if types_map is None: + return + for idtf, sc_type in types_map: + await asc_keynodes.resolve(idtf, sc_type) + + async def disconnect(self) -> None: + """Disconnect from the server""" + await asc_client.disconnect() + self.logger.info("Disconnected from url: %s", repr(self._url)) + + async def add_modules(self, *modules: AScModule) -> None: + """Add modules to the server and register them if server is registered""" + if self.is_registered: + await self._register(*modules) + self._modules |= {*modules} + self.logger.info("Added modules: %s", ", ".join(map(repr, modules))) + + async def remove_modules(self, *modules: AScModule) -> None: + """Remove modules from the server and unregister them if server is registered""" + if self.is_registered: + await self._unregister(*modules) + self._modules -= {*modules} + self.logger.info("Removed modules: %s", ", ".join(map(repr, modules))) + + async def clear_modules(self) -> None: + """Remove all modules from the server and unregister them if server is registered""" + if self.is_registered: + await self._unregister(*self._modules) + self.logger.info("Removed all modules: %s", ", ".join(map(repr, self._modules))) + self._modules.clear() + + def register_modules(self) -> _AFinisher: + """Register all modules in the server""" + + async def enter(): + if self.is_registered: + self.logger.warning("Modules are already registered") + else: + await self._register(*self._modules) + self.is_registered = True + self.logger.info("Registered modules successfully") + + return _AFinisher(enter, self.unregister_modules, self.logger) + + async def unregister_modules(self) -> None: + """Unregister all modules from the server""" + if not self.is_registered: + self.logger.warning("Modules are already unregistered") + return + await self._unregister(*self._modules) + self.is_registered = False + self.logger.info("Unregistered modules successfully") + + async def _register_after_reconnect(self): + if not self.is_registered: + return + await self._unregister(*self._modules) + await self._register(*self._modules) + self.logger.info("Re-registered modules successfully") + + def start(self) -> _AFinisher: + """Connect and register modules""" + + async def enter(): + await self.connect() + await self.register_modules() + + return _AFinisher(enter, self.stop, self.logger) + + async def stop(self) -> None: + """Disconnect and unregister modules""" + await self.unregister_modules() + await self.disconnect() + + async def _register(self, *modules: AScModule) -> None: + if not asc_client.is_connected(): + self.logger.error("Failed to register: connection lost") + raise ConnectionError(f"Connection to url {repr(self._url)} lost") + for module in modules: + if not isinstance(module, AScModule): + self.logger.error("Failed to register: type of %s is not ScModule", repr(module)) + raise TypeError(f"{repr(module)} is not ScModule") + await module._register() # pylint: disable=protected-access + + async def _unregister(self, *modules: AScModule) -> None: + if not asc_client.is_connected(): + self.logger.error("Failed to unregister: connection to %s lost", repr(self._url)) + raise ConnectionError(f"Connection to {repr(self._url)} lost") + for module in modules: + await module._unregister() # pylint: disable=protected-access + + def serve(self) -> None: + """Serve agents until a SIGINT signal (^C, or stop in IDE) is received""" + self.logger.info("* Serving ScServer...") + try: + asyncio.get_event_loop().run_forever() + except KeyboardInterrupt: + self.logger.info("^C SIGINT was interrupted") + + +class _AFinisher: + """Class for calling finish method in with-statement""" + + def __init__( + self, enter_method: Callable[[], Awaitable], exit_method: Callable[[], Awaitable], logger: Logger + ) -> None: + self._enter_method = enter_method + self._exit_method = exit_method + self._logger = logger + + def __await__(self): + return self._enter_method().__await__() + + async def __aenter__(self) -> None: + await self + + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + if exc_val is not None: + self._logger.error("Raised error %s, finishing", repr(exc_val)) + await self._exit_method() diff --git a/src/asc_kpm/asc_sets/__init__.py b/src/asc_kpm/asc_sets/__init__.py new file mode 100644 index 0000000..f0812be --- /dev/null +++ b/src/asc_kpm/asc_sets/__init__.py @@ -0,0 +1,4 @@ +from asc_kpm.asc_sets.asc_numbered_set import AScNumberedSet +from asc_kpm.asc_sets.asc_oriented_set import AScOrientedSet +from asc_kpm.asc_sets.asc_set import AScSet +from asc_kpm.asc_sets.asc_structure import AScStructure diff --git a/src/asc_kpm/asc_sets/asc_numbered_set.py b/src/asc_kpm/asc_sets/asc_numbered_set.py new file mode 100644 index 0000000..38f71b3 --- /dev/null +++ b/src/asc_kpm/asc_sets/asc_numbered_set.py @@ -0,0 +1,72 @@ +from typing import Iterator, List + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScTemplate + +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.asc_sets.asc_set import AScSet + + +class AScNumberedSet(AScSet): + """ + ScNumberedSet is a class for handling numbered set structure in kb. + + It has main set_node and edged elements: + Edge to each element marked with 'rrel_1', 'rrel_2', and so on nodes. + """ + + async def add(self, *elements: ScAddr) -> None: + """Add elements to ScNumberedSet""" + if elements: + template = ScTemplate() + for index, element in enumerate(elements, await self.len() + 1): + template.triple_with_relation( + self._set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + element, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.rrel_index(index), + ) + await asc_client.template_generate(template) + + async def __aiter__(self) -> Iterator[ScAddr]: + for element in await self.elements_list: + yield element + + @property + async def elements_list(self) -> List[ScAddr]: + """Iterate by ScNumberedSet elements""" + templ = ScTemplate() + templ.triple_with_relation( + self._set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + sc_types.UNKNOWN, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + sc_types.NODE_VAR_ROLE, + ) + results = await asc_client.template_search(templ) + sorted_results = sorted((result for result in results), key=lambda res: res[4].value) + # Sort rrel elements addrs + return [result[2] for result in sorted_results] + + async def at(self, i: int) -> ScAddr: + templ = ScTemplate() + templ.triple_with_relation( + self._set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + sc_types.UNKNOWN, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.rrel_index(i + 1), + ) + results = await asc_client.template_search(templ) + if not results: + raise KeyError("No element by index") + return results[0][2] + + async def remove(self, *elements: ScAddr) -> None: + """Clear and add existing elements without given ones""" + # TODO: optimize + elements_new = [element for element in await self.elements_list if element not in elements] + await self.clear() + await self.add(*elements_new) diff --git a/src/asc_kpm/asc_sets/asc_oriented_set.py b/src/asc_kpm/asc_sets/asc_oriented_set.py new file mode 100644 index 0000000..2e94723 --- /dev/null +++ b/src/asc_kpm/asc_sets/asc_oriented_set.py @@ -0,0 +1,132 @@ +from typing import Iterator, List, Optional + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScTemplate, ScTemplateResult + +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.asc_sets.asc_set import AScSet +from asc_kpm.utils.aio_common_utils import create_edge, create_role_relation, search_role_relation_template +from sc_kpm.identifiers import CommonIdentifiers, ScAlias + + +class AScOrientedSet(AScSet): + """ + ScOrientedSet is a class for handling oriented set structure in kb. + + It has main set_node and edged elements: + Edge to the first element marked with 'rrel_1' node. + The other have edges between edges from set_node marked with 'nrel_basic_sequence'. + """ + + async def add(self, *elements: ScAddr) -> None: + """Add elements to ScOrientedSet""" + if elements: + elements_iterator = iter(elements) + current_edge = ( + await self._create_first_element_edge(next(elements_iterator)) + if await self.is_empty() + else await self._get_last_edge_and_delete_rrel_last() + ) + for element in elements_iterator: + current_edge = await self._create_next_edge(current_edge, element) + await self._mark_edge_with_rrel_last(current_edge) + + async def __aiter__(self) -> Iterator[ScAddr]: + """Iterate by ScOrientedSet elements""" + start_template = await search_role_relation_template( + self._set_node, await asc_keynodes.get_valid(CommonIdentifiers.RREL_ONE) + ) + if not start_template: + return + yield start_template.get(ScAlias.ELEMENT) + next_edge = start_template.get(ScAlias.ACCESS_EDGE) + while True: + elem_search_result = await self._search_next_element_template(next_edge) + if elem_search_result is None: + return + yield elem_search_result.get(ScAlias.ELEMENT) + next_edge = elem_search_result.get(ScAlias.ACCESS_EDGE) + + @property + async def elements_list(self) -> List[ScAddr]: + """List of elements with order""" + return [el async for el in self] + + async def remove(self, *elements: ScAddr) -> None: + """Clear and add existing elements without given ones""" + # TODO: optimize + elements_new = [element for element in await self.elements_list if element not in elements] + await self.clear() + await self.add(*elements_new) + + async def _create_first_element_edge(self, element: ScAddr) -> ScAddr: + """Create marked with rrel_1 edge to first element""" + return await create_role_relation( + self._set_node, element, await asc_keynodes.get_valid(CommonIdentifiers.RREL_ONE) + ) + + async def _get_last_edge_and_delete_rrel_last(self) -> Optional[ScAddr]: + """Search last edge of ScOrientedSet is it exists""" + # Search marked last edge + template = ScTemplate() + template.triple_with_relation( + self._set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.ACCESS_EDGE, + sc_types.UNKNOWN, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.RELATION_EDGE, + await asc_keynodes.get_valid(CommonIdentifiers.RREL_LAST), + ) + last_elem_templates = await asc_client.template_search(template) + if last_elem_templates: + last_elem_template = last_elem_templates[0] + await asc_client.delete_elements( + last_elem_template.get(ScAlias.RELATION_EDGE) + ) # Delete edge between rrel_last and edge + return last_elem_template.get(ScAlias.ACCESS_EDGE) + + # Search unmarked last edge + next_elem_result = await search_role_relation_template( + self._set_node, await asc_keynodes.get_valid(CommonIdentifiers.RREL_ONE) + ) + while True: + next_edge = next_elem_result.get(ScAlias.ACCESS_EDGE) + next_elem_result = await self._search_next_element_template(next_edge) + if next_elem_result is None: + return next_edge + + async def _create_next_edge(self, previous_edge: ScAddr, element: ScAddr) -> ScAddr: + """Create edge to element and connect with previous edge""" + template = ScTemplate() + template.triple( + self._set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.ACCESS_EDGE, + element, + ) + template.triple_with_relation( + previous_edge, + sc_types.EDGE_D_COMMON_VAR, + ScAlias.ACCESS_EDGE, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.get_valid(CommonIdentifiers.NREL_BASIC_SEQUENCE), + ) + return (await asc_client.template_generate(template)).get(ScAlias.ACCESS_EDGE) + + @staticmethod + async def _mark_edge_with_rrel_last(last_edge: ScAddr) -> None: + await create_edge( + sc_types.EDGE_ACCESS_CONST_POS_PERM, await asc_keynodes.get_valid(CommonIdentifiers.RREL_LAST), last_edge + ) + + async def _search_next_element_template(self, cur_element_edge: ScAddr) -> Optional[ScTemplateResult]: + templ = ScTemplate() + templ.triple_with_relation( + cur_element_edge, + sc_types.EDGE_D_COMMON_VAR, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.ACCESS_EDGE, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.get_valid(CommonIdentifiers.NREL_BASIC_SEQUENCE), + ) + templ.triple(self._set_node, ScAlias.ACCESS_EDGE, sc_types.UNKNOWN >> ScAlias.ELEMENT) + search_results = await asc_client.template_search(templ) + return search_results[0] if search_results else None diff --git a/src/asc_kpm/asc_sets/asc_set.py b/src/asc_kpm/asc_sets/asc_set.py new file mode 100644 index 0000000..8d158c1 --- /dev/null +++ b/src/asc_kpm/asc_sets/asc_set.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +from typing import Iterator + +from sc_client import ScType +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScConstruction, ScTemplate, ScTemplateResult + +from asc_kpm.utils.aio_common_utils import create_node + + +class AScSet: + """ + ScSet is a class for handling set construction in kb. + + It has main set_node and edged elements. + """ + + def __init__(self, set_node: ScAddr) -> None: + self._set_node = set_node + + @classmethod + async def create(cls, *elements: ScAddr, set_node: ScAddr = None, set_node_type: ScType = None): + if set_node is None: + if set_node_type is None: + set_node_type = sc_types.NODE_CONST + set_node = await create_node(set_node_type) + sc_set = cls(set_node) + await sc_set.add(*elements) + return sc_set + + async def add(self, *elements: ScAddr) -> None: + """Add elements to ScSet""" + if elements: + construction = ScConstruction() + for element in elements: + construction.create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, self._set_node, element) + await asc_client.create_elements(construction) + + @property + def set_node(self) -> ScAddr: + """Get the main element of ScSet""" + return self._set_node + + def __eq__(self, other: AScSet) -> bool: + return self._set_node == other._set_node + + @property + async def elements_set(self) -> set[ScAddr]: + """Set of elements without order and duplicates""" + search_results = await self._elements_search_results() + elements = {result[2] for result in search_results} + return elements + + async def len(self) -> int: + """Get ScSet power""" + return len(await self.elements_set) # No duplicates + + async def is_empty(self) -> bool: + """Check if ScSet doesn't contain any element""" + return not bool(await self._elements_search_results()) + + async def __aiter__(self) -> Iterator[ScAddr]: + """Iterate by ScSet elements""" + for element in await self.elements_set: + yield element + + async def __aexit__(self, exc_type, exc_val, exc_tb): + pass + + async def contains(self, element: ScAddr) -> bool: + """Check if ScSet contains element""" + return element in await self.elements_set + + async def remove(self, *elements: ScAddr) -> None: + """Remove the connections between set_node and elements""" + templ = ScTemplate() + for element in elements: + templ.triple(self._set_node, sc_types.EDGE_ACCESS_VAR_POS_PERM, element) + template_results = await asc_client.template_search(templ) + await asc_client.delete_elements(*(res[1] for res in template_results)) + + async def clear(self) -> None: + """Remove the connections between set_node and all elements""" + template_results = await self._elements_search_results() + await asc_client.delete_elements(*(res[1] for res in template_results)) + + async def _elements_search_results(self) -> list[ScTemplateResult]: + """Template search of all elements""" + templ = ScTemplate() + templ.triple(self._set_node, sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_types.UNKNOWN) + return await asc_client.template_search(templ) diff --git a/src/asc_kpm/asc_sets/asc_structure.py b/src/asc_kpm/asc_sets/asc_structure.py new file mode 100644 index 0000000..75a4640 --- /dev/null +++ b/src/asc_kpm/asc_sets/asc_structure.py @@ -0,0 +1,25 @@ +from sc_client import ScType +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr +from sc_client.sc_exceptions import InvalidTypeError + +from asc_kpm.asc_sets.asc_set import AScSet + + +class AScStructure(AScSet): + """ + ScStructure is a class for handling structure construction in kb. + + It has main set_node with type NODE_CONST_STRUCT and edged elements. + """ + + @classmethod + async def create(cls, *elements: ScAddr, set_node: ScAddr = None, set_node_type: ScType = None): + if set_node_type is None: + set_node_type = sc_types.NODE_CONST_STRUCT + if set_node is not None: + set_node_type = (await asc_client.check_elements(set_node))[0] + if not set_node_type.is_struct(): + raise InvalidTypeError + return await super().create(*elements, set_node=set_node, set_node_type=set_node_type) diff --git a/src/asc_kpm/utils/__init__.py b/src/asc_kpm/utils/__init__.py new file mode 100644 index 0000000..1b9eeae --- /dev/null +++ b/src/asc_kpm/utils/__init__.py @@ -0,0 +1,27 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from asc_kpm.utils import aio_action_utils, aio_iteration_utils +from asc_kpm.utils.aio_common_utils import ( + check_edge, + create_binary_relation, + create_edge, + create_edges, + create_link, + create_links, + create_node, + create_nodes, + create_norole_relation, + create_role_relation, + delete_edges, + get_edge, + get_edges, + get_element_by_norole_relation, + get_element_by_role_relation, + get_link_content_data, + get_system_idtf, + search_role_relation_template, +) diff --git a/src/asc_kpm/utils/aio_action_utils.py b/src/asc_kpm/utils/aio_action_utils.py new file mode 100644 index 0000000..62a95de --- /dev/null +++ b/src/asc_kpm/utils/aio_action_utils.py @@ -0,0 +1,174 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" +import asyncio +from asyncio import Future +from typing import Dict, List, Tuple, Union + +from sc_client.constants import sc_types +from sc_client.constants.common import ScEventType +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import AScEventParams, ScAddr, ScConstruction, ScTemplate + +from asc_kpm.asc_keynodes_ import Idtf, asc_keynodes +from asc_kpm.asc_sets import AScStructure +from asc_kpm.utils.aio_common_utils import ( + check_edge, + create_edge, + create_node, + create_norole_relation, + create_role_relation, + get_element_by_role_relation, +) +from sc_kpm.identifiers import CommonIdentifiers, QuestionStatus, ScAlias + +COMMON_WAIT_TIME: float = 5 + + +async def check_action_class(action_class: Union[ScAddr, Idtf], action_node: ScAddr) -> bool: + action_class = await asc_keynodes.get_valid(action_class) if isinstance(action_class, Idtf) else action_class + templ = ScTemplate() + templ.triple(action_class, sc_types.EDGE_ACCESS_VAR_POS_PERM, action_node) + templ.triple( + await asc_keynodes.get_valid(CommonIdentifiers.QUESTION), sc_types.EDGE_ACCESS_VAR_POS_PERM, action_node + ) + search_results = await asc_client.template_search(templ) + return len(search_results) > 0 + + +async def get_action_arguments(action_node: ScAddr, count: int) -> List[ScAddr]: + arguments = [] + for index in range(1, count + 1): + argument = await get_element_by_role_relation(action_node, await asc_keynodes.rrel_index(index)) + arguments.append(argument) + return arguments + + +async def create_action_answer(action_node: ScAddr, *elements: ScAddr) -> None: + asc_structure = await AScStructure.create(*elements) + answer_struct_node = asc_structure.set_node + await create_norole_relation( + action_node, answer_struct_node, await asc_keynodes.get_valid(CommonIdentifiers.NREL_ANSWER) + ) + + +async def get_action_answer(action_node: ScAddr) -> ScAddr: + templ = ScTemplate() + templ.triple_with_relation( + action_node, + sc_types.EDGE_D_COMMON_VAR >> ScAlias.RELATION_EDGE, + sc_types.NODE_VAR_STRUCT >> ScAlias.ELEMENT, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.get_valid(CommonIdentifiers.NREL_ANSWER), + ) + if search_results := await asc_client.template_search(templ): + return search_results[0].get(ScAlias.ELEMENT) + return ScAddr(0) + + +IsDynamic = bool + + +async def execute_agent( + arguments: Dict[ScAddr, IsDynamic], + concepts: List[Idtf], + initiation: Idtf = QuestionStatus.QUESTION_INITIATED, + reaction: Idtf = QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY, + wait_time: float = COMMON_WAIT_TIME, +) -> Tuple[ScAddr, bool]: + question = await call_agent(arguments, concepts, initiation) + await wait_agent(wait_time, question) + result = await check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, await asc_keynodes.get_valid(reaction), question) + return question, result + + +async def call_agent( + arguments: Dict[ScAddr, IsDynamic], + concepts: List[Idtf], + initiation: Idtf = QuestionStatus.QUESTION_INITIATED, +) -> ScAddr: + question = await create_action(*concepts) + await add_action_arguments(question, arguments) + await call_action(question, initiation) + return question + + +async def create_action(*concepts: Idtf) -> ScAddr: + construction = ScConstruction() + construction.create_node(sc_types.NODE_CONST, ScAlias.ACTION_NODE) + for concept in concepts: + construction.create_edge( + sc_types.EDGE_ACCESS_CONST_POS_PERM, + await asc_keynodes.resolve(concept, sc_types.NODE_CONST_CLASS), + ScAlias.ACTION_NODE, + ) + action_node = (await asc_client.create_elements(construction))[0] + return action_node + + +async def add_action_arguments(action_node: ScAddr, arguments: Dict[ScAddr, IsDynamic]) -> None: + rrel_dynamic_arg = await asc_keynodes.get_valid(CommonIdentifiers.RREL_DYNAMIC_ARGUMENT) + index: int + argument: ScAddr + for index, (argument, is_dynamic) in enumerate(arguments.items(), 1): + if argument.is_valid(): + rrel_i = await asc_keynodes.rrel_index(index) + if is_dynamic: + dynamic_node = await create_node(sc_types.NODE_CONST) + await create_role_relation(action_node, dynamic_node, rrel_dynamic_arg, rrel_i) + await create_edge(sc_types.EDGE_ACCESS_CONST_POS_TEMP, dynamic_node, argument) + else: + await create_role_relation(action_node, argument, rrel_i) + + +async def execute_action( + action_node: ScAddr, + initiation: Idtf = QuestionStatus.QUESTION_INITIATED, + reaction: Idtf = QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY, + wait_time: float = COMMON_WAIT_TIME, +) -> bool: + await call_action(action_node, initiation) + await wait_agent(wait_time, action_node) + result = await check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, await asc_keynodes.get_valid(reaction), action_node) + return result + + +async def call_action(action_node: ScAddr, initiation: Idtf = QuestionStatus.QUESTION_INITIATED) -> None: + initiation_node = await asc_keynodes.resolve(initiation, sc_types.NODE_CONST_CLASS) + await create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, initiation_node, action_node) + + +async def wait_agent(seconds: float, question_node: ScAddr, reaction_node: ScAddr = None) -> None: + reaction_node = reaction_node or await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED) + finish_future = Future() + + async def event_callback(_: ScAddr, __: ScAddr, trg: ScAddr) -> None: + if trg != reaction_node: + return + finish_future.set_result(True) + + async def timer(): + await asyncio.sleep(seconds) + finish_future.set_result(False) + + asyncio.create_task(timer()) + event_params = AScEventParams(question_node, ScEventType.ADD_INGOING_EDGE, event_callback) + sc_event = (await asc_client.events_create(event_params))[0] + if not await check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, reaction_node, question_node): + await finish_future + await asc_client.events_destroy(sc_event) + # TODO: return status in 0.2.0 + + +async def finish_action(action_node: ScAddr, status: Idtf = QuestionStatus.QUESTION_FINISHED) -> ScAddr: + return await create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, await asc_keynodes.get_valid(status), action_node) + + +async def finish_action_with_status(action_node: ScAddr, is_success: bool = True) -> None: + status = ( + QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY if is_success else QuestionStatus.QUESTION_FINISHED_UNSUCCESSFULLY + ) + await finish_action(action_node, status) + await finish_action(action_node, QuestionStatus.QUESTION_FINISHED) diff --git a/src/asc_kpm/utils/aio_common_utils.py b/src/asc_kpm/utils/aio_common_utils.py new file mode 100644 index 0000000..52a1a1d --- /dev/null +++ b/src/asc_kpm/utils/aio_common_utils.py @@ -0,0 +1,151 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from typing import List, Optional, Union + +from sc_client import ScType +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScConstruction, ScLinkContent, ScLinkContentType, ScTemplate, ScTemplateResult +from sc_client.models.sc_construction import ScLinkContentData + +from asc_kpm.asc_keynodes_ import Idtf, asc_keynodes +from sc_kpm.identifiers import CommonIdentifiers, ScAlias + + +async def create_nodes(*node_types: ScType) -> List[ScAddr]: + construction = ScConstruction() + for node_type in node_types: + construction.create_node(node_type) + return await asc_client.create_elements(construction) + + +async def create_node(node_type: ScType) -> ScAddr: + return (await create_nodes(node_type))[0] + + +async def create_links( + *contents: Union[str, int], + content_type: ScLinkContentType = ScLinkContentType.STRING, + link_type: ScType = sc_types.LINK_CONST, +) -> List[ScAddr]: + construction = ScConstruction() + for content in contents: + link_content = ScLinkContent(content, content_type) + construction.create_link(link_type, link_content) + return await asc_client.create_elements(construction) + + +async def create_link( + content: Union[str, int], + content_type: ScLinkContentType = ScLinkContentType.STRING, + link_type: ScType = sc_types.LINK_CONST, +) -> ScAddr: + return (await create_links(content, content_type=content_type, link_type=link_type))[0] + + +async def create_edge(edge_type: ScType, src: ScAddr, trg: ScAddr) -> ScAddr: + return (await create_edges(edge_type, src, trg))[0] + + +async def create_edges(edge_type: ScType, src: ScAddr, *targets: ScAddr) -> List[ScAddr]: + construction = ScConstruction() + for trg in targets: + construction.create_edge(edge_type, src, trg) + return await asc_client.create_elements(construction) + + +async def create_binary_relation(edge_type: ScType, src: ScAddr, trg: ScAddr, *relations: ScAddr) -> ScAddr: + construction = ScConstruction() + construction.create_edge(edge_type, src, trg, ScAlias.RELATION_EDGE) + for relation in relations: + construction.create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, relation, ScAlias.RELATION_EDGE) + return (await asc_client.create_elements(construction))[0] + + +async def create_role_relation(src: ScAddr, trg: ScAddr, *rrel_nodes: ScAddr) -> ScAddr: + return await create_binary_relation(sc_types.EDGE_ACCESS_CONST_POS_PERM, src, trg, *rrel_nodes) + + +async def create_norole_relation(src: ScAddr, trg: ScAddr, *nrel_nodes: ScAddr) -> ScAddr: + return await create_binary_relation(sc_types.EDGE_D_COMMON_CONST, src, trg, *nrel_nodes) + + +async def check_edge(edge_type: ScType, source: ScAddr, target: ScAddr) -> bool: + return bool(await get_edges(source, target, edge_type)) + + +async def get_edge(source: ScAddr, target: ScAddr, edge_type: ScType) -> ScAddr: + edges = await get_edges(source, target, edge_type) + return edges[0] if edges else ScAddr(0) + + +async def get_edges(source: ScAddr, target: ScAddr, *edge_types: ScType) -> List[ScAddr]: + result_edges = [] + for edge_type in edge_types: + templ = ScTemplate() + templ.triple(source, edge_type, target) + results = await asc_client.template_search(templ) + result_edges.extend(result[1] for result in results) + return result_edges + + +async def get_system_idtf(addr: ScAddr) -> Idtf: + nrel_system_idtf = await asc_keynodes.get_valid(CommonIdentifiers.NREL_SYSTEM_IDENTIFIER) + + templ = ScTemplate() + templ.triple_with_relation( + addr, + sc_types.EDGE_D_COMMON_VAR, + sc_types.LINK_VAR >> ScAlias.LINK, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + nrel_system_idtf, + ) + result = await asc_client.template_search(templ) + if result: + return await get_link_content_data(result[0].get(ScAlias.LINK)) + return "" + + +async def _search_relation_template(src: ScAddr, rel_node: ScAddr, rel_type: ScType) -> Optional[ScTemplateResult]: + template = ScTemplate() + template.triple_with_relation( + src, + rel_type >> ScAlias.ACCESS_EDGE, + sc_types.UNKNOWN >> ScAlias.ELEMENT, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + rel_node, + ) + result = await asc_client.template_search(template) + return result[0] if result else None + + +async def search_role_relation_template(src: ScAddr, rrel_node: ScAddr) -> Optional[ScTemplateResult]: + return await _search_relation_template(src, rrel_node, sc_types.EDGE_ACCESS_VAR_POS_PERM) + + +async def search_norole_relation_template(src: ScAddr, nrel_node: ScAddr) -> Optional[ScTemplateResult]: + return await _search_relation_template(src, nrel_node, sc_types.EDGE_D_COMMON_VAR) + + +async def get_element_by_role_relation(src: ScAddr, rrel_node: ScAddr) -> ScAddr: + search_result = await search_role_relation_template(src, rrel_node) + return search_result.get(ScAlias.ELEMENT) if search_result else ScAddr(0) + + +async def get_element_by_norole_relation(src: ScAddr, nrel_node: ScAddr) -> ScAddr: + search_result = await search_norole_relation_template(src, nrel_node) + return search_result.get(ScAlias.ELEMENT) if search_result else ScAddr(0) + + +async def get_link_content_data(link: ScAddr) -> ScLinkContentData: + content_part = await asc_client.get_link_content(link) + return content_part[0].data + + +async def delete_edges(source: ScAddr, target: ScAddr, *edge_types: ScType) -> bool: + edges = await get_edges(source, target, *edge_types) + return await asc_client.delete_elements(*edges) diff --git a/src/asc_kpm/utils/aio_iteration_utils.py b/src/asc_kpm/utils/aio_iteration_utils.py new file mode 100644 index 0000000..1ce2eb3 --- /dev/null +++ b/src/asc_kpm/utils/aio_iteration_utils.py @@ -0,0 +1,13 @@ +from typing import Iterable, Iterator + +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr +from sc_client.models.sc_construction import ScLinkContentData + +from sc_kpm.utils.iteration_utils import iter_link_contents_data + + +async def iter_links_data(links: Iterable[ScAddr]) -> Iterator[ScLinkContentData]: + """Iterate by contents data in links""" + contents = await asc_client.get_link_content(*links) + return iter_link_contents_data(contents) diff --git a/src/sc_kpm/__init__.py b/src/sc_kpm/__init__.py index 7f04b92..1cc672c 100644 --- a/src/sc_kpm/__init__.py +++ b/src/sc_kpm/__init__.py @@ -7,7 +7,7 @@ from sc_kpm import utils from sc_kpm.logging import set_root_config from sc_kpm.sc_agent import ScAgent, ScAgentClassic -from sc_kpm.sc_keynodes import ScKeynodes +from sc_kpm.sc_keynodes_ import sc_keynodes from sc_kpm.sc_module import ScModule from sc_kpm.sc_result import ScResult from sc_kpm.sc_server import ScServer diff --git a/src/sc_kpm/identifiers.py b/src/sc_kpm/identifiers.py index aaedff7..c6550e8 100644 --- a/src/sc_kpm/identifiers.py +++ b/src/sc_kpm/identifiers.py @@ -3,14 +3,14 @@ Distributed under the MIT License (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from dataclasses import dataclass +from typing import List, Optional, Tuple +from sc_client import ScType from sc_client.constants import sc_types -from sc_kpm.sc_keynodes import Idtf, ScKeynodes +from sc_kpm.sc_keynodes_ import Idtf -@dataclass(frozen=True) class CommonIdentifiers: QUESTION: Idtf = "question" EXACT_VALUE: Idtf = "exact_value" @@ -24,7 +24,6 @@ class CommonIdentifiers: CONCEPT_FILENAME: Idtf = "concept_filename" -@dataclass(frozen=True) class QuestionStatus: QUESTION_INITIATED: Idtf = "question_initiated" QUESTION_FINISHED: Idtf = "question_finished" @@ -32,7 +31,6 @@ class QuestionStatus: QUESTION_FINISHED_UNSUCCESSFULLY: Idtf = "question_finished_unsuccessfully" -@dataclass(frozen=True) class ScAlias: ACTION_NODE: str = "_action_node" RELATION_EDGE: str = "_relation_edge" @@ -50,27 +48,23 @@ class _IdentifiersResolver: is_resolved = False @classmethod - def resolve(cls) -> None: - if cls.is_resolved: - return - types_map = { - CommonIdentifiers.QUESTION: sc_types.NODE_CONST_CLASS, - CommonIdentifiers.EXACT_VALUE: sc_types.NODE_CONST_CLASS, - CommonIdentifiers.RREL_DYNAMIC_ARGUMENT: sc_types.NODE_CONST_ROLE, - CommonIdentifiers.RREL_ONE: sc_types.NODE_CONST_ROLE, - CommonIdentifiers.RREL_TWO: sc_types.NODE_CONST_ROLE, - CommonIdentifiers.RREL_LAST: sc_types.NODE_CONST_ROLE, - CommonIdentifiers.NREL_BASIC_SEQUENCE: sc_types.NODE_CONST_NOROLE, - CommonIdentifiers.NREL_SYSTEM_IDENTIFIER: sc_types.NODE_CONST_NOROLE, - CommonIdentifiers.NREL_ANSWER: sc_types.NODE_CONST_NOROLE, - CommonIdentifiers.CONCEPT_FILENAME: sc_types.NODE_CONST_CLASS, - QuestionStatus.QUESTION_INITIATED: sc_types.NODE_CONST_CLASS, - QuestionStatus.QUESTION_FINISHED: sc_types.NODE_CONST_CLASS, - QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY: sc_types.NODE_CONST_CLASS, - QuestionStatus.QUESTION_FINISHED_UNSUCCESSFULLY: sc_types.NODE_CONST_CLASS, - } - - for idtf, sc_type in types_map.items(): - ScKeynodes.resolve(idtf, sc_type) - + def get_types_map(cls) -> Optional[List[Tuple[str, ScType]]]: + if not cls.is_resolved: + return None cls.is_resolved = True + return [ + (CommonIdentifiers.QUESTION, sc_types.NODE_CONST_CLASS), + (CommonIdentifiers.EXACT_VALUE, sc_types.NODE_CONST_CLASS), + (CommonIdentifiers.RREL_DYNAMIC_ARGUMENT, sc_types.NODE_CONST_ROLE), + (CommonIdentifiers.RREL_ONE, sc_types.NODE_CONST_ROLE), + (CommonIdentifiers.RREL_TWO, sc_types.NODE_CONST_ROLE), + (CommonIdentifiers.RREL_LAST, sc_types.NODE_CONST_ROLE), + (CommonIdentifiers.NREL_BASIC_SEQUENCE, sc_types.NODE_CONST_NOROLE), + (CommonIdentifiers.NREL_SYSTEM_IDENTIFIER, sc_types.NODE_CONST_NOROLE), + (CommonIdentifiers.NREL_ANSWER, sc_types.NODE_CONST_NOROLE), + (CommonIdentifiers.CONCEPT_FILENAME, sc_types.NODE_CONST_CLASS), + (QuestionStatus.QUESTION_INITIATED, sc_types.NODE_CONST_CLASS), + (QuestionStatus.QUESTION_FINISHED, sc_types.NODE_CONST_CLASS), + (QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY, sc_types.NODE_CONST_CLASS), + (QuestionStatus.QUESTION_FINISHED_UNSUCCESSFULLY, sc_types.NODE_CONST_CLASS), + ] diff --git a/src/sc_kpm/sc_agent.py b/src/sc_kpm/sc_agent.py index bcb80af..491647a 100644 --- a/src/sc_kpm/sc_agent.py +++ b/src/sc_kpm/sc_agent.py @@ -8,14 +8,14 @@ from logging import getLogger from typing import Optional, Union -from sc_client import client from sc_client.constants import sc_types from sc_client.constants.common import ScEventType -from sc_client.constants.exceptions import InvalidValueError +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScEvent, ScEventParams +from sc_client.sc_exceptions import InvalidValueError from sc_kpm.identifiers import QuestionStatus -from sc_kpm.sc_keynodes import Idtf, ScKeynodes +from sc_kpm.sc_keynodes_ import Idtf, sc_keynodes from sc_kpm.sc_result import ScResult from sc_kpm.utils.action_utils import check_action_class @@ -36,19 +36,20 @@ def _register(self) -> None: self.logger.warning("Almost registered") return event_params = ScEventParams(self._event_element, self._event_type, self._callback) - self._event = client.events_create(event_params)[0] + self._event = sc_client.events_create(event_params)[0] self.logger.info("Registered with ScEvent: %s - %s", repr(self._event_element), repr(self._event_type)) def _unregister(self) -> None: if self._event is None: self.logger.warning("ScEvent was already destroyed or not registered") return - client.events_destroy(self._event) + sc_client.events_destroy(self._event) self._event = None self.logger.info("Unregistered ScEvent: %s - %s", repr(self._event_element), repr(self._event_type)) - def _callback(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: - return self.on_event(event_element, event_edge, action_element) + def _callback(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr): + result = self.on_event(event_element, event_edge, action_element) + self.logger.info(result) @abstractmethod def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: @@ -58,7 +59,7 @@ def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: Sc class ScAgent(ScAgentAbstract, ABC): def __init__(self, event_element: Union[Idtf, ScAddr], event_type: ScEventType) -> None: if isinstance(event_element, Idtf): - event_element = ScKeynodes.resolve(event_element, sc_types.NODE_CONST_CLASS) + event_element = sc_keynodes.resolve(event_element, sc_types.NODE_CONST_CLASS) if not event_element.is_valid(): self.logger.error("Failed to initialize ScAgent: event_class is invalid") raise InvalidValueError(f"event_class of {self.__class__.__name__} is invalid") @@ -77,11 +78,11 @@ def __init__( ) -> None: super().__init__(event_element, event_type) self._action_class_name = action_class_name - self._action_class = ScKeynodes.resolve(action_class_name, sc_types.NODE_CONST_CLASS) + self._action_class = sc_keynodes.resolve(action_class_name, sc_types.NODE_CONST_CLASS) def __repr__(self) -> str: description = f"ClassicScAgent(action_class_name={repr(self._action_class_name)}" - if self._event_element != ScKeynodes.get(QuestionStatus.QUESTION_INITIATED): + if self._event_element != sc_keynodes.get(QuestionStatus.QUESTION_INITIATED): description = f"{description}, event_class={repr(self._event_element)}" if self._event_type != ScEventType.ADD_OUTGOING_EDGE: description = f"{description}, event_type={repr(self._event_type)}" diff --git a/src/sc_kpm/sc_keynodes.py b/src/sc_kpm/sc_keynodes.py deleted file mode 100644 index 6bc8cc8..0000000 --- a/src/sc_kpm/sc_keynodes.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai -Distributed under the MIT License -(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) -""" - -from logging import Logger, getLogger -from typing import Dict, Optional - -from sc_client import client -from sc_client.client import delete_elements -from sc_client.constants.exceptions import InvalidValueError -from sc_client.constants.sc_types import NODE_CONST_ROLE, ScType -from sc_client.models import ScAddr, ScIdtfResolveParams - -Idtf = str - - -class ScKeynodesMeta(type): - """Metaclass to use ScKeynodes without creating an instance of a class""" - - def __init__(cls, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - cls._dict: Dict[Idtf, ScAddr] = {} - cls._logger: Logger = getLogger(f"{__name__}.{cls.__name__}") - cls._max_rrel_index: int = 10 - - def __call__(cls, *args, **kwargs) -> None: - raise TypeError(f"Use {cls.__name__} without initialization") - - def __getitem__(cls, identifier: Idtf) -> ScAddr: - """Get keynode, cannot be invalid ScAddr(0)""" - addr = cls.get(identifier) # pylint: disable=no-value-for-parameter - if not addr.is_valid(): - cls._logger.error("Failed to get ScAddr by %s keynode: ScAddr is invalid", identifier) - raise InvalidValueError(f"ScAddr of {identifier} is invalid") - return addr - - def delete(cls, identifier: Idtf) -> bool: - """Delete keynode from the kb and memory and return boolean status""" - addr = cls.__getitem__(identifier) # pylint: disable=no-value-for-parameter - del cls._dict[identifier] - return delete_elements(addr) - - def get(cls, identifier: Idtf) -> ScAddr: - """Get keynode, can be ScAddr(0)""" - return cls.resolve(identifier, None) # pylint: disable=no-value-for-parameter - - def resolve(cls, identifier: Idtf, sc_type: Optional[ScType]) -> ScAddr: - """Get keynode. If sc_type is valid, an element will be created in the KB""" - addr = cls._dict.get(identifier) - if addr is None: - params = ScIdtfResolveParams(idtf=identifier, type=sc_type) - addr = client.resolve_keynodes(params)[0] - if addr.is_valid(): - cls._dict[identifier] = addr - cls._logger.debug("Resolved %s identifier with type %s: %s", repr(identifier), repr(sc_type), repr(addr)) - return addr - - def rrel_index(cls, index: int) -> ScAddr: - """Get rrel_i node. Max rrel index is 10""" - if not isinstance(index, int): - raise TypeError("Index of rrel node must be int") - if index > cls._max_rrel_index: - raise KeyError(f"You cannot use rrel more than {cls._max_rrel_index}") - return cls.resolve(f"rrel_{index}", NODE_CONST_ROLE) # pylint: disable=no-value-for-parameter - - -class ScKeynodes(metaclass=ScKeynodesMeta): - """Class which provides the ability to cache the identifier and ScAddr of keynodes stored in the KB.""" diff --git a/src/sc_kpm/sc_keynodes_.py b/src/sc_kpm/sc_keynodes_.py new file mode 100644 index 0000000..1084704 --- /dev/null +++ b/src/sc_kpm/sc_keynodes_.py @@ -0,0 +1,65 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from logging import Logger, getLogger +from typing import Dict, Optional + +from sc_client.constants.sc_types import NODE_CONST_ROLE, ScType +from sc_client.core.sc_client_instance import sc_client +from sc_client.models import ScAddr, ScIdtfResolveParams +from sc_client.sc_exceptions import InvalidValueError + +Idtf = str + + +class ScKeynodes: + """Class which provides the ability to cache the identifier and ScAddr of keynodes stored in the KB.""" + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._dict: Dict[Idtf, ScAddr] = {} + self._logger: Logger = getLogger(f"{__name__}.{self.__class__.__name__}") + self._max_rrel_index: int = 10 + + def __getitem__(self, identifier: Idtf) -> ScAddr: + """Get keynode, cannot be invalid ScAddr(0)""" + addr = self.get(identifier) + if not addr.is_valid(): + self._logger.error("Failed to get ScAddr by %s keynode: ScAddr is invalid", identifier) + raise InvalidValueError(f"ScAddr of {identifier} is invalid") + return addr + + def delete(self, identifier: Idtf) -> bool: + """Delete keynode from the kb and memory and return boolean status""" + addr = self[identifier] + del self._dict[identifier] + return sc_client.delete_elements(addr) + + def get(self, identifier: Idtf) -> ScAddr: + """Get keynode, can be ScAddr(0)""" + return self.resolve(identifier, None) + + def resolve(self, identifier: Idtf, sc_type: Optional[ScType]) -> ScAddr: + """Get keynode. If sc_type is valid, an element will be created in the KB""" + addr = self._dict.get(identifier) + if addr is None: + params = ScIdtfResolveParams(idtf=identifier, type=sc_type) + addr = sc_client.resolve_keynodes(params)[0] + if addr.is_valid(): + self._dict[identifier] = addr + self._logger.debug("Resolved %s identifier with type %s: %s", repr(identifier), repr(sc_type), repr(addr)) + return addr + + def rrel_index(self, index: int) -> ScAddr: + """Get rrel_i node. Max rrel index is 10""" + if not isinstance(index, int): + raise TypeError("Index of rrel node must be int") + if index > self._max_rrel_index: + raise KeyError(f"You cannot use rrel more than {self._max_rrel_index}") + return self.resolve(f"rrel_{index}", NODE_CONST_ROLE) + + +sc_keynodes = ScKeynodes() diff --git a/src/sc_kpm/sc_server.py b/src/sc_kpm/sc_server.py index f4fcd99..e24f4e1 100644 --- a/src/sc_kpm/sc_server.py +++ b/src/sc_kpm/sc_server.py @@ -11,9 +11,10 @@ from logging import Logger, getLogger from typing import Callable -from sc_client import client +from sc_client.core.sc_client_instance import sc_client from sc_kpm.identifiers import _IdentifiersResolver +from sc_kpm.sc_keynodes_ import sc_keynodes from sc_kpm.sc_module import ScModuleAbstract @@ -64,13 +65,21 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({', '.join(map(repr, self._modules))})" def connect(self) -> _Finisher: - client.connect(self._url) + sc_client.connect(self._url) self.logger.info("Connected by url: %s", repr(self._url)) - _IdentifiersResolver.resolve() + self.resolve_identifiers() return _Finisher(self.disconnect, self.logger) + @staticmethod + def resolve_identifiers(): + types_map = _IdentifiersResolver.get_types_map() + if types_map is None: + return + for idtf, sc_type in types_map: + sc_keynodes.resolve(idtf, sc_type) + def disconnect(self) -> None: - client.disconnect() + sc_client.disconnect() self.logger.info("Disconnected from url: %s", repr(self._url)) def add_modules(self, *modules: ScModuleAbstract) -> None: @@ -118,7 +127,7 @@ def stop(self) -> None: self.disconnect() def _register(self, *modules: ScModuleAbstract) -> None: - if not client.is_connected(): + if not sc_client.is_connected(): self.logger.error("Failed to register: connection lost") raise ConnectionError(f"Connection to url {repr(self._url)} lost") for module in modules: @@ -128,7 +137,7 @@ def _register(self, *modules: ScModuleAbstract) -> None: module._register() # pylint: disable=protected-access def _unregister(self, *modules: ScModuleAbstract) -> None: - if not client.is_connected(): + if not sc_client.is_connected(): self.logger.error("Failed to unregister: connection to %s lost", repr(self._url)) raise ConnectionError(f"Connection to {repr(self._url)} lost") for module in modules: diff --git a/src/sc_kpm/sc_sets/sc_numbered_set.py b/src/sc_kpm/sc_sets/sc_numbered_set.py index dbba43d..d89e3c2 100644 --- a/src/sc_kpm/sc_sets/sc_numbered_set.py +++ b/src/sc_kpm/sc_sets/sc_numbered_set.py @@ -1,10 +1,10 @@ from typing import Iterator, List -from sc_client.client import template_generate, template_search from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScTemplate -from sc_kpm.sc_keynodes import ScKeynodes +from sc_kpm.sc_keynodes_ import sc_keynodes from sc_kpm.sc_sets.sc_set import ScSet @@ -26,9 +26,9 @@ def add(self, *elements: ScAddr) -> None: sc_types.EDGE_ACCESS_VAR_POS_PERM, element, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes.rrel_index(index), + sc_keynodes.rrel_index(index), ) - template_generate(template) + sc_client.template_generate(template) def __iter__(self) -> Iterator[ScAddr]: return iter(self.elements_list) @@ -44,7 +44,7 @@ def elements_list(self) -> List[ScAddr]: sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_types.NODE_VAR_ROLE, ) - results = template_search(templ) + results = sc_client.template_search(templ) sorted_results = sorted((result for result in results), key=lambda res: res[4].value) # Sort rrel elements addrs return [result[2] for result in sorted_results] @@ -56,9 +56,9 @@ def __getitem__(self, i: int) -> ScAddr: sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_types.UNKNOWN, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes.rrel_index(i + 1), + sc_keynodes.rrel_index(i + 1), ) - results = template_search(templ) + results = sc_client.template_search(templ) if not results: raise KeyError("No element by index") return results[0][2] diff --git a/src/sc_kpm/sc_sets/sc_oriented_set.py b/src/sc_kpm/sc_sets/sc_oriented_set.py index 7b5a4f1..6d81a83 100644 --- a/src/sc_kpm/sc_sets/sc_oriented_set.py +++ b/src/sc_kpm/sc_sets/sc_oriented_set.py @@ -1,11 +1,11 @@ from typing import Iterator, List, Optional -from sc_client.client import delete_elements, template_generate, template_search from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScTemplate, ScTemplateResult from sc_kpm.identifiers import CommonIdentifiers, ScAlias -from sc_kpm.sc_keynodes import ScKeynodes +from sc_kpm.sc_keynodes_ import sc_keynodes from sc_kpm.sc_sets.sc_set import ScSet from sc_kpm.utils.common_utils import create_edge, create_role_relation, search_role_relation_template @@ -34,7 +34,7 @@ def add(self, *elements: ScAddr) -> None: def __iter__(self) -> Iterator[ScAddr]: """Iterate by ScOrientedSet elements""" - start_template = search_role_relation_template(self._set_node, ScKeynodes[CommonIdentifiers.RREL_ONE]) + start_template = search_role_relation_template(self._set_node, sc_keynodes[CommonIdentifiers.RREL_ONE]) if not start_template: return yield start_template.get(ScAlias.ELEMENT) @@ -60,7 +60,7 @@ def remove(self, *elements: ScAddr) -> None: def _create_first_element_edge(self, element: ScAddr) -> ScAddr: """Create marked with rrel_1 edge to first element""" - return create_role_relation(self._set_node, element, ScKeynodes[CommonIdentifiers.RREL_ONE]) + return create_role_relation(self._set_node, element, sc_keynodes[CommonIdentifiers.RREL_ONE]) def _get_last_edge_and_delete_rrel_last(self) -> Optional[ScAddr]: """Search last edge of ScOrientedSet is it exists""" @@ -71,16 +71,18 @@ def _get_last_edge_and_delete_rrel_last(self) -> Optional[ScAddr]: sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.ACCESS_EDGE, sc_types.UNKNOWN, sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.RELATION_EDGE, - ScKeynodes[CommonIdentifiers.RREL_LAST], + sc_keynodes[CommonIdentifiers.RREL_LAST], ) - last_elem_templates = template_search(template) + last_elem_templates = sc_client.template_search(template) if last_elem_templates: last_elem_template = last_elem_templates[0] - delete_elements(last_elem_template.get(ScAlias.RELATION_EDGE)) # Delete edge between rrel_last and edge + sc_client.delete_elements( + last_elem_template.get(ScAlias.RELATION_EDGE) + ) # Delete edge between rrel_last and edge return last_elem_template.get(ScAlias.ACCESS_EDGE) # Search unmarked last edge - next_elem_result = search_role_relation_template(self._set_node, ScKeynodes[CommonIdentifiers.RREL_ONE]) + next_elem_result = search_role_relation_template(self._set_node, sc_keynodes[CommonIdentifiers.RREL_ONE]) while True: next_edge = next_elem_result.get(ScAlias.ACCESS_EDGE) next_elem_result = self._search_next_element_template(next_edge) @@ -100,13 +102,13 @@ def _create_next_edge(self, previous_edge: ScAddr, element: ScAddr) -> ScAddr: sc_types.EDGE_D_COMMON_VAR, ScAlias.ACCESS_EDGE, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes[CommonIdentifiers.NREL_BASIC_SEQUENCE], + sc_keynodes[CommonIdentifiers.NREL_BASIC_SEQUENCE], ) - return template_generate(template).get(ScAlias.ACCESS_EDGE) + return sc_client.template_generate(template).get(ScAlias.ACCESS_EDGE) @staticmethod def _mark_edge_with_rrel_last(last_edge: ScAddr) -> None: - create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, ScKeynodes[CommonIdentifiers.RREL_LAST], last_edge) + create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, sc_keynodes[CommonIdentifiers.RREL_LAST], last_edge) def _search_next_element_template(self, cur_element_edge: ScAddr) -> Optional[ScTemplateResult]: templ = ScTemplate() @@ -115,8 +117,8 @@ def _search_next_element_template(self, cur_element_edge: ScAddr) -> Optional[Sc sc_types.EDGE_D_COMMON_VAR, sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.ACCESS_EDGE, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes[CommonIdentifiers.NREL_BASIC_SEQUENCE], + sc_keynodes[CommonIdentifiers.NREL_BASIC_SEQUENCE], ) templ.triple(self._set_node, ScAlias.ACCESS_EDGE, sc_types.UNKNOWN >> ScAlias.ELEMENT) - search_results = template_search(templ) + search_results = sc_client.template_search(templ) return search_results[0] if search_results else None diff --git a/src/sc_kpm/sc_sets/sc_set.py b/src/sc_kpm/sc_sets/sc_set.py index 4dbab4e..f88bd19 100644 --- a/src/sc_kpm/sc_sets/sc_set.py +++ b/src/sc_kpm/sc_sets/sc_set.py @@ -2,8 +2,9 @@ from typing import Iterator -from sc_client.client import create_elements, delete_elements, template_search -from sc_client.constants import ScType, sc_types +from sc_client import ScType +from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScConstruction, ScTemplate, ScTemplateResult from sc_kpm.utils.common_utils import create_node @@ -41,7 +42,7 @@ def add(self, *elements: ScAddr) -> None: construction = ScConstruction() for element in elements: construction.create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, self._set_node, element) - create_elements(construction) + sc_client.create_elements(construction) @property def set_node(self) -> ScAddr: @@ -83,16 +84,16 @@ def remove(self, *elements: ScAddr) -> None: templ = ScTemplate() for element in elements: templ.triple(self._set_node, sc_types.EDGE_ACCESS_VAR_POS_PERM, element) - template_results = template_search(templ) - delete_elements(*(res[1] for res in template_results)) + template_results = sc_client.template_search(templ) + sc_client.delete_elements(*(res[1] for res in template_results)) def clear(self) -> None: """Remove the connections between set_node and all elements""" template_results = self._elements_search_results() - delete_elements(*(res[1] for res in template_results)) + sc_client.delete_elements(*(res[1] for res in template_results)) def _elements_search_results(self) -> list[ScTemplateResult]: """Template search of all elements""" templ = ScTemplate() templ.triple(self._set_node, sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_types.UNKNOWN) - return template_search(templ) + return sc_client.template_search(templ) diff --git a/src/sc_kpm/sc_sets/sc_structure.py b/src/sc_kpm/sc_sets/sc_structure.py index 2511dfe..c26daf2 100644 --- a/src/sc_kpm/sc_sets/sc_structure.py +++ b/src/sc_kpm/sc_sets/sc_structure.py @@ -1,7 +1,8 @@ -from sc_client.client import check_elements -from sc_client.constants import ScType, sc_types -from sc_client.constants.exceptions import InvalidTypeError +from sc_client import ScType +from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr +from sc_client.sc_exceptions import InvalidTypeError from sc_kpm.sc_sets.sc_set import ScSet @@ -17,7 +18,7 @@ def __init__(self, *elements: ScAddr, set_node: ScAddr = None, set_node_type: Sc if set_node_type is None: set_node_type = sc_types.NODE_CONST_STRUCT if set_node is not None: - set_node_type = check_elements(set_node)[0] + set_node_type = sc_client.check_elements(set_node)[0] if not set_node_type.is_struct(): raise InvalidTypeError super().__init__(*elements, set_node=set_node, set_node_type=set_node_type) diff --git a/src/sc_kpm/utils/__init__.py b/src/sc_kpm/utils/__init__.py index ed1a747..a25bd01 100644 --- a/src/sc_kpm/utils/__init__.py +++ b/src/sc_kpm/utils/__init__.py @@ -4,7 +4,7 @@ (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from sc_kpm.utils import action_utils +from sc_kpm.utils import action_utils, iteration_utils from sc_kpm.utils.common_utils import ( check_edge, create_binary_relation, diff --git a/src/sc_kpm/utils/action_utils.py b/src/sc_kpm/utils/action_utils.py index f2a84b1..f2ef317 100644 --- a/src/sc_kpm/utils/action_utils.py +++ b/src/sc_kpm/utils/action_utils.py @@ -7,15 +7,13 @@ from threading import Event from typing import Dict, List, Tuple, Union -from sc_client import client -from sc_client.client import events_create, events_destroy from sc_client.constants import sc_types from sc_client.constants.common import ScEventType +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScConstruction, ScEventParams, ScTemplate from sc_kpm.identifiers import CommonIdentifiers, QuestionStatus, ScAlias -from sc_kpm.sc_keynodes import Idtf, ScKeynodes -from sc_kpm.sc_result import ScResult +from sc_kpm.sc_keynodes_ import Idtf, sc_keynodes from sc_kpm.sc_sets.sc_structure import ScStructure from sc_kpm.utils.common_utils import ( check_edge, @@ -30,25 +28,25 @@ def check_action_class(action_class: Union[ScAddr, Idtf], action_node: ScAddr) -> bool: - action_class = ScKeynodes[action_class] if isinstance(action_class, Idtf) else action_class + action_class = sc_keynodes[action_class] if isinstance(action_class, Idtf) else action_class templ = ScTemplate() templ.triple(action_class, sc_types.EDGE_ACCESS_VAR_POS_PERM, action_node) - templ.triple(ScKeynodes[CommonIdentifiers.QUESTION], sc_types.EDGE_ACCESS_VAR_POS_PERM, action_node) - search_results = client.template_search(templ) + templ.triple(sc_keynodes[CommonIdentifiers.QUESTION], sc_types.EDGE_ACCESS_VAR_POS_PERM, action_node) + search_results = sc_client.template_search(templ) return len(search_results) > 0 def get_action_arguments(action_node: ScAddr, count: int) -> List[ScAddr]: arguments = [] for index in range(1, count + 1): - argument = get_element_by_role_relation(action_node, ScKeynodes.rrel_index(index)) + argument = get_element_by_role_relation(action_node, sc_keynodes.rrel_index(index)) arguments.append(argument) return arguments def create_action_answer(action_node: ScAddr, *elements: ScAddr) -> None: answer_struct_node = ScStructure(*elements).set_node - create_norole_relation(action_node, answer_struct_node, ScKeynodes[CommonIdentifiers.NREL_ANSWER]) + create_norole_relation(action_node, answer_struct_node, sc_keynodes[CommonIdentifiers.NREL_ANSWER]) def get_action_answer(action_node: ScAddr) -> ScAddr: @@ -58,9 +56,9 @@ def get_action_answer(action_node: ScAddr) -> ScAddr: sc_types.EDGE_D_COMMON_VAR >> ScAlias.RELATION_EDGE, sc_types.NODE_VAR_STRUCT >> ScAlias.ELEMENT, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes[CommonIdentifiers.NREL_ANSWER], + sc_keynodes[CommonIdentifiers.NREL_ANSWER], ) - if search_results := client.template_search(templ): + if search_results := sc_client.template_search(templ): return search_results[0].get(ScAlias.ELEMENT) return ScAddr(0) @@ -77,7 +75,7 @@ def execute_agent( ) -> Tuple[ScAddr, bool]: question = call_agent(arguments, concepts, initiation) wait_agent(wait_time, question) - result = check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, ScKeynodes[reaction], question) + result = check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_keynodes[reaction], question) return question, result @@ -98,19 +96,20 @@ def create_action(*concepts: Idtf) -> ScAddr: for concept in concepts: construction.create_edge( sc_types.EDGE_ACCESS_CONST_POS_PERM, - ScKeynodes.resolve(concept, sc_types.NODE_CONST_CLASS), + sc_keynodes.resolve(concept, sc_types.NODE_CONST_CLASS), ScAlias.ACTION_NODE, ) - action_node = client.create_elements(construction)[0] + action_node = sc_client.create_elements(construction)[0] return action_node def add_action_arguments(action_node: ScAddr, arguments: Dict[ScAddr, IsDynamic]) -> None: - rrel_dynamic_arg = ScKeynodes[CommonIdentifiers.RREL_DYNAMIC_ARGUMENT] + rrel_dynamic_arg = sc_keynodes[CommonIdentifiers.RREL_DYNAMIC_ARGUMENT] + index: int argument: ScAddr for index, (argument, is_dynamic) in enumerate(arguments.items(), 1): if argument.is_valid(): - rrel_i = ScKeynodes.rrel_index(index) + rrel_i = sc_keynodes.rrel_index(index) if is_dynamic: dynamic_node = create_node(sc_types.NODE_CONST) create_role_relation(action_node, dynamic_node, rrel_dynamic_arg, rrel_i) @@ -127,35 +126,34 @@ def execute_action( ) -> bool: call_action(action_node, initiation) wait_agent(wait_time, action_node) - result = check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, ScKeynodes[reaction], action_node) + result = check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_keynodes[reaction], action_node) return result def call_action(action_node: ScAddr, initiation: Idtf = QuestionStatus.QUESTION_INITIATED) -> None: - initiation_node = ScKeynodes.resolve(initiation, sc_types.NODE_CONST_CLASS) + initiation_node = sc_keynodes.resolve(initiation, sc_types.NODE_CONST_CLASS) create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, initiation_node, action_node) def wait_agent(seconds: float, question_node: ScAddr, reaction_node: ScAddr = None) -> None: - reaction_node = reaction_node or ScKeynodes[QuestionStatus.QUESTION_FINISHED] + reaction_node = reaction_node or sc_keynodes[QuestionStatus.QUESTION_FINISHED] finish_event = Event() - def event_callback(_: ScAddr, __: ScAddr, trg: ScAddr) -> ScResult: + def event_callback(_: ScAddr, __: ScAddr, trg: ScAddr) -> None: if trg != reaction_node: - return ScResult.SKIP + return finish_event.set() - return ScResult.OK event_params = ScEventParams(question_node, ScEventType.ADD_INGOING_EDGE, event_callback) - sc_event = events_create(event_params)[0] + sc_event = sc_client.events_create(event_params)[0] if not check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, reaction_node, question_node): finish_event.wait(seconds) - events_destroy(sc_event) + sc_client.events_destroy(sc_event) # TODO: return status in 0.2.0 def finish_action(action_node: ScAddr, status: Idtf = QuestionStatus.QUESTION_FINISHED) -> ScAddr: - return create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, ScKeynodes[status], action_node) + return create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, sc_keynodes[status], action_node) def finish_action_with_status(action_node: ScAddr, is_success: bool = True) -> None: diff --git a/src/sc_kpm/utils/common_utils.py b/src/sc_kpm/utils/common_utils.py index f32db8c..5446ee7 100644 --- a/src/sc_kpm/utils/common_utils.py +++ b/src/sc_kpm/utils/common_utils.py @@ -6,21 +6,21 @@ from typing import List, Optional, Union -from sc_client import client +from sc_client import ScType from sc_client.constants import sc_types -from sc_client.constants.sc_types import ScType +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScConstruction, ScLinkContent, ScLinkContentType, ScTemplate, ScTemplateResult from sc_client.models.sc_construction import ScLinkContentData from sc_kpm.identifiers import CommonIdentifiers, ScAlias -from sc_kpm.sc_keynodes import Idtf, ScKeynodes +from sc_kpm.sc_keynodes_ import Idtf, sc_keynodes def create_nodes(*node_types: ScType) -> List[ScAddr]: construction = ScConstruction() for node_type in node_types: construction.create_node(node_type) - return client.create_elements(construction) + return sc_client.create_elements(construction) def create_node(node_type: ScType) -> ScAddr: @@ -36,7 +36,7 @@ def create_links( for content in contents: link_content = ScLinkContent(content, content_type) construction.create_link(link_type, link_content) - return client.create_elements(construction) + return sc_client.create_elements(construction) def create_link( @@ -55,7 +55,7 @@ def create_edges(edge_type: ScType, src: ScAddr, *targets: ScAddr) -> List[ScAdd construction = ScConstruction() for trg in targets: construction.create_edge(edge_type, src, trg) - return client.create_elements(construction) + return sc_client.create_elements(construction) def create_binary_relation(edge_type: ScType, src: ScAddr, trg: ScAddr, *relations: ScAddr) -> ScAddr: @@ -63,7 +63,7 @@ def create_binary_relation(edge_type: ScType, src: ScAddr, trg: ScAddr, *relatio construction.create_edge(edge_type, src, trg, ScAlias.RELATION_EDGE) for relation in relations: construction.create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, relation, ScAlias.RELATION_EDGE) - return client.create_elements(construction)[0] + return sc_client.create_elements(construction)[0] def create_role_relation(src: ScAddr, trg: ScAddr, *rrel_nodes: ScAddr) -> ScAddr: @@ -88,13 +88,13 @@ def get_edges(source: ScAddr, target: ScAddr, *edge_types: ScType) -> List[ScAdd for edge_type in edge_types: templ = ScTemplate() templ.triple(source, edge_type, target) - results = client.template_search(templ) + results = sc_client.template_search(templ) result_edges.extend(result[1] for result in results) return result_edges def get_system_idtf(addr: ScAddr) -> Idtf: - nrel_system_idtf = ScKeynodes[CommonIdentifiers.NREL_SYSTEM_IDENTIFIER] + nrel_system_idtf = sc_keynodes[CommonIdentifiers.NREL_SYSTEM_IDENTIFIER] templ = ScTemplate() templ.triple_with_relation( @@ -104,7 +104,7 @@ def get_system_idtf(addr: ScAddr) -> Idtf: sc_types.EDGE_ACCESS_VAR_POS_PERM, nrel_system_idtf, ) - result = client.template_search(templ) + result = sc_client.template_search(templ) if result: return get_link_content_data(result[0].get(ScAlias.LINK)) return "" @@ -119,7 +119,7 @@ def _search_relation_template(src: ScAddr, rel_node: ScAddr, rel_type: ScType) - sc_types.EDGE_ACCESS_VAR_POS_PERM, rel_node, ) - result = client.template_search(template) + result = sc_client.template_search(template) return result[0] if result else None @@ -142,9 +142,9 @@ def get_element_by_norole_relation(src: ScAddr, nrel_node: ScAddr) -> ScAddr: def get_link_content_data(link: ScAddr) -> ScLinkContentData: - content_part = client.get_link_content(link) + content_part = sc_client.get_link_content(link) return content_part[0].data def delete_edges(source: ScAddr, target: ScAddr, *edge_types: ScType) -> bool: - return client.delete_elements(*get_edges(source, target, *edge_types)) + return sc_client.delete_elements(*get_edges(source, target, *edge_types)) diff --git a/src/sc_kpm/utils/iteration_utils.py b/src/sc_kpm/utils/iteration_utils.py index 3790be0..ea7bfad 100644 --- a/src/sc_kpm/utils/iteration_utils.py +++ b/src/sc_kpm/utils/iteration_utils.py @@ -1,8 +1,7 @@ from typing import Iterable, Iterator -from sc_client.client import get_link_content -from sc_client.models import ScAddr -from sc_client.models.sc_construction import ScLinkContent, ScLinkContentData +from sc_client.core.sc_client_instance import sc_client +from sc_client.models import ScAddr, ScLinkContent, ScLinkContentData def iter_link_contents_data(contents: Iterable[ScLinkContent]) -> Iterator[ScLinkContentData]: @@ -13,5 +12,5 @@ def iter_link_contents_data(contents: Iterable[ScLinkContent]) -> Iterator[ScLin def iter_links_data(links: Iterable[ScAddr]) -> Iterator[ScLinkContentData]: """Iterate by contents data in links""" - contents = get_link_content(*links) + contents = sc_client.get_link_content(*links) return iter_link_contents_data(contents) diff --git a/tests/test_sc_sets/__init__.py b/tests/test_asc_kpm/__init__.py similarity index 100% rename from tests/test_sc_sets/__init__.py rename to tests/test_asc_kpm/__init__.py diff --git a/tests/test_asc_kpm/base_test_case.py b/tests/test_asc_kpm/base_test_case.py new file mode 100644 index 0000000..3eb8f23 --- /dev/null +++ b/tests/test_asc_kpm/base_test_case.py @@ -0,0 +1,23 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" +import logging +from unittest import IsolatedAsyncioTestCase + +from asc_kpm.asc_server import AScServer + +SC_SERVER_URL = "ws://localhost:8090/ws_json" + +logging.basicConfig(filename="../testing.log", filemode="w", level=logging.INFO, force=True) + + +class AsyncioScKpmTestCase(IsolatedAsyncioTestCase): + async def asyncSetUp(self) -> None: + self.logger = logging.getLogger(f"{self.__module__}.{self.__class__.__name__}") + self.server = AScServer(SC_SERVER_URL) + await self.server.connect() + + async def asyncTearDown(self) -> None: + await self.server.disconnect() diff --git a/tests/test_asc_kpm/test_asc_keynodes.py b/tests/test_asc_kpm/test_asc_keynodes.py new file mode 100644 index 0000000..dae2353 --- /dev/null +++ b/tests/test_asc_kpm/test_asc_keynodes.py @@ -0,0 +1,49 @@ +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScIdtfResolveParams +from sc_client.sc_exceptions import InvalidValueError +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_keynodes_ import asc_keynodes + + +class KeynodesTests(AsyncioScKpmTestCase): + async def test_get_existed_keynode(self): + idtf = "idtf_existed_keynode" + params = ScIdtfResolveParams(idtf=idtf, type=sc_types.NODE_CONST) + addr = (await asc_client.resolve_keynodes(params))[0] + result = await asc_keynodes.get_valid(idtf) + self.assertEqual(result, addr) + + async def test_get_unknown_idtf(self): + idtf = "idtf_unknown_idtf" + with self.assertRaises(InvalidValueError): + await asc_keynodes.get_valid(idtf) + self.assertEqual(await asc_keynodes.get(idtf), ScAddr(0)) + + async def test_resolve_keynode(self): + idtf = "idtf_new_keynode" + addr = await asc_keynodes.resolve(idtf, sc_types.NODE_CONST) + self.assertTrue(await asc_client.delete_elements(addr)) + self.assertTrue(addr.is_valid()) + + async def test_delete_keynode(self): + idtf = "idtf_to_delete_keynode" + await asc_keynodes.resolve(idtf, sc_types.NODE_CONST) + self.assertTrue(await asc_keynodes.delete(idtf)) + self.assertFalse((await asc_keynodes.get(idtf)).is_valid()) + with self.assertRaises(InvalidValueError): + await asc_keynodes.delete(idtf) + + async def test_rrel(self): + rrel_1 = await asc_keynodes.rrel_index(1) + self.assertTrue(rrel_1.is_valid()) + self.assertTrue((await asc_client.check_elements(rrel_1))[0].is_role()) + + async def test_large_rrel(self): + with self.assertRaises(KeyError): + await asc_keynodes.rrel_index(asc_keynodes._max_rrel_index + 1) + + async def test_wrong_rrel(self): + with self.assertRaises(TypeError): + await asc_keynodes.rrel_index("str") diff --git a/tests/test_utils/__init__.py b/tests/test_asc_kpm/test_asc_sets/__init__.py similarity index 100% rename from tests/test_utils/__init__.py rename to tests/test_asc_kpm/test_asc_sets/__init__.py diff --git a/tests/test_asc_kpm/test_asc_sets/test_asc_numbered_set.py b/tests/test_asc_kpm/test_asc_sets/test_asc_numbered_set.py new file mode 100644 index 0000000..936381c --- /dev/null +++ b/tests/test_asc_kpm/test_asc_sets/test_asc_numbered_set.py @@ -0,0 +1,143 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScTemplate +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.asc_sets import AScNumberedSet +from asc_kpm.utils import create_link, create_node, create_nodes + + +class ScNumberedSetTestCase(AsyncioScKpmTestCase): + async def test_create_with_set_node(self): + set_node = await create_node(sc_types.NODE_CONST) + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + await AScNumberedSet.create(element1, element2, set_node=set_node) + await self._assert_two_elements_num_set_template(set_node, element1, element2) + + async def test_create_without_set_node(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element1, element2) + await self._assert_two_elements_num_set_template(sc_set.set_node, element1, element2) + + async def test_create_with_set_type(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element1, element2, set_node_type=sc_types.NODE_CONST_STRUCT) + self.assertEqual((await asc_client.check_elements(sc_set.set_node))[0], sc_types.NODE_CONST_STRUCT) + await self._assert_two_elements_num_set_template(sc_set.set_node, element1, element2) + + async def test_create_large(self): + elements = await create_nodes(*[sc_types.NODE_CONST] * 3) + sc_set = await AScNumberedSet.create(*elements) + self.assertEqual(await sc_set.elements_list, elements) + + async def test_create_copy_num_set_node(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element1, element2) + sc_set_copy = await AScNumberedSet.create(set_node=sc_set.set_node) + await self._assert_two_elements_num_set_template(sc_set_copy.set_node, element1, element2) + + async def test_add(self): + element1 = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element1) + element2 = await create_node(sc_types.NODE_CONST) + await sc_set.add(element2) + await self._assert_two_elements_num_set_template(sc_set.set_node, element1, element2) + + async def test_iterate(self): + set_node = await create_node(sc_types.NODE_CONST) + elements = [ + await create_node(sc_types.NODE_CONST), + await create_link("abc"), + await create_node(sc_types.NODE_CONST), + ] + sc_set = await AScNumberedSet.create(*elements, set_node=set_node) + self.assertEqual([element async for element in sc_set], elements) + + async def test_get_elements(self): + set_node = await create_node(sc_types.NODE_CONST) + elements = [ + await create_node(sc_types.NODE_CONST), + await create_link("abc"), + await create_node(sc_types.NODE_CONST), + ] + sc_set = await AScNumberedSet.create(*elements, set_node=set_node) + self.assertEqual(await sc_set.elements_list, elements) + + async def test_get_elements_empty(self): + sc_set = await AScNumberedSet.create() + self.assertEqual(await sc_set.elements_list, []) + + async def test_get_power(self): + set_node = await create_node(sc_types.NODE_CONST) + elements = [ + await create_node(sc_types.NODE_CONST), + await create_link("abc"), + await create_node(sc_types.NODE_CONST), + ] + sc_set = await AScNumberedSet.create(*elements, set_node=set_node) + self.assertEqual(await sc_set.len(), len(elements)) + self.assertTrue(sc_set) + + async def test_get_power_empty(self): + sc_set = await AScNumberedSet.create() + self.assertEqual(await sc_set.len(), 0) + self.assertTrue(await sc_set.is_empty()) + + async def test_get_by_index(self): + element0 = await create_node(sc_types.NODE_CONST) + element1 = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element0, element1) + self.assertEqual(await sc_set.at(0), element0) + self.assertEqual(await sc_set.at(1), element1) + with self.assertRaises(KeyError): + await sc_set.at(2) + + async def test_contain(self): + element_in = await create_node(sc_types.NODE_CONST) + element_not_in = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element_in) + self.assertTrue(await sc_set.contains(element_in)) + self.assertFalse(await sc_set.contains(element_not_in)) + + async def test_remove(self): + element1 = await create_node(sc_types.NODE_CONST) + element_to_remove = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScNumberedSet.create(element1, element_to_remove, element2) + self.assertEqual(await sc_set.len(), 3) + await sc_set.remove(element_to_remove) + self.assertEqual(await sc_set.len(), 2) + self.assertEqual(await sc_set.elements_list, [element1, element2]) + await sc_set.clear() + self.assertTrue(await sc_set.is_empty()) + self.assertEqual(await sc_set.elements_list, []) + + async def _assert_two_elements_num_set_template(self, set_node: ScAddr, element1: ScAddr, element2: ScAddr) -> None: + template = ScTemplate() + template.triple_with_relation( + set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + element1, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.rrel_index(1), + ) + template.triple_with_relation( + set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + element2, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.rrel_index(2), + ) + results = await asc_client.template_search(template) + self.assertEqual(len(results), 1) diff --git a/tests/test_asc_kpm/test_asc_sets/test_asc_oriented_set.py b/tests/test_asc_kpm/test_asc_sets/test_asc_oriented_set.py new file mode 100644 index 0000000..a96aed5 --- /dev/null +++ b/tests/test_asc_kpm/test_asc_sets/test_asc_oriented_set.py @@ -0,0 +1,143 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScTemplate +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.asc_sets import AScOrientedSet +from asc_kpm.utils import create_link, create_node +from sc_kpm.identifiers import CommonIdentifiers, ScAlias + + +class AScOrientedSetTestCase(AsyncioScKpmTestCase): + async def test_create_with_set_node(self): + set_node = await create_node(sc_types.NODE_CONST) + start_element = await create_node(sc_types.NODE_CONST) + other_element = await create_node(sc_types.NODE_CONST) + await AScOrientedSet.create(start_element, other_element, set_node=set_node) + await self._assert_two_elements_oriented_set_template(set_node, start_element, other_element) + + async def test_create_without_set_node(self): + start_element = await create_node(sc_types.NODE_CONST) + other_element = await create_node(sc_types.NODE_CONST) + oriented_set = await AScOrientedSet.create(start_element, other_element) + await self._assert_two_elements_oriented_set_template(oriented_set.set_node, start_element, other_element) + + async def test_add_to_oriented_set(self): + start_element = await create_node(sc_types.NODE_CONST) + oriented_set = await AScOrientedSet.create(start_element) + other_element = await create_node(sc_types.NODE_CONST) + await oriented_set.add(other_element) + await self._assert_two_elements_oriented_set_template(oriented_set.set_node, start_element, other_element) + + async def test_add_without_rrel_last(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + element3 = await create_node(sc_types.NODE_CONST) + oriented_set = await AScOrientedSet.create(element1, element2) + template = ScTemplate() + template.triple_with_relation( + oriented_set.set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + sc_types.UNKNOWN, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.RELATION_EDGE, + await asc_keynodes.get_valid(CommonIdentifiers.RREL_LAST), + ) + rrel_last_edge = (await asc_client.template_search(template))[0].get(ScAlias.RELATION_EDGE) + await asc_client.delete_elements(rrel_last_edge) + oriented_set_copy = await AScOrientedSet.create(set_node=oriented_set.set_node) + await oriented_set_copy.add(element3) + self.assertEqual(await oriented_set_copy.len(), 3) + + async def test_iterate(self): + set_node = await create_node(sc_types.NODE_CONST) + elements = [ + await create_node(sc_types.NODE_CONST), + await create_link("abc"), + await create_node(sc_types.NODE_CONST), + ] + sc_set = await AScOrientedSet.create(*elements, set_node=set_node) + self.assertEqual([element async for element in sc_set], elements) + + async def test_get_oriented_set_elements(self): + set_node = await create_node(sc_types.NODE_CONST) + elements = [ + await create_node(sc_types.NODE_CONST), + await create_link("abc"), + await create_node(sc_types.NODE_CONST), + ] + oriented_set = await AScOrientedSet.create(*elements, set_node=set_node) + self.assertEqual(await oriented_set.elements_list, elements) + + async def test_get_power(self): + set_node = await create_node(sc_types.NODE_CONST) + elements = [ + await create_node(sc_types.NODE_CONST), + await create_link("abc"), + await create_node(sc_types.NODE_CONST), + ] + sc_set = await AScOrientedSet.create(*elements, set_node=set_node) + self.assertEqual(await sc_set.len(), len(elements)) + + async def test_get_power_one(self): + sc_set = await AScOrientedSet.create(await create_node(sc_types.NODE_CONST)) + self.assertEqual(await sc_set.len(), 1) + self.assertTrue(sc_set) + + async def test_get_power_empty(self): + sc_set = await AScOrientedSet.create() + self.assertEqual(await sc_set.len(), 0) + self.assertTrue(await sc_set.is_empty()) + + async def test_contains(self): + element_in = await create_node(sc_types.NODE_CONST) + element_not_in = await create_node(sc_types.NODE_CONST) + sc_set = await AScOrientedSet.create(element_in) + self.assertTrue(await sc_set.contains(element_in)) + self.assertFalse(await sc_set.contains(element_not_in)) + + async def test_remove(self): + element1 = await create_node(sc_types.NODE_CONST) + element_to_remove = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScOrientedSet.create(element1, element_to_remove, element2) + self.assertEqual(await sc_set.len(), 3) + await sc_set.remove(element_to_remove) + self.assertEqual(await sc_set.len(), 2) + self.assertEqual(await sc_set.elements_list, [element1, element2]) + await sc_set.clear() + self.assertTrue(await sc_set.is_empty()) + self.assertEqual(await sc_set.elements_list, []) + + async def _assert_two_elements_oriented_set_template( + self, set_node: ScAddr, start_element: ScAddr, other_element: ScAddr + ) -> None: + edge1, edge2 = "edge1", "edge2" + template = ScTemplate() + template.triple_with_relation( + set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> edge1, + start_element, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.rrel_index(1), + ) + template.triple( + set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM >> edge2, + other_element, + ) + template.triple_with_relation( + edge1, + sc_types.EDGE_D_COMMON_VAR, + edge2, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.get_valid(CommonIdentifiers.NREL_BASIC_SEQUENCE), + ) + results = await asc_client.template_search(template) + self.assertEqual(len(results), 1) diff --git a/tests/test_asc_kpm/test_asc_sets/test_asc_set.py b/tests/test_asc_kpm/test_asc_sets/test_asc_set.py new file mode 100644 index 0000000..d44765e --- /dev/null +++ b/tests/test_asc_kpm/test_asc_sets/test_asc_set.py @@ -0,0 +1,126 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.models import ScAddr, ScTemplate +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_sets import AScSet +from asc_kpm.utils import create_node + + +class AScSetTestCase(AsyncioScKpmTestCase): + async def test_create_with_set_node(self): + set_node = await create_node(sc_types.NODE_CONST) + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + await AScSet.create(element1, element2, set_node=set_node) + await self._assert_two_elements_set_template(set_node, element1, element2) + + async def test_create_without_set_node(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create(element1, element2) + self.assertEqual((await asc_client.check_elements(sc_set.set_node))[0], sc_types.NODE_CONST) + await self._assert_two_elements_set_template(sc_set.set_node, element1, element2) + + async def test_create_with_set_type(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create(element1, element2, set_node_type=sc_types.NODE_CONST_STRUCT) + self.assertEqual((await asc_client.check_elements(sc_set.set_node))[0], sc_types.NODE_CONST_STRUCT) + await self._assert_two_elements_set_template(sc_set.set_node, element1, element2) + + async def test_create_copy_set_node(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create(element1, element2) + sc_set_copy = await AScSet.create(set_node=sc_set.set_node) + self.assertEqual(sc_set, sc_set_copy) + await self._assert_two_elements_set_template(sc_set_copy.set_node, element1, element2) + + async def test_get_elements_set(self): + elements = {await create_node(sc_types.NODE_CONST), await create_node(sc_types.NODE_CONST)} + sc_set = await AScSet.create(*elements) + self.assertEqual(await sc_set.elements_set, elements) + + async def test_get_elements_set_empty(self): + sc_set = await AScSet.create() + self.assertEqual(await sc_set.elements_set, set()) + + async def test_iterate(self): + elements = {await create_node(sc_types.NODE_CONST), await create_node(sc_types.NODE_CONST)} + sc_set = await AScSet.create(*elements) + async for set_element in sc_set: + self.assertIn(set_element, elements) + + async def test_add(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create(element1) + await sc_set.add(element2) + self.assertEqual((await asc_client.check_elements(sc_set.set_node))[0], sc_types.NODE_CONST) + await self._assert_two_elements_set_template(sc_set.set_node, element1, element2) + + async def test_add_element_twice(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + elements = {element1, element2} + sc_set = await AScSet.create(*elements) + await sc_set.add(element2) # element is added second time + self.assertEqual(await sc_set.elements_set, elements) # element1 will not duplicate + + async def test_get_power(self): + element = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create() + self.assertEqual(await sc_set.len(), 0) + await sc_set.add(element) + self.assertEqual(await sc_set.len(), 1) + + async def test_booleans(self): + """Test __bool__(), is_empty() and __contains__""" + sc_set = await AScSet.create() + self.assertTrue(await sc_set.is_empty()) + + element = await create_node(sc_types.NODE_CONST) + await sc_set.add(element) + self.assertFalse(await sc_set.is_empty()) + + self.assertTrue(await sc_set.contains(element)) + self.assertFalse(await sc_set.contains(ScAddr(0))) + + async def test_remove(self): + element = await create_node(sc_types.NODE_CONST) + element_to_remove = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create(element, element_to_remove) + self.assertEqual(await sc_set.len(), 2) + await sc_set.remove(element_to_remove) + self.assertEqual(await sc_set.len(), 1) + self.assertEqual(await sc_set.elements_set, {element}) + + async def test_clear(self): + element1 = await create_node(sc_types.NODE_CONST) + element2 = await create_node(sc_types.NODE_CONST) + sc_set = await AScSet.create(element1, element2) + self.assertFalse(await sc_set.is_empty()) + await sc_set.clear() + self.assertTrue(await sc_set.is_empty()) + + async def _assert_two_elements_set_template(self, set_node: ScAddr, element1: ScAddr, element2: ScAddr) -> None: + template = ScTemplate() + template.triple( + set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + element1, + ) + template.triple( + set_node, + sc_types.EDGE_ACCESS_VAR_POS_PERM, + element2, + ) + results = await asc_client.template_search(template) + self.assertEqual(len(results), 1) diff --git a/tests/test_asc_kpm/test_asc_sets/test_asc_structure.py b/tests/test_asc_kpm/test_asc_sets/test_asc_structure.py new file mode 100644 index 0000000..8b0ef0b --- /dev/null +++ b/tests/test_asc_kpm/test_asc_sets/test_asc_structure.py @@ -0,0 +1,37 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from sc_client.sc_exceptions import InvalidTypeError +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_sets import AScSet, AScStructure +from asc_kpm.utils import create_node + + +class AScStructureTestCase(AsyncioScKpmTestCase): + async def test_is_sc_set_child(self): + """Save sc-set methods""" + self.assertIsInstance(await AScStructure.create(), AScSet) + + async def test_create(self): + struct = await AScStructure.create() + self.assertTrue((await asc_client.check_elements(struct.set_node))[0] == sc_types.NODE_CONST_STRUCT) + + async def test_create_valid_type(self): + node_var_struct = await create_node(sc_types.NODE_VAR_STRUCT) + self.assertIsNotNone(await AScStructure.create(set_node_type=sc_types.NODE_VAR_STRUCT)) + self.assertIsNotNone(await AScStructure.create(set_node=node_var_struct)) + + async def test_create_wrong_type(self): + with self.assertRaises(InvalidTypeError): + await AScStructure.create(set_node_type=sc_types.NODE_CONST) + + async def test_create_wrong_struct_node(self): + node_const = await create_node(sc_types.NODE_CONST) + with self.assertRaises(InvalidTypeError): + await AScStructure.create(set_node=node_const) diff --git a/tests/test_asc_kpm/test_classes.py b/tests/test_asc_kpm/test_classes.py new file mode 100644 index 0000000..8100207 --- /dev/null +++ b/tests/test_asc_kpm/test_classes.py @@ -0,0 +1,143 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" +import asyncio + +from sc_client.constants.common import ScEventType +from sc_client.models import ScAddr +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_agent import AScAgent, AScAgentClassic +from asc_kpm.asc_module import AScModule +from asc_kpm.utils.aio_action_utils import execute_agent, finish_action_with_status +from sc_kpm import ScResult +from sc_kpm.identifiers import CommonIdentifiers + +WAIT_TIME = 1 + + +class CommonTests(AsyncioScKpmTestCase): + async def test_sc_agents(self): + class Agent(AScAgent): + ACTION_CLASS_NAME = "test_agent" + + @classmethod + async def ainit(cls, **kwargs): + return await super().ainit(cls.ACTION_CLASS_NAME, ScEventType.ADD_OUTGOING_EDGE) + + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + await finish_action_with_status(action_element, True) + return ScResult.OK + + class AgentClassic(AScAgentClassic): + ACTION_CLASS_NAME = "test_agent_classic" + + @classmethod + async def ainit(cls, **kwargs): + return await super().ainit(cls.ACTION_CLASS_NAME) + + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + await finish_action_with_status(action_element, True) + return ScResult.OK + + module = await AScModule.ainit(await Agent.ainit(), await AgentClassic.ainit()) + await self.server.add_modules(module) + arguments = dict( + arguments={}, + concepts=[], + initiation=Agent.ACTION_CLASS_NAME, + wait_time=WAIT_TIME, + ) + arguments_classic = dict( + arguments={}, + concepts=[CommonIdentifiers.QUESTION, AgentClassic.ACTION_CLASS_NAME], + wait_time=WAIT_TIME, + ) + self.assertFalse((await execute_agent(**arguments))[1]) + self.assertFalse((await execute_agent(**arguments_classic))[1]) + async with self.server.register_modules(): + self.assertTrue((await execute_agent(**arguments))[1]) + self.assertTrue((await execute_agent(**arguments_classic))[1]) + self.assertFalse((await execute_agent(**arguments))[1]) + self.assertFalse((await execute_agent(**arguments_classic))[1]) + await self.server.remove_modules(module) + + async def test_sc_module(self): + class TestAgent(AScAgent): + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + await finish_action_with_status(action_element, True) + return ScResult.OK + + async def is_executing_successful(i: int) -> bool: + return ( + await execute_agent( + arguments={}, + concepts=[], + initiation=f"agent{i}", + wait_time=WAIT_TIME, + ) + )[1] + + agent1 = await TestAgent.ainit("agent1", ScEventType.ADD_OUTGOING_EDGE) + agent2 = await TestAgent.ainit("agent2", ScEventType.ADD_OUTGOING_EDGE) + agent3 = await TestAgent.ainit("agent3", ScEventType.ADD_OUTGOING_EDGE) + + module1 = await AScModule.ainit(agent1) + module2 = await AScModule.ainit(agent2) + await self.server.add_modules(module1) + await module1.add_agent(agent3) + async with self.server.register_modules(): + self.assertTrue(await is_executing_successful(1)) + self.assertFalse(await is_executing_successful(2)) + self.assertTrue(await is_executing_successful(3)) + + await self.server.add_modules(module2) + self.assertTrue(await is_executing_successful(2)) + + await module1.remove_agent(agent3) + self.assertTrue(await is_executing_successful(1)) + self.assertFalse(await is_executing_successful(3)) + await self.server.remove_modules(module1, module2) + + async def test_sc_server(self): + class TestAgent(AScAgent): + ACTION_CLASS_NAME = "some_agent" + + @classmethod + async def ainit(cls, **kwargs): + return await super().ainit(cls.ACTION_CLASS_NAME, ScEventType.ADD_OUTGOING_EDGE) + + async def on_event(self, event_element: ScAddr, event_edge: ScAddr, action_element: ScAddr) -> ScResult: + await finish_action_with_status(action_element, True) + return ScResult.OK + + async def is_executing_successful() -> bool: + return ( + await execute_agent( + arguments={}, + concepts=[], + initiation=TestAgent.ACTION_CLASS_NAME, + wait_time=WAIT_TIME, + ) + )[1] + + module = await AScModule.ainit(await TestAgent.ainit()) + await self.server.add_modules(module) + self.assertFalse(await is_executing_successful()) + await self.server.register_modules() + self.assertTrue(await is_executing_successful()) + await self.server.unregister_modules() + self.assertFalse(await is_executing_successful()) + + await self.server.remove_modules(module) + + async def test_server_sc_server(self): + async def terminate(): + await asyncio.sleep(0.01) + asyncio.get_event_loop().stop() + + async with self.server.register_modules(): + asyncio.create_task(terminate()) + self.server.serve() diff --git a/tests/test_asc_kpm/test_utils/__init__.py b/tests/test_asc_kpm/test_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_asc_kpm/test_utils/test_aio_action_utils.py b/tests/test_asc_kpm/test_utils/test_aio_action_utils.py new file mode 100644 index 0000000..0e9396f --- /dev/null +++ b/tests/test_asc_kpm/test_utils/test_aio_action_utils.py @@ -0,0 +1,154 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" +import time + +from sc_client.constants import sc_types +from sc_client.constants.common import ScEventType +from sc_client.core.asc_client_instance import asc_client +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm import AScAgent, AScModule +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.utils.aio_action_utils import ( + add_action_arguments, + call_action, + call_agent, + check_action_class, + create_action, + execute_action, + execute_agent, + finish_action_with_status, + wait_agent, +) +from asc_kpm.utils.aio_common_utils import check_edge, create_edge, create_node +from sc_kpm.identifiers import CommonIdentifiers, QuestionStatus +from sc_kpm.sc_result import ScResult + +test_node_idtf = "test_node" + + +class AScAgentTest(AScAgent): + async def on_event(self, _src, _edge, target_node) -> ScResult: + self.logger.info(f"Agent's started") + await finish_action_with_status(target_node) + return ScResult.OK + + +class AScModuleTest(AScModule): + @classmethod + async def ainit(cls): + return await super().ainit( + await AScAgentTest.ainit(test_node_idtf, ScEventType.ADD_OUTGOING_EDGE), + await AScAgentTest.ainit(test_node_idtf, ScEventType.ADD_INGOING_EDGE), + ) + + +class TestActionUtils(AsyncioScKpmTestCase): + async def test_validate_action(self): + action_class_idtf = "test_action_class" + action_class_node = await asc_keynodes.resolve(action_class_idtf, sc_types.NODE_CONST) + question = await asc_keynodes.get_valid(CommonIdentifiers.QUESTION) + test_node = await create_node(sc_types.NODE_CONST) + self.assertFalse(await check_action_class(action_class_node, test_node)) + self.assertFalse(await check_action_class(action_class_idtf, test_node)) + class_edge = await create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, action_class_node, test_node) + self.assertFalse(await check_action_class(action_class_node, test_node)) + self.assertFalse(await check_action_class(action_class_idtf, test_node)) + await create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, question, test_node) + self.assertTrue(await check_action_class(action_class_node, test_node)) + self.assertTrue(await check_action_class(action_class_idtf, test_node)) + await asc_client.delete_elements(class_edge) + self.assertFalse(await check_action_class(action_class_node, test_node)) + self.assertFalse(await check_action_class(action_class_idtf, test_node)) + + async def test_execute_agent(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + results = await execute_agent({}, [], test_node_idtf) + assert results[1] + await self.server.remove_modules(module) + + async def test_call_agent(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + question = await call_agent({}, [], test_node_idtf) + await wait_agent(1, question, await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED)) + result = await check_edge( + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY), + question, + ) + self.assertTrue(result) + await self.server.remove_modules(module) + + async def test_wrong_execute_agent(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + self.assertFalse((await execute_agent({}, [], "wrong_agent", wait_time=1))[1]) + await self.server.remove_modules(module) + + async def test_execute_action(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + action_node = await create_action() + await add_action_arguments(action_node, {}) + assert await execute_action(action_node, test_node_idtf) + await self.server.remove_modules(module) + + async def test_call_action(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + action_node = await create_action() + await add_action_arguments(action_node, {}) + await call_action(action_node, test_node_idtf) + await wait_agent(1, action_node, await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED)) + result = await check_edge( + sc_types.EDGE_ACCESS_VAR_POS_PERM, + await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY), + action_node, + ) + self.assertTrue(result) + await self.server.remove_modules(module) + + async def test_wrong_execute_action(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + action_node = await create_action() + await add_action_arguments(action_node, {}) + self.assertFalse(await execute_action(action_node, "wrong_agent", wait_time=1)) + await self.server.remove_modules(module) + + async def test_wait_action(self): + module = await AScModuleTest.ainit() + await self.server.add_modules(module) + async with self.server.register_modules(): + action_node = await create_action() + timeout = 0.5 + # Action is not finished while waiting + start_time = time.time() + await wait_agent(timeout, action_node, await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED)) + timedelta = time.time() - start_time + self.assertGreater(timedelta, timeout) + # Action is finished while waiting + await call_action(action_node, test_node_idtf) + start_time = time.time() + await wait_agent(timeout, action_node, await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED)) + timedelta = time.time() - start_time + self.assertLess(timedelta, timeout) + # Action finished before waiting + await call_action(action_node, test_node_idtf) + time.sleep(0.1) + start_time = time.time() + await wait_agent(timeout, action_node, await asc_keynodes.get_valid(QuestionStatus.QUESTION_FINISHED)) + timedelta = time.time() - start_time + self.assertLess(timedelta, timeout) + await self.server.remove_modules(module) diff --git a/tests/test_asc_kpm/test_utils/test_aio_common_utils.py b/tests/test_asc_kpm/test_utils/test_aio_common_utils.py new file mode 100644 index 0000000..d381d0b --- /dev/null +++ b/tests/test_asc_kpm/test_utils/test_aio_common_utils.py @@ -0,0 +1,142 @@ +""" +This source file is part of an OSTIS project. For the latest info, see https://github.com/ostis-ai +Distributed under the MIT License +(See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) +""" + +from sc_client.constants import sc_types +from sc_client.core.asc_client_instance import asc_client +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.asc_keynodes_ import asc_keynodes +from asc_kpm.utils.aio_common_utils import ( + check_edge, + create_binary_relation, + create_edge, + create_edges, + create_link, + create_links, + create_node, + create_nodes, + create_norole_relation, + create_role_relation, + delete_edges, + get_edge, + get_edges, + get_element_by_norole_relation, + get_element_by_role_relation, + get_link_content_data, + get_system_idtf, +) + + +class TestActionUtils(AsyncioScKpmTestCase): + async def test_node_utils(self): + node = await create_node(sc_types.NODE_VAR_ROLE) + node_2 = await create_node(sc_types.NODE_CONST_CLASS) + assert node.is_valid() and node_2.is_valid() + + result = await asc_client.check_elements(node, node_2) + assert len(result) == 2 + assert result[0].is_node() and result[0].is_var() and result[0].is_role() + assert result[1].is_node() and result[1].is_const() and result[1].is_class() + + nodes_counter = 10 + node_list = await create_nodes(*[sc_types.NODE_CONST for _ in range(nodes_counter)]) + for node in node_list: + assert node.is_valid() + + result = await asc_client.check_elements(*node_list) + assert len(result) == nodes_counter + for result_item in result: + assert result_item.is_node() and result_item.is_const() + + async def test_link_utils(self): + link_content = "my link content" + link = await create_link(link_content) + assert link.is_valid() + assert await get_link_content_data(link) == link_content + + link_counter = 10 + link_list = await create_links(*[link_content for _ in range(link_counter)]) + assert len(link_list) == link_counter + for link in link_list: + assert link.is_valid() + + result = await asc_client.check_elements(*link_list) + for result_item in result: + assert result_item.is_valid() and result_item.is_link() + + async def test_edge_utils(self): + source, target = await create_nodes(sc_types.NODE_CONST_CLASS, sc_types.NODE_CONST) + empty = await get_edge(source, target, sc_types.EDGE_ACCESS_VAR_POS_PERM) + assert empty.is_valid() is False + + edge = await create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, source, target) + assert edge.is_valid() + same_edge = await get_edge(source, target, sc_types.EDGE_ACCESS_VAR_POS_PERM) + assert same_edge.is_valid() and same_edge.value == edge.value + assert await check_edge(sc_types.EDGE_ACCESS_VAR_POS_PERM, source, target) + + source, target, target2 = await create_nodes( + sc_types.NODE_CONST_CLASS, sc_types.NODE_CONST, sc_types.NODE_CONST + ) + edges = await create_edges(sc_types.EDGE_ACCESS_CONST_POS_PERM, source, target, target2) + assert all(edge_.is_valid() for edge_ in edges) + same_target1 = await get_edge(source, target, sc_types.EDGE_ACCESS_VAR_POS_PERM) + same_target2 = await get_edge(source, target2, sc_types.EDGE_ACCESS_VAR_POS_PERM) + assert edges == [same_target1, same_target2] + + edge_counter = 10 + for _ in range(edge_counter): + await create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, source, target) + result = await get_edges(source, target, sc_types.EDGE_ACCESS_VAR_POS_PERM) + assert len(result) == edge_counter + 1 + for edge in result: + assert edge.is_valid() + + async def test_relation_utils(self): + src, rrel_trg, nrel_trg = await create_nodes(sc_types.NODE_CONST, sc_types.NODE_CONST, sc_types.NODE_CONST) + rrel_node = await create_node(sc_types.NODE_CONST_ROLE) + nrel_node = await create_node(sc_types.NODE_CONST_NOROLE) + + rrel_edge_1 = await create_binary_relation(sc_types.EDGE_ACCESS_CONST_POS_PERM, src, rrel_trg, rrel_node) + rrel_edge_2 = await create_role_relation(src, rrel_trg, rrel_node) + nrel_edge_1 = await create_binary_relation(sc_types.EDGE_D_COMMON_CONST, src, nrel_trg, nrel_node) + nrel_edge_2 = await create_norole_relation(src, nrel_trg, nrel_node) + edges = [rrel_edge_1, rrel_edge_2, nrel_edge_1, nrel_edge_2] + for edge in edges: + assert edge.is_valid() + + result = await asc_client.check_elements(*edges) + for result_item in result: + assert result_item.is_valid() and result_item.is_edge() and result_item.is_const() + assert result[0].is_pos() and result[1].is_pos() + assert result[2].is_pos() is False and result[3].is_pos() is False + + expected_rrel_target = await get_element_by_role_relation(src, rrel_node) + expected_empty = await get_element_by_role_relation(src, nrel_node) + assert expected_rrel_target.is_valid() + assert expected_rrel_target.value == rrel_trg.value + assert expected_empty.is_valid() is False + + expected_nrel_target = await get_element_by_norole_relation(src, nrel_node) + expected_empty = await get_element_by_norole_relation(src, rrel_node) + assert expected_nrel_target.is_valid() + assert expected_nrel_target.value == nrel_trg.value + assert expected_empty.is_valid() is False + + async def test_get_system_idtf(self): + test_idtf = "rrel_1" + test_node = await asc_keynodes.get_valid(test_idtf) + assert await get_system_idtf(test_node) == test_idtf + + async def test_deletion_utils(self): + src, rrel_trg, nrel_trg = await create_nodes(sc_types.NODE_CONST, sc_types.NODE_CONST, sc_types.NODE_CONST) + rrel_edge = await create_binary_relation(sc_types.EDGE_ACCESS_CONST_POS_PERM, src, rrel_trg) + nrel_edge = await create_norole_relation(src, nrel_trg) + assert await delete_edges(src, rrel_trg, sc_types.EDGE_ACCESS_VAR_POS_PERM) + assert await asc_client.delete_elements(nrel_edge, src, rrel_trg, nrel_trg) + + result = (await asc_client.check_elements(rrel_edge))[0] + assert result.is_valid() is False diff --git a/tests/test_asc_kpm/test_utils/test_aio_iteration_utils.py b/tests/test_asc_kpm/test_utils/test_aio_iteration_utils.py new file mode 100644 index 0000000..a21a867 --- /dev/null +++ b/tests/test_asc_kpm/test_utils/test_aio_iteration_utils.py @@ -0,0 +1,14 @@ +from sc_client.core.asc_client_instance import asc_client +from test_asc_kpm.base_test_case import AsyncioScKpmTestCase + +from asc_kpm.utils import create_links +from asc_kpm.utils.aio_iteration_utils import iter_links_data + + +class TestActionUtils(AsyncioScKpmTestCase): + async def test_iter_links_data(self): + links_data = ["content1", "content2"] + links = await create_links(*links_data) + links_data_from_iterator = list(await iter_links_data(links)) + self.assertEqual(links_data_from_iterator, links_data) + await asc_client.delete_elements(*links) diff --git a/tests/test_keynodes.py b/tests/test_keynodes.py deleted file mode 100644 index 77455cf..0000000 --- a/tests/test_keynodes.py +++ /dev/null @@ -1,49 +0,0 @@ -from common_tests import BaseTestCase -from sc_client import client -from sc_client.client import check_elements, delete_elements -from sc_client.constants import sc_types -from sc_client.constants.exceptions import InvalidValueError -from sc_client.models import ScAddr, ScIdtfResolveParams - -from sc_kpm import ScKeynodes - - -class KeynodesTests(BaseTestCase): - def test_get_existed_keynode(self): - idtf = "idtf_existed_keynode" - params = ScIdtfResolveParams(idtf=idtf, type=sc_types.NODE_CONST) - addr = client.resolve_keynodes(params)[0] - result = ScKeynodes[idtf] - self.assertEqual(result, addr) - - def test_get_unknown_idtf(self): - idtf = "idtf_unknown_idtf" - self.assertRaises(InvalidValueError, ScKeynodes.__getitem__, idtf) - self.assertEqual(ScKeynodes.get(idtf), ScAddr(0)) - - def test_resolve_keynode(self): - idtf = "idtf_new_keynode" - addr = ScKeynodes.resolve(idtf, sc_types.NODE_CONST) - self.assertTrue(delete_elements(addr)) - self.assertTrue(addr.is_valid()) - - def test_delete_keynode(self): - idtf = "idtf_to_delete_keynode" - ScKeynodes.resolve(idtf, sc_types.NODE_CONST) - self.assertTrue(ScKeynodes.delete(idtf)) - self.assertFalse(ScKeynodes.get(idtf).is_valid()) - self.assertRaises(InvalidValueError, ScKeynodes.delete, idtf) - - def test_keynodes_initialization(self): - self.assertRaises(TypeError, ScKeynodes) - - def test_rrel(self): - rrel_1 = ScKeynodes.rrel_index(1) - self.assertTrue(rrel_1.is_valid()) - self.assertTrue(check_elements(rrel_1)[0].is_role()) - - def test_large_rrel(self): - self.assertRaises(KeyError, ScKeynodes.rrel_index, ScKeynodes._max_rrel_index + 1) - - def test_wrong_rrel(self): - self.assertRaises(TypeError, ScKeynodes.rrel_index, "str") diff --git a/tests/test_sc_kpm/__init__.py b/tests/test_sc_kpm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/common_tests.py b/tests/test_sc_kpm/common_tests.py similarity index 87% rename from tests/common_tests.py rename to tests/test_sc_kpm/common_tests.py index 42ebdcb..4bd4911 100644 --- a/tests/common_tests.py +++ b/tests/test_sc_kpm/common_tests.py @@ -10,7 +10,7 @@ SC_SERVER_URL = "ws://localhost:8090/ws_json" -logging.basicConfig(filename="testing.log", filemode="w", level=logging.INFO, force=True) +logging.basicConfig(filename="../testing.log", filemode="w", level=logging.INFO, force=True) class BaseTestCase(TestCase): diff --git a/tests/test_classes.py b/tests/test_sc_kpm/test_classes.py similarity index 89% rename from tests/test_classes.py rename to tests/test_sc_kpm/test_classes.py index 864b89a..ab4eea1 100644 --- a/tests/test_classes.py +++ b/tests/test_sc_kpm/test_classes.py @@ -3,17 +3,14 @@ Distributed under the MIT License (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -import os -import signal -import threading from sc_client.constants.common import ScEventType from sc_client.models import ScAddr +from test_sc_kpm.common_tests import BaseTestCase from sc_kpm import ScAgent, ScAgentClassic, ScModule, ScResult from sc_kpm.identifiers import CommonIdentifiers from sc_kpm.utils.action_utils import execute_agent, finish_action_with_status -from tests.common_tests import BaseTestCase WAIT_TIME = 1 @@ -124,16 +121,17 @@ def is_executing_successful() -> bool: self.server.unregister_modules() self.assertFalse(is_executing_successful()) - main_pid = os.getpid() - - def execute_and_send_sigint(): - self.assertTrue(is_executing_successful()) - os.kill(main_pid, signal.SIGINT) - self.assertFalse(is_executing_successful()) - - with self.server.register_modules(): - thread = threading.Thread(target=execute_and_send_sigint, daemon=True) - thread.start() - self.server.serve() + # main_pid = os.getpid() + # + # async def execute_and_send_sigint(): + # self.assertTrue(is_executing_successful()) + # await asyncio.sleep(0.01) + # os.kill(main_pid, signal.SIGINT) + # self.assertFalse(is_executing_successful()) + # + # with self.server.register_modules(): + # asyncio.create_task(execute_and_send_sigint()) + # # asyncio.get_event_loop().run_until_complete(asyncio.sleep(0)) + # self.server.serve() self.server.remove_modules(module) diff --git a/tests/test_sc_kpm/test_keynodes.py b/tests/test_sc_kpm/test_keynodes.py new file mode 100644 index 0000000..b45de06 --- /dev/null +++ b/tests/test_sc_kpm/test_keynodes.py @@ -0,0 +1,45 @@ +from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client +from sc_client.models import ScAddr, ScIdtfResolveParams +from sc_client.sc_exceptions import InvalidValueError +from test_sc_kpm.common_tests import BaseTestCase + +from sc_kpm.sc_keynodes_ import sc_keynodes + + +class KeynodesTests(BaseTestCase): + def test_get_existed_keynode(self): + idtf = "idtf_existed_keynode" + params = ScIdtfResolveParams(idtf=idtf, type=sc_types.NODE_CONST) + addr = sc_client.resolve_keynodes(params)[0] + result = sc_keynodes[idtf] + self.assertEqual(result, addr) + + def test_get_unknown_idtf(self): + idtf = "idtf_unknown_idtf" + self.assertRaises(InvalidValueError, sc_keynodes.__getitem__, idtf) + self.assertEqual(sc_keynodes.get(idtf), ScAddr(0)) + + def test_resolve_keynode(self): + idtf = "idtf_new_keynode" + addr = sc_keynodes.resolve(idtf, sc_types.NODE_CONST) + self.assertTrue(sc_client.delete_elements(addr)) + self.assertTrue(addr.is_valid()) + + def test_delete_keynode(self): + idtf = "idtf_to_delete_keynode" + sc_keynodes.resolve(idtf, sc_types.NODE_CONST) + self.assertTrue(sc_keynodes.delete(idtf)) + self.assertFalse(sc_keynodes.get(idtf).is_valid()) + self.assertRaises(InvalidValueError, sc_keynodes.delete, idtf) + + def test_rrel(self): + rrel_1 = sc_keynodes.rrel_index(1) + self.assertTrue(rrel_1.is_valid()) + self.assertTrue(sc_client.check_elements(rrel_1)[0].is_role()) + + def test_large_rrel(self): + self.assertRaises(KeyError, sc_keynodes.rrel_index, sc_keynodes._max_rrel_index + 1) + + def test_wrong_rrel(self): + self.assertRaises(TypeError, sc_keynodes.rrel_index, "str") diff --git a/tests/test_sc_kpm/test_sc_sets/__init__.py b/tests/test_sc_kpm/test_sc_sets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_sc_sets/test_sc_numbered_set.py b/tests/test_sc_kpm/test_sc_sets/test_sc_numbered_set.py similarity index 92% rename from tests/test_sc_sets/test_sc_numbered_set.py rename to tests/test_sc_kpm/test_sc_sets/test_sc_numbered_set.py index 0acb481..ef921d4 100644 --- a/tests/test_sc_sets/test_sc_numbered_set.py +++ b/tests/test_sc_kpm/test_sc_sets/test_sc_numbered_set.py @@ -4,14 +4,14 @@ (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from sc_client.client import check_elements, template_search from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScTemplate +from test_sc_kpm.common_tests import BaseTestCase -from sc_kpm import ScKeynodes -from sc_kpm.sc_sets.sc_numbered_set import ScNumberedSet +from sc_kpm.sc_keynodes_ import sc_keynodes +from sc_kpm.sc_sets import ScNumberedSet from sc_kpm.utils.common_utils import create_link, create_node, create_nodes -from tests.common_tests import BaseTestCase class ScNumberedSetTestCase(BaseTestCase): @@ -32,7 +32,7 @@ def test_create_with_set_type(self): element1 = create_node(sc_types.NODE_CONST) element2 = create_node(sc_types.NODE_CONST) sc_set = ScNumberedSet(element1, element2, set_node_type=sc_types.NODE_CONST_STRUCT) - self.assertEqual(check_elements(sc_set.set_node)[0], sc_types.NODE_CONST_STRUCT) + self.assertEqual(sc_client.check_elements(sc_set.set_node)[0], sc_types.NODE_CONST_STRUCT) self._assert_two_elements_num_set_template(sc_set.set_node, element1, element2) def test_create_large(self): @@ -130,14 +130,14 @@ def _assert_two_elements_num_set_template(self, set_node: ScAddr, element1: ScAd sc_types.EDGE_ACCESS_VAR_POS_PERM, element1, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes.rrel_index(1), + sc_keynodes.rrel_index(1), ) template.triple_with_relation( set_node, sc_types.EDGE_ACCESS_VAR_POS_PERM, element2, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes.rrel_index(2), + sc_keynodes.rrel_index(2), ) - results = template_search(template) + results = sc_client.template_search(template) self.assertEqual(len(results), 1) diff --git a/tests/test_sc_sets/test_sc_oriented_set.py b/tests/test_sc_kpm/test_sc_sets/test_sc_oriented_set.py similarity index 90% rename from tests/test_sc_sets/test_sc_oriented_set.py rename to tests/test_sc_kpm/test_sc_sets/test_sc_oriented_set.py index 23d3203..ec240a1 100644 --- a/tests/test_sc_sets/test_sc_oriented_set.py +++ b/tests/test_sc_kpm/test_sc_sets/test_sc_oriented_set.py @@ -4,15 +4,15 @@ (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from sc_client.client import delete_elements, template_search from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScTemplate +from test_sc_kpm.common_tests import BaseTestCase -from sc_kpm import ScKeynodes from sc_kpm.identifiers import CommonIdentifiers, ScAlias -from sc_kpm.sc_sets.sc_oriented_set import ScOrientedSet +from sc_kpm.sc_keynodes_ import sc_keynodes +from sc_kpm.sc_sets import ScOrientedSet from sc_kpm.utils.common_utils import create_link, create_node -from tests.common_tests import BaseTestCase class ScOrientedSetTestCase(BaseTestCase): @@ -47,10 +47,10 @@ def test_add_without_rrel_last(self): sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_types.UNKNOWN, sc_types.EDGE_ACCESS_VAR_POS_PERM >> ScAlias.RELATION_EDGE, - ScKeynodes[CommonIdentifiers.RREL_LAST], + sc_keynodes[CommonIdentifiers.RREL_LAST], ) - rrel_last_edge = template_search(template)[0].get(ScAlias.RELATION_EDGE) - delete_elements(rrel_last_edge) + rrel_last_edge = sc_client.template_search(template)[0].get(ScAlias.RELATION_EDGE) + sc_client.delete_elements(rrel_last_edge) oriented_set_continue = ScOrientedSet(set_node=oriented_set.set_node) oriented_set_continue.add(element3) self.assertEqual(len(oriented_set_continue), 3) @@ -126,7 +126,7 @@ def _assert_two_elements_oriented_set_template( sc_types.EDGE_ACCESS_VAR_POS_PERM >> edge1, start_element, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes.rrel_index(1), + sc_keynodes.rrel_index(1), ) template.triple( set_node, @@ -138,7 +138,7 @@ def _assert_two_elements_oriented_set_template( sc_types.EDGE_D_COMMON_VAR, edge2, sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes[CommonIdentifiers.NREL_BASIC_SEQUENCE], + sc_keynodes[CommonIdentifiers.NREL_BASIC_SEQUENCE], ) - results = template_search(template) + results = sc_client.template_search(template) self.assertEqual(len(results), 1) diff --git a/tests/test_sc_sets/test_sc_set.py b/tests/test_sc_kpm/test_sc_sets/test_sc_set.py similarity index 90% rename from tests/test_sc_sets/test_sc_set.py rename to tests/test_sc_kpm/test_sc_sets/test_sc_set.py index e6e117a..dc9d4bb 100644 --- a/tests/test_sc_sets/test_sc_set.py +++ b/tests/test_sc_kpm/test_sc_sets/test_sc_set.py @@ -4,13 +4,13 @@ (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from sc_client.client import check_elements, template_search from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScAddr, ScTemplate +from test_sc_kpm.common_tests import BaseTestCase -from sc_kpm.sc_sets.sc_set import ScSet +from sc_kpm.sc_sets import ScSet from sc_kpm.utils.common_utils import create_node -from tests.common_tests import BaseTestCase class ScSetTestCase(BaseTestCase): @@ -25,14 +25,14 @@ def test_create_without_set_node(self): element1 = create_node(sc_types.NODE_CONST) element2 = create_node(sc_types.NODE_CONST) sc_set = ScSet(element1, element2) - self.assertEqual(check_elements(sc_set.set_node)[0], sc_types.NODE_CONST) + self.assertEqual(sc_client.check_elements(sc_set.set_node)[0], sc_types.NODE_CONST) self._assert_two_elements_set_template(sc_set.set_node, element1, element2) def test_create_with_set_type(self): element1 = create_node(sc_types.NODE_CONST) element2 = create_node(sc_types.NODE_CONST) sc_set = ScSet(element1, element2, set_node_type=sc_types.NODE_CONST_STRUCT) - self.assertEqual(check_elements(sc_set.set_node)[0], sc_types.NODE_CONST_STRUCT) + self.assertEqual(sc_client.check_elements(sc_set.set_node)[0], sc_types.NODE_CONST_STRUCT) self._assert_two_elements_set_template(sc_set.set_node, element1, element2) def test_create_copy_set_node(self): @@ -63,7 +63,7 @@ def test_add(self): element2 = create_node(sc_types.NODE_CONST) sc_set = ScSet(element1) sc_set.add(element2) - self.assertEqual(check_elements(sc_set.set_node)[0], sc_types.NODE_CONST) + self.assertEqual(sc_client.check_elements(sc_set.set_node)[0], sc_types.NODE_CONST) self._assert_two_elements_set_template(sc_set.set_node, element1, element2) def test_add_element_twice(self): @@ -124,5 +124,5 @@ def _assert_two_elements_set_template(self, set_node: ScAddr, element1: ScAddr, sc_types.EDGE_ACCESS_VAR_POS_PERM, element2, ) - results = template_search(template) + results = sc_client.template_search(template) self.assertEqual(len(results), 1) diff --git a/tests/test_sc_sets/test_sc_structure.py b/tests/test_sc_kpm/test_sc_sets/test_sc_structure.py similarity index 81% rename from tests/test_sc_sets/test_sc_structure.py rename to tests/test_sc_kpm/test_sc_sets/test_sc_structure.py index af9f50c..98cf8fb 100644 --- a/tests/test_sc_sets/test_sc_structure.py +++ b/tests/test_sc_kpm/test_sc_sets/test_sc_structure.py @@ -4,13 +4,13 @@ (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from sc_client.client import check_elements from sc_client.constants import sc_types -from sc_client.constants.exceptions import InvalidTypeError +from sc_client.core.sc_client_instance import sc_client +from sc_client.sc_exceptions import InvalidTypeError +from test_sc_kpm.common_tests import BaseTestCase from sc_kpm.sc_sets import ScSet, ScStructure from sc_kpm.utils.common_utils import create_node -from tests.common_tests import BaseTestCase class ScStructureTestCase(BaseTestCase): @@ -20,7 +20,7 @@ def test_is_sc_set_child(self): def test_create(self): struct = ScStructure() - self.assertTrue(check_elements(struct.set_node)[0] == sc_types.NODE_CONST_STRUCT) + self.assertTrue(sc_client.check_elements(struct.set_node)[0] == sc_types.NODE_CONST_STRUCT) def test_create_valid_type(self): node_var_struct = create_node(sc_types.NODE_VAR_STRUCT) diff --git a/tests/test_sc_kpm/test_utils/__init__.py b/tests/test_sc_kpm/test_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_utils/test_action_utils.py b/tests/test_sc_kpm/test_utils/test_action_utils.py similarity index 83% rename from tests/test_utils/test_action_utils.py rename to tests/test_sc_kpm/test_utils/test_action_utils.py index e5c9002..a002e20 100644 --- a/tests/test_utils/test_action_utils.py +++ b/tests/test_sc_kpm/test_utils/test_action_utils.py @@ -5,12 +5,14 @@ """ import time -from sc_client.client import delete_elements from sc_client.constants import sc_types from sc_client.constants.common import ScEventType +from sc_client.core.sc_client_instance import sc_client +from test_sc_kpm.common_tests import BaseTestCase -from sc_kpm import ScAgent, ScKeynodes, ScModule +from sc_kpm import ScAgent, ScModule from sc_kpm.identifiers import CommonIdentifiers, QuestionStatus +from sc_kpm.sc_keynodes_ import sc_keynodes from sc_kpm.sc_result import ScResult from sc_kpm.utils.action_utils import ( add_action_arguments, @@ -24,7 +26,6 @@ wait_agent, ) from sc_kpm.utils.common_utils import check_edge, create_edge, create_node -from tests.common_tests import BaseTestCase test_node_idtf = "test_node" @@ -47,8 +48,8 @@ def __init__(self): class TestActionUtils(BaseTestCase): def test_validate_action(self): action_class_idtf = "test_action_class" - action_class_node = ScKeynodes.resolve(action_class_idtf, sc_types.NODE_CONST) - question = ScKeynodes[CommonIdentifiers.QUESTION] + action_class_node = sc_keynodes.resolve(action_class_idtf, sc_types.NODE_CONST) + question = sc_keynodes[CommonIdentifiers.QUESTION] test_node = create_node(sc_types.NODE_CONST) self.assertFalse(check_action_class(action_class_node, test_node)) self.assertFalse(check_action_class(action_class_idtf, test_node)) @@ -58,7 +59,7 @@ def test_validate_action(self): create_edge(sc_types.EDGE_ACCESS_CONST_POS_PERM, question, test_node) self.assertTrue(check_action_class(action_class_node, test_node)) self.assertTrue(check_action_class(action_class_idtf, test_node)) - delete_elements(class_edge) + sc_client.delete_elements(class_edge) self.assertFalse(check_action_class(action_class_node, test_node)) self.assertFalse(check_action_class(action_class_idtf, test_node)) @@ -74,9 +75,9 @@ def test_call_agent(self): self.server.add_modules(module) with self.server.register_modules(): question = call_agent({}, [], test_node_idtf) - wait_agent(1, question, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) + wait_agent(1, question, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) result = check_edge( - sc_types.EDGE_ACCESS_VAR_POS_PERM, ScKeynodes[QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY], question + sc_types.EDGE_ACCESS_VAR_POS_PERM, sc_keynodes[QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY], question ) self.assertTrue(result) self.server.remove_modules(module) @@ -104,10 +105,10 @@ def test_call_action(self): action_node = create_action() add_action_arguments(action_node, {}) call_action(action_node, test_node_idtf) - wait_agent(1, action_node, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) + wait_agent(1, action_node, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) result = check_edge( sc_types.EDGE_ACCESS_VAR_POS_PERM, - ScKeynodes[QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY], + sc_keynodes[QuestionStatus.QUESTION_FINISHED_SUCCESSFULLY], action_node, ) self.assertTrue(result) @@ -130,20 +131,20 @@ def test_wait_action(self): timeout = 0.5 # Action is not finished while waiting start_time = time.time() - wait_agent(timeout, action_node, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) + wait_agent(timeout, action_node, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) timedelta = time.time() - start_time self.assertGreater(timedelta, timeout) # Action is finished while waiting call_action(action_node, test_node_idtf) start_time = time.time() - wait_agent(timeout, action_node, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) + wait_agent(timeout, action_node, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) timedelta = time.time() - start_time self.assertLess(timedelta, timeout) # Action finished before waiting call_action(action_node, test_node_idtf) time.sleep(0.1) start_time = time.time() - wait_agent(timeout, action_node, ScKeynodes[QuestionStatus.QUESTION_FINISHED]) + wait_agent(timeout, action_node, sc_keynodes[QuestionStatus.QUESTION_FINISHED]) timedelta = time.time() - start_time self.assertLess(timedelta, timeout) self.server.remove_modules(module) diff --git a/tests/test_utils/test_common_utils.py b/tests/test_sc_kpm/test_utils/test_common_utils.py similarity index 91% rename from tests/test_utils/test_common_utils.py rename to tests/test_sc_kpm/test_utils/test_common_utils.py index 2d3d9cb..e7ea825 100644 --- a/tests/test_utils/test_common_utils.py +++ b/tests/test_sc_kpm/test_utils/test_common_utils.py @@ -4,11 +4,11 @@ (See an accompanying file LICENSE or a copy at https://opensource.org/licenses/MIT) """ -from sc_client import client -from sc_client.client import delete_elements from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client +from test_sc_kpm.common_tests import BaseTestCase -from sc_kpm import ScKeynodes +from sc_kpm.sc_keynodes_ import sc_keynodes from sc_kpm.utils.common_utils import ( check_edge, create_binary_relation, @@ -28,7 +28,6 @@ get_link_content_data, get_system_idtf, ) -from tests.common_tests import BaseTestCase class TestActionUtils(BaseTestCase): @@ -37,7 +36,7 @@ def test_node_utils(self): node_2 = create_node(sc_types.NODE_CONST_CLASS) assert node.is_valid() and node_2.is_valid() - result = client.check_elements(node, node_2) + result = sc_client.check_elements(node, node_2) assert len(result) == 2 assert result[0].is_node() and result[0].is_var() and result[0].is_role() assert result[1].is_node() and result[1].is_const() and result[1].is_class() @@ -47,7 +46,7 @@ def test_node_utils(self): for node in node_list: assert node.is_valid() - result = client.check_elements(*node_list) + result = sc_client.check_elements(*node_list) assert len(result) == nodes_counter for result_item in result: assert result_item.is_node() and result_item.is_const() @@ -64,7 +63,7 @@ def test_link_utils(self): for link in link_list: assert link.is_valid() - result = client.check_elements(*link_list) + result = sc_client.check_elements(*link_list) for result_item in result: assert result_item.is_valid() and result_item.is_link() @@ -107,7 +106,7 @@ def test_relation_utils(self): for edge in edges: assert edge.is_valid() - result = client.check_elements(*edges) + result = sc_client.check_elements(*edges) for result_item in result: assert result_item.is_valid() and result_item.is_edge() and result_item.is_const() assert result[0].is_pos() and result[1].is_pos() @@ -127,7 +126,7 @@ def test_relation_utils(self): def test_get_system_idtf(self): test_idtf = "rrel_1" - test_node = ScKeynodes[test_idtf] + test_node = sc_keynodes[test_idtf] assert get_system_idtf(test_node) == test_idtf def test_deletion_utils(self): @@ -135,7 +134,7 @@ def test_deletion_utils(self): rrel_edge = create_binary_relation(sc_types.EDGE_ACCESS_CONST_POS_PERM, src, rrel_trg) nrel_edge = create_norole_relation(src, nrel_trg) assert delete_edges(src, rrel_trg, sc_types.EDGE_ACCESS_VAR_POS_PERM) - assert delete_elements(nrel_edge, src, rrel_trg, nrel_trg) + assert sc_client.delete_elements(nrel_edge, src, rrel_trg, nrel_trg) - result = client.check_elements(rrel_edge)[0] + result = sc_client.check_elements(rrel_edge)[0] assert result.is_valid() is False diff --git a/tests/test_utils/test_iteration_utils.py b/tests/test_sc_kpm/test_utils/test_iteration_utils.py similarity index 82% rename from tests/test_utils/test_iteration_utils.py rename to tests/test_sc_kpm/test_utils/test_iteration_utils.py index 7c0f778..ebbfa5f 100644 --- a/tests/test_utils/test_iteration_utils.py +++ b/tests/test_sc_kpm/test_utils/test_iteration_utils.py @@ -1,7 +1,7 @@ -from common_tests import BaseTestCase -from sc_client.client import create_elements, delete_elements from sc_client.constants import sc_types +from sc_client.core.sc_client_instance import sc_client from sc_client.models import ScConstruction, ScLinkContent, ScLinkContentType +from test_sc_kpm.common_tests import BaseTestCase from sc_kpm.utils import create_links from sc_kpm.utils.iteration_utils import iter_link_contents_data, iter_links_data @@ -16,14 +16,14 @@ def test_iter_link_contents_data(self): construction = ScConstruction() construction.create_link(sc_types.LINK_CONST, content1) construction.create_link(sc_types.LINK_CONST, content2) - links = create_elements(construction) + links = sc_client.create_elements(construction) links_data_from_iterator = list(iter_link_contents_data(contents)) self.assertEqual(links_data_from_iterator, data) - delete_elements(*links) + sc_client.delete_elements(*links) def test_iter_links_data(self): links_data = ["content1", "content2"] links = create_links(*links_data) links_data_from_iterator = list(iter_links_data(links)) self.assertEqual(links_data_from_iterator, links_data) - delete_elements(*links) + sc_client.delete_elements(*links)