From c5c94b4936f00cf8248073e8a1b85f14bab5dd87 Mon Sep 17 00:00:00 2001 From: Angelo Probst Date: Thu, 26 Sep 2024 21:05:37 -0300 Subject: [PATCH 1/2] supporting `set` instead of `list` --- hyperon_das/client.py | 8 ++--- hyperon_das/das.py | 9 +---- hyperon_das/decorators.py | 2 +- hyperon_das/link_filters.py | 1 - .../query_engines/local_query_engine.py | 33 +++++++++++-------- .../query_engines/query_engine_protocol.py | 4 +-- .../query_engines/remote_query_engine.py | 7 ++-- tests/unit/test_decorators.py | 2 +- 8 files changed, 31 insertions(+), 35 deletions(-) diff --git a/hyperon_das/client.py b/hyperon_das/client.py index 60bde4c4..c3e048f3 100644 --- a/hyperon_das/client.py +++ b/hyperon_das/client.py @@ -23,7 +23,7 @@ def __init__(self, host: str, port: int, name: Optional[str] = None) -> None: if not host and not port: das_error(ValueError("'host' and 'port' are mandatory parameters")) self.name = name if name else f'client_{host}:{port}' - self.url = connect_to_server(host, port) + self.status_code, self.url = connect_to_server(host, port) def _send_request(self, payload) -> Any: try: @@ -164,9 +164,7 @@ def commit_changes(self, **kwargs) -> Tuple[int, int]: else: raise e - def get_incoming_links( - self, atom_handle: str, **kwargs - ) -> tuple[int | None, IncomingLinksT | Iterator]: + def get_incoming_links(self, atom_handle: str, **kwargs) -> IncomingLinksT | Iterator: payload = { 'action': 'get_incoming_links', 'input': {'atom_handle': atom_handle, 'kwargs': kwargs}, @@ -175,7 +173,7 @@ def get_incoming_links( return self._send_request(payload) except HTTPError as e: logger().debug(f'Error during `get_incoming_links` request on remote Das: {str(e)}') - return None, [] + return [] def create_field_index( self, diff --git a/hyperon_das/das.py b/hyperon_das/das.py index 4cfd6d2e..fc6731b3 100644 --- a/hyperon_das/das.py +++ b/hyperon_das/das.py @@ -218,13 +218,6 @@ def get_atom(self, handle: str) -> AtomT: Args: handle (str): Atom's handle. - Keyword Args: - no_target_format (bool, optional): If True, a list of target handles is returned in - link's `targets` field. If False, a list with actual target documents is returned - instead. Defaults to False. - targets_document (bool, optional): Set this parameter to True to return a tuple containing the document as first element - and the targets as second element. Defaults to False. - Returns: Dict: A Python dict with all atom data. @@ -802,7 +795,7 @@ def get_traversal_cursor(self, handle: str, **kwargs) -> TraverseEngine: A TraverseEngine is like a cursor which points to an atom in the hypergraph and can be used to probe for links and neighboring atoms and then move on by - following links. It's functioning is closely tied to the cache system in order + following links. Its functioning is closely tied to the cache system in order to optimize the order in which atoms are presented to the caller when probing the neighborhood and to use cache's "atom paging" capabilities to minimize latency when used in remote DAS. diff --git a/hyperon_das/decorators.py b/hyperon_das/decorators.py index e4830ff5..ec74a8e9 100644 --- a/hyperon_das/decorators.py +++ b/hyperon_das/decorators.py @@ -24,7 +24,7 @@ def wrapper(*args, **kwargs): logger().debug( f'{retry_count + 1} successful connection attempt at [host={args[1]}]' ) - return response + return status, response except Exception as e: raise RetryConnectionError( message="An error occurs while connecting to the server", diff --git a/hyperon_das/link_filters.py b/hyperon_das/link_filters.py index bc0d7329..0611fa5c 100644 --- a/hyperon_das/link_filters.py +++ b/hyperon_das/link_filters.py @@ -41,7 +41,6 @@ class FlatTypeTemplate(LinkFilter): def __init__( self, target_types: list[str], link_type: str = WILDCARD, toplevel_only: bool = False ): - self.filter_type = LinkFilterType.FLAT_TYPE_TEMPLATE self.link_type = link_type self.target_types = target_types diff --git a/hyperon_das/query_engines/local_query_engine.py b/hyperon_das/query_engines/local_query_engine.py index 2a79966a..cea83e77 100644 --- a/hyperon_das/query_engines/local_query_engine.py +++ b/hyperon_das/query_engines/local_query_engine.py @@ -4,7 +4,14 @@ from hyperon_das_atomdb import WILDCARD, AtomDB from hyperon_das_atomdb.adapters import InMemoryDB -from hyperon_das_atomdb.database import AtomT, HandleListT, IncomingLinksT, LinkT +from hyperon_das_atomdb.database import ( + AtomT, + HandleListT, + HandleSetT, + HandleT, + IncomingLinksT, + LinkT, +) from hyperon_das_atomdb.exceptions import AtomDoesNotExist from hyperon_das.cache.cache_controller import CacheController @@ -86,9 +93,9 @@ def _get_related_links( self, link_type: str, target_types: list[str] | None = None, - link_targets: list[str] | None = None, + link_targets: HandleListT | None = None, **kwargs, - ) -> HandleListT: + ) -> HandleSetT: if link_type != WILDCARD and target_types is not None: return self.local_backend.get_matched_type_template( [link_type, *target_types], **kwargs @@ -97,7 +104,7 @@ def _get_related_links( try: return self.local_backend.get_matched_links(link_type, link_targets, **kwargs) except AtomDoesNotExist: - return None, [] + return set() elif link_type != WILDCARD: return self.local_backend.get_all_links(link_type, **kwargs) else: @@ -145,10 +152,11 @@ def _process_link(self, query: dict) -> List[dict]: def _generate_target_handles( self, targets: List[Dict[str, Any]] - ) -> list[str | list[str] | list[Any]]: # multiple levels of nested lists due to recursion - targets_hash: list[str | list[str] | list[Any]] = [] + ) -> list[HandleT | HandleListT | list[Any]]: # multiple levels of nested lists due to + # recursion + targets_hash: list[HandleT | HandleListT | list[Any]] = [] for target in targets: - handle: str | list[str] | None = None + handle: HandleT | HandleListT | None = None if target["atom_type"] == "node": handle = self.local_backend.node_handle(target["type"], target["name"]) elif target["atom_type"] == "link": @@ -190,7 +198,7 @@ def get_atom(self, handle: str, **kwargs) -> Dict[str, Any]: def get_atoms(self, handles: str, **kwargs) -> List[Dict[str, Any]]: return [self.local_backend.get_atom(handle, **kwargs) for handle in handles] - def get_link_handles(self, link_filter: LinkFilter) -> List[str]: + def get_link_handles(self, link_filter: LinkFilter) -> HandleSetT: if link_filter.filter_type == LinkFilterType.FLAT_TYPE_TEMPLATE: return self.local_backend.get_matched_type_template( [link_filter.link_type, *link_filter.target_types], @@ -201,10 +209,9 @@ def get_link_handles(self, link_filter: LinkFilter) -> List[str]: link_filter.link_type, link_filter.targets, toplevel_only=link_filter.toplevel_only ) elif link_filter.filter_type == LinkFilterType.NAMED_TYPE: - _, answer = self.local_backend.get_all_links( + return self.local_backend.get_all_links( link_filter.link_type, toplevel_only=link_filter.toplevel_only ) - return answer else: das_error(ValueError("Invalid LinkFilterType: {link_filter.filter_type}")) @@ -318,13 +325,13 @@ def create_context( ) -> Context: # type: ignore das_error(NotImplementedError("Contexts are not implemented for non-server local DAS")) - def get_atoms_by_field(self, query: list[OrderedDict[str, str]]) -> List[str]: + def get_atoms_by_field(self, query: list[OrderedDict[str, str]]) -> HandleListT: return self.local_backend.get_atoms_by_field(query) def get_atoms_by_text_field( self, text_value: str, field: Optional[str] = None, text_index_id: Optional[str] = None - ) -> List[str]: + ) -> HandleListT: return self.local_backend.get_atoms_by_text_field(text_value, field, text_index_id) - def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> List[str]: + def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> HandleListT: return self.local_backend.get_node_by_name_starting_with(node_type, startswith) diff --git a/hyperon_das/query_engines/query_engine_protocol.py b/hyperon_das/query_engines/query_engine_protocol.py index d2435039..032f939f 100644 --- a/hyperon_das/query_engines/query_engine_protocol.py +++ b/hyperon_das/query_engines/query_engine_protocol.py @@ -45,7 +45,7 @@ def get_atoms(self, handles: List[str], **kwargs) -> List[Dict[str, Any]]: ... @abstractmethod - def get_links(link_filter: LinkFilter) -> List[LinkT]: + def get_links(self, link_filter: LinkFilter) -> List[LinkT]: """ Retrieves all links that match the passed filtering criteria. @@ -58,7 +58,7 @@ def get_links(link_filter: LinkFilter) -> List[LinkT]: ... @abstractmethod - def get_link_handles(link_filter: LinkFilter) -> List[LinkT]: + def get_link_handles(self, link_filter: LinkFilter) -> List[LinkT]: """ Retrieve the handle of all links that match the passed filtering criteria. diff --git a/hyperon_das/query_engines/remote_query_engine.py b/hyperon_das/query_engines/remote_query_engine.py index abbbccf6..da209e7d 100644 --- a/hyperon_das/query_engines/remote_query_engine.py +++ b/hyperon_das/query_engines/remote_query_engine.py @@ -89,10 +89,9 @@ def custom_query(self, index_id: str, query: Query, **kwargs) -> Iterator: kwargs.pop('no_iterator', None) if kwargs.get('cursor') is None: kwargs['cursor'] = 0 - cursor, answer = self.remote_das.custom_query(index_id, query=query, **kwargs) + answer = self.remote_das.custom_query(index_id, query=query, **kwargs) kwargs['backend'] = self.remote_das kwargs['index_id'] = index_id - kwargs['cursor'] = cursor kwargs['is_remote'] = True return CustomQuery(ListIterator(answer), **kwargs) @@ -147,8 +146,8 @@ def count_atoms(self, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, def commit(self, **kwargs) -> None: if self.__mode == 'read-write': if self.local_query_engine.has_buffer(): - return self.remote_das.commit_changes(buffer=self.local_query_engine.buffer) - return self.remote_das.commit_changes() + self.remote_das.commit_changes(buffer=self.local_query_engine.buffer) + self.remote_das.commit_changes() elif self.__mode == 'read-only': das_error(PermissionError("Commit can't be executed in read mode")) else: diff --git a/tests/unit/test_decorators.py b/tests/unit/test_decorators.py index 88e346e7..edc5f016 100644 --- a/tests/unit/test_decorators.py +++ b/tests/unit/test_decorators.py @@ -16,7 +16,7 @@ def successful_function(self, host, port): result = successful_function({}, 'localhost', 80) - assert result == 'Success' + assert result == (200, 'Success') @patch('hyperon_das.logger') From 1cb63b20e7d501d77a2963feb64a5d843a7ee00c Mon Sep 17 00:00:00 2001 From: Angelo Probst Date: Fri, 27 Sep 2024 10:09:52 -0300 Subject: [PATCH 2/2] supporting `set` instead of `list` --- hyperon_das/das.py | 137 ++++++++++-------- .../query_engines/query_engine_protocol.py | 41 +++--- .../query_engines/remote_query_engine.py | 25 ++-- 3 files changed, 114 insertions(+), 89 deletions(-) diff --git a/hyperon_das/das.py b/hyperon_das/das.py index fc6731b3..19c3df87 100644 --- a/hyperon_das/das.py +++ b/hyperon_das/das.py @@ -2,7 +2,15 @@ from hyperon_das_atomdb import AtomDB, AtomDoesNotExist from hyperon_das_atomdb.adapters import InMemoryDB, RedisMongoDB -from hyperon_das_atomdb.database import AtomT, IncomingLinksT, LinkT, NodeT +from hyperon_das_atomdb.database import ( + AtomT, + HandleListT, + HandleSetT, + HandleT, + IncomingLinksT, + LinkT, + NodeT, +) from hyperon_das_atomdb.exceptions import InvalidAtomDB from hyperon_das.cache.cache_controller import CacheController @@ -27,49 +35,51 @@ class DistributedAtomSpace: def __init__(self, system_parameters: Dict[str, Any] = {}, **kwargs) -> None: """ - Creates a new DAS object. - A DAS client can run locally or locally and remote, connecting to remote DASs instances to query remote atoms, - if there're different versions of the same atom in local and one of the remote DASs, the local version is returned. - When running along a remote DAS a host and port is mandatory, by default local instances of the DBs are created, - remote instances can be configured using kwargs options. - + Creates a new DAS object. A DAS client can run locally or locally and remote, + connecting to remote DAS instances to query remote atoms. If there are different + versions of the same atom in local and one of the remote DAS instances, the local + version is returned. When running along a remote DAS, a host and port is mandatory. + By default, local instances of the DBs are created, remote instances can be configured + using kwargs options. Args: system_parameters (Dict[str, Any]): Sets the system parameters. Defaults to { - 'running_on_server': False, 'cache_enabled': False, 'attention_broker_hostname': 'localhost', - 'attention_broker_port': 27000}. + 'running_on_server': False, 'cache_enabled': False, 'attention_broker_hostname': + 'localhost', 'attention_broker_port': 27000}. Keyword Args: - atomdb (str, optional): AtomDB type supported values are 'ram' and 'redis_mongo'. Defaults to 'ram'. - query_engine (str, optional): Set the type of connection for the query engine, values are 'remote' or - 'local'. - When this arg is set to 'remote', additional kwargs are required as host and port to connect - to the remote query engine - and the arg mode is used to configure the read/write privileges. - Defaults to 'local' - host (str, optional): Sets the host for the remote query engine, it's mandatory - when the query_engine is equal to 'remote'. - port (str, optional): Sets the port for the remote query engine, it's mandatory - when the query_engine is equal to 'remote'. - mode (str, optional): Set query engine's ACL privileges, only available - when the query_engine is set to 'remote', accepts 'read-only' or 'read-write'. - Defaults to 'read-only' - mongo_hostname (str, optional): MongoDB's hostname, the local or remote query engine can - connect to a remote server or run locally. - Defaults to 'localhost' - mongo_port (int, optional): MongoDB port, set this arg if the port is not the standard. Defaults to 27017. - mongo_username (str, optional): Username used for authentication in the MongoDB database. - Defaults to 'mongo'. - mongo_password (str, optional): Password used for authentication in the MongoDB database. - Defaults to 'mongo'. + atomdb (str, optional): AtomDB type supported values are 'ram' and 'redis_mongo'. + Defaults to 'ram'. + query_engine (str, optional): Set the type of connection for the query engine, + values are 'remote' or 'local'. When this arg is set to 'remote', additional + kwargs are required as host and port to connect to the remote query engine and + the arg mode is used to configure the read/write privileges. Defaults to 'local'. + host (str, optional): Sets the host for the remote query engine, it's mandatory when + the query_engine is equal to 'remote'. + port (str, optional): Sets the port for the remote query engine, it's mandatory when + the query_engine is equal to 'remote'. + mode (str, optional): Set query engine's ACL privileges, only available when the + query_engine is set to 'remote', accepts 'read-only' or 'read-write'. Defaults + to 'read-only'. + mongo_hostname (str, optional): MongoDB's hostname, the local or remote query engine + can connect to a remote server or run locally. Defaults to 'localhost'. + mongo_port (int, optional): MongoDB port, set this arg if the port is not the + standard. Defaults to 27017. + mongo_username (str, optional): Username used for authentication in the MongoDB + database. Defaults to 'mongo'. + mongo_password (str, optional): Password used for authentication in the MongoDB + database. Defaults to 'mongo'. mongo_tls_ca_file (Any, optional): Full system path to the TLS certificate. - redis_hostname (str, optional): Redis hostname, the local or remote query engine can connect - to a remote server or run locally. Defaults to 'localhost' - redis_port (int, optional): Redis port, set this arg if the port is not the standard. Defaults to 6379. - redis_username (str, optional): Username used for authentication in the Redis database, - no credentials (username/password) are needed when running locally. - redis_password (str, optional): Password used for authentication in the Redis database. - redis_cluster (bool, optional): Indicates whether Redis is configured in cluster mode. Defaults to True. + redis_hostname (str, optional): Redis hostname, the local or remote query engine can + connect to a remote server or run locally. Defaults to 'localhost'. + redis_port (int, optional): Redis port, set this arg if the port is not the + standard. Defaults to 6379. + redis_username (str, optional): Username used for authentication in the Redis + database, no credentials (username/password) are needed when running locally. + redis_password (str, optional): Password used for authentication in the Redis + database. + redis_cluster (bool, optional): Indicates whether Redis is configured in cluster + mode. Defaults to True. redis_ssl (bool, optional): Set Redis to encrypt the connection. Defaults to True. """ self.system_parameters = system_parameters @@ -160,7 +170,7 @@ def compute_node_handle(node_type: str, node_name: str) -> str: Computes the handle of a node, given its type and name. Note that this is a static method which don't actually query the stored atomspace - in order to compute the handle. Instead, it just run a MD5 hashing algorithm on + in order to compute the handle. Instead, it just runs an MD5 hashing algorithm on the parameters that uniquely identify nodes (i.e. type and name) This means e.g. that two nodes with the same type and the same name are considered to be the exact same entity as they will have the same handle. @@ -181,19 +191,19 @@ def compute_node_handle(node_type: str, node_name: str) -> str: return AtomDB.node_handle(node_type, node_name) @staticmethod - def compute_link_handle(link_type: str, link_targets: List[str]) -> str: + def compute_link_handle(link_type: str, link_targets: HandleListT) -> str: """ Computes the handle of a link, given its type and targets' handles. Note that this is a static method which don't actually query the stored atomspace - in order to compute the handle. Instead, it just run a MD5 hashing algorithm on + in order to compute the handle. Instead, it just runs an MD5 hashing algorithm on the parameters that uniquely identify links (i.e. type and list of targets) This means e.g. that two links with the same type and the same targets are considered to be the exact same entity as they will have the same handle. Args: link_type (str): Link type. - link_targets (List[str]): List with the target handles. + link_targets (HandleListT): List with the target handles. Returns: str: Link's handle. @@ -209,14 +219,15 @@ def compute_link_handle(link_type: str, link_targets: List[str]) -> str: """ return AtomDB.link_handle(link_type, link_targets) - def get_atom(self, handle: str) -> AtomT: + def get_atom(self, handle: HandleT) -> AtomT: """ Retrieve an atom given its handle. - A handle is a MD5 hash of a node in the graph. It cam be computed using `compute_node_handle()' or `compute_link_handle()`. + A handle is a MD5 hash of a node in the graph. It can be computed using + `compute_node_handle()' or `compute_link_handle()`. Args: - handle (str): Atom's handle. + handle (HandleT): Atom's handle. Returns: Dict: A Python dict with all atom data. @@ -238,11 +249,11 @@ def get_atom(self, handle: str) -> AtomT: """ return self.query_engine.get_atom(handle, no_target_format=True) - def get_atoms(self, handles: List[str]) -> List[AtomT]: + def get_atoms(self, handles: HandleListT) -> List[AtomT]: """ Retrieve atoms given a list of handles. - A handle is a MD5 hash of a node in the graph. It cam be computed using + A handle is a MD5 hash of a node in the graph. It can be computed using `compute_node_handle()' or `compute_link_handle()`. It is preferable to call get atoms() passing a list of handles rather than @@ -250,7 +261,7 @@ def get_atoms(self, handles: List[str]) -> List[AtomT]: most one remote request, if necessary. Args: - handles (List[str]): List with Atom's handles. + handles (HandleListT): List with Atom's handles. Returns: Dict: A list of Python dicts with all atom data. @@ -311,14 +322,14 @@ def get_node(self, node_type: str, node_name: str) -> NodeT: node_handle = self.backend.node_handle(node_type, node_name) return self.get_atom(node_handle) - def get_link(self, link_type: str, link_targets: List[str]) -> LinkT: + def get_link(self, link_type: str, link_targets: HandleListT) -> LinkT: """ Retrieve a link given its type and list of targets. Targets are hashes of the nodes these hashes or handles can be created using the function 'compute_node_handle'. Args: link_type (str): Link type - link_targets (List[str]): List of target handles. + link_targets (HandleListT): List of target handles. Returns: Dict: A Python dict with all link data. @@ -367,7 +378,7 @@ def get_links(self, link_filter: LinkFilter) -> List[LinkT]: """ return self.query_engine.get_links(link_filter) - def get_link_handles(self, link_filter: LinkFilter) -> List[str]: + def get_link_handles(self, link_filter: LinkFilter) -> HandleSetT: """ Retrieve the handle of all links that match the passed filtering criteria. @@ -375,16 +386,16 @@ def get_link_handles(self, link_filter: LinkFilter) -> List[str]: link_filter (LinkFilter): Filtering criteria to be used to select links Returns: - List[str]: A list of link handles + HandleSetT: Link handles """ return self.query_engine.get_link_handles(link_filter) - def get_incoming_links(self, atom_handle: str, **kwargs) -> IncomingLinksT: + def get_incoming_links(self, atom_handle: HandleT, **kwargs) -> IncomingLinksT: """ Retrieve all links which has the passed handle as one of its targets. Args: - atom_handle (str): Atom's handle + atom_handle (HandleT): Atom's handle Keyword Args: handles_only (bool, optional): Returns a list of links handles. @@ -567,7 +578,7 @@ def custom_query( index_id, [{'field': k, 'value': v} for k, v in query.items()], **kwargs ) - def get_atoms_by_field(self, query: Query) -> List[str]: + def get_atoms_by_field(self, query: Query) -> HandleListT: """ Search for the atoms containing field and value, performance is improved if an index was previously created. @@ -578,7 +589,7 @@ def get_atoms_by_field(self, query: Query) -> List[str]: eg: {'name': 'human'} Returns: - List[str]: List of atom's ids + HandleListT: List of atom's ids """ return self.query_engine.get_atoms_by_field( @@ -587,7 +598,7 @@ def get_atoms_by_field(self, query: Query) -> List[str]: def get_atoms_by_text_field( self, text_value: str, field: Optional[str] = None, text_index_id: Optional[str] = None - ) -> List[str]: + ) -> HandleListT: """ Performs a text search, if a text index is previously created performance a token index search, otherwise will perform a regex search using binary tree and the argument 'field' is mandatory. @@ -599,11 +610,11 @@ def get_atoms_by_text_field( field (Optional[str]): Field to check the text_value text_index_id (Optional[str]): Text index Returns: - List[str]: List of atom's ids + HandleListT: List of atom's ids """ return self.query_engine.get_atoms_by_text_field(text_value, field, text_index_id) - def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> List[str]: + def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> HandleListT: """ Performs a search in the nodes names searchin for a node starting with the 'startswith' value. @@ -612,7 +623,7 @@ def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> Lis node_type (str): Node type startswith (str): String to search for Returns: - List[str]: List of atom's ids + HandleListT: List of atom's ids """ return self.query_engine.get_node_by_name_starting_with(node_type, startswith) @@ -627,7 +638,7 @@ def commit_changes(self, **kwargs): This is called a "Remote DAS" in the documentation. Remote DAS is connected to a remote DAS Server which is used to make queries, - traversing, etc but it also keeps a local Atomspace in RAM which is + traversing, etc. but it also keeps a local Atomspace in RAM which is used as a cache. Atom changes are made initially in this local cache. When commit_changes() is called in this type of DAS, these changes are propagated to the remote DAS Server. @@ -651,7 +662,7 @@ def add_node(self, node_params: Dict[str, Any]) -> Dict[str, Any]: Adds a node to DAS. A node is represented by a Python dict which may contain any number of keys associated to - values of any type (including lists, sets, nested dicts, etc) , which are all + values of any type (including lists, sets, nested dicts, etc.), which are all recorded with the node, but must contain at least the keys "type" and "name" mapping to strings which define the node uniquely, i.e. two nodes with the same "type" and "name" are considered to be the same entity. @@ -683,7 +694,7 @@ def add_link(self, link_params: Dict[str, Any]) -> Dict[str, Any]: Adds a link to DAS. A link is represented by a Python dict which may contain any number of keys associated to - values of any type (including lists, sets, nested dicts, etc) , which are all + values of any type (including lists, sets, nested dicts, etc.), which are all recorded with the link, but must contain at least the keys "type" and "targets". "type" should map to a string and "targets" to a list of Python dict, each of them being itself a representation of either a node or a nested link. "type" and "targets" define the diff --git a/hyperon_das/query_engines/query_engine_protocol.py b/hyperon_das/query_engines/query_engine_protocol.py index 032f939f..c67e2e99 100644 --- a/hyperon_das/query_engines/query_engine_protocol.py +++ b/hyperon_das/query_engines/query_engine_protocol.py @@ -1,7 +1,14 @@ from abc import ABC, abstractmethod from typing import Any, Dict, Iterator, List, Optional, Union -from hyperon_das_atomdb.database import IncomingLinksT, LinkT +from hyperon_das_atomdb.database import ( + AtomT, + HandleListT, + HandleSetT, + HandleT, + IncomingLinksT, + LinkT, +) from hyperon_das.context import Context from hyperon_das.link_filters import LinkFilter @@ -11,7 +18,7 @@ class QueryEngine(ABC): @abstractmethod - def get_atom(self, handle: str) -> Dict[str, Any]: + def get_atom(self, handle: HandleT) -> AtomT: """ Retrieves an atom from the database using its unique handle. @@ -19,15 +26,15 @@ def get_atom(self, handle: str) -> Dict[str, Any]: the atom's data as a dictionary. If no atom with the given handle exists, an exception is thrown. Args: - handle (str): The unique handle of the atom to retrieve. + handle (HandleT): The unique handle of the atom to retrieve. Returns: - Dict[str, Any]: A dictionary containing the atom's data. + AtomT: A dictionary containing the atom's data. """ ... @abstractmethod - def get_atoms(self, handles: List[str], **kwargs) -> List[Dict[str, Any]]: + def get_atoms(self, handles: HandleListT, **kwargs) -> List[AtomT]: """ Retrieves atoms from the database using their unique handles. @@ -37,10 +44,10 @@ def get_atoms(self, handles: List[str], **kwargs) -> List[Dict[str, Any]]: Remote query engines do a single request to remote DAS in order to get all the requested atoms. Args: - handles (List[str]): Unique handle of the atoms to retrieve. + handles (HandleListT): List of atoms handles to retrieve. Returns: - List[Dict[str, Any]]: List with requested atoms. + List[AtomT]: List with requested atoms. """ ... @@ -58,7 +65,7 @@ def get_links(self, link_filter: LinkFilter) -> List[LinkT]: ... @abstractmethod - def get_link_handles(self, link_filter: LinkFilter) -> List[LinkT]: + def get_link_handles(self, link_filter: LinkFilter) -> HandleSetT: """ Retrieve the handle of all links that match the passed filtering criteria. @@ -66,12 +73,12 @@ def get_link_handles(self, link_filter: LinkFilter) -> List[LinkT]: link_filter (LinkFilter): Filtering criteria to be used to select links Returns: - List[str]: A list of link handles + HandleSetT: Link handles """ ... @abstractmethod - def get_incoming_links(self, atom_handle: str, **kwargs) -> IncomingLinksT: + def get_incoming_links(self, atom_handle: HandleT, **kwargs) -> IncomingLinksT: """ Retrieves incoming links for a specified atom handle. @@ -81,7 +88,7 @@ def get_incoming_links(self, atom_handle: str, **kwargs) -> IncomingLinksT: implementation and the provided keyword arguments. Args: - atom_handle (str): The unique handle of the atom for which incoming links are to be + atom_handle (HandleT): The unique handle of the atom for which incoming links are to be retrieved. Keyword Args: @@ -291,7 +298,7 @@ def commit(self, **kwargs) -> None: ... @abstractmethod - def get_atoms_by_field(self, query: Query) -> List[str]: + def get_atoms_by_field(self, query: Query) -> HandleListT: """ Retrieves a list of atom handles based on a specified field query. @@ -303,14 +310,14 @@ def get_atoms_by_field(self, query: Query) -> List[str]: query (Query): The query specifying the field and value(s) to filter atoms by. Returns: - List[str]: A list of atom handles that match the query criteria. + HandleListT: A list of atom handles that match the query criteria. """ ... @abstractmethod def get_atoms_by_text_field( self, text_value: str, field: Optional[str] = None, text_index_id: Optional[str] = None - ) -> List[str]: + ) -> HandleListT: """ Retrieves a list of atom handles based on a text field value, with optional field and index ID. @@ -326,12 +333,12 @@ def get_atoms_by_text_field( optimize the search process if provided. Defaults to None. Returns: - List[str]: A list of atom handles that match the search criteria. + HandleListT: A list of atom handles that match the search criteria. """ ... @abstractmethod - def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> List[str]: + def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> HandleListT: """ Retrieves a list of node handles where the node name starts with a specified string. @@ -343,6 +350,6 @@ def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> Lis startswith (str): The initial string of the node names to match. Returns: - List[str]: A list of handles for the nodes that match the search criteria. + HandleListT: A list of handles for the nodes that match the search criteria. """ ... diff --git a/hyperon_das/query_engines/remote_query_engine.py b/hyperon_das/query_engines/remote_query_engine.py index da209e7d..5bf5662c 100644 --- a/hyperon_das/query_engines/remote_query_engine.py +++ b/hyperon_das/query_engines/remote_query_engine.py @@ -1,7 +1,14 @@ from enum import Enum from typing import Any, Dict, Iterator, List, Optional -from hyperon_das_atomdb.database import IncomingLinksT, LinkT +from hyperon_das_atomdb.database import ( + AtomT, + HandleListT, + HandleSetT, + HandleT, + IncomingLinksT, + LinkT, +) from hyperon_das_atomdb.exceptions import AtomDoesNotExist from hyperon_das.cache.cache_controller import CacheController @@ -46,7 +53,7 @@ def __init__( def mode(self): return self.__mode - def get_atom(self, handle: str, **kwargs) -> Dict[str, Any]: + def get_atom(self, handle: HandleT, **kwargs) -> AtomT: atom = self.cache_controller.get_atom(handle) if atom is None: try: @@ -58,7 +65,7 @@ def get_atom(self, handle: str, **kwargs) -> Dict[str, Any]: das_error(exception) return atom - def get_atoms(self, handles: List[str], **kwargs) -> List[Dict[str, Any]]: + def get_atoms(self, handles: HandleListT, **kwargs) -> List[AtomT]: return self.cache_controller.get_atoms(handles) def get_links(self, link_filter: LinkFilter) -> List[LinkT]: @@ -67,11 +74,11 @@ def get_links(self, link_filter: LinkFilter) -> List[LinkT]: links.extend(remote_links) return links - def get_link_handles(self, link_filter: LinkFilter) -> List[str]: + def get_link_handles(self, link_filter: LinkFilter) -> HandleSetT: # TODO Implement get_link_handles() in faas client - return [link['handle'] for link in self.get_links(link_filter)] + return {link['handle'] for link in self.get_links(link_filter)} - def get_incoming_links(self, atom_handle: str, **kwargs) -> IncomingLinksT: + def get_incoming_links(self, atom_handle: HandleT, **kwargs) -> IncomingLinksT: links = self.local_query_engine.get_incoming_links(atom_handle, **kwargs) remote_links = self.remote_das.get_incoming_links(atom_handle, **kwargs) links.extend(remote_links) @@ -188,13 +195,13 @@ def create_context( ) -> Context: return self.remote_das.create_context(name, queries) - def get_atoms_by_field(self, query: Query) -> List[str]: + def get_atoms_by_field(self, query: Query) -> HandleListT: return self.remote_das.get_atoms_by_field(query) def get_atoms_by_text_field( self, text_value: str, field: Optional[str] = None, text_index_id: Optional[str] = None - ) -> List[str]: + ) -> HandleListT: return self.remote_das.get_atoms_by_text_field(text_value, field, text_index_id) - def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> List[str]: + def get_node_by_name_starting_with(self, node_type: str, startswith: str) -> HandleListT: return self.remote_das.get_node_by_name_starting_with(node_type, startswith)