@@ -94,16 +94,34 @@ class AbstractAPI:
94
94
"""
95
95
96
96
_root_url : str = None
97
+ """Servername and context path of the root of the API"""
97
98
98
99
_session : requests .Session = None
100
+ """Reference to the session information"""
99
101
100
102
ssl_config : SSLConfig
103
+ """Security configuration and location of certificate files"""
101
104
102
105
_client_name : str = "hiro-graph-client"
106
+ """Used in header 'User-Agent'"""
103
107
104
108
_max_tries : int = 2
109
+ """Retries for backoff"""
105
110
106
111
_timeout : int = 600
112
+ """Timeout for requests-methods as needed by package 'requests'."""
113
+
114
+ _raise_exceptions : bool = True
115
+ """Raise an exception when the status-code of results indicates an error"""
116
+
117
+ _proxies : dict = None
118
+ """Proxy configuration as needed by package 'requests'."""
119
+
120
+ _headers : dict = {}
121
+ """Common headers for HTTP requests."""
122
+
123
+ _log_communication_on_error : bool = False
124
+ """Dump request and response into logging on errors"""
107
125
108
126
def __init__ (self ,
109
127
root_url : str = None ,
@@ -121,11 +139,15 @@ def __init__(self,
121
139
"""
122
140
Constructor
123
141
142
+ A note regarding headers: If you set a value in the dict to *None*, it will not show up in the HTTP-request
143
+ headers. Use this to erase entries from existing default headers or headers copied from *apstract_api* (when
144
+ given).
145
+
124
146
:param root_url: Root uri of the HIRO API, like *https://core.arago.co*.
125
147
:param session: The requests.Session object for the connection pool. Required.
126
148
:param raise_exceptions: Raise exceptions on HTTP status codes that denote an error. Default is True.
127
149
:param proxies: Proxy configuration for *requests*. Default is None.
128
- :param headers: Optional custom HTTP headers. Will override the internal headers. Default is None.
150
+ :param headers: Optional custom HTTP headers. Will be merged with the internal default headers. Default is None.
129
151
:param timeout: Optional timeout for requests. Default is 600 (10 min).
130
152
:param client_name: Optional name for the client. Will also be part of the "User-Agent" header unless *headers*
131
153
is given with another value for "User-Agent". Default is "hiro-graph-client".
@@ -135,43 +157,49 @@ def __init__(self,
135
157
detected. Default is not to do this.
136
158
:param max_tries: Max tries for BACKOFF. Default is 2.
137
159
:param abstract_api: Set all parameters by copying them from the instance given by this parameter. Overrides
138
- all other parameters.
139
- """
140
- self ._root_url = getattr (abstract_api , '_root_url' , root_url )
141
- self ._session = getattr (abstract_api , '_session' , session )
160
+ all other parameters except headers, which will be merged with existing ones.
161
+ """
162
+
163
+ if isinstance (abstract_api , AbstractAPI ):
164
+ root_url = abstract_api ._root_url
165
+ session = abstract_api ._session
166
+ raise_exceptions = abstract_api ._raise_exceptions
167
+ proxies = abstract_api ._proxies
168
+ initial_headers = abstract_api ._headers .copy ()
169
+ timeout = abstract_api ._timeout
170
+ client_name = abstract_api ._client_name
171
+ ssl_config = abstract_api .ssl_config
172
+ log_communication_on_error = abstract_api ._log_communication_on_error
173
+ max_tries = abstract_api ._max_tries
174
+ else :
175
+ initial_headers = {
176
+ 'Content-Type' : 'application/json' ,
177
+ 'Accept' : 'text/plain, application/json' ,
178
+ 'User-Agent' : f"{ client_name or self ._client_name } { __version__ } "
179
+ }
180
+
181
+ self ._root_url = root_url
182
+ self ._session = session
142
183
143
184
if not self ._root_url :
144
185
raise ValueError ("'root_url' must not be empty." )
145
186
146
187
if not self ._session :
147
188
raise ValueError ("'session' must not be empty." )
148
189
149
- self ._proxies = getattr (abstract_api , '_proxies' , proxies )
150
- self ._raise_exceptions = getattr (abstract_api , '_raise_exceptions' , raise_exceptions )
151
- self ._timeout = getattr (abstract_api , '_timeout' , timeout or self ._timeout )
152
- self ._log_communication_on_error = getattr (abstract_api , '_log_communication_on_error' ,
153
- log_communication_on_error or False )
190
+ self ._client_name = client_name or self ._client_name
191
+ self ._headers = AbstractAPI ._merge_headers (initial_headers , headers )
154
192
155
- self .ssl_config = getattr ( abstract_api , ' ssl_config' , ssl_config or SSLConfig () )
193
+ self .ssl_config = ssl_config or SSLConfig ()
156
194
157
195
if not self .ssl_config .verify :
158
196
requests .packages .urllib3 .disable_warnings (requests .packages .urllib3 .exceptions .InsecureRequestWarning )
159
197
160
- self ._client_name = getattr (abstract_api , '_client_name' , client_name or self ._client_name )
161
-
162
- if abstract_api :
163
- self ._headers = getattr (abstract_api , '_headers' , None )
164
- else :
165
- self ._headers = {
166
- 'Content-Type' : 'application/json' ,
167
- 'Accept' : 'text/plain, application/json' ,
168
- 'User-Agent' : f"{ self ._client_name } { __version__ } "
169
- }
170
-
171
- if headers :
172
- self ._headers .update ({self ._capitalize_header (k ): v for k , v in headers .items ()})
173
-
174
- self ._max_tries = getattr (abstract_api , '_max_tries' , max_tries )
198
+ self ._proxies = proxies
199
+ self ._raise_exceptions = raise_exceptions
200
+ self ._timeout = timeout or self ._timeout
201
+ self ._log_communication_on_error = log_communication_on_error or False
202
+ self ._max_tries = max_tries
175
203
176
204
def _get_max_tries (self ):
177
205
return self ._max_tries
@@ -429,6 +457,22 @@ def _get_proxies(self) -> dict:
429
457
"""
430
458
return self ._proxies .copy () if self ._proxies else None
431
459
460
+ @staticmethod
461
+ def _merge_headers (headers : dict , override : dict ) -> dict :
462
+ """
463
+ Merge headers with override.
464
+
465
+ :param headers: Headers to merge into.
466
+ :param override: Dict of headers that override *headers*. If a header key is set to value None,
467
+ it will be removed from *headers*.
468
+ :return: The merged headers.
469
+ """
470
+ if isinstance (headers , dict ) and isinstance (override , dict ):
471
+ headers .update ({AbstractAPI ._capitalize_header (k ): v for k , v in override .items ()})
472
+ headers = {k : v for k , v in headers .items () if v is not None }
473
+
474
+ return headers
475
+
432
476
def _get_headers (self , override : dict = None ) -> dict :
433
477
"""
434
478
Create a header dict for requests. Uses abstract method *self._handle_token()*.
@@ -437,11 +481,8 @@ def _get_headers(self, override: dict = None) -> dict:
437
481
it will be removed from the headers.
438
482
:return: A dict containing header values for requests.
439
483
"""
440
- headers = self ._headers .copy ()
441
484
442
- if isinstance (override , dict ):
443
- headers .update ({self ._capitalize_header (k ): v for k , v in override .items ()})
444
- headers = {k : v for k , v in headers .items () if v is not None }
485
+ headers = AbstractAPI ._merge_headers (self ._headers .copy (), override )
445
486
446
487
token = self ._handle_token ()
447
488
if token :
@@ -672,15 +713,21 @@ class GraphConnectionHandler(AbstractAPI):
672
713
"""Default pool_maxsize for requests.adapters.HTTPAdapter."""
673
714
674
715
_pool_block = False
716
+ """As used by requests.adapters.HTTPAdapter."""
675
717
676
718
_version_info : dict = None
719
+ """Stores the result of /api/version"""
720
+
721
+ custom_endpoints : dict = None
722
+ """Override API endpoints."""
677
723
678
724
_lock : threading .RLock
679
725
"""Reentrant mutex for thread safety"""
680
726
681
727
def __init__ (self ,
682
728
root_url : str = None ,
683
729
custom_endpoints : dict = None ,
730
+ version_info : dict = None ,
684
731
pool_maxsize : int = None ,
685
732
pool_block : bool = None ,
686
733
connection_handler = None ,
@@ -709,6 +756,8 @@ def __init__(self,
709
756
:param root_url: Root url for HIRO, like https://core.arago.co.
710
757
:param custom_endpoints: Optional map of {name:endpoint_path, ...} that overrides or adds to the endpoints taken
711
758
from /api/version. Example see above.
759
+ :param version_info: Optional full dict of the JSON result received via /api/version. Setting this will use it
760
+ as the valid API version information and avoids the internal API-call altogether.
712
761
:param pool_maxsize: Size of a connection pool for a single connection. See requests.adapters.HTTPAdapter.
713
762
Default is 10. *pool_maxsize* is ignored when *session* is set.
714
763
:param pool_block: Block any connections that exceed the pool_maxsize. Default is False: Allow more connections,
@@ -720,20 +769,22 @@ def __init__(self,
720
769
"""
721
770
self ._lock = threading .RLock ()
722
771
723
- root_url = getattr (connection_handler , '_root_url' , root_url )
724
- session = getattr (connection_handler , '_session' , None )
725
-
726
- if not root_url :
727
- raise ValueError ("'root_url' must not be empty." )
772
+ if isinstance (connection_handler , GraphConnectionHandler ):
773
+ root_url = connection_handler ._root_url
774
+ session = connection_handler ._session
775
+ custom_endpoints = connection_handler .custom_endpoints
776
+ version_info = connection_handler ._version_info
777
+ else :
778
+ if not root_url :
779
+ raise ValueError ("'root_url' must not be empty." )
728
780
729
- if not session :
730
781
adapter = requests .adapters .HTTPAdapter (
731
782
pool_maxsize = pool_maxsize or self ._pool_maxsize ,
732
783
pool_connections = 1 ,
733
784
pool_block = pool_block or self ._pool_block
734
785
)
735
786
session = requests .Session ()
736
- session .mount (root_url , adapter )
787
+ session .mount (prefix = root_url , adapter = adapter )
737
788
738
789
super ().__init__ (
739
790
root_url = root_url ,
@@ -743,8 +794,8 @@ def __init__(self,
743
794
** kwargs
744
795
)
745
796
746
- self .custom_endpoints = getattr ( connection_handler , '_custom_endpoints' , custom_endpoints )
747
- self ._version_info = getattr ( connection_handler , '_version_info' , None )
797
+ self .custom_endpoints = custom_endpoints
798
+ self ._version_info = version_info
748
799
749
800
self .get_version ()
750
801
@@ -929,6 +980,7 @@ class FixedTokenApiHandler(AbstractTokenApiHandler):
929
980
"""
930
981
931
982
_token : str
983
+ """Stores the fixed token."""
932
984
933
985
def __init__ (self , token : str = None , * args , ** kwargs ):
934
986
"""
@@ -966,6 +1018,7 @@ class EnvironmentTokenApiHandler(AbstractTokenApiHandler):
966
1018
"""
967
1019
968
1020
_env_var : str
1021
+ """Stores the name of the environment variable."""
969
1022
970
1023
def __init__ (self , env_var : str = 'HIRO_TOKEN' , * args , ** kwargs ):
971
1024
"""
@@ -1123,6 +1176,7 @@ class PasswordAuthTokenApiHandler(AbstractTokenApiHandler):
1123
1176
_client_secret : str
1124
1177
1125
1178
_secure_logging : bool = True
1179
+ """Avoid logging of sensitive data."""
1126
1180
1127
1181
def __init__ (self ,
1128
1182
username : str = None ,
@@ -1299,7 +1353,10 @@ class AuthenticatedAPIHandler(AbstractAPI):
1299
1353
"""
1300
1354
1301
1355
_api_handler : AbstractTokenApiHandler
1356
+ """Stores the TokenApiHandler used for this API."""
1357
+
1302
1358
_api_name : str
1359
+ """Name of the API."""
1303
1360
1304
1361
def __init__ (self ,
1305
1362
api_handler : AbstractTokenApiHandler ,
0 commit comments