pyxecm 2.0.0__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 +2 -1
- pyxecm/avts.py +79 -33
- pyxecm/customizer/api/app.py +45 -796
- pyxecm/customizer/api/auth/__init__.py +1 -0
- pyxecm/customizer/api/{auth.py → auth/functions.py} +2 -64
- 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/{metrics.py → common/metrics.py} +1 -1
- pyxecm/customizer/api/common/models.py +21 -0
- pyxecm/customizer/api/{payload_list.py → common/payload_list.py} +6 -1
- pyxecm/customizer/api/common/router.py +72 -0
- pyxecm/customizer/api/settings.py +25 -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 +568 -326
- pyxecm/customizer/customizer.py +204 -430
- pyxecm/customizer/guidewire.py +907 -43
- pyxecm/customizer/k8s.py +243 -56
- pyxecm/customizer/m365.py +104 -15
- pyxecm/customizer/payload.py +1943 -885
- pyxecm/customizer/pht.py +19 -2
- pyxecm/customizer/servicenow.py +22 -5
- pyxecm/customizer/settings.py +9 -6
- pyxecm/helper/xml.py +69 -0
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +2104 -1535
- pyxecm/otca.py +569 -0
- pyxecm/otcs.py +201 -37
- pyxecm/otds.py +35 -13
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/METADATA +6 -29
- pyxecm-2.0.1.dist-info/RECORD +76 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
- pyxecm-2.0.0.dist-info/RECORD +0 -54
- /pyxecm/customizer/api/{models.py → auth/models.py} +0 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
pyxecm/customizer/m365.py
CHANGED
|
@@ -116,9 +116,9 @@ class M365:
|
|
|
116
116
|
# Set the data for the token request
|
|
117
117
|
m365_config["tokenData"] = {
|
|
118
118
|
"client_id": client_id,
|
|
119
|
-
"scope": "https://graph.microsoft.com/.default",
|
|
120
119
|
"client_secret": client_secret,
|
|
121
120
|
"grant_type": "client_credentials",
|
|
121
|
+
"scope": "https://graph.microsoft.com/.default",
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
m365_config["meUrl"] = m365_config["graphUrl"] + "me"
|
|
@@ -157,7 +157,7 @@ class M365:
|
|
|
157
157
|
|
|
158
158
|
return self.config()["tokenData"]
|
|
159
159
|
|
|
160
|
-
def credentials_user(self, username: str, password: str) -> dict:
|
|
160
|
+
def credentials_user(self, username: str, password: str, scope: str = "Files.ReadWrite") -> dict:
|
|
161
161
|
"""Get user credentials.
|
|
162
162
|
|
|
163
163
|
In some cases MS Graph APIs cannot be called via
|
|
@@ -173,6 +173,10 @@ class M365:
|
|
|
173
173
|
The M365 username.
|
|
174
174
|
password (str):
|
|
175
175
|
The password of the M365 user.
|
|
176
|
+
scope (str):
|
|
177
|
+
The scope of the delegated permission.
|
|
178
|
+
It is important to provide a scope for the intended operation
|
|
179
|
+
like "Files.ReadWrite".
|
|
176
180
|
|
|
177
181
|
Returns:
|
|
178
182
|
dict:
|
|
@@ -180,13 +184,14 @@ class M365:
|
|
|
180
184
|
|
|
181
185
|
"""
|
|
182
186
|
|
|
187
|
+
# Use OAuth2 / ROPC (Resource Owner Password Credentials):
|
|
183
188
|
credentials = {
|
|
184
189
|
"client_id": self.config()["clientId"],
|
|
185
|
-
"scope": "https://graph.microsoft.com/.default",
|
|
186
190
|
"client_secret": self.config()["clientSecret"],
|
|
187
191
|
"grant_type": "password",
|
|
188
192
|
"username": username,
|
|
189
193
|
"password": password,
|
|
194
|
+
"scope": scope,
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
return credentials
|
|
@@ -233,10 +238,14 @@ class M365:
|
|
|
233
238
|
"""
|
|
234
239
|
|
|
235
240
|
request_header = {
|
|
236
|
-
"Authorization": "Bearer {}".format(self._user_access_token),
|
|
237
241
|
"Content-Type": content_type,
|
|
238
242
|
}
|
|
239
243
|
|
|
244
|
+
if not self._user_access_token:
|
|
245
|
+
self.logger.error("No M365 user is authenticated! Cannot include Bearer token in request header!")
|
|
246
|
+
else:
|
|
247
|
+
request_header["Authorization"] = "Bearer {}".format(self._user_access_token)
|
|
248
|
+
|
|
240
249
|
return request_header
|
|
241
250
|
|
|
242
251
|
# end method definition
|
|
@@ -359,6 +368,14 @@ class M365:
|
|
|
359
368
|
)
|
|
360
369
|
time.sleep((retries + 1) * 60)
|
|
361
370
|
retries += 1
|
|
371
|
+
elif response.status_code in [404, 429] and retries < REQUEST_MAX_RETRIES:
|
|
372
|
+
self.logger.warning(
|
|
373
|
+
"M365 Graph API is too slow or throtteling calls -> %s; retrying in %s seconds...",
|
|
374
|
+
response.status_code,
|
|
375
|
+
(retries + 1) * 60,
|
|
376
|
+
)
|
|
377
|
+
time.sleep((retries + 1) * 60)
|
|
378
|
+
retries += 1
|
|
362
379
|
else:
|
|
363
380
|
# Handle plain HTML responses to not pollute the logs
|
|
364
381
|
content_type = response.headers.get("content-type", None)
|
|
@@ -679,7 +696,7 @@ class M365:
|
|
|
679
696
|
Returns:
|
|
680
697
|
str | None:
|
|
681
698
|
The access token. Also stores access token in self._access_token.
|
|
682
|
-
None in case of error.
|
|
699
|
+
None in case of an error.
|
|
683
700
|
|
|
684
701
|
"""
|
|
685
702
|
|
|
@@ -710,7 +727,7 @@ class M365:
|
|
|
710
727
|
self.logger.warning(
|
|
711
728
|
"Unable to connect to -> %s : %s",
|
|
712
729
|
self.config()["authenticationUrl"],
|
|
713
|
-
exception,
|
|
730
|
+
str(exception),
|
|
714
731
|
)
|
|
715
732
|
return None
|
|
716
733
|
|
|
@@ -718,9 +735,8 @@ class M365:
|
|
|
718
735
|
authenticate_dict = self.parse_request_response(authenticate_response)
|
|
719
736
|
if not authenticate_dict:
|
|
720
737
|
return None
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
self.logger.debug("Access Token -> %s", access_token)
|
|
738
|
+
access_token = authenticate_dict["access_token"]
|
|
739
|
+
self.logger.debug("Access Token -> %s", access_token)
|
|
724
740
|
else:
|
|
725
741
|
self.logger.error(
|
|
726
742
|
"Failed to request an M365 Access Token; error -> %s",
|
|
@@ -735,7 +751,7 @@ class M365:
|
|
|
735
751
|
|
|
736
752
|
# end method definition
|
|
737
753
|
|
|
738
|
-
def authenticate_user(self, username: str, password: str) -> str | None:
|
|
754
|
+
def authenticate_user(self, username: str, password: str, scope: str | None = None) -> str | None:
|
|
739
755
|
"""Authenticate at M365 Graph API with username and password.
|
|
740
756
|
|
|
741
757
|
Args:
|
|
@@ -743,10 +759,14 @@ class M365:
|
|
|
743
759
|
The name (email) of the M365 user.
|
|
744
760
|
password (str):
|
|
745
761
|
The password of the M365 user.
|
|
762
|
+
scope (str):
|
|
763
|
+
The scope of the delegated permission. E.g. "Files.ReadWrite".
|
|
764
|
+
Multiple delegated permissions should be separated by spaces.
|
|
746
765
|
|
|
747
766
|
Returns:
|
|
748
767
|
str | None:
|
|
749
768
|
The access token for the user. Also stores access token in self._access_token.
|
|
769
|
+
None in case of an error.
|
|
750
770
|
|
|
751
771
|
"""
|
|
752
772
|
|
|
@@ -764,12 +784,13 @@ class M365:
|
|
|
764
784
|
return None
|
|
765
785
|
|
|
766
786
|
self.logger.debug(
|
|
767
|
-
"Requesting M365 Access Token for user -> %s from -> %s",
|
|
787
|
+
"Requesting M365 Access Token for user -> %s from -> %s%s",
|
|
768
788
|
username,
|
|
769
789
|
request_url,
|
|
790
|
+
" with scope -> '{}'".format(scope) if scope else "",
|
|
770
791
|
)
|
|
771
792
|
|
|
772
|
-
authenticate_post_body = self.credentials_user(username, password)
|
|
793
|
+
authenticate_post_body = self.credentials_user(username=username, password=password, scope=scope)
|
|
773
794
|
authenticate_response = None
|
|
774
795
|
|
|
775
796
|
try:
|
|
@@ -784,7 +805,7 @@ class M365:
|
|
|
784
805
|
"Unable to connect to -> %s with username -> %s: %s",
|
|
785
806
|
self.config()["authenticationUrl"],
|
|
786
807
|
username,
|
|
787
|
-
exception,
|
|
808
|
+
str(exception),
|
|
788
809
|
)
|
|
789
810
|
return None
|
|
790
811
|
|
|
@@ -1354,6 +1375,74 @@ class M365:
|
|
|
1354
1375
|
|
|
1355
1376
|
# end method definition
|
|
1356
1377
|
|
|
1378
|
+
def get_user_drive(self, user_id: str, me: bool = False) -> dict | None:
|
|
1379
|
+
"""Get the mysite (OneDrive) of the user.
|
|
1380
|
+
|
|
1381
|
+
It may be required to do this before certain other operations
|
|
1382
|
+
are possible. These operations may require that the mydrive
|
|
1383
|
+
is initialized for that user. If you get errors like
|
|
1384
|
+
"User's mysite not found." this may be the case.
|
|
1385
|
+
|
|
1386
|
+
Args:
|
|
1387
|
+
user_id (str):
|
|
1388
|
+
The M365 GUID of the user (can also be the M365 email of the user).
|
|
1389
|
+
me (bool, optional):
|
|
1390
|
+
Should be True if the user itself is accessing the drive.
|
|
1391
|
+
|
|
1392
|
+
Returns:
|
|
1393
|
+
dict:
|
|
1394
|
+
A list of user licenses or None if request fails.
|
|
1395
|
+
|
|
1396
|
+
Example:
|
|
1397
|
+
{
|
|
1398
|
+
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#drives/$entity',
|
|
1399
|
+
'createdDateTime': '2025-04-10T23:43:26Z',
|
|
1400
|
+
'description': '',
|
|
1401
|
+
'id': 'b!VsxYN1IbrEqbiwMiba_M7FCkNAhL5LRFnQEZpEYbxDAxvvcvUMAhSqfgWW_4eAUP',
|
|
1402
|
+
'lastModifiedDateTime': '2025-04-12T15:50:20Z',
|
|
1403
|
+
'name': 'OneDrive',
|
|
1404
|
+
'webUrl': 'https://ideateqa-my.sharepoint.com/personal/jbenham_qa_idea-te_eimdemo_com/Documents',
|
|
1405
|
+
'driveType': 'business',
|
|
1406
|
+
'createdBy': {
|
|
1407
|
+
'user': {
|
|
1408
|
+
'displayName': 'System Account'
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
'lastModifiedBy': {
|
|
1412
|
+
'user': {
|
|
1413
|
+
'email': 'jbenham@qa.idea-te.eimdemo.com',
|
|
1414
|
+
'id': '470060cc-4d9f-439e-8a6e-8d567c5bda80',
|
|
1415
|
+
'displayName': 'Jeff Benham'
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
1418
|
+
'owner': {
|
|
1419
|
+
'user': {...}
|
|
1420
|
+
},
|
|
1421
|
+
'quota': {
|
|
1422
|
+
'deleted': 0,
|
|
1423
|
+
'remaining': 1099511518137,
|
|
1424
|
+
'state': 'normal',
|
|
1425
|
+
'total': 1099511627776,
|
|
1426
|
+
'used': 109639
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
"""
|
|
1431
|
+
|
|
1432
|
+
request_url = self.config()["meUrl"] if me else self.config()["usersUrl"] + "/" + user_id
|
|
1433
|
+
request_url += "/drive"
|
|
1434
|
+
request_header = self.request_header_user() if me else self.request_header()
|
|
1435
|
+
|
|
1436
|
+
return self.do_request(
|
|
1437
|
+
url=request_url,
|
|
1438
|
+
method="GET",
|
|
1439
|
+
headers=request_header,
|
|
1440
|
+
timeout=REQUEST_TIMEOUT,
|
|
1441
|
+
failure_message="Failed to get mySite (drive) of M365 user -> {}".format(user_id),
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
# end method definition
|
|
1445
|
+
|
|
1357
1446
|
def get_groups(
|
|
1358
1447
|
self,
|
|
1359
1448
|
max_number: int = 250,
|
|
@@ -4799,7 +4888,7 @@ class M365:
|
|
|
4799
4888
|
"""
|
|
4800
4889
|
|
|
4801
4890
|
request_url = self.config()["sitesUrl"] + "/" + site_id + "/pages"
|
|
4802
|
-
|
|
4891
|
+
request_header = self.request_header()
|
|
4803
4892
|
|
|
4804
4893
|
# Page payload for a basic site page
|
|
4805
4894
|
payload = {
|
|
@@ -4814,7 +4903,7 @@ class M365:
|
|
|
4814
4903
|
response = self.do_request(
|
|
4815
4904
|
url=request_url,
|
|
4816
4905
|
method="POST",
|
|
4817
|
-
headers=
|
|
4906
|
+
headers=request_header,
|
|
4818
4907
|
json_data=payload,
|
|
4819
4908
|
timeout=REQUEST_TIMEOUT,
|
|
4820
4909
|
failure_message="Failed to create SharePoint page -> '{}' in SharePoint site -> '{}'".format(
|