11import json
22import re
3+ import traceback
4+ from functools import wraps
35
46from urllib .parse import parse_qs , quote_plus , urljoin
5- from bs4 import BeautifulSoup
6- from requests .models import HTTPError
77
88from . import constants as C
99from . import exceptions
1010
11+ try :
12+ from bs4 import BeautifulSoup
13+ HAS_BS4 = True
14+ except ImportError :
15+ HAS_BS4 = False
16+ BS4_IMPORT_ERROR = traceback .format_exc ()
17+ BeautifulSoup = None
18+
19+ try :
20+ from requests .models import HTTPError
21+ HAS_REQUESTS = True
22+ except ImportError :
23+ HAS_REQUESTS = False
24+ REQUESTS_IMPORT_ERROR = traceback .format_exc ()
25+ HTTPError = None
26+
1127_GIGYA_SDK_BUILD_NUMBER = None
1228
1329
30+ def require_bs4 (func ):
31+ # A decorator to check for the 'beautifulsoup4' library before executing a function.
32+ @wraps (func )
33+ def wrapper (* args , ** kwargs ):
34+ if not HAS_BS4 :
35+ raise exceptions .SapLaunchpadError (f"The 'beautifulsoup4' library is required. Error: { BS4_IMPORT_ERROR } " )
36+ return func (* args , ** kwargs )
37+ return wrapper
38+
39+
40+ def require_requests (func ):
41+ # A decorator to check for the 'requests' library before executing a function.
42+ @wraps (func )
43+ def wrapper (* args , ** kwargs ):
44+ if not HAS_REQUESTS :
45+ raise exceptions .SapLaunchpadError (f"The 'requests' library is required. Error: { REQUESTS_IMPORT_ERROR } " )
46+ return func (* args , ** kwargs )
47+ return wrapper
48+
49+
50+ @require_requests
51+ @require_bs4
1452def login (client , username , password ):
1553 # Main authentication function.
1654 #
@@ -69,6 +107,8 @@ def login(client, username, password):
69107 client .post (endpoint , data = meta , headers = C .GIGYA_HEADERS )
70108
71109
110+ @require_requests
111+ @require_bs4
72112def get_sso_endpoint_meta (client , url , ** kwargs ):
73113 # Scrapes an HTML page to find the next SSO form action URL and its input fields.
74114 method = 'POST' if kwargs .get ('data' ) or kwargs .get ('json' ) else 'GET'
@@ -100,6 +140,7 @@ def get_sso_endpoint_meta(client, url, **kwargs):
100140 return (endpoint , metadata )
101141
102142
143+ @require_requests
103144def _get_gigya_login_params (client , url , data ):
104145 # Follows a redirect and extracts parameters from the resulting URL's query string.
105146 gigya_idp_res = client .post (url , data = data )
@@ -109,9 +150,10 @@ def _get_gigya_login_params(client, url, data):
109150 return params
110151
111152
153+ @require_requests
112154def _gigya_websdk_bootstrap (client , params ):
113155 # Performs the initial bootstrap call to the Gigya WebSDK.
114- page_url = f'{ C .URL_ACCOUNT_SAML_PROXY } ?apiKey=' + params ['apiKey' ],
156+ page_url = f'{ C .URL_ACCOUNT_SAML_PROXY } ?apiKey=' + params ['apiKey' ]
115157 params .update ({
116158 'pageURL' : page_url ,
117159 'sdk' : 'js_latest' ,
@@ -124,6 +166,7 @@ def _gigya_websdk_bootstrap(client, params):
124166 headers = C .GIGYA_HEADERS )
125167
126168
169+ @require_requests
127170def _gigya_login (client , username , password , api_key ):
128171 # Performs a login using the standard Gigya accounts.login API.
129172 # This avoids a custom SAP endpoint that triggers password change notifications.
@@ -154,6 +197,7 @@ def _gigya_login(client, username, password, api_key):
154197 return login_response .get ('login_token' )
155198
156199
200+ @require_requests
157201def _get_id_token (client , saml_params , login_token ):
158202 # Exchanges a Gigya login token for a JWT ID token.
159203 query_params = {
@@ -166,6 +210,7 @@ def _get_id_token(client, saml_params, login_token):
166210 return token
167211
168212
213+ @require_requests
169214def _get_uid (client , saml_params , login_token ):
170215 # Retrieves the user's unique ID (UID) using the login token.
171216 query_params = {
@@ -177,6 +222,7 @@ def _get_uid(client, saml_params, login_token):
177222 return uid
178223
179224
225+ @require_requests
180226def _get_uid_details (client , uid , id_token ):
181227 # Fetches detailed account information for a given UID.
182228 url = f'{ C .URL_ACCOUNT_CORE_API } /accounts/{ uid } '
@@ -187,16 +233,18 @@ def _get_uid_details(client, uid, id_token):
187233 return uid_details_response
188234
189235
236+ @require_requests
190237def _is_uid_linked_multiple_sids (uid_details ):
191238 # Checks if a Universal ID (UID) is linked to more than one S-User ID.
192239 accounts = uid_details ['accounts' ]
193240 linked = []
194- for _ , v in accounts .items ():
241+ for _account_type , v in accounts .items ():
195242 linked .extend (v ['linkedAccounts' ])
196243
197244 return len (linked ) > 1
198245
199246
247+ @require_requests
200248def _select_account (client , uid , sid , id_token ):
201249 # Selects a specific S-User ID when a Universal ID is linked to multiple accounts.
202250 url = f'{ C .URL_ACCOUNT_CORE_API } /accounts/{ uid } /selectedAccount'
@@ -207,6 +255,7 @@ def _select_account(client, uid, sid, id_token):
207255 return client .request ('PUT' , url , headers = headers , json = data )
208256
209257
258+ @require_requests
210259def _get_sdk_build_number (client , api_key ):
211260 # Fetches the gigya.js file to extract and cache the SDK build number.
212261 global _GIGYA_SDK_BUILD_NUMBER
@@ -224,6 +273,7 @@ def _get_sdk_build_number(client, api_key):
224273 return build_number
225274
226275
276+ @require_requests
227277def _cdc_api_request (client , endpoint , saml_params , query_params ):
228278 # Helper to make requests to the Gigya/CDC API, handling common parameters and errors.
229279 url = '/' .join ((C .URL_ACCOUNT_CDC_API , endpoint ))
0 commit comments