1- # SPDX-FileCopyrightText: (C) 2023 - 2025 Intel Corporation
1+ # SPDX-FileCopyrightText: (C) 2023 - 2026 Intel Corporation
22# SPDX-License-Identifier: Apache-2.0
33
44import os
55import json
66import re
77import requests
8- import sys
98from http import HTTPStatus
109from urllib .parse import urljoin
1110
11+
1212class RESTResult (dict ):
1313 def __init__ (self , statusCode , errors = None ):
1414 super ().__init__ ()
1515 self .statusCode = statusCode
1616 self .errors = errors
1717 return
1818
19+ @property
20+ def status_code (self ):
21+ return self .statusCode
22+
23+ def json (self ):
24+ return dict (self )
25+
26+ @property
27+ def text (self ):
28+ return json .dumps (dict (self ))
29+
30+
1931class RESTClient :
20- def __init__ (self , url , rootcert = None , auth = None ):
32+ def __init__ (self , url = None , token = None , auth = None ,
33+ rootcert = None , verify_ssl = False , timeout = 10 ):
2134 self .url = url
22- self . rootcert = rootcert
23- if not self .url .endswith ("/" ):
35+
36+ if self . url and not self .url .endswith ("/" ):
2437 self .url = self .url + "/"
38+
39+ # Handle SSL verification (support both bool and path)
40+ self .verify_ssl = verify_ssl if verify_ssl is not False else False
41+ if rootcert :
42+ self .verify_ssl = rootcert
43+
44+ self .timeout = timeout
2545 self .session = requests .session ()
26- if auth :
46+
47+ # If token provided directly, use it (skip authentication)
48+ if token :
49+ self .token = token
50+ elif auth :
2751 self ._parseAuth (auth )
2852 return
2953
@@ -46,10 +70,10 @@ def _parseAuth(self, auth):
4670 res = self .authenticate (user , pw )
4771 if not res :
4872 error_message = (
49- f"Failed to authenticate\n "
50- f" URL: { self .url } \n "
51- f" status: { res .statusCode } \n "
52- f" errors: { res .errors } "
73+ f"Failed to authenticate\n "
74+ f" URL: { self .url } \n "
75+ f" status: { res .statusCode } \n "
76+ f" errors: { res .errors } "
5377 )
5478 raise RuntimeError (error_message )
5579 return
@@ -58,6 +82,38 @@ def _parseAuth(self, auth):
5882 def isAuthenticated (self ):
5983 return hasattr (self , 'token' ) and self .token is not None
6084
85+ def _headers (self ):
86+ headers = {
87+ "Content-Type" : "application/json"
88+ }
89+ if hasattr (self , 'token' ) and self .token :
90+ headers ["Authorization" ] = f"Token { self .token } "
91+ return headers
92+
93+ def request (self , method , path , ** kwargs ):
94+ """
95+ Returns raw requests.Response object for compatibility with API tests
96+ """
97+ # Ensure path starts with /
98+ if not path .startswith ('/' ):
99+ path = '/' + path
100+
101+ url = urljoin (self .url , path .lstrip ('/' ))
102+
103+ # Merge headers
104+ headers = self ._headers ()
105+ if 'headers' in kwargs :
106+ headers .update (kwargs .pop ('headers' ))
107+
108+ return self .session .request (
109+ method = method ,
110+ url = url ,
111+ headers = headers ,
112+ verify = self .verify_ssl ,
113+ timeout = self .timeout ,
114+ ** kwargs
115+ )
116+
61117 def decodeReply (self , reply , expectedStatus , successContent = None ):
62118 result = RESTResult (statusCode = reply .status_code )
63119 decoded = False
@@ -69,10 +125,11 @@ def decodeReply(self, reply, expectedStatus, successContent=None):
69125 content = reply .content
70126 else :
71127 content = {
72- 'data' : reply .content ,
128+ 'data' : reply .content ,
73129 }
74130 if 'Content-Disposition' in reply .headers :
75- fname = re .findall ("filename=(.+)" , reply .headers ['Content-Disposition' ])[0 ]
131+ fname = re .findall ("filename=(.+)" ,
132+ reply .headers ['Content-Disposition' ])[0 ]
76133 content ['filename' ] = fname
77134 decoded = True
78135
@@ -99,11 +156,15 @@ def authenticate(self, user, password):
99156 auth_url = urljoin (self .url , "auth" )
100157 try :
101158 reply = self .session .post (auth_url , data = {'username' : user , 'password' : password },
102- verify = self .rootcert )
159+ verify = self .verify_ssl )
103160 except requests .exceptions .ConnectionError as err :
104- result = RESTResult ("ConnectionError" , errors = ("Connection error" , str (err )))
161+ result = RESTResult (
162+ "ConnectionError" , errors = (
163+ "Connection error" , str (err )))
105164 else :
106- result = self .decodeReply (reply , HTTPStatus .OK , successContent = {'authenticated' : True })
165+ result = self .decodeReply (
166+ reply , HTTPStatus .OK , successContent = {
167+ 'authenticated' : True })
107168 if reply .status_code == HTTPStatus .OK :
108169 data = json .loads (reply .content )
109170 self .token = data ['token' ]
@@ -120,7 +181,8 @@ def prepareDataArgs(self, data, files):
120181 if not files :
121182 data_args = {'json' : data }
122183 elif self .dataIsNested (data ):
123- raise ValueError ("requests library can't combine files and nested dictionaries" )
184+ raise ValueError (
185+ "requests library can't combine files and nested dictionaries" )
124186 return data_args
125187
126188 def _create (self , endpoint , data , files = None ):
@@ -137,7 +199,7 @@ def _create(self, endpoint, data, files=None):
137199 headers = {'Authorization' : f"Token { self .token } " }
138200 data_args = self .prepareDataArgs (data , files )
139201 reply = self .session .post (full_path , ** data_args , files = files ,
140- headers = headers , verify = self .rootcert )
202+ headers = headers , verify = self .verify_ssl )
141203 return self .decodeReply (reply , HTTPStatus .CREATED )
142204
143205 def _get (self , endpoint , parameters ):
@@ -152,7 +214,7 @@ def _get(self, endpoint, parameters):
152214 full_path = urljoin (self .url , endpoint )
153215 headers = {'Authorization' : f"Token { self .token } " }
154216 reply = self .session .get (full_path , params = parameters , headers = headers ,
155- verify = self .rootcert )
217+ verify = self .verify_ssl )
156218 return self .decodeReply (reply , HTTPStatus .OK )
157219
158220 def _update (self , endpoint , data , files = None ):
@@ -169,7 +231,7 @@ def _update(self, endpoint, data, files=None):
169231 headers = {'Authorization' : f"Token { self .token } " }
170232 data_args = self .prepareDataArgs (data , files )
171233 reply = self .session .post (full_path , ** data_args , files = files ,
172- headers = headers , verify = self .rootcert )
234+ headers = headers , verify = self .verify_ssl )
173235 return self .decodeReply (reply , HTTPStatus .OK )
174236
175237 def _delete (self , endpoint ):
@@ -181,7 +243,10 @@ def _delete(self, endpoint):
181243 """
182244 full_path = urljoin (self .url , endpoint )
183245 headers = {'Authorization' : f"Token { self .token } " }
184- reply = self .session .delete (full_path , headers = headers , verify = self .rootcert )
246+ reply = self .session .delete (
247+ full_path ,
248+ headers = headers ,
249+ verify = self .verify_ssl )
185250 return self .decodeReply (reply , HTTPStatus .OK )
186251
187252 def _separateFiles (self , data , fields ):
0 commit comments