pyxecm 1.6__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +7 -4
- pyxecm/avts.py +727 -254
- pyxecm/coreshare.py +686 -467
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +163 -0
- pyxecm/customizer/api/auth/__init__.py +1 -0
- pyxecm/customizer/api/auth/functions.py +92 -0
- pyxecm/customizer/api/auth/models.py +13 -0
- pyxecm/customizer/api/auth/router.py +78 -0
- pyxecm/customizer/api/common/__init__.py +1 -0
- pyxecm/customizer/api/common/functions.py +47 -0
- pyxecm/customizer/api/common/metrics.py +92 -0
- pyxecm/customizer/api/common/models.py +21 -0
- pyxecm/customizer/api/common/payload_list.py +870 -0
- pyxecm/customizer/api/common/router.py +72 -0
- pyxecm/customizer/api/settings.py +128 -0
- pyxecm/customizer/api/terminal/__init__.py +1 -0
- pyxecm/customizer/api/terminal/router.py +87 -0
- pyxecm/customizer/api/v1_csai/__init__.py +1 -0
- pyxecm/customizer/api/v1_csai/router.py +87 -0
- pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
- pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
- pyxecm/customizer/api/v1_maintenance/models.py +12 -0
- pyxecm/customizer/api/v1_maintenance/router.py +76 -0
- pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
- pyxecm/customizer/api/v1_otcs/functions.py +61 -0
- pyxecm/customizer/api/v1_otcs/router.py +179 -0
- pyxecm/customizer/api/v1_payload/__init__.py +1 -0
- pyxecm/customizer/api/v1_payload/functions.py +179 -0
- pyxecm/customizer/api/v1_payload/models.py +51 -0
- pyxecm/customizer/api/v1_payload/router.py +499 -0
- pyxecm/customizer/browser_automation.py +721 -286
- pyxecm/customizer/customizer.py +1076 -1425
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +1186 -0
- pyxecm/customizer/k8s.py +901 -379
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +2967 -920
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +18228 -7820
- pyxecm/customizer/pht.py +717 -286
- pyxecm/customizer/salesforce.py +516 -342
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +611 -372
- pyxecm/customizer/settings.py +445 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +83 -43
- pyxecm/helper/data.py +2406 -870
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +596 -171
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +235 -141
- pyxecm/otawp.py +2668 -1220
- pyxecm/otca.py +569 -0
- pyxecm/otcs.py +7956 -3237
- pyxecm/otds.py +2178 -925
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1272 -325
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.1.dist-info/METADATA +122 -0
- pyxecm-2.0.1.dist-info/RECORD +76 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
- pyxecm-1.6.dist-info/METADATA +0 -53
- pyxecm-1.6.dist-info/RECORD +0 -32
- {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,39 +1,12 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
https://
|
|
6
|
-
https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/d599f15995d348a1b45ba5603e2aba9b/78b1d8aac783455684a7de7a8a5b0c04.html
|
|
7
|
-
|
|
8
|
-
Class: SuccessFactors
|
|
9
|
-
Methods:
|
|
10
|
-
|
|
11
|
-
__init__ : class initializer
|
|
12
|
-
config : Returns config data set
|
|
13
|
-
credentials: Returns the token data
|
|
14
|
-
idp_data: Return the IDP data used to request the SAML assertion
|
|
15
|
-
request_header: Returns the request header for SuccessFactors API calls
|
|
16
|
-
parse_request_response: Parse the REST API responses and convert
|
|
17
|
-
them to Python dict in a safe way
|
|
18
|
-
exist_result_item: Check if an dict item is in the response
|
|
19
|
-
of the SuccessFactors API call
|
|
20
|
-
get_result_value: Check if a defined value (based on a key) is in the SuccessFactors API response
|
|
21
|
-
|
|
22
|
-
get_saml_assertion: Get SAML Assertion for SuccessFactors authentication
|
|
23
|
-
authenticate : Authenticates at SuccessFactors API
|
|
24
|
-
|
|
25
|
-
get_country: Get information for a Country / Countries
|
|
26
|
-
get_user: Get a SuccessFactors user based on its ID.
|
|
27
|
-
get_user_account: Get information for a SuccessFactors User Account
|
|
28
|
-
update_user: Update user data. E.g. update the user password or email.
|
|
29
|
-
get_employee: Get a list of employee(s) matching given criterias.
|
|
30
|
-
get_entities_metadata: Get the schema (metadata) for a list of entities
|
|
31
|
-
(list can be empty to get it for all)
|
|
32
|
-
get_entity_metadata: Get the schema (metadata) for an entity
|
|
1
|
+
"""SuccessFactors Module to interact with the SuccessFactors API.
|
|
2
|
+
|
|
3
|
+
See:
|
|
4
|
+
https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/how-to-initiate-an-oauth-connection-to-successfactors-employee-central/ba-p/13332388
|
|
5
|
+
https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/d599f15995d348a1b45ba5603e2aba9b/78b1d8aac783455684a7de7a8a5b0c04.html
|
|
33
6
|
"""
|
|
34
7
|
|
|
35
8
|
__author__ = "Dr. Marc Diefenbruch"
|
|
36
|
-
__copyright__ = "Copyright 2024, OpenText"
|
|
9
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
37
10
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
38
11
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
39
12
|
__email__ = "mdiefenb@opentext.com"
|
|
@@ -42,11 +15,11 @@ import json
|
|
|
42
15
|
import logging
|
|
43
16
|
import time
|
|
44
17
|
import urllib.parse
|
|
45
|
-
import requests
|
|
46
18
|
|
|
19
|
+
import requests
|
|
47
20
|
import xmltodict
|
|
48
21
|
|
|
49
|
-
|
|
22
|
+
default_logger = logging.getLogger("pyxecm.customizer.sucessfactors")
|
|
50
23
|
|
|
51
24
|
request_login_headers = {
|
|
52
25
|
"Content-Type": "application/x-www-form-urlencoded", # "application/json",
|
|
@@ -57,8 +30,11 @@ REQUEST_TIMEOUT = 60
|
|
|
57
30
|
REQUEST_MAX_RETRIES = 5
|
|
58
31
|
REQUEST_RETRY_DELAY = 60
|
|
59
32
|
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
|
|
34
|
+
class SuccessFactors:
|
|
35
|
+
"""Class SuccessFactors is used to retrieve and automate stettings in SuccessFactors."""
|
|
36
|
+
|
|
37
|
+
logger: logging.Logger = default_logger
|
|
62
38
|
|
|
63
39
|
_config: dict
|
|
64
40
|
_access_token = None
|
|
@@ -74,35 +50,49 @@ class SuccessFactors(object):
|
|
|
74
50
|
password: str = "",
|
|
75
51
|
company_id: str = "",
|
|
76
52
|
authorization_url: str = "",
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
logger: logging.Logger = default_logger,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize the SuccessFactors object.
|
|
79
56
|
|
|
80
57
|
Args:
|
|
81
|
-
base_url (str):
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
58
|
+
base_url (str):
|
|
59
|
+
The base URL of the SuccessFactors tenant.
|
|
60
|
+
as_url (str):
|
|
61
|
+
The Application Service URL of the SuccessFactors tenant.
|
|
62
|
+
client_id (str):
|
|
63
|
+
The SuccessFactors Client ID.
|
|
64
|
+
client_secret (str):
|
|
65
|
+
The SuccessFactors Client Secret.
|
|
66
|
+
username (str):
|
|
67
|
+
The user name in SuccessFactors.
|
|
68
|
+
password (str):
|
|
69
|
+
The password of the SuccessFactors user.
|
|
70
|
+
company_id (str):
|
|
71
|
+
The SuccessFactors company ID.
|
|
72
|
+
authorization_url (str, optional):
|
|
73
|
+
URL for SuccessFactors login.
|
|
74
|
+
If not given it will be constructed with default values using base_url.
|
|
75
|
+
logger (logging.Logger, optional):
|
|
76
|
+
The logging object to use for all log messages. Defaults to default_logger.
|
|
77
|
+
|
|
89
78
|
"""
|
|
79
|
+
if logger != default_logger:
|
|
80
|
+
self.logger = logger.getChild("successfactors")
|
|
81
|
+
for logfilter in logger.filters:
|
|
82
|
+
self.logger.addFilter(logfilter)
|
|
90
83
|
|
|
91
84
|
successfactors_config = {}
|
|
92
85
|
|
|
93
86
|
# this class assumes that the base URL is provided without
|
|
94
87
|
# a trailing "/". Otherwise the trailing slash is removed.
|
|
95
|
-
|
|
96
|
-
base_url = base_url[:-1]
|
|
88
|
+
base_url = base_url.removesuffix("/")
|
|
97
89
|
|
|
98
90
|
# Set the authentication endpoints and credentials
|
|
99
91
|
successfactors_config["baseUrl"] = base_url
|
|
100
92
|
successfactors_config["asUrl"] = as_url
|
|
101
93
|
successfactors_config["clientId"] = client_id
|
|
102
94
|
successfactors_config["clientSecret"] = client_secret
|
|
103
|
-
successfactors_config["username"] = username.split("@")[
|
|
104
|
-
0
|
|
105
|
-
] # we don't want the company ID in the user name
|
|
95
|
+
successfactors_config["username"] = username.split("@")[0] # we don't want the company ID in the user name
|
|
106
96
|
successfactors_config["password"] = password
|
|
107
97
|
if company_id:
|
|
108
98
|
successfactors_config["companyId"] = company_id
|
|
@@ -114,13 +104,9 @@ class SuccessFactors(object):
|
|
|
114
104
|
if authorization_url:
|
|
115
105
|
successfactors_config["authenticationUrl"] = authorization_url
|
|
116
106
|
else:
|
|
117
|
-
successfactors_config["authenticationUrl"] =
|
|
118
|
-
successfactors_config["baseUrl"] + "/oauth/token"
|
|
119
|
-
)
|
|
107
|
+
successfactors_config["authenticationUrl"] = successfactors_config["baseUrl"] + "/oauth/token"
|
|
120
108
|
|
|
121
|
-
successfactors_config["idpUrl"] =
|
|
122
|
-
successfactors_config["baseUrl"] + "/oauth/idp"
|
|
123
|
-
)
|
|
109
|
+
successfactors_config["idpUrl"] = successfactors_config["baseUrl"] + "/oauth/idp"
|
|
124
110
|
|
|
125
111
|
if not username:
|
|
126
112
|
# Set the data for the token request
|
|
@@ -128,13 +114,10 @@ class SuccessFactors(object):
|
|
|
128
114
|
"grant_type": "client_credentials",
|
|
129
115
|
"client_id": client_id,
|
|
130
116
|
"client_secret": client_secret,
|
|
131
|
-
# "username": successfactors_config["username"],
|
|
132
|
-
# "password": password,
|
|
133
117
|
}
|
|
134
118
|
else:
|
|
135
119
|
# Set the data for the token request
|
|
136
120
|
successfactors_config["authenticationData"] = {
|
|
137
|
-
# "grant_type": "password",
|
|
138
121
|
"grant_type": "urn:ietf:params:oauth:grant-type:saml2-bearer",
|
|
139
122
|
"company_id": successfactors_config["companyId"],
|
|
140
123
|
"username": successfactors_config["username"],
|
|
@@ -146,7 +129,6 @@ class SuccessFactors(object):
|
|
|
146
129
|
successfactors_config["idpData"] = {
|
|
147
130
|
"client_id": client_id,
|
|
148
131
|
"user_id": successfactors_config["username"],
|
|
149
|
-
# "use_email": True,
|
|
150
132
|
"token_url": successfactors_config["authenticationUrl"],
|
|
151
133
|
"private_key": client_secret,
|
|
152
134
|
}
|
|
@@ -156,43 +138,54 @@ class SuccessFactors(object):
|
|
|
156
138
|
# end method definition
|
|
157
139
|
|
|
158
140
|
def config(self) -> dict:
|
|
159
|
-
"""
|
|
141
|
+
"""Return the configuration dictionary.
|
|
160
142
|
|
|
161
143
|
Returns:
|
|
162
|
-
dict:
|
|
144
|
+
dict:
|
|
145
|
+
The configuration dictionary.
|
|
146
|
+
|
|
163
147
|
"""
|
|
164
148
|
return self._config
|
|
165
149
|
|
|
166
150
|
# end method definition
|
|
167
151
|
|
|
168
152
|
def credentials(self) -> dict:
|
|
169
|
-
"""Return the login credentials
|
|
153
|
+
"""Return the login credentials.
|
|
170
154
|
|
|
171
155
|
Returns:
|
|
172
|
-
dict:
|
|
156
|
+
dict:
|
|
157
|
+
A dictionary with login credentials for SuccessFactors.
|
|
158
|
+
|
|
173
159
|
"""
|
|
174
160
|
return self.config()["authenticationData"]
|
|
175
161
|
|
|
176
162
|
# end method definition
|
|
177
163
|
|
|
178
164
|
def idp_data(self) -> dict:
|
|
179
|
-
"""Return the IDP data used to request the SAML assertion
|
|
165
|
+
"""Return the IDP data used to request the SAML assertion.
|
|
180
166
|
|
|
181
167
|
Returns:
|
|
182
|
-
dict:
|
|
168
|
+
dict:
|
|
169
|
+
A dictionary with IDP data for SuccessFactors.
|
|
170
|
+
|
|
183
171
|
"""
|
|
184
172
|
return self.config()["idpData"]
|
|
185
173
|
|
|
186
174
|
# end method definition
|
|
187
175
|
|
|
188
176
|
def request_header(self, content_type: str = "application/json") -> dict:
|
|
189
|
-
"""
|
|
190
|
-
|
|
177
|
+
"""Return the request header used for Application calls.
|
|
178
|
+
|
|
179
|
+
Consists of Bearer access token and Content Type
|
|
191
180
|
|
|
192
181
|
Args:
|
|
193
|
-
content_type (str, optional):
|
|
194
|
-
|
|
195
|
-
|
|
182
|
+
content_type (str, optional):
|
|
183
|
+
The content type for the request. Defaults to "application/json".
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
dict:
|
|
187
|
+
The request header values.
|
|
188
|
+
|
|
196
189
|
"""
|
|
197
190
|
|
|
198
191
|
request_header = {
|
|
@@ -210,43 +203,47 @@ class SuccessFactors(object):
|
|
|
210
203
|
additional_error_message: str = "",
|
|
211
204
|
show_error: bool = True,
|
|
212
205
|
) -> dict | None:
|
|
213
|
-
"""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
206
|
+
"""Convert the request response (JSon) to a Python dict in a safe way.
|
|
207
|
+
|
|
208
|
+
It also handles exceptions. It first tries to load the response.text
|
|
209
|
+
via json.loads() that produces a dict output. Only if response.text is
|
|
210
|
+
not set or is empty it just converts the response_object to a dict using
|
|
211
|
+
the vars() built-in method.
|
|
218
212
|
|
|
219
213
|
Args:
|
|
220
|
-
response_object (object):
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
214
|
+
response_object (object):
|
|
215
|
+
This is reponse object delivered by the request call.
|
|
216
|
+
additional_error_message (str, optional):
|
|
217
|
+
Provide a more specific error message in case of an error.
|
|
218
|
+
show_error (bool):
|
|
219
|
+
True: write an error to the log file
|
|
220
|
+
False: write a warning to the log file
|
|
221
|
+
|
|
225
222
|
Returns:
|
|
226
|
-
dict:
|
|
223
|
+
dict:
|
|
224
|
+
The response information or None in case of an error.
|
|
225
|
+
|
|
227
226
|
"""
|
|
228
227
|
|
|
229
228
|
if not response_object:
|
|
230
229
|
return None
|
|
231
230
|
|
|
232
231
|
try:
|
|
233
|
-
if response_object.text
|
|
234
|
-
dict_object = json.loads(response_object.text)
|
|
235
|
-
else:
|
|
236
|
-
dict_object = vars(response_object)
|
|
232
|
+
dict_object = json.loads(response_object.text) if response_object.text else vars(response_object)
|
|
237
233
|
except json.JSONDecodeError as exception:
|
|
238
234
|
if additional_error_message:
|
|
239
235
|
message = "Cannot decode response as JSon. {}; error -> {}".format(
|
|
240
|
-
additional_error_message,
|
|
236
|
+
additional_error_message,
|
|
237
|
+
exception,
|
|
241
238
|
)
|
|
242
239
|
else:
|
|
243
240
|
message = "Cannot decode response as JSon; error -> {}".format(
|
|
244
|
-
exception
|
|
241
|
+
exception,
|
|
245
242
|
)
|
|
246
243
|
if show_error:
|
|
247
|
-
logger.error(message)
|
|
244
|
+
self.logger.error(message)
|
|
248
245
|
else:
|
|
249
|
-
logger.warning(message)
|
|
246
|
+
self.logger.warning(message)
|
|
250
247
|
return None
|
|
251
248
|
else:
|
|
252
249
|
return dict_object
|
|
@@ -262,6 +259,7 @@ class SuccessFactors(object):
|
|
|
262
259
|
value (str): value to find in the item with the matching key
|
|
263
260
|
Returns:
|
|
264
261
|
bool: True if the value was found, False otherwise
|
|
262
|
+
|
|
265
263
|
"""
|
|
266
264
|
|
|
267
265
|
if not response:
|
|
@@ -269,12 +267,12 @@ class SuccessFactors(object):
|
|
|
269
267
|
|
|
270
268
|
if "d" in response:
|
|
271
269
|
data = response["d"]
|
|
272
|
-
if not
|
|
270
|
+
if key not in data:
|
|
273
271
|
return False
|
|
274
272
|
if value == data[key]:
|
|
275
273
|
return True
|
|
276
274
|
else:
|
|
277
|
-
if not
|
|
275
|
+
if key not in response:
|
|
278
276
|
return False
|
|
279
277
|
if value == response[key]:
|
|
280
278
|
return True
|
|
@@ -296,11 +294,13 @@ class SuccessFactors(object):
|
|
|
296
294
|
key (str): property name (key)
|
|
297
295
|
index (int, optional): Index to use (1st element has index 0).
|
|
298
296
|
Defaults to 0.
|
|
297
|
+
|
|
299
298
|
Returns:
|
|
300
299
|
str: value for the key, None otherwise
|
|
300
|
+
|
|
301
301
|
"""
|
|
302
302
|
|
|
303
|
-
if not response or
|
|
303
|
+
if not response or "d" not in response:
|
|
304
304
|
return None
|
|
305
305
|
|
|
306
306
|
data = response["d"]
|
|
@@ -313,24 +313,22 @@ class SuccessFactors(object):
|
|
|
313
313
|
return None
|
|
314
314
|
try:
|
|
315
315
|
value = results[index][key]
|
|
316
|
-
except IndexError
|
|
317
|
-
logger.error(
|
|
318
|
-
"Index error with index -> %s and key -> %s
|
|
316
|
+
except IndexError:
|
|
317
|
+
self.logger.error(
|
|
318
|
+
"Index error with index -> %s and key -> %s",
|
|
319
319
|
str(index),
|
|
320
320
|
key,
|
|
321
|
-
str(e),
|
|
322
321
|
)
|
|
323
322
|
return None
|
|
324
|
-
except KeyError
|
|
325
|
-
logger.error(
|
|
326
|
-
"Key error with index -> %s and key -> %s
|
|
323
|
+
except KeyError:
|
|
324
|
+
self.logger.error(
|
|
325
|
+
"Key error with index -> %s and key -> %s",
|
|
327
326
|
str(index),
|
|
328
327
|
key,
|
|
329
|
-
str(e),
|
|
330
328
|
)
|
|
331
329
|
return None
|
|
332
330
|
else: # simple response - try to find key in response directly:
|
|
333
|
-
if not
|
|
331
|
+
if key not in data:
|
|
334
332
|
return None
|
|
335
333
|
value = data[key]
|
|
336
334
|
|
|
@@ -345,13 +343,15 @@ class SuccessFactors(object):
|
|
|
345
343
|
None
|
|
346
344
|
Returns:
|
|
347
345
|
str: Assertion. Also stores access token in self._assertion. None in case of error
|
|
346
|
+
|
|
348
347
|
"""
|
|
349
348
|
|
|
350
349
|
request_url = self.config()["idpUrl"]
|
|
351
350
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
351
|
+
self.logger.debug(
|
|
352
|
+
"Requesting SuccessFactors SAML Assertion from -> %s",
|
|
353
|
+
request_url,
|
|
354
|
+
)
|
|
355
355
|
|
|
356
356
|
idp_post_body = self.config()["idpData"]
|
|
357
357
|
|
|
@@ -362,24 +362,22 @@ class SuccessFactors(object):
|
|
|
362
362
|
response = requests.post(
|
|
363
363
|
request_url,
|
|
364
364
|
data=idp_post_body,
|
|
365
|
-
# headers=request_header,
|
|
366
365
|
timeout=REQUEST_TIMEOUT,
|
|
367
366
|
)
|
|
368
|
-
except requests.exceptions.ConnectionError
|
|
369
|
-
logger.error(
|
|
370
|
-
"Unable to get SAML assertion from -> %s
|
|
367
|
+
except requests.exceptions.ConnectionError:
|
|
368
|
+
self.logger.error(
|
|
369
|
+
"Unable to get SAML assertion from -> %s",
|
|
371
370
|
self.config()["idpUrl"],
|
|
372
|
-
exception,
|
|
373
371
|
)
|
|
374
372
|
return None
|
|
375
373
|
|
|
376
374
|
if response.ok:
|
|
377
375
|
assertion = response.text
|
|
378
376
|
self._assertion = assertion
|
|
379
|
-
logger.debug("Assertion -> %s", self._assertion)
|
|
377
|
+
self.logger.debug("Assertion -> %s", self._assertion)
|
|
380
378
|
return assertion
|
|
381
379
|
|
|
382
|
-
logger.error(
|
|
380
|
+
self.logger.error(
|
|
383
381
|
"Failed to request an SuccessFactors SAML Assertion; error -> %s",
|
|
384
382
|
response.text,
|
|
385
383
|
)
|
|
@@ -391,10 +389,14 @@ class SuccessFactors(object):
|
|
|
391
389
|
"""Authenticate at SuccessFactors with client ID and client secret.
|
|
392
390
|
|
|
393
391
|
Args:
|
|
394
|
-
revalidate (bool, optional):
|
|
395
|
-
|
|
392
|
+
revalidate (bool, optional):
|
|
393
|
+
Determine if a re-athentication is enforced
|
|
394
|
+
(e.g. if session has timed out with 401 error).
|
|
395
|
+
|
|
396
396
|
Returns:
|
|
397
|
-
str
|
|
397
|
+
str | None:
|
|
398
|
+
Access token. Also stores access token in self._access_token. None in case of error
|
|
399
|
+
|
|
398
400
|
"""
|
|
399
401
|
|
|
400
402
|
if not self._assertion:
|
|
@@ -402,7 +404,7 @@ class SuccessFactors(object):
|
|
|
402
404
|
|
|
403
405
|
# Already authenticated and session still valid?
|
|
404
406
|
if self._access_token and not revalidate:
|
|
405
|
-
logger.debug(
|
|
407
|
+
self.logger.debug(
|
|
406
408
|
"Session still valid - return existing access token -> %s",
|
|
407
409
|
str(self._access_token),
|
|
408
410
|
)
|
|
@@ -410,9 +412,10 @@ class SuccessFactors(object):
|
|
|
410
412
|
|
|
411
413
|
request_url = self.config()["authenticationUrl"]
|
|
412
414
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
415
|
+
self.logger.debug(
|
|
416
|
+
"Requesting SuccessFactors Access Token from -> %s",
|
|
417
|
+
request_url,
|
|
418
|
+
)
|
|
416
419
|
|
|
417
420
|
authenticate_post_body = self.credentials()
|
|
418
421
|
authenticate_post_body["assertion"] = self._assertion
|
|
@@ -421,14 +424,14 @@ class SuccessFactors(object):
|
|
|
421
424
|
self._access_token = None
|
|
422
425
|
|
|
423
426
|
try:
|
|
427
|
+
# Don't use header here:
|
|
424
428
|
response = requests.post(
|
|
425
429
|
request_url,
|
|
426
430
|
data=authenticate_post_body,
|
|
427
|
-
# headers=request_header,
|
|
428
431
|
timeout=REQUEST_TIMEOUT,
|
|
429
432
|
)
|
|
430
433
|
except requests.exceptions.ConnectionError as exception:
|
|
431
|
-
logger.warning(
|
|
434
|
+
self.logger.warning(
|
|
432
435
|
"Unable to connect to -> %s : %s",
|
|
433
436
|
self.config()["authenticationUrl"],
|
|
434
437
|
exception,
|
|
@@ -437,13 +440,13 @@ class SuccessFactors(object):
|
|
|
437
440
|
|
|
438
441
|
if response.ok:
|
|
439
442
|
authenticate_dict = self.parse_request_response(response)
|
|
440
|
-
if not authenticate_dict or
|
|
443
|
+
if not authenticate_dict or "access_token" not in authenticate_dict:
|
|
441
444
|
return None
|
|
442
445
|
# Store authentication access_token:
|
|
443
446
|
self._access_token = authenticate_dict["access_token"]
|
|
444
|
-
logger.debug("Access Token -> %s", self._access_token)
|
|
447
|
+
self.logger.debug("Access Token -> %s", self._access_token)
|
|
445
448
|
else:
|
|
446
|
-
logger.error(
|
|
449
|
+
self.logger.error(
|
|
447
450
|
"Failed to request an SuccessFactors Access Token; error -> %s",
|
|
448
451
|
response.text,
|
|
449
452
|
)
|
|
@@ -454,59 +457,65 @@ class SuccessFactors(object):
|
|
|
454
457
|
# end method definition
|
|
455
458
|
|
|
456
459
|
def get_country(self, code: str = "") -> dict | None:
|
|
457
|
-
"""Get information for a
|
|
460
|
+
"""Get information for a country / countries.
|
|
458
461
|
|
|
459
462
|
Args:
|
|
460
|
-
code (str):
|
|
463
|
+
code (str):
|
|
464
|
+
3 character code for contry selection, like "USA"
|
|
461
465
|
|
|
462
466
|
Returns:
|
|
463
|
-
dict | None:
|
|
464
|
-
|
|
465
|
-
|
|
467
|
+
dict | None:
|
|
468
|
+
Country details
|
|
469
|
+
|
|
470
|
+
Example return data in "d" dictionary:
|
|
471
|
+
{
|
|
472
|
+
'__metadata': {
|
|
473
|
+
'uri': "https://apisalesdemo2.successfactors.eu/odata/v2/UserAccount('twalker')",
|
|
474
|
+
'type': 'SFOData.UserAccount'
|
|
475
|
+
},
|
|
476
|
+
'username': 'twalker',
|
|
477
|
+
'lastModifiedDateTime': '/Date(1692701804000+0000)/',
|
|
478
|
+
'accountUuid': '5c7390e0-d9d2-e348-1700-2b02b3a61aa5',
|
|
479
|
+
'createdDateTime': '/Date(1420745485000+0000)/',
|
|
480
|
+
'timeZone': 'US/Eastern',
|
|
481
|
+
'lastInactivationDateTime': None,
|
|
482
|
+
'accountIsLocked': 'FALSE',
|
|
483
|
+
'accountStatus': 'ACTIVE',
|
|
484
|
+
'defaultLocale': 'en_US',
|
|
485
|
+
'lastLoginFailedDateTime': None,
|
|
486
|
+
'accountId': '90',
|
|
487
|
+
'sapGlobalUserId': None,
|
|
488
|
+
'personIdExternal': '82094',
|
|
489
|
+
'userType': 'employee',
|
|
490
|
+
'email': 'twalker@m365x41497014.onmicrosoft.com',
|
|
491
|
+
'user': {'__deferred': {...}}
|
|
492
|
+
}
|
|
466
493
|
|
|
467
|
-
{
|
|
468
|
-
'__metadata': {
|
|
469
|
-
'uri': "https://apisalesdemo2.successfactors.eu/odata/v2/UserAccount('twalker')",
|
|
470
|
-
'type': 'SFOData.UserAccount'
|
|
471
|
-
},
|
|
472
|
-
'username': 'twalker',
|
|
473
|
-
'lastModifiedDateTime': '/Date(1692701804000+0000)/',
|
|
474
|
-
'accountUuid': '5c7390e0-d9d2-e348-1700-2b02b3a61aa5',
|
|
475
|
-
'createdDateTime': '/Date(1420745485000+0000)/',
|
|
476
|
-
'timeZone': 'US/Eastern',
|
|
477
|
-
'lastInactivationDateTime': None,
|
|
478
|
-
'accountIsLocked': 'FALSE',
|
|
479
|
-
'accountStatus': 'ACTIVE',
|
|
480
|
-
'defaultLocale': 'en_US',
|
|
481
|
-
'lastLoginFailedDateTime': None,
|
|
482
|
-
'accountId': '90',
|
|
483
|
-
'sapGlobalUserId': None,
|
|
484
|
-
'personIdExternal': '82094',
|
|
485
|
-
'userType': 'employee',
|
|
486
|
-
'email': 'twalker@m365x41497014.onmicrosoft.com',
|
|
487
|
-
'user': {'__deferred': {...}}
|
|
488
|
-
}
|
|
489
494
|
"""
|
|
490
495
|
|
|
491
496
|
if not self._access_token:
|
|
492
497
|
self.authenticate()
|
|
493
498
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
499
|
+
request_url = (
|
|
500
|
+
self.config()["asUrl"]
|
|
501
|
+
+ "Country(code='{}')".format(
|
|
502
|
+
code,
|
|
503
|
+
)
|
|
504
|
+
if code
|
|
505
|
+
else self.config()["asUrl"] + "Country"
|
|
506
|
+
)
|
|
500
507
|
|
|
501
508
|
request_header = self.request_header()
|
|
502
509
|
|
|
503
510
|
response = requests.get(
|
|
504
|
-
request_url,
|
|
511
|
+
request_url,
|
|
512
|
+
headers=request_header,
|
|
513
|
+
timeout=REQUEST_TIMEOUT,
|
|
505
514
|
)
|
|
506
515
|
if response.status_code == 200:
|
|
507
516
|
return self.parse_request_response(response)
|
|
508
517
|
else:
|
|
509
|
-
logger.error(
|
|
518
|
+
self.logger.error(
|
|
510
519
|
"Failed to retrieve country data; status -> %s; error -> %s",
|
|
511
520
|
response.status_code,
|
|
512
521
|
response.text,
|
|
@@ -523,13 +532,23 @@ class SuccessFactors(object):
|
|
|
523
532
|
field_operation: str = "eq",
|
|
524
533
|
max_results: int = 1,
|
|
525
534
|
) -> dict | None:
|
|
526
|
-
"""Get information for a
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
535
|
+
"""Get information for a SuccessFactors user account.
|
|
536
|
+
|
|
537
|
+
Inactive users are not returned by default. To query inactive users,
|
|
538
|
+
you can explicitly include the status in a $filter or use a key predicate.
|
|
539
|
+
If you want to query all users, use query option $filter=status in 't','f','T','F','e','d'.
|
|
530
540
|
|
|
531
541
|
Args:
|
|
532
|
-
user_id (str):
|
|
542
|
+
user_id (str):
|
|
543
|
+
The login name of the user (e.g. "twalker")
|
|
544
|
+
field_name (str):
|
|
545
|
+
The field name of the filter.
|
|
546
|
+
field_value (str):
|
|
547
|
+
The filter value to compare the field with.
|
|
548
|
+
field_operation (str):
|
|
549
|
+
The operation of the filter. Like "in".
|
|
550
|
+
max_results (int):
|
|
551
|
+
The maximum number of results to return. Default is 1.
|
|
533
552
|
|
|
534
553
|
Returns:
|
|
535
554
|
dict | None: User Account details
|
|
@@ -600,6 +619,7 @@ class SuccessFactors(object):
|
|
|
600
619
|
'salaryBudgetTotalRaisePercentage': None,
|
|
601
620
|
...
|
|
602
621
|
}
|
|
622
|
+
|
|
603
623
|
"""
|
|
604
624
|
|
|
605
625
|
if not self._access_token:
|
|
@@ -614,7 +634,9 @@ class SuccessFactors(object):
|
|
|
614
634
|
query = {}
|
|
615
635
|
if field_name and field_value:
|
|
616
636
|
query["$filter"] = "{} {} {}".format(
|
|
617
|
-
field_name,
|
|
637
|
+
field_name,
|
|
638
|
+
field_operation,
|
|
639
|
+
field_value,
|
|
618
640
|
)
|
|
619
641
|
if max_results > 0:
|
|
620
642
|
query["$top"] = max_results
|
|
@@ -625,12 +647,14 @@ class SuccessFactors(object):
|
|
|
625
647
|
request_header = self.request_header()
|
|
626
648
|
|
|
627
649
|
response = requests.get(
|
|
628
|
-
request_url,
|
|
650
|
+
request_url,
|
|
651
|
+
headers=request_header,
|
|
652
|
+
timeout=REQUEST_TIMEOUT,
|
|
629
653
|
)
|
|
630
654
|
if response.status_code == 200:
|
|
631
655
|
return self.parse_request_response(response)
|
|
632
656
|
else:
|
|
633
|
-
logger.error(
|
|
657
|
+
self.logger.error(
|
|
634
658
|
"Failed to retrieve user data; status -> %s; error -> %s",
|
|
635
659
|
response.status_code,
|
|
636
660
|
response.text,
|
|
@@ -640,41 +664,44 @@ class SuccessFactors(object):
|
|
|
640
664
|
# end method definition
|
|
641
665
|
|
|
642
666
|
def get_user_account(self, username: str) -> dict | None:
|
|
643
|
-
"""Get information for a SuccessFactors User Account
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
667
|
+
"""Get information for a SuccessFactors User Account.
|
|
668
|
+
|
|
669
|
+
Inactive users are not returned by default. To query inactive users,
|
|
670
|
+
you can explicitly include the status in a $filter or use a key predicate.
|
|
671
|
+
If you want to query all users, use query option $filter=status in 't','f','T','F','e','d'.
|
|
647
672
|
|
|
648
673
|
Args:
|
|
649
|
-
username (str):
|
|
674
|
+
username (str):
|
|
675
|
+
The login name of the user (e.g. "twalker").
|
|
650
676
|
|
|
651
677
|
Returns:
|
|
652
|
-
dict | None:
|
|
653
|
-
|
|
654
|
-
|
|
678
|
+
dict | None:
|
|
679
|
+
User Account details.
|
|
680
|
+
|
|
681
|
+
Example return data in "d" dictionary:
|
|
682
|
+
{
|
|
683
|
+
'__metadata': {
|
|
684
|
+
'uri': "https://apisalesdemo2.successfactors.eu/odata/v2/UserAccount('twalker')",
|
|
685
|
+
'type': 'SFOData.UserAccount'
|
|
686
|
+
},
|
|
687
|
+
'username': 'twalker',
|
|
688
|
+
'lastModifiedDateTime': '/Date(1692701804000+0000)/',
|
|
689
|
+
'accountUuid': '5c7390e0-d9d2-e348-1700-2b02b3a61aa5',
|
|
690
|
+
'createdDateTime': '/Date(1420745485000+0000)/',
|
|
691
|
+
'timeZone': 'US/Eastern',
|
|
692
|
+
'lastInactivationDateTime': None,
|
|
693
|
+
'accountIsLocked': 'FALSE',
|
|
694
|
+
'accountStatus': 'ACTIVE',
|
|
695
|
+
'defaultLocale': 'en_US',
|
|
696
|
+
'lastLoginFailedDateTime': None,
|
|
697
|
+
'accountId': '90',
|
|
698
|
+
'sapGlobalUserId': None,
|
|
699
|
+
'personIdExternal': '82094',
|
|
700
|
+
'userType': 'employee',
|
|
701
|
+
'email': 'twalker@m365x41497014.onmicrosoft.com',
|
|
702
|
+
'user': {'__deferred': {...}}
|
|
703
|
+
}
|
|
655
704
|
|
|
656
|
-
{
|
|
657
|
-
'__metadata': {
|
|
658
|
-
'uri': "https://apisalesdemo2.successfactors.eu/odata/v2/UserAccount('twalker')",
|
|
659
|
-
'type': 'SFOData.UserAccount'
|
|
660
|
-
},
|
|
661
|
-
'username': 'twalker',
|
|
662
|
-
'lastModifiedDateTime': '/Date(1692701804000+0000)/',
|
|
663
|
-
'accountUuid': '5c7390e0-d9d2-e348-1700-2b02b3a61aa5',
|
|
664
|
-
'createdDateTime': '/Date(1420745485000+0000)/',
|
|
665
|
-
'timeZone': 'US/Eastern',
|
|
666
|
-
'lastInactivationDateTime': None,
|
|
667
|
-
'accountIsLocked': 'FALSE',
|
|
668
|
-
'accountStatus': 'ACTIVE',
|
|
669
|
-
'defaultLocale': 'en_US',
|
|
670
|
-
'lastLoginFailedDateTime': None,
|
|
671
|
-
'accountId': '90',
|
|
672
|
-
'sapGlobalUserId': None,
|
|
673
|
-
'personIdExternal': '82094',
|
|
674
|
-
'userType': 'employee',
|
|
675
|
-
'email': 'twalker@m365x41497014.onmicrosoft.com',
|
|
676
|
-
'user': {'__deferred': {...}}
|
|
677
|
-
}
|
|
678
705
|
"""
|
|
679
706
|
|
|
680
707
|
if not self._access_token:
|
|
@@ -689,35 +716,34 @@ class SuccessFactors(object):
|
|
|
689
716
|
while True:
|
|
690
717
|
try:
|
|
691
718
|
response = requests.get(
|
|
692
|
-
request_url,
|
|
719
|
+
request_url,
|
|
720
|
+
headers=request_header,
|
|
721
|
+
timeout=REQUEST_TIMEOUT,
|
|
693
722
|
)
|
|
694
723
|
response.raise_for_status() # This will raise an HTTPError for bad responses
|
|
695
724
|
return self.parse_request_response(response)
|
|
696
|
-
except requests.exceptions.HTTPError
|
|
697
|
-
logger.error(
|
|
698
|
-
"Failed to retrieve user data from SuccessFactors; status -> %s
|
|
725
|
+
except requests.exceptions.HTTPError:
|
|
726
|
+
self.logger.error(
|
|
727
|
+
"Failed to retrieve user data from SuccessFactors; status -> %s",
|
|
699
728
|
response.status_code,
|
|
700
|
-
str(http_err),
|
|
701
729
|
)
|
|
702
730
|
except requests.exceptions.Timeout:
|
|
703
|
-
logger.warning(
|
|
731
|
+
self.logger.warning(
|
|
704
732
|
"Failed to retrieve user data from SuccessFactors. The request timed out.",
|
|
705
733
|
)
|
|
706
|
-
except requests.exceptions.ConnectionError
|
|
707
|
-
logger.error(
|
|
708
|
-
"Cannot connect to SuccessFactors to retrieve user data; status -> %s
|
|
734
|
+
except requests.exceptions.ConnectionError:
|
|
735
|
+
self.logger.error(
|
|
736
|
+
"Cannot connect to SuccessFactors to retrieve user data; status -> %s",
|
|
709
737
|
response.status_code,
|
|
710
|
-
str(conn_err),
|
|
711
738
|
)
|
|
712
|
-
except requests.exceptions.RequestException
|
|
713
|
-
logger.error(
|
|
714
|
-
"Failed to retrieve user data from SuccessFactors; status -> %s
|
|
739
|
+
except requests.exceptions.RequestException:
|
|
740
|
+
self.logger.error(
|
|
741
|
+
"Failed to retrieve user data from SuccessFactors; status -> %s",
|
|
715
742
|
response.status_code,
|
|
716
|
-
str(req_err),
|
|
717
743
|
)
|
|
718
744
|
retries += 1
|
|
719
745
|
if retries <= REQUEST_MAX_RETRIES:
|
|
720
|
-
logger.info("Retrying in %s seconds...", str(REQUEST_RETRY_DELAY))
|
|
746
|
+
self.logger.info("Retrying in %s seconds...", str(REQUEST_RETRY_DELAY))
|
|
721
747
|
time.sleep(retries * REQUEST_RETRY_DELAY)
|
|
722
748
|
else:
|
|
723
749
|
break
|
|
@@ -732,13 +758,19 @@ class SuccessFactors(object):
|
|
|
732
758
|
update_data: dict,
|
|
733
759
|
) -> dict:
|
|
734
760
|
"""Update user data. E.g. update the user password or email.
|
|
735
|
-
|
|
761
|
+
|
|
762
|
+
See: https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/d599f15995d348a1b45ba5603e2aba9b/47c39724e7654b99a6be2f71fce1c50b.html?locale=en-US
|
|
736
763
|
|
|
737
764
|
Args:
|
|
738
|
-
user_id (str):
|
|
739
|
-
|
|
765
|
+
user_id (str):
|
|
766
|
+
The ID of the user (e.g. 106020)
|
|
767
|
+
update_data (dict):
|
|
768
|
+
The data to update the user with.
|
|
769
|
+
|
|
740
770
|
Returns:
|
|
741
|
-
dict:
|
|
771
|
+
dict:
|
|
772
|
+
Request response or None if an error occured.
|
|
773
|
+
|
|
742
774
|
"""
|
|
743
775
|
|
|
744
776
|
if not self._access_token:
|
|
@@ -758,10 +790,10 @@ class SuccessFactors(object):
|
|
|
758
790
|
timeout=REQUEST_TIMEOUT,
|
|
759
791
|
)
|
|
760
792
|
if response.ok:
|
|
761
|
-
logger.debug("User with ID -> %s updated successfully.", user_id)
|
|
793
|
+
self.logger.debug("User with ID -> %s updated successfully.", user_id)
|
|
762
794
|
return self.parse_request_response(response)
|
|
763
795
|
else:
|
|
764
|
-
logger.error(
|
|
796
|
+
self.logger.error(
|
|
765
797
|
"Failed to update user with ID -> %s; status -> %s; error -> %s",
|
|
766
798
|
user_id,
|
|
767
799
|
response.status_code,
|
|
@@ -782,109 +814,116 @@ class SuccessFactors(object):
|
|
|
782
814
|
"""Get a list of employee(s) matching given criterias.
|
|
783
815
|
|
|
784
816
|
Args:
|
|
785
|
-
entity (str, optional):
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
817
|
+
entity (str, optional):
|
|
818
|
+
Entity type to query. Examples are "PerPerson" (default),
|
|
819
|
+
"PerPersonal", "PerEmail", "PersonKey", ...
|
|
820
|
+
field_name (str):
|
|
821
|
+
Field to search in. E.g. personIdExternal, firstName, lastName,
|
|
822
|
+
fullName, email, dateOfBirth, gender, nationality, maritalStatus,
|
|
823
|
+
employeeId.
|
|
824
|
+
field_value (str):
|
|
825
|
+
Value to match in the Field
|
|
826
|
+
field_operation (str):
|
|
827
|
+
The operation to apply for the filter.
|
|
828
|
+
max_results (int):
|
|
829
|
+
The maximum number of results to return. Default is 1.
|
|
791
830
|
|
|
792
831
|
Returns:
|
|
793
|
-
dict | None:
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
{
|
|
826
|
-
'
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
832
|
+
dict | None:
|
|
833
|
+
Dictionary with the SuccessFactors object data or None in case the request failed.
|
|
834
|
+
|
|
835
|
+
Example result values for "PerPerson" inside the "d" structure:
|
|
836
|
+
"results": [
|
|
837
|
+
{
|
|
838
|
+
'__metadata': {...},
|
|
839
|
+
'personIdExternal': '109031',
|
|
840
|
+
'lastModifiedDateTime': '/Date(1442346839000+0000)/',
|
|
841
|
+
'lastModifiedBy': 'admindlr',
|
|
842
|
+
'createdDateTime': '/Date(1442346265000+0000)/',
|
|
843
|
+
'dateOfBirth': '/Date(-501206400000)/',
|
|
844
|
+
'perPersonUuid': '0378B0E6F41444EBB90345B56D537D3D',
|
|
845
|
+
'createdOn': '/Date(1442353465000)/',
|
|
846
|
+
'lastModifiedOn': '/Date(1442354039000)/',
|
|
847
|
+
'countryOfBirth': 'RUS',
|
|
848
|
+
'createdBy': 'admindlr',
|
|
849
|
+
'regionOfBirth': None,
|
|
850
|
+
'personId': '771',
|
|
851
|
+
'personalInfoNav': {...},
|
|
852
|
+
'emergencyContactNav': {...},
|
|
853
|
+
'secondaryAssignmentsNav': {...},
|
|
854
|
+
'personEmpTerminationInfoNav': {...},
|
|
855
|
+
'phoneNav': {...},
|
|
856
|
+
'employmentNav': {...},
|
|
857
|
+
...
|
|
858
|
+
}
|
|
859
|
+
]
|
|
860
|
+
|
|
861
|
+
Example result values for "PerPersonal" inside the "d" structure:
|
|
862
|
+
"results": [
|
|
863
|
+
{
|
|
864
|
+
'__metadata': {
|
|
865
|
+
'uri': "https://apisalesdemo2.successfactors.eu/odata/v2/PerPersonal(personIdExternal='108729',startDate=datetime'2017-03-13T00:00:00')",
|
|
866
|
+
'type': 'SFOData.PerPersonal'
|
|
867
|
+
},
|
|
868
|
+
'personIdExternal': '108729',
|
|
869
|
+
'startDate': '/Date(1489363200000)/',
|
|
870
|
+
'lastModifiedDateTime': '/Date(1489442337000+0000)/',
|
|
871
|
+
'endDate': '/Date(253402214400000)/',
|
|
872
|
+
'createdDateTime': '/Date(1489442337000+0000)/',
|
|
873
|
+
'suffix': None,
|
|
874
|
+
'attachmentId': None,
|
|
875
|
+
'preferredName': 'Hillary',
|
|
876
|
+
'lastNameAlt1': None,
|
|
877
|
+
'firstName': 'Hillary',
|
|
878
|
+
'nationality': 'USA',
|
|
879
|
+
'salutation': '30085',
|
|
880
|
+
'maritalStatus': '10825',
|
|
881
|
+
'lastName': 'Lawson',
|
|
882
|
+
'gender': 'F',
|
|
883
|
+
'firstNameAlt1': None,
|
|
884
|
+
'createdOn': '/Date(1489445937000)/',
|
|
885
|
+
'middleNameAlt1': None,
|
|
886
|
+
'lastModifiedBy': '82094',
|
|
887
|
+
'lastModifiedOn': '/Date(1489445937000)/',
|
|
888
|
+
'createdBy': '82094',
|
|
889
|
+
'middleName': None,
|
|
890
|
+
'nativePreferredLang': '10249',
|
|
891
|
+
'localNavAUS': {'__deferred': {...}},
|
|
892
|
+
'localNavBGD': {'__deferred': {...}},
|
|
893
|
+
'localNavHKG': {'__deferred': {...}},
|
|
894
|
+
'localNavMYS': {'__deferred': {...}},
|
|
895
|
+
'localNavAUT': {'__deferred': {...}},
|
|
896
|
+
'localNavLKA': {'__deferred': {...}},
|
|
897
|
+
'localNavPOL': {'__deferred': {...}},
|
|
898
|
+
'localNavCZE': {'__deferred': {...}},
|
|
899
|
+
'localNavTWN': {'__deferred': {...}},
|
|
900
|
+
'localNavARE': {'__deferred': {...}},
|
|
901
|
+
'localNavARG': {'__deferred': {...}},
|
|
902
|
+
'localNavCAN': {'__deferred': {...}},
|
|
903
|
+
'localNavNOR': {'__deferred': {...}},
|
|
904
|
+
'localNavOMN': {'__deferred': {...}},
|
|
905
|
+
'localNavPER': {'__deferred': {...}},
|
|
906
|
+
'localNavSGP': {'__deferred': {...}},
|
|
907
|
+
'localNavVEN': {'__deferred': {...}},
|
|
908
|
+
'localNavZAF': {'__deferred': {...}},
|
|
909
|
+
'localNavCHL': {'__deferred': {...}},
|
|
910
|
+
'localNavCHE': {'__deferred': {...}},
|
|
911
|
+
'localNavDNK': {'__deferred': {...}},
|
|
912
|
+
'localNavGTM': {'__deferred': {...}},
|
|
913
|
+
'localNavNZL': {'__deferred': {...}},
|
|
914
|
+
'salutationNav': {'__deferred': {...}},
|
|
915
|
+
'localNavCHN': {'__deferred': {...}},
|
|
916
|
+
'localNavVNM': {'__deferred': {...}},
|
|
917
|
+
'localNavIDN': {'__deferred': {...}},
|
|
918
|
+
'localNavPRT': {'__deferred': {...}},
|
|
919
|
+
'localNavCOL': {'__deferred': {...}},
|
|
920
|
+
'localNavHUN': {'__deferred': {...}},
|
|
921
|
+
'localNavSWE': {'__deferred': {...}},
|
|
922
|
+
'localNavESP': {'__deferred': {...}},
|
|
923
|
+
'localNavUSA': {'__deferred': {...}},
|
|
924
|
+
'nativePreferredLangNav': {'__deferred': {...}},
|
|
925
|
+
'maritalStatusNav': {'__deferred': {...}}, ...}
|
|
926
|
+
|
|
888
927
|
"""
|
|
889
928
|
|
|
890
929
|
if not self._access_token:
|
|
@@ -894,7 +933,9 @@ class SuccessFactors(object):
|
|
|
894
933
|
query = {}
|
|
895
934
|
if field_name and field_value:
|
|
896
935
|
query["$filter"] = "{} {} {}".format(
|
|
897
|
-
field_name,
|
|
936
|
+
field_name,
|
|
937
|
+
field_operation,
|
|
938
|
+
field_value,
|
|
898
939
|
)
|
|
899
940
|
if max_results > 0:
|
|
900
941
|
query["$top"] = max_results
|
|
@@ -907,12 +948,14 @@ class SuccessFactors(object):
|
|
|
907
948
|
request_header = self.request_header()
|
|
908
949
|
|
|
909
950
|
response = requests.get(
|
|
910
|
-
request_url,
|
|
951
|
+
request_url,
|
|
952
|
+
headers=request_header,
|
|
953
|
+
timeout=REQUEST_TIMEOUT,
|
|
911
954
|
)
|
|
912
955
|
if response.status_code == 200:
|
|
913
956
|
return self.parse_request_response(response)
|
|
914
957
|
else:
|
|
915
|
-
logger.error(
|
|
958
|
+
self.logger.error(
|
|
916
959
|
"Failed to retrieve employee data; status -> %s; error -> %s",
|
|
917
960
|
response.status_code,
|
|
918
961
|
response.text,
|
|
@@ -922,16 +965,20 @@ class SuccessFactors(object):
|
|
|
922
965
|
# end method definition
|
|
923
966
|
|
|
924
967
|
def get_entities_metadata(self, entities: list | None = None) -> dict | None:
|
|
925
|
-
"""Get the schema (metadata) for a list of entities (list can be empty to get it for all)
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
968
|
+
"""Get the schema (metadata) for a list of entities (list can be empty to get it for all).
|
|
969
|
+
|
|
970
|
+
IMPORTANT: A metadata request using $metadata returns an XML serialization of the service,
|
|
971
|
+
including the entity data model (EDM) and the service operation descriptions.
|
|
972
|
+
The metadata response supports only application/xml type.
|
|
929
973
|
|
|
930
974
|
Args:
|
|
931
|
-
entities (list):
|
|
975
|
+
entities (list):
|
|
976
|
+
A list of entities to deliver metadata for.
|
|
932
977
|
|
|
933
978
|
Returns:
|
|
934
|
-
dict | None:
|
|
979
|
+
dict | None:
|
|
980
|
+
Dictionary with the SuccessFactors object data or None in case the request failed.
|
|
981
|
+
|
|
935
982
|
"""
|
|
936
983
|
|
|
937
984
|
if not self._access_token:
|
|
@@ -946,12 +993,14 @@ class SuccessFactors(object):
|
|
|
946
993
|
request_header["Accept"] = "application/xml"
|
|
947
994
|
|
|
948
995
|
response = requests.get(
|
|
949
|
-
request_url,
|
|
996
|
+
request_url,
|
|
997
|
+
headers=request_header,
|
|
998
|
+
timeout=REQUEST_TIMEOUT,
|
|
950
999
|
)
|
|
951
1000
|
if response.status_code == 200:
|
|
952
1001
|
return xmltodict.parse(response.text)
|
|
953
1002
|
else:
|
|
954
|
-
logger.error(
|
|
1003
|
+
self.logger.error(
|
|
955
1004
|
"Failed to retrieve entity data; status -> %s; error -> %s",
|
|
956
1005
|
response.status_code,
|
|
957
1006
|
response.text,
|
|
@@ -961,13 +1010,16 @@ class SuccessFactors(object):
|
|
|
961
1010
|
# end method definition
|
|
962
1011
|
|
|
963
1012
|
def get_entity_metadata(self, entity: str) -> dict | None:
|
|
964
|
-
"""Get the schema (metadata) for an entity
|
|
1013
|
+
"""Get the schema (metadata) for an entity.
|
|
965
1014
|
|
|
966
1015
|
Args:
|
|
967
|
-
entity (str):
|
|
1016
|
+
entity (str):
|
|
1017
|
+
The entity to deliver metadata for.
|
|
968
1018
|
|
|
969
1019
|
Returns:
|
|
970
|
-
dict | None:
|
|
1020
|
+
dict | None:
|
|
1021
|
+
Dictionary with the SuccessFactors object data or None in case the request failed.
|
|
1022
|
+
|
|
971
1023
|
"""
|
|
972
1024
|
|
|
973
1025
|
if not self._access_token:
|
|
@@ -977,18 +1029,20 @@ class SuccessFactors(object):
|
|
|
977
1029
|
return None
|
|
978
1030
|
|
|
979
1031
|
request_url = self.config()["asUrl"] + "Entity('{}')?$format=JSON".format(
|
|
980
|
-
entity
|
|
1032
|
+
entity,
|
|
981
1033
|
)
|
|
982
1034
|
|
|
983
1035
|
request_header = self.request_header()
|
|
984
1036
|
|
|
985
1037
|
response = requests.get(
|
|
986
|
-
request_url,
|
|
1038
|
+
request_url,
|
|
1039
|
+
headers=request_header,
|
|
1040
|
+
timeout=REQUEST_TIMEOUT,
|
|
987
1041
|
)
|
|
988
1042
|
if response.status_code == 200:
|
|
989
1043
|
return self.parse_request_response(response)
|
|
990
1044
|
else:
|
|
991
|
-
logger.error(
|
|
1045
|
+
self.logger.error(
|
|
992
1046
|
"Failed to retrieve entity data; status -> %s; error -> %s",
|
|
993
1047
|
response.status_code,
|
|
994
1048
|
response.text,
|
|
@@ -1004,14 +1058,21 @@ class SuccessFactors(object):
|
|
|
1004
1058
|
email_type: int = 8448, # 8448
|
|
1005
1059
|
) -> dict:
|
|
1006
1060
|
"""Update user email.
|
|
1007
|
-
|
|
1061
|
+
|
|
1062
|
+
See: https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/d599f15995d348a1b45ba5603e2aba9b/7b3daeb3d77d491bb401345eede34bb5.html?locale=en-US
|
|
1008
1063
|
|
|
1009
1064
|
Args:
|
|
1010
|
-
user_id (str):
|
|
1011
|
-
|
|
1012
|
-
|
|
1065
|
+
user_id (str):
|
|
1066
|
+
The ID of the user (e.g. 106020).
|
|
1067
|
+
email_address (str):
|
|
1068
|
+
The new email address of user.
|
|
1069
|
+
email_type (int):
|
|
1070
|
+
Type of the email. 8448 = Business
|
|
1071
|
+
|
|
1013
1072
|
Returns:
|
|
1014
|
-
dict:
|
|
1073
|
+
dict:
|
|
1074
|
+
Request response or None if an error occured.
|
|
1075
|
+
|
|
1015
1076
|
"""
|
|
1016
1077
|
|
|
1017
1078
|
if not self._access_token:
|
|
@@ -1022,7 +1083,8 @@ class SuccessFactors(object):
|
|
|
1022
1083
|
update_data = {
|
|
1023
1084
|
"__metadata": {
|
|
1024
1085
|
"uri": "PerEmail(emailType='{}',personIdExternal='{}')".format(
|
|
1025
|
-
email_type,
|
|
1086
|
+
email_type,
|
|
1087
|
+
user_id,
|
|
1026
1088
|
),
|
|
1027
1089
|
"type": "SFOData.PerEmail",
|
|
1028
1090
|
},
|
|
@@ -1038,14 +1100,14 @@ class SuccessFactors(object):
|
|
|
1038
1100
|
timeout=REQUEST_TIMEOUT,
|
|
1039
1101
|
)
|
|
1040
1102
|
if response.ok:
|
|
1041
|
-
logger.debug(
|
|
1103
|
+
self.logger.debug(
|
|
1042
1104
|
"Email of user with ID -> %s successfully updated to -> %s.",
|
|
1043
1105
|
user_id,
|
|
1044
1106
|
email_address,
|
|
1045
1107
|
)
|
|
1046
1108
|
return self.parse_request_response(response)
|
|
1047
1109
|
else:
|
|
1048
|
-
logger.error(
|
|
1110
|
+
self.logger.error(
|
|
1049
1111
|
"Failed to set email of user with ID -> %s; status -> %s; error -> %s",
|
|
1050
1112
|
user_id,
|
|
1051
1113
|
response.status_code,
|