diff --git a/src/README.md b/src/README.md index 69e2cb6..621e808 100644 --- a/src/README.md +++ b/src/README.md @@ -142,7 +142,7 @@ query_result = hiro_client.query('ogit\\/_type:"ogit/MARS/Machine"') print(query_result) ``` -## Handler sharing +## Token Handler sharing When you need to access multiple APIs of HIRO, share the TokenApiHandler between the API objects to avoid unnecessary requests for token- and version-information against HIRO. The TokenApiHandler will share a `requests.Session`, token- @@ -168,6 +168,55 @@ hiro_app_client: HiroApp = HiroApp( ) ``` +## Connection sharing + +You can also let TokenApiHandlers share a common connection session instead of letting each of them create their own. +This might prove useful in a multithreading environment where tokens have to be set externally or change often (i.e. +one token per user per thread). This also ensures, that version-requests happen only once when the connection is +initialized. + +Use the parameters `pool_maxsize` and `pool_block` to further tune the connection parameters for parallel access to +the backend. See [requests Session Objects](https://docs.python-requests.org/en/latest/user/advanced/#session-objects) +and Python documentation of `requests.adapters.HTTPAdapter` for more information. + +```python +from hiro_graph_client import HiroGraph, HiroApp, FixedTokenApiHandler, GraphConnectionHandler + +connection_handler = GraphConnectionHandler( + root_url="https://core.arago.co", + pool_maxsize=200, # Optional: Max pool of cached connections for this connection session + pool_block=True, # Optional: Do not allow more parallel connections than pool_maxsize + client_name="Your Graph Client 0.0.1" # Optional: Will be used in the header 'User-Agent' +) + +# Work with token of user 1 + +user1_client: HiroGraph = HiroGraph( + api_handler=FixedTokenApiHandler( + connection_handler=connection_handler, + token='token user 1' + ) +) + +# Work with token of user 2 (Shared Token Handler) + +user2_api_handler = FixedTokenApiHandler( + connection_handler=connection_handler, + token='token user 2' +) + +user2_client: HiroGraph = HiroGraph( + api_handler=user2_api_handler +) + +hiro_app_client: HiroApp = HiroApp( + api_handler=user2_api_handler +) + +``` + +Everything written in [Token Handler Sharing](#token-handler-sharing) still applies. + ## SSL Configuration SSL parameters are configured using the class `SSLConfig`. This class translates the parameters given to the required diff --git a/src/hiro_graph_client/VERSION b/src/hiro_graph_client/VERSION index acf69b4..7cbea07 100644 --- a/src/hiro_graph_client/VERSION +++ b/src/hiro_graph_client/VERSION @@ -1 +1 @@ -5.1.0 \ No newline at end of file +5.2.0 \ No newline at end of file diff --git a/src/hiro_graph_client/__init__.py b/src/hiro_graph_client/__init__.py index 894afd3..9e05f8d 100644 --- a/src/hiro_graph_client/__init__.py +++ b/src/hiro_graph_client/__init__.py @@ -8,9 +8,9 @@ from hiro_graph_client.authclient import HiroAuth from hiro_graph_client.authzclient import HiroAuthz from hiro_graph_client.client import HiroGraph -from hiro_graph_client.clientlib import AbstractTokenApiHandler, AuthenticationTokenError, FixedTokenError, \ - TokenUnauthorizedError, PasswordAuthTokenApiHandler, FixedTokenApiHandler, EnvironmentTokenApiHandler, \ - SSLConfig +from hiro_graph_client.clientlib import AbstractTokenApiHandler, GraphConnectionHandler, AuthenticationTokenError, \ + FixedTokenError, TokenUnauthorizedError, PasswordAuthTokenApiHandler, FixedTokenApiHandler, \ + EnvironmentTokenApiHandler, SSLConfig from hiro_graph_client.iamclient import HiroIam from hiro_graph_client.kiclient import HiroKi from hiro_graph_client.variablesclient import HiroVariables @@ -19,7 +19,7 @@ this_directory = path.abspath(path.dirname(__file__)) __all__ = [ - 'HiroGraph', 'HiroAuth', 'HiroApp', 'HiroIam', 'HiroKi', 'HiroAuthz', 'HiroVariables', + 'HiroGraph', 'HiroAuth', 'HiroApp', 'HiroIam', 'HiroKi', 'HiroAuthz', 'HiroVariables', 'GraphConnectionHandler', 'AbstractTokenApiHandler', 'PasswordAuthTokenApiHandler', 'FixedTokenApiHandler', 'EnvironmentTokenApiHandler', 'AuthenticationTokenError', 'FixedTokenError', 'TokenUnauthorizedError', '__version__', 'SSLConfig' diff --git a/src/hiro_graph_client/clientlib.py b/src/hiro_graph_client/clientlib.py index 6af84f4..36d3df2 100644 --- a/src/hiro_graph_client/clientlib.py +++ b/src/hiro_graph_client/clientlib.py @@ -93,6 +93,10 @@ class AbstractAPI: tool methods for handling headers, url query parts and response error checking. """ + _root_url: str = None + + _session: requests.Session = None + accept_all_certs: bool = False ssl_config: SSLConfig @@ -101,20 +105,20 @@ class AbstractAPI: _max_tries: int = 2 - _session: requests.Session - """Reference to the requests.Session containing the connection pool""" + _timeout: int = 600 def __init__(self, - root_url: str, - session: requests.Session, + root_url: str = None, + session: requests.Session = None, raise_exceptions: bool = True, proxies: dict = None, headers: dict = None, - timeout: int = 600, + timeout: int = None, client_name: str = None, ssl_config: SSLConfig = None, log_communication_on_error: bool = None, - max_tries: int = None): + max_tries: int = None, + abstract_api=None): """ Constructor @@ -132,38 +136,45 @@ def __init__(self, :param log_communication_on_error: Log socket communication when an error (status_code of HTTP Response) is detected. Default is not to do this. :param max_tries: Max tries for BACKOFF. Default is 2. + :param abstract_api: Set all parameters by copying them from the instance given by this parameter. Overrides + all other parameters. """ + self._root_url = getattr(abstract_api, '_root_url', root_url) + self._session = getattr(abstract_api, '_session', session) - if not root_url: + if not self._root_url: raise ValueError("'root_url' must not be empty.") - self._root_url = root_url - self._proxies = proxies - self._raise_exceptions = raise_exceptions - self._timeout = timeout - self._log_communication_on_error = log_communication_on_error or False - self._session = session + if not self._session: + raise ValueError("'session' must not be empty.") - self.ssl_config = ssl_config if ssl_config else SSLConfig() + self._proxies = getattr(abstract_api, '_proxies', proxies) + self._raise_exceptions = getattr(abstract_api, '_raise_exceptions', raise_exceptions) + self._timeout = getattr(abstract_api, '_timeout', timeout or self._timeout) + self._log_communication_on_error = getattr(abstract_api, '_log_communication_on_error', + log_communication_on_error or False) + + self.ssl_config = getattr(abstract_api, 'ssl_config', ssl_config or SSLConfig()) if not self.ssl_config.verify: AbstractAPI.accept_all_certs = True requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) - if client_name: - self._client_name = client_name + self._client_name = getattr(abstract_api, '_client_name', client_name or self._client_name) - self._headers = { - 'Content-Type': 'application/json', - 'Accept': 'text/plain, application/json', - 'User-Agent': f"{self._client_name} {__version__}" - } + if abstract_api: + self._headers = getattr(abstract_api, '_headers', None) + else: + self._headers = { + 'Content-Type': 'application/json', + 'Accept': 'text/plain, application/json', + 'User-Agent': f"{self._client_name} {__version__}" + } - if headers: - self._headers.update({self._capitalize_header(k): v for k, v in headers.items()}) + if headers: + self._headers.update({self._capitalize_header(k): v for k, v in headers.items()}) - if max_tries is not None: - self._max_tries = max_tries + self._max_tries = getattr(abstract_api, '_max_tries', max_tries) def _get_max_tries(self): return self._max_tries @@ -415,7 +426,7 @@ def _delete() -> Any: def _get_proxies(self) -> dict: """ - Create a copy of proxies if they exists or return None + Create a copy of proxies if they exist or return None :return: copy of self._proxies or None """ @@ -527,8 +538,7 @@ def _body_str(body: Union[str, bytes], encoding: str) -> str: ok = self._check_response_ok(res) - if (not ok and self._log_communication_on_error) or logger.isEnabledFor( - logging.DEBUG): + if (not ok and self._log_communication_on_error) or logger.isEnabledFor(logging.DEBUG): log_message = f''' ################ request ################ {res.request.method} {res.request.url} @@ -602,7 +612,7 @@ def _check_content_type(res: requests.Response, expected_media_type: str) -> Non :param res: The response object :param expected_media_type: The expected Media-Type. :raise WrongContentTypeError: When the Media-Type of the Content-Type is not *expected_media_type* or - the header is is missing completely. + the header is missing completely. """ content_type = res.headers.get('Content-Type') if not content_type: @@ -652,31 +662,33 @@ def _handle_token(self) -> Optional[str]: ################################################################################################################### -# TokenApiHandler classes +# ConnectionHandler class ################################################################################################################### -class AbstractTokenApiHandler(AbstractAPI): +class GraphConnectionHandler(AbstractAPI): """ - Root class for all TokenApiHandler classes. This class also handles resolving the current api endpoints. - Also creates the requests.Session which will be shared among the API Modules using this TokenApiHandler. + Contains information about a Graph Connection. This class also handles resolving the current api endpoints. + Also creates the requests.Session which will be shared among the API Modules using this connection. """ _pool_maxsize = 10 """Default pool_maxsize for requests.adapters.HTTPAdapter.""" + _pool_block = False + + _version_info: dict = None + + _lock: threading.RLock + """Reentrant mutex for thread safety""" + def __init__(self, - root_url: str, - raise_exceptions: bool = True, - proxies: dict = None, - headers: dict = None, - timeout: int = 600, - client_name: str = None, + root_url: str = None, custom_endpoints: dict = None, - ssl_config: SSLConfig = None, - log_communication_on_error: bool = None, - max_tries: int = None, pool_maxsize: int = None, - pool_block: bool = None): + pool_block: bool = None, + connection_handler=None, + *args, + **kwargs): """ Constructor @@ -692,7 +704,8 @@ def __init__(self, This object creates the *requests.Session* and *requests.adapters.HTTPAdapter* for this *root_url*. The *pool_maxsize* of such a session can be set via the parameter in the constructor. When a TokenApiHandler is - shared between different API objects (like HiroGraph, HiroApp, etc.), this session and its pool are also shared. + shared between different API objects (like HiroGraph, HiroApp, etc.), this session and its pool are also + shared. :param root_url: Root url for HIRO, like https://core.arago.co. :param raise_exceptions: Raise exceptions on HTTP status codes that denote an error. Default is True. @@ -700,40 +713,52 @@ def __init__(self, :param headers: Optional custom HTTP headers. Will override the internal headers. Default is None. :param timeout: Optional timeout for requests. Default is 600 (10 min). :param client_name: Optional name for the client. Will also be part of the "User-Agent" header unless *headers* - is given with another value for "User-Agent". Default is "hiro-graph-client". + is given with another value for "User-Agent". Default is "hiro-graph-client". :param custom_endpoints: Optional map of {name:endpoint_path, ...} that overrides or adds to the endpoints taken from /api/version. Example see above. - :param ssl_config Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib - will be used. + :param ssl_config: Optional configuration for SSL connections. If this is omitted, the defaults of `requests` + lib will be used. :param log_communication_on_error: Log socket communication when an error (status_code of HTTP Response) is detected. Default is not to do this. :param max_tries: Max tries for BACKOFF. Default is 2. :param pool_maxsize: Size of a connection pool for a single connection. See requests.adapters.HTTPAdapter. - Default is 10. + Default is 10. *pool_maxsize* is ignored when *session* is set. :param pool_block: Block any connections that exceed the pool_maxsize. Default is False: Allow more connections, - but do not cache them. See requests.adapters.HTTPAdapter. + but do not cache them. See requests.adapters.HTTPAdapter. *pool_block* is ignored when *session* is set. + :param connection_handler: Copy parameters from this already existing connection handler. Overrides all other + parameters. + :param args: Unnamed parameters for parent class. See there. + :param kwargs: Named parameters for parent class. See there. """ - session = requests.Session() - adapter = requests.adapters.HTTPAdapter( - pool_maxsize=pool_maxsize or self._pool_maxsize, - pool_connections=1, - pool_block=pool_block or False + self._lock = threading.RLock() + + root_url = getattr(connection_handler, '_root_url', root_url) + session = getattr(connection_handler, '_session', None) + + if not root_url: + raise ValueError("'root_url' must not be empty.") + + if not session: + adapter = requests.adapters.HTTPAdapter( + pool_maxsize=pool_maxsize or self._pool_maxsize, + pool_connections=1, + pool_block=pool_block or self._pool_block + ) + session = requests.Session() + session.mount(root_url, adapter) + + super().__init__( + root_url=root_url, + session=session, + abstract_api=connection_handler, + *args, + **kwargs ) - session.mount(root_url, adapter) - - super().__init__(root_url=root_url, - session=session, - raise_exceptions=raise_exceptions, - proxies=proxies, - timeout=timeout, - headers=headers, - client_name=client_name, - ssl_config=ssl_config, - log_communication_on_error=log_communication_on_error, - max_tries=max_tries) - - self._version_info = None - self.custom_endpoints = custom_endpoints + + self.custom_endpoints = getattr(connection_handler, '_custom_endpoints', custom_endpoints) + self._version_info = getattr(connection_handler, '_version_info', None) + + self.get_version() @staticmethod def _remove_slash(endpoint: str) -> str: @@ -757,10 +782,7 @@ def get_api_endpoint_of(self, api_name: str) -> str: if endpoint: return self._remove_slash(self._root_url + endpoint) - if not self._version_info: - self._version_info = self.get_version() - - api_entry: dict = self._version_info.get(api_name) + api_entry: dict = self.get_version().get(api_name) if not api_entry: raise ValueError("No API named '{}' found.".format(api_name)) @@ -821,10 +843,7 @@ def _construct_result(_endpoint: str, _protocol: str) -> Tuple[ if endpoint: return _construct_result(endpoint, protocol) - if not self._version_info: - self._version_info = self.get_version() - - api_entry: dict = self._version_info.get(api_name) + api_entry: dict = self.get_version().get(api_name) if not api_entry: raise ValueError("No WS API named '{}' found.".format(api_name)) @@ -835,14 +854,77 @@ def _construct_result(_endpoint: str, _protocol: str) -> Tuple[ # REST API operations ############################################################################################################### - def get_version(self) -> dict: + def get_version(self, force_update: bool = False) -> dict: """ HIRO REST query API: `GET self._endpoint + '/api/version'` + :param force_update: Force updating the internal cache with version_info via API request. :return: The result payload """ - url = self._root_url + '/api/version' - return self.get(url) + with self._lock: + if not self._version_info or force_update: + url = self._root_url + '/api/version' + self._version_info = self.get(url) + + return self._version_info + + +################################################################################################################### +# TokenApiHandler classes +################################################################################################################### + +class AbstractTokenApiHandler(GraphConnectionHandler): + """ + Root class for all TokenApiHandler classes. This adds token handling. + """ + + def __init__(self, *args, **kwargs): + """ + Constructor + + Example for custom_endpoints (see params below): + + :: + + { + "graph": "/api/graph/7.2", + "auth": "/api/auth/6.2", + "action-ws": ("/api/action-ws/1.0", "action-1.0.0") + } + + This object creates the *requests.Session* and *requests.adapters.HTTPAdapter* for this *root_url*. The + *pool_maxsize* of such a session can be set via the parameter in the constructor. When a TokenApiHandler is + shared between different API objects (like HiroGraph, HiroApp, etc.), this session and its pool are also + shared. + + :param root_url: Root url for HIRO, like https://core.arago.co. + :param raise_exceptions: Raise exceptions on HTTP status codes that denote an error. Default is True. + :param proxies: Proxy configuration for *requests*. Default is None. + :param headers: Optional custom HTTP headers. Will override the internal headers. Default is None. + :param timeout: Optional timeout for requests. Default is 600 (10 min). + :param client_name: Optional name for the client. Will also be part of the "User-Agent" header unless *headers* + is given with another value for "User-Agent". Default is "hiro-graph-client". + :param custom_endpoints: Optional map of {name:endpoint_path, ...} that overrides or adds to the endpoints taken + from /api/version. Example see above. + :param ssl_config: Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib + will be used. + :param log_communication_on_error: Log socket communication when an error (status_code of HTTP Response) is + detected. Default is not to do this. + :param max_tries: Max tries for BACKOFF. Default is 2. + :param pool_maxsize: Size of a connection pool for a single connection. See requests.adapters.HTTPAdapter. + Default is 10. *pool_maxsize* is ignored when *connection_handler* is set and already contains a + session. + :param pool_block: Block any connections that exceed the pool_maxsize. Default is False: Allow more connections, + but do not cache them. See requests.adapters.HTTPAdapter. *pool_block* is ignored when + *connection_handler* is set and already contains a session. + :param connection_handler: Copy all parameters from this already existing connection handler. This feature + allows for several distinct TokenApiHandlers to operate on the same connection without querying + unnecessary version information for each or building their own requests.Sessions. Overrides all other + parameters. + :param args: Unnamed parameters for parent class. See there. + :param kwargs: Named parameters for parent class. See there. + """ + super().__init__(*args, **kwargs) ############################################################################################################### # Token handling @@ -897,25 +979,12 @@ class FixedTokenApiHandler(AbstractTokenApiHandler): _token: str - def __init__(self, - root_url: str, - token: str, - raise_exceptions: bool = True, - proxies: dict = None, - headers: dict = None, - timeout: int = 600, - client_name: str = None, - custom_endpoints: dict = None, - ssl_config: SSLConfig = None, - log_communication_on_error: bool = None, - max_tries: int = None, - pool_maxsize: int = None, - pool_block: bool = None): + def __init__(self, token: str = None, *args, **kwargs): """ Constructor - :param root_url: Root url for HIRO, like https://core.arago.co. :param token: The fixed token to use. + :param root_url: Root url for HIRO, like https://core.arago.co. :param raise_exceptions: Raise exceptions on HTTP status codes that denote an error. Default is True. :param proxies: Proxy configuration for *requests*. Default is None. :param headers: Optional custom HTTP headers. Will override the internal headers. Default is None. @@ -924,30 +993,25 @@ def __init__(self, is given with another value for "User-Agent". Default is "hiro-graph-client". :param custom_endpoints: Optional map of [name:endpoint_path] that overrides or adds to the endpoints taken from /api/version. - :param ssl_config Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib + :param ssl_config: Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib will be used. :param log_communication_on_error: Log socket communication when an error (status_code of HTTP Response) is detected. Default is not to do this. :param max_tries: Max tries for BACKOFF. Default is 2. :param pool_maxsize: Size of a connection pool for a single connection. See requests.adapters.HTTPAdapter. - Default is 10. + Default is 10. *pool_maxsize* is ignored when *connection_handler* is set and already contains a + session. :param pool_block: Block any connections that exceed the pool_maxsize. Default is False: Allow more connections, - but do not cache them. See requests.adapters.HTTPAdapter. + but do not cache them. See requests.adapters.HTTPAdapter. *pool_block* is ignored when + *connection_handler* is set and already contains a session. + :param connection_handler: Copy all parameters from this already existing connection handler. This feature + allows for several distinct TokenApiHandlers to operate on the same connection without querying + unnecessary version information for each or building their own requests.Sessions. Overrides all other + parameters. + :param args: Unnamed parameters for parent class. See there. + :param kwargs: Named parameters for parent class. See there. """ - super().__init__( - root_url=root_url, - raise_exceptions=raise_exceptions, - proxies=proxies, - timeout=timeout, - headers=headers, - client_name=client_name, - custom_endpoints=custom_endpoints, - ssl_config=ssl_config, - log_communication_on_error=log_communication_on_error, - max_tries=max_tries, - pool_maxsize=pool_maxsize, - pool_block=pool_block - ) + super().__init__(*args, **kwargs) self._token = token @@ -973,20 +1037,7 @@ class EnvironmentTokenApiHandler(AbstractTokenApiHandler): _env_var: str - def __init__(self, - root_url: str, - env_var: str = 'HIRO_TOKEN', - raise_exceptions: bool = True, - proxies: dict = None, - headers: dict = None, - timeout: int = 600, - client_name: str = None, - custom_endpoints: dict = None, - ssl_config: SSLConfig = None, - log_communication_on_error: bool = None, - max_tries: int = None, - pool_maxsize: int = None, - pool_block: bool = None): + def __init__(self, env_var: str = 'HIRO_TOKEN', *args, **kwargs): """ Constructor @@ -1000,30 +1051,25 @@ def __init__(self, is given with another value for "User-Agent". Default is "hiro-graph-client". :param custom_endpoints: Optional map of [name:endpoint_path] that overrides or adds to the endpoints taken from /api/version. - :param ssl_config Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib + :param ssl_config: Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib will be used. :param log_communication_on_error: Log socket communication when an error (status_code of HTTP Response) is detected. Default is not to do this. :param max_tries: Max tries for BACKOFF. Default is 2. :param pool_maxsize: Size of a connection pool for a single connection. See requests.adapters.HTTPAdapter. - Default is 10. + Default is 10. *pool_maxsize* is ignored when *connection_handler* is set and already contains a + session. :param pool_block: Block any connections that exceed the pool_maxsize. Default is False: Allow more connections, - but do not cache them. See requests.adapters.HTTPAdapter. + but do not cache them. See requests.adapters.HTTPAdapter. *pool_block* is ignored when + *connection_handler* is set and already contains a session. + :param connection_handler: Copy all parameters from this already existing connection handler. This feature + allows for several distinct TokenApiHandlers to operate on the same connection without querying + unnecessary version information for each or building their own requests.Sessions. Overrides all other + parameters. + :param args: Unnamed parameters for parent class. See there. + :param kwargs: Named parameters for parent class. See there. """ - super().__init__( - root_url=root_url, - raise_exceptions=raise_exceptions, - proxies=proxies, - headers=headers, - timeout=timeout, - client_name=client_name, - custom_endpoints=custom_endpoints, - ssl_config=ssl_config, - log_communication_on_error=log_communication_on_error, - max_tries=max_tries, - pool_maxsize=pool_maxsize, - pool_block=pool_block - ) + super().__init__(*args, **kwargs) self._env_var = env_var @@ -1151,7 +1197,7 @@ class PasswordAuthTokenApiHandler(AbstractTokenApiHandler): API Tokens will be fetched using this class. It does not handle any automatic token fetching, refresh or token expiry. This has to be checked and triggered by the *caller*. - The methods of this class are thread-safe so it can be shared between several HIRO objects. + The methods of this class are thread-safe, so it can be shared between several HIRO objects. It is built this way to avoid endless calling loops when resolving tokens. """ @@ -1170,23 +1216,12 @@ class PasswordAuthTokenApiHandler(AbstractTokenApiHandler): _secure_logging: bool = True def __init__(self, - root_url: str, - username: str, - password: str, - client_id: str, - client_secret: str, + username: str = None, + password: str = None, + client_id: str = None, + client_secret: str = None, secure_logging: bool = True, - raise_exceptions: bool = True, - proxies: dict = None, - headers: dict = None, - timeout: int = 600, - client_name: str = None, - custom_endpoints: dict = None, - ssl_config: SSLConfig = None, - log_communication_on_error: bool = None, - max_tries: int = None, - pool_maxsize: int = None, - pool_block: bool = None): + *args, **kwargs): """ Constructor @@ -1204,30 +1239,25 @@ def __init__(self, is given with another value for "User-Agent". Default is "hiro-graph-client". :param custom_endpoints: Optional map of [name:endpoint_path] that overrides or adds to the endpoints taken from /api/version. - :param ssl_config Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib + :param ssl_config: Optional configuration for SSL connections. If this is omitted, the defaults of `requests` lib will be used. :param log_communication_on_error: Log socket communication when an error (status_code of HTTP Response) is detected. Default is not to do this. :param max_tries: Max tries for BACKOFF. Default is 2. :param pool_maxsize: Size of a connection pool for a single connection. See requests.adapters.HTTPAdapter. - Default is 10. + Default is 10. *pool_maxsize* is ignored when *connection_handler* is set and already contains a + _session. :param pool_block: Block any connections that exceed the pool_maxsize. Default is False: Allow more connections, - but do not cache them. See requests.adapters.HTTPAdapter. + but do not cache them. See requests.adapters.HTTPAdapter. *pool_block* is ignored when + *connection_handler* is set and already contains a _session. + :param connection_handler: Copy all parameters from this already existing connection handler. This feature + allows for several distinct TokenApiHandlers to operate on the same connection without querying + unnecessary version information for each or building their own requests.Sessions. Overrides all other + parameters. + :param args: Unnamed parameters for parent class. See there. + :param kwargs: Named parameters for parent class. See there. """ - super().__init__( - root_url=root_url, - raise_exceptions=raise_exceptions, - proxies=proxies, - headers=headers, - timeout=timeout, - client_name=client_name, - custom_endpoints=custom_endpoints, - ssl_config=ssl_config, - log_communication_on_error=log_communication_on_error, - max_tries=max_tries, - pool_maxsize=pool_maxsize, - pool_block=pool_block - ) + super().__init__(*args, **kwargs) self._username = username self._password = password @@ -1394,16 +1424,7 @@ def __init__(self, if not api_handler or not api_name: raise ValueError("Cannot authenticate against HIRO without *api_handler* and *api_name*.") - super().__init__(root_url=api_handler._root_url, - session=api_handler._session, - raise_exceptions=api_handler._raise_exceptions, - proxies=api_handler._proxies, - headers=api_handler._headers, - timeout=api_handler._timeout, - client_name=api_handler._client_name, - ssl_config=api_handler.ssl_config, - log_communication_on_error=api_handler._log_communication_on_error, - max_tries=api_handler._get_max_tries()) + super().__init__(abstract_api=api_handler) self._api_handler = api_handler self._api_name = api_name diff --git a/src/hiro_graph_client/eventswebsocket.py b/src/hiro_graph_client/eventswebsocket.py index ed687b3..337088d 100644 --- a/src/hiro_graph_client/eventswebsocket.py +++ b/src/hiro_graph_client/eventswebsocket.py @@ -356,7 +356,7 @@ def _set_next_token_refresh(self): replace_existing=True) def _token_refresh_thread(self): - logger.debug("Updating token for session") + logger.debug("Updating token for _session") message: dict = { "type": "token", diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 2abac93..ca7a1ee 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1,26 +1,47 @@ -from hiro_graph_client import PasswordAuthTokenApiHandler, HiroGraph, SSLConfig +from hiro_graph_client import PasswordAuthTokenApiHandler, HiroGraph, SSLConfig, GraphConnectionHandler from .testconfig import CONFIG class TestClient: - hiro_api_handler = PasswordAuthTokenApiHandler( + connection_handler = GraphConnectionHandler( root_url=CONFIG.get('URL'), + ssl_config=SSLConfig(verify=False) + ) + + hiro_api_handler = PasswordAuthTokenApiHandler( username=CONFIG.get('USERNAME'), password=CONFIG.get('PASSWORD'), client_id=CONFIG.get('CLIENT_ID'), client_secret=CONFIG.get('CLIENT_SECRET'), secure_logging=False, - ssl_config=SSLConfig(verify=False) + connection_handler=connection_handler + ) + + hiro_api_handler2 = PasswordAuthTokenApiHandler( + username=CONFIG.get('USERNAME'), + password=CONFIG.get('PASSWORD'), + client_id=CONFIG.get('CLIENT_ID'), + client_secret=CONFIG.get('CLIENT_SECRET'), + secure_logging=False, + connection_handler=connection_handler ) def test_simple_query(self): hiro_client: HiroGraph = HiroGraph(api_handler=self.hiro_api_handler) - attributes = { - "/start_date": None, - "/end_date": None, - "/status": None, - "/inActiveTimespan": None - } + hiro_client.get_node(node_id="ckqjkt42s0fgf0883pf0cb0hx_ckqjl014l0hvr0883hxcvmcwq", meta=True) + + hiro_client: HiroGraph = HiroGraph(api_handler=self.hiro_api_handler2) + + hiro_client.get_node(node_id="ckqjkt42s0fgf0883pf0cb0hx_ckqjl014l0hvr0883hxcvmcwq", meta=True) - hiro_client.update_node(node_id="ckqjkt42s0fgf0883pf0cb0hx_ckshomsv84i9x0783cla0g2bv", data=attributes) + # def query(_id: str): + # hiro_client.get_node(node_id=_id) + # + # t1 = threading.Thread(target=query, args=("ckqjkt42s0fgf0883pf0cb0hx_ckqjl014l0hvr0883hxcvmcwq",)) + # t2 = threading.Thread(target=query, args=("ckqjkt42s0fgf0883pf0cb0hx_ckshomsv84i9x0783cla0g2bv",)) + # t3 = threading.Thread(target=query, args=("ckqjkt42s0fgf0883pf0cb0hx_ckshomsv84i9x0783cla0g2bv",)) + # + # t1.start() + # t2.start() + # t3.start()