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.

Files changed (78) hide show
  1. pyxecm/__init__.py +7 -4
  2. pyxecm/avts.py +727 -254
  3. pyxecm/coreshare.py +686 -467
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +163 -0
  9. pyxecm/customizer/api/auth/__init__.py +1 -0
  10. pyxecm/customizer/api/auth/functions.py +92 -0
  11. pyxecm/customizer/api/auth/models.py +13 -0
  12. pyxecm/customizer/api/auth/router.py +78 -0
  13. pyxecm/customizer/api/common/__init__.py +1 -0
  14. pyxecm/customizer/api/common/functions.py +47 -0
  15. pyxecm/customizer/api/common/metrics.py +92 -0
  16. pyxecm/customizer/api/common/models.py +21 -0
  17. pyxecm/customizer/api/common/payload_list.py +870 -0
  18. pyxecm/customizer/api/common/router.py +72 -0
  19. pyxecm/customizer/api/settings.py +128 -0
  20. pyxecm/customizer/api/terminal/__init__.py +1 -0
  21. pyxecm/customizer/api/terminal/router.py +87 -0
  22. pyxecm/customizer/api/v1_csai/__init__.py +1 -0
  23. pyxecm/customizer/api/v1_csai/router.py +87 -0
  24. pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
  25. pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
  26. pyxecm/customizer/api/v1_maintenance/models.py +12 -0
  27. pyxecm/customizer/api/v1_maintenance/router.py +76 -0
  28. pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
  29. pyxecm/customizer/api/v1_otcs/functions.py +61 -0
  30. pyxecm/customizer/api/v1_otcs/router.py +179 -0
  31. pyxecm/customizer/api/v1_payload/__init__.py +1 -0
  32. pyxecm/customizer/api/v1_payload/functions.py +179 -0
  33. pyxecm/customizer/api/v1_payload/models.py +51 -0
  34. pyxecm/customizer/api/v1_payload/router.py +499 -0
  35. pyxecm/customizer/browser_automation.py +721 -286
  36. pyxecm/customizer/customizer.py +1076 -1425
  37. pyxecm/customizer/exceptions.py +35 -0
  38. pyxecm/customizer/guidewire.py +1186 -0
  39. pyxecm/customizer/k8s.py +901 -379
  40. pyxecm/customizer/log.py +107 -0
  41. pyxecm/customizer/m365.py +2967 -920
  42. pyxecm/customizer/nhc.py +1169 -0
  43. pyxecm/customizer/openapi.py +258 -0
  44. pyxecm/customizer/payload.py +18228 -7820
  45. pyxecm/customizer/pht.py +717 -286
  46. pyxecm/customizer/salesforce.py +516 -342
  47. pyxecm/customizer/sap.py +58 -41
  48. pyxecm/customizer/servicenow.py +611 -372
  49. pyxecm/customizer/settings.py +445 -0
  50. pyxecm/customizer/successfactors.py +408 -346
  51. pyxecm/customizer/translate.py +83 -48
  52. pyxecm/helper/__init__.py +5 -2
  53. pyxecm/helper/assoc.py +83 -43
  54. pyxecm/helper/data.py +2406 -870
  55. pyxecm/helper/logadapter.py +27 -0
  56. pyxecm/helper/web.py +229 -101
  57. pyxecm/helper/xml.py +596 -171
  58. pyxecm/maintenance_page/__init__.py +5 -0
  59. pyxecm/maintenance_page/__main__.py +6 -0
  60. pyxecm/maintenance_page/app.py +51 -0
  61. pyxecm/maintenance_page/settings.py +28 -0
  62. pyxecm/maintenance_page/static/favicon.avif +0 -0
  63. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  64. pyxecm/otac.py +235 -141
  65. pyxecm/otawp.py +2668 -1220
  66. pyxecm/otca.py +569 -0
  67. pyxecm/otcs.py +7956 -3237
  68. pyxecm/otds.py +2178 -925
  69. pyxecm/otiv.py +36 -21
  70. pyxecm/otmm.py +1272 -325
  71. pyxecm/otpd.py +231 -127
  72. pyxecm-2.0.1.dist-info/METADATA +122 -0
  73. pyxecm-2.0.1.dist-info/RECORD +76 -0
  74. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
  75. pyxecm-1.6.dist-info/METADATA +0 -53
  76. pyxecm-1.6.dist-info/RECORD +0 -32
  77. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info/licenses}/LICENSE +0 -0
  78. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1186 @@
1
+ """Guidewire Module to interact with the Guidewire REST API ("Cloud API").
2
+
3
+ See: https://www.guidewire.com/de/developers/apis/cloud-apis
4
+ """
5
+
6
+ __author__ = "Dr. Marc Diefenbruch"
7
+ __copyright__ = "Copyright (C) 2024-2025, OpenText"
8
+ __credits__ = ["Kai-Philip Gatzweiler"]
9
+ __maintainer__ = "Dr. Marc Diefenbruch"
10
+ __email__ = "mdiefenb@opentext.com"
11
+
12
+ import logging
13
+ import platform
14
+ import sys
15
+ import urllib.parse
16
+ from importlib.metadata import version
17
+
18
+ import requests
19
+ from requests.auth import HTTPBasicAuth
20
+
21
+ APP_NAME = "pyxecm"
22
+ APP_VERSION = version("pyxecm")
23
+ MODULE_NAME = APP_NAME + ".customizer.guidewire"
24
+
25
+ PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
26
+ OS_INFO = f"{platform.system()} {platform.release()}"
27
+ ARCH_INFO = platform.machine()
28
+ REQUESTS_VERSION = requests.__version__
29
+
30
+ USER_AGENT = (
31
+ f"{APP_NAME}/{APP_VERSION} ({MODULE_NAME}/{APP_VERSION}; "
32
+ f"Python/{PYTHON_VERSION}; {OS_INFO}; {ARCH_INFO}; Requests/{REQUESTS_VERSION})"
33
+ )
34
+
35
+ default_logger = logging.getLogger(MODULE_NAME)
36
+
37
+
38
+ class Guidewire:
39
+ """Class Guidewire is used to retrieve and automate stettings and objects in Guidewire."""
40
+
41
+ _config: dict
42
+ _scope = None
43
+ _access_token = None
44
+
45
+ def __init__(
46
+ self,
47
+ base_url: str,
48
+ auth_type: str,
49
+ client_id: str = "",
50
+ client_secret: str = "",
51
+ username: str = "",
52
+ password: str = "",
53
+ scope: str = "",
54
+ logger: logging.Logger = default_logger,
55
+ ) -> None:
56
+ """Initialize the Guidewire API client.
57
+
58
+ Args:
59
+ base_url (str):
60
+ The base URL of the Guidewire Cloud API.
61
+ auth_type (str):
62
+ The authorization type, either "oauth" or "basic".
63
+ client_id (str):
64
+ The Client ID for authentication (optional, required for client credential flow).
65
+ client_secret (str):
66
+ The Client Secret for authentication (optional, required for client credential flow).
67
+ username (str):
68
+ The username for authentication (optional, required for password-based authentication).
69
+ password (str):
70
+ The password for authentication (optional, required for password-based authentication).
71
+ scope (str):
72
+ The OAuth2 scope (optional).
73
+ logger:
74
+ The logging object used for all log messages. Default is default_logger.
75
+
76
+ """
77
+
78
+ if logger != default_logger:
79
+ self.logger = logger.getChild("guidewire")
80
+ for logfilter in logger.filters:
81
+ self.logger.addFilter(logfilter)
82
+
83
+ self._scope = scope
84
+
85
+ guidewire_config = {}
86
+ # Store the credentials and parameters in a config dictionary:
87
+ guidewire_config["baseUrl"] = base_url.rstrip("/")
88
+ guidewire_config["authType"] = auth_type
89
+ guidewire_config["clientId"] = client_id
90
+ guidewire_config["clientSecret"] = client_secret
91
+ guidewire_config["username"] = username
92
+ guidewire_config["password"] = password
93
+ guidewire_config["restUrl"] = guidewire_config["baseUrl"] + "/rest" # "/api/v1"
94
+ guidewire_config["tokenUrl"] = guidewire_config["restUrl"] + "/oauth2/token"
95
+
96
+ guidewire_config["adminUrl"] = guidewire_config["restUrl"] + "/admin/v1"
97
+ guidewire_config["claimUrl"] = guidewire_config["restUrl"] + "/claim/v1"
98
+ guidewire_config["accountUrl"] = guidewire_config["restUrl"] + "/account/v1"
99
+
100
+ self._config = guidewire_config
101
+
102
+ self._session = requests.Session()
103
+
104
+ # end method definition
105
+
106
+ def config(self) -> dict:
107
+ """Return the configuration dictionary.
108
+
109
+ Returns:
110
+ dict:
111
+ The configuration dictionary with all settings.
112
+
113
+ """
114
+
115
+ return self._config
116
+
117
+ # end method definition
118
+
119
+ def authenticate(self, auth_type: str) -> HTTPBasicAuth | str | None:
120
+ """Authenticate with the Guidewire API using either client credentials or username/password.
121
+
122
+ Args:
123
+ auth_type (str):
124
+ The Authorization type. This can be "basic" or "oauth".
125
+
126
+ Returns:
127
+ bool:
128
+ True if authentication is successful, False otherwise.
129
+
130
+ """
131
+
132
+ self._session.headers.update(self.request_header())
133
+
134
+ if auth_type == "basic":
135
+ username = self.config()["username"]
136
+ password = self.config()["password"]
137
+ if not self._session:
138
+ self._session = requests.Session()
139
+ self._session.auth = HTTPBasicAuth(username, password)
140
+ return self._session.auth
141
+
142
+ request_url = self.config()["tokenUrl"]
143
+
144
+ if self.config()["clientId"] and self.config()["clientSecret"]:
145
+ auth_data = {
146
+ "grant_type": "client_credentials",
147
+ "client_id": self.config()["clientId"],
148
+ "client_secret": self.config()["clientSecret"],
149
+ }
150
+ elif self.config()["username"] and self.config()["password"]:
151
+ auth_data = {
152
+ "grant_type": "password",
153
+ "username": self.config()["username"],
154
+ "password": self.config()["password"],
155
+ "client_id": self.config()["clientId"], # Required for some OAuth2 flows
156
+ "client_secret": self.config()["clientSecret"], # Required for some OAuth2 flows
157
+ }
158
+ else:
159
+ self.logger.error("Authentication requires either client credentials or username/password.")
160
+ return False
161
+
162
+ if self._scope:
163
+ auth_data["scope"] = self._scope
164
+
165
+ response = requests.post(request_url, data=auth_data)
166
+ if response.status_code == 200:
167
+ self.token = response.json().get("access_token")
168
+ return True
169
+
170
+ return False
171
+
172
+ # end method definition
173
+
174
+ def request_header(self, content_type: str = "application/json") -> dict:
175
+ """Generate request headers including authentication token.
176
+
177
+ Args:
178
+ content_type (str, optional):
179
+ Custom content type for the request.
180
+ Typical value for Guidewire is application/json.
181
+
182
+ Returns:
183
+ dict:
184
+ A dictionary containing authorization headers.
185
+
186
+ """
187
+
188
+ request_header = {
189
+ "User-Agent": USER_AGENT,
190
+ "Content-Type": content_type,
191
+ }
192
+
193
+ if self.config()["authType"] == "oauth":
194
+ if not self._access_token:
195
+ self.logger.error("Authentication required. Call authenticate() first.")
196
+ return None
197
+ request_header["Authorization"] = ("Bearer {}".format(self._access_token),)
198
+
199
+ return request_header
200
+
201
+ # end method definition
202
+
203
+ def do_request(self, method: str, url: str, data: dict | None = None, params: dict | None = None) -> dict:
204
+ """Send a request to the Guidewire REST API.
205
+
206
+ Args:
207
+ method (str):
208
+ The HTTP method to use (GET, POST, PUT, DELETE).
209
+ url (str):
210
+ The API endpoint to call.
211
+ data (dict):
212
+ The request payload (if applicable).
213
+ params (dict):
214
+ The URL parameters (if applicable).
215
+
216
+ Returns:
217
+ dict:
218
+ Response as a dictionary.
219
+
220
+ """
221
+
222
+ response = self._session.request(
223
+ method=method, url=url, headers=self.request_header(), data=data, params=params
224
+ )
225
+
226
+ return response.json() if response.content else {}
227
+
228
+ # end method definition
229
+
230
+ def process_parameters(
231
+ self, fields: list | None = None, filters: list | None = None, page_size: int | None = 25
232
+ ) -> str | None:
233
+ """Determine the request parameters (filters, fields).
234
+
235
+ Args:
236
+ fields (list | None, optional):
237
+ List of filter values. Defaults to None.
238
+ filters (list | None, optional):
239
+ List of filter values. Defaults to None.
240
+ page_size (int, optional):
241
+ The maximum number of groups to return.
242
+
243
+ Returns:
244
+ str | None:
245
+ Encoded URL parameters for the request URL.
246
+
247
+ """
248
+
249
+ query = {}
250
+
251
+ if fields:
252
+ fields = ",".join(fields)
253
+ query["fields"] = fields
254
+
255
+ for filter_dict in filters or []:
256
+ if "op" not in filter_dict:
257
+ filter_dict["op"] = "eq"
258
+ if "attribute" not in filter_dict:
259
+ self.logger.error("Missing attribute in filter condition!")
260
+ return None
261
+ if "value" not in filter_dict:
262
+ self.logger.error("Missing value(s) in filter condition!")
263
+ return None
264
+ elif isinstance(filter_dict["value"], list):
265
+ filter_dict["value"] = ",".join(filter_dict["value"])
266
+ query["filter"] = (
267
+ filter_dict.get("attribute") + ":" + filter_dict.get("op") + ":" + filter_dict.get("value")
268
+ )
269
+
270
+ if page_size:
271
+ query["pageSize"] = page_size
272
+
273
+ encoded_query = urllib.parse.urlencode(query=query, doseq=True)
274
+
275
+ return encoded_query
276
+
277
+ # end method definition
278
+
279
+ def get_groups(
280
+ self,
281
+ fields: list | None = None,
282
+ filters: list | None = None,
283
+ page_size: int = 25,
284
+ next_page_url: str | None = None,
285
+ ) -> dict:
286
+ """Retrieve a list of Guidewire groups.
287
+
288
+ Args:
289
+ fields (list):
290
+ The list of fields in the results. If None, all default
291
+ fields are returned.
292
+ Fields for Guidewire accounts:
293
+ - *all = return all fields
294
+ - *default = return just the default list of fields
295
+ - *summary = return the fields defined for giving a summary
296
+ - *detail = details
297
+ - displayName
298
+ - groupType
299
+ - id
300
+ - loadFactor
301
+ - name
302
+ - organization
303
+ - parent
304
+ - securityZone
305
+ - supervisor
306
+ filters (list):
307
+ List of dictionaries with three keys each:
308
+ - "attribute" - name of the attribute to use for the filter (available attributes see above)
309
+ - "op" - operator:
310
+ * eq - equal
311
+ * ne - not equal
312
+ * lt - less than - also usable for dates (before)
313
+ * gt - greater than - also usable for dates (after)
314
+ * le - less or equal
315
+ * ge - greater or equal
316
+ * in - is in list
317
+ * ni - is NOT in list
318
+ * sw - starts with
319
+ * cn - contains
320
+ - "value": the value to filter for. Either literal or list of values
321
+ page_size (int, optional):
322
+ The maximum number of groups to return.
323
+ next_page_url (str, optional):
324
+ The Guidewire URL to retrieve the next page of Guidewire groups (pagination).
325
+ This is used for the iterator get_groups_iterator() below.
326
+
327
+ Returns:
328
+ dict:
329
+ JSON response containing account data. None in case of an error.
330
+
331
+ Example reponse:
332
+ {
333
+ 'count': 25,
334
+ 'data': [
335
+ {
336
+ 'attributes': {
337
+ 'displayName': 'Actuary Unit',
338
+ 'groupType': {...},
339
+ 'id': 'pc:S_I-NOU3hb3FU0qTfu8fd',
340
+ 'loadFactor': 100,
341
+ 'name': 'Actuary Unit',
342
+ 'organization': {...},
343
+ 'parent': {...},
344
+ 'securityZone': {...},
345
+ 'supervisor': {...}
346
+ },
347
+ 'checksum': '0',
348
+ 'links': {
349
+ 'self': {...}
350
+ }
351
+ },
352
+ ...
353
+ ],
354
+ 'links': {
355
+ 'first': {
356
+ 'href': '/admin/v1/groups?fields=%2Adefault',
357
+ 'methods': ['get']
358
+ },
359
+ 'next': {
360
+ 'href': '/admin/v1/groups?fields=%2Adefault&pageOffset=25',
361
+ 'methods': ['get']
362
+ },
363
+ 'self': {...}
364
+ }
365
+ }
366
+
367
+ """
368
+
369
+ if not next_page_url:
370
+ request_url = self.config()["adminUrl"] + "/groups"
371
+
372
+ encoded_query = self.process_parameters(fields=fields, filters=filters, page_size=page_size)
373
+ if encoded_query:
374
+ request_url += "?" + encoded_query
375
+ else:
376
+ request_url = self.config()["restUrl"] + next_page_url
377
+
378
+ return self.do_request(method="GET", url=request_url)
379
+
380
+ # end method definition
381
+
382
+ def get_groups_iterator(self, fields: list | None = None, filters: list | None = None, page_size: int = 25) -> iter:
383
+ """Get an iterator object that can be used to traverse all Guidewire groups.
384
+
385
+ Returning a generator avoids loading a large number of nodes into memory at once. Instead you
386
+ can iterate over the potential large list of groups.
387
+
388
+ Example usage:
389
+ groups = guidewire_object.get_groups_iterator()
390
+ for group in groups:
391
+ logger.info("Traversing Guidewire group -> '%s'...", group.get("attributes", {}).get("displayName"))
392
+
393
+ Returns:
394
+ iter:
395
+ A generator yielding one Guidewire group per iteration.
396
+ If the REST API fails, returns no value.
397
+
398
+ """
399
+
400
+ next_page_url = None
401
+
402
+ while True:
403
+ response = self.get_groups(fields=fields, filters=filters, page_size=page_size, next_page_url=next_page_url)
404
+ if not response or "data" not in response:
405
+ # Don't return None! Plain return is what we need for iterators.
406
+ # Natural Termination: If the generator does not yield, it behaves
407
+ # like an empty iterable when used in a loop or converted to a list:
408
+ return
409
+
410
+ # Yield users one at a time:
411
+ yield from response["data"]
412
+
413
+ # See if we have an additional result page.
414
+ # If not terminate the iterator and return
415
+ # no value.
416
+ next_page_url = response.get("links", {}).get("next", {}).get("href")
417
+ if not next_page_url:
418
+ # Don't return None! Plain return is what we need for iterators.
419
+ # Natural Termination: If the generator does not yield, it behaves
420
+ # like an empty iterable when used in a loop or converted to a list:
421
+ return
422
+
423
+ # end method definition
424
+
425
+ def get_group(self, group_id: str) -> dict:
426
+ """Retrieve details of a specific group.
427
+
428
+ Args:
429
+ group_id: The unique identifier of the group.
430
+
431
+ Returns:
432
+ dict: JSON response containing group details.
433
+
434
+ Example response;
435
+ {
436
+ 'data': {
437
+ 'attributes': {
438
+ 'displayName': 'Actuary Unit',
439
+ 'groupType': {
440
+ 'code': 'actuary',
441
+ 'name': 'Actuary unit'
442
+ },
443
+ 'id': 'pc:S_I-NOU3hb3FU0qTfu8fd',
444
+ 'loadFactor': 100,
445
+ 'name': 'Actuary Unit',
446
+ 'organization': {
447
+ 'displayName': 'Enigma Fire & Casualty',
448
+ 'id': 'systemTables:1',
449
+ 'type': 'Organization',
450
+ 'uri': '/admin/v1/organizations/systemTables:1'
451
+ },
452
+ 'parent': {
453
+ 'displayName': 'Enigma Fire & Casualty',
454
+ 'id': 'systemTables:1',
455
+ 'type': 'Group',
456
+ 'uri': '/admin/v1/groups/systemTables:1'
457
+ },
458
+ 'securityZone': {
459
+ 'displayName': 'HO UW',
460
+ 'id': 'pc:So-lJXKuecOco_hGZ_8iR'
461
+ },
462
+ 'supervisor': {
463
+ 'displayName': 'Super Visor',
464
+ 'id': 'pc:S1cZ06yduoQadHVOcVCyv',
465
+ 'type': 'User',
466
+ 'uri': '/admin/v1/users/pc:S1cZ06yduoQadHVOcVCyv'
467
+ }
468
+ },
469
+ 'checksum': '0',
470
+ 'links': {...}
471
+ }
472
+ }
473
+
474
+ """
475
+
476
+ request_url = self.config()["adminUrl"] + "/groups/" + str(group_id)
477
+
478
+ return self.do_request(method="GET", url=request_url)
479
+
480
+ # end method definition
481
+
482
+ def get_users(
483
+ self,
484
+ fields: list | None = None,
485
+ filters: list | None = None,
486
+ page_size: int = 25,
487
+ next_page_url: str | None = None,
488
+ ) -> dict:
489
+ """Retrieve a list of Guidewire users.
490
+
491
+ Args:
492
+ fields (list):
493
+ The list of fields in the results. If None, all default
494
+ fields are returned.
495
+ Fields for Guidewire accounts:
496
+ - *all = return all fields
497
+ - *default = return just the default list of fields
498
+ - *summary = return the fields defined for giving a summary
499
+ - *detail = details
500
+ - displayName
501
+ - groupType
502
+ - id
503
+ - loadFactor
504
+ - name
505
+ - organization
506
+ - parent
507
+ - securityZone
508
+ - supervisor
509
+ filters (list):
510
+ List of dictionaries with three keys each:
511
+ - "attribute" - name of the attribute to use for the filter (available attributes see above)
512
+ - "op" - operator:
513
+ * eq - equal
514
+ * ne - not equal
515
+ * lt - less than - also usable for dates (before)
516
+ * gt - greater than - also usable for dates (after)
517
+ * le - less or equal
518
+ * ge - greater or equal
519
+ * in - is in list
520
+ * ni - is NOT in list
521
+ * sw - starts with
522
+ * cn - contains
523
+ - "value": the value to filter for. Either literal or list of values
524
+ page_size (int, optional):
525
+ The maximum number of groups to return.
526
+ next_page_url (str, optional):
527
+ The Guidewire URL to retrieve the next page of Guidewire groups (pagination).
528
+ This is used for the iterator get_groups_iterator() below.
529
+
530
+ Returns:
531
+ dict:
532
+ JSON response containing account data.
533
+
534
+ Example reponse:
535
+ {
536
+ 'count': 10,
537
+ 'data': [
538
+ {
539
+ 'attributes': {
540
+ 'active': True,
541
+ 'displayName': 'Alice Applegate',
542
+ 'externalUser': False,
543
+ 'firstName': 'Alice',
544
+ 'groups': [
545
+ {
546
+ 'displayName': 'Eastern Region Underwriting',
547
+ 'id': 'pc:SDrypgK62o6oS1TxOGcvF',
548
+ 'type': 'Group',
549
+ 'uri': '/admin/v1/groups/pc:SDrypgK62o6oS1TxOGcvF'
550
+ },
551
+ {
552
+ 'displayName': 'Los Angeles Branch UW',
553
+ 'id': 'pc:SJxAbEha2jYpG9Mb5_KAo',
554
+ 'type': 'Group',
555
+ 'uri': '/admin/v1/groups/pc:SJxAbEha2jYpG9Mb5_KAo'
556
+ }
557
+ ],
558
+ 'id': 'pc:Si6MBM-35EAhneDubeFsl',
559
+ 'lastName': 'Applegate',
560
+ 'organization': {
561
+ 'displayName': 'Enigma Fire & Casualty',
562
+ 'id': 'systemTables:1',
563
+ 'type': 'Organization',
564
+ 'uri': '/admin/v1/organizations/systemTables:1'
565
+ },
566
+ 'roles': [
567
+ {
568
+ 'displayName': 'Reinsurance Manager',
569
+ 'id': 'reinsurance_manager',
570
+ 'type': 'Role',
571
+ 'uri': '/admin/v1/roles/reinsurance_manager'
572
+ },
573
+ {
574
+ 'displayName': 'Underwriter',
575
+ 'id': 'underwriter',
576
+ 'type': 'Role',
577
+ 'uri': '/admin/v1/roles/underwriter'
578
+ }
579
+ ],
580
+ 'useOrgAddress': True,
581
+ 'useProducerCodeSecurity': False,
582
+ 'userType': {
583
+ 'code': 'underwriter',
584
+ 'name': 'Underwriter'
585
+ },
586
+ 'username': 'aapplegate',
587
+ 'uwAuthorityProfiles': [
588
+ {
589
+ 'displayName': 'Underwriter 1',
590
+ 'id': 'pc:underwriter1',
591
+ 'type': 'UWAuthorityProfile',
592
+ 'uri': '/admin/v1/uw-authority-profiles/pc:underwriter1'
593
+ }
594
+ ],
595
+ 'vacationStatus': {
596
+ 'code': 'atwork',
597
+ 'name': 'At work'
598
+ },
599
+ 'workPhone': {
600
+ 'displayName': '213-555-8164',
601
+ 'number': '2135558164'
602
+ }
603
+ },
604
+ 'checksum': 'ec4710cd2af59bdc1cd7e15a18707d84',
605
+ 'links': {
606
+ 'self': {'href': '/admin/v1/users/pc:Si6MBM-35EAhneDubeFsl', 'methods': ['delete', 'get', 'patch']}
607
+ }
608
+ },
609
+ ...
610
+ ],
611
+ 'links': {
612
+ 'first': {'href': '/admin/v1/users?fields=%2Adefault&pageSize=20', 'methods': ['get']},
613
+ 'next': {'href': '/admin/v1/users?fields=%2Adefault&pageSize=20&pageOffset=20', 'methods': ['get']},
614
+ 'self': {'href': '/admin/v1/users?fields=%2Adefault&pageSize=20', 'methods': ['get']}
615
+ }
616
+ }
617
+
618
+ """
619
+
620
+ if not next_page_url:
621
+ request_url = self.config()["adminUrl"] + "/users"
622
+
623
+ encoded_query = self.process_parameters(fields=fields, filters=filters, page_size=page_size)
624
+ if encoded_query:
625
+ request_url += "?" + encoded_query
626
+ else:
627
+ request_url = self.config()["restUrl"] + next_page_url
628
+
629
+ return self.do_request(method="GET", url=request_url)
630
+
631
+ # end method definition
632
+
633
+ def get_users_iterator(self, fields: list | None = None, filters: list | None = None, page_size: int = 25) -> iter:
634
+ """Get an iterator object that can be used to traverse all Guidewire users.
635
+
636
+ Returning a generator avoids loading a large number of nodes into memory at once. Instead you
637
+ can iterate over the potential large list of users.
638
+
639
+ Example usage:
640
+ users = guidewire_object.get_users_iterator()
641
+ for user in users:
642
+ logger.info("Traversing Guidewire user -> '%s'...", user.get("attributes", {}).get("displayName"))
643
+
644
+ Args:
645
+ fields (list):
646
+ The list of fields in the results. If None, all default
647
+ fields are returned.
648
+ Fields for Guidewire accounts:
649
+ - *all = return all fields
650
+ - *default = return just the default list of fields
651
+ - *summary = return the fields defined for giving a summary
652
+ - *detail = details
653
+ - active
654
+ - displayName
655
+ - externalUser
656
+ - firstName
657
+ - id
658
+ - lastName
659
+ - organization
660
+ - useOrgAddress
661
+ - useProducerCodeSecurity
662
+ - username
663
+ filters (list):
664
+ List of dictionaries with three keys each:
665
+ - "attribute" - name of the attribute to use for the filter (available attributes see above)
666
+ - "op" - operator:
667
+ * eq - equal
668
+ * ne - not equal
669
+ * lt - less than - also usable for dates (before)
670
+ * gt - greater than - also usable for dates (after)
671
+ * le - less or equal
672
+ * ge - greater or equal
673
+ * in - is in list
674
+ * ni - is NOT in list
675
+ * sw - starts with
676
+ * cn - contains
677
+ - "value": the value to filter for. Either literal or list of values
678
+ page_size (int, optional):
679
+ The maximum number of groups to return.
680
+ next_page_url (str, optional):
681
+ The Guidewire URL to retrieve the next page of Guidewire groups (pagination).
682
+ This is used for the iterator get_groups_iterator() below.
683
+
684
+ Returns:
685
+ iter:
686
+ A generator yielding one Guidewire user per iteration.
687
+ If the REST API fails, returns no value.
688
+
689
+ """
690
+
691
+ next_page_url = None
692
+
693
+ while True:
694
+ response = self.get_users(fields=fields, filters=filters, page_size=page_size, next_page_url=next_page_url)
695
+ if not response or "data" not in response:
696
+ # Don't return None! Plain return is what we need for iterators.
697
+ # Natural Termination: If the generator does not yield, it behaves
698
+ # like an empty iterable when used in a loop or converted to a list:
699
+ return
700
+
701
+ # Yield users one at a time:
702
+ yield from response["data"]
703
+
704
+ # See if we have an additional result page.
705
+ # If not terminate the iterator and return
706
+ # no value.
707
+ next_page_url = response.get("links", {}).get("next", {}).get("href")
708
+ if not next_page_url:
709
+ # Don't return None! Plain return is what we need for iterators.
710
+ # Natural Termination: If the generator does not yield, it behaves
711
+ # like an empty iterable when used in a loop or converted to a list:
712
+ return
713
+
714
+ # end method definition
715
+
716
+ def get_user(self, user_id: str) -> dict:
717
+ """Retrieve details of a specific user.
718
+
719
+ Args:
720
+ user_id:
721
+ The unique identifier of the group.
722
+
723
+ Returns:
724
+ dict:
725
+ JSON response containing group details.
726
+
727
+ Example response;
728
+ {
729
+ 'data': {
730
+ 'attributes': {
731
+ 'active': True,
732
+ 'displayName': 'Alice Applegate',
733
+ 'externalUser': False,
734
+ 'firstName': 'Alice',
735
+ 'groups': [{...}, {...}],
736
+ 'id': 'pc:Si6MBM-35EAhneDubeFsl',
737
+ 'lastName': 'Applegate',
738
+ 'organization': {
739
+ 'displayName': 'Enigma Fire & Casualty',
740
+ 'id': 'systemTables:1',
741
+ 'type': 'Organization',
742
+ 'uri': '/admin/v1/organizations/systemTables:1'
743
+ },
744
+ 'roles': [{...}, {...}],
745
+ 'useOrgAddress': True,
746
+ 'useProducerCodeSecurity': False,
747
+ 'userType': {
748
+ 'code': 'underwriter',
749
+ 'name': 'Underwriter'
750
+ },
751
+ 'username': 'aapplegate',
752
+ 'uwAuthorityProfiles': [{...}],
753
+ 'vacationStatus': {
754
+ 'code': 'atwork',
755
+ 'name': 'At work'
756
+ },
757
+ 'workPhone': {
758
+ 'displayName': '213-555-8164',
759
+ 'number': '2135558164'
760
+ }
761
+ },
762
+ 'checksum': 'ec4710cd2af59bdc1cd7e15a18707d84',
763
+ 'links': {
764
+ 'producer-codes': {
765
+ 'href': '/admin/v1/users/pc:Si6MBM-35EAhneDubeFsl/producer-codes',
766
+ 'methods': [...]
767
+ },
768
+ 'self': {
769
+ 'href': '/admin/v1/users/pc:Si6MBM-35EAhneDubeFsl',
770
+ 'methods': [...]
771
+ }
772
+ }
773
+ }
774
+ }
775
+
776
+ """
777
+
778
+ request_url = self.config()["adminUrl"] + "/users/" + str(user_id)
779
+
780
+ return self.do_request(method="GET", url=request_url)
781
+
782
+ # end method definition
783
+
784
+ def update_user(self, user_id: str, user_data: dict) -> dict:
785
+ """Update an existing user.
786
+
787
+ Args:
788
+ user_id:
789
+ The unique identifier of the user.
790
+ user_data:
791
+ Dictionary containing updated user information.
792
+
793
+ Returns:
794
+ dict:
795
+ Response with updated user details.
796
+
797
+ """
798
+
799
+ request_url = self.config()["adminUrl"] + "/users/" + str(user_id)
800
+
801
+ return self.do_request(method="PUT", url=request_url, data=user_data)
802
+
803
+ # end method definition
804
+
805
+ def get_accounts(
806
+ self,
807
+ fields: list | None = None,
808
+ filters: list | None = None,
809
+ page_size: int = 25,
810
+ next_page_url: str | None = None,
811
+ ) -> dict | None:
812
+ """Retrieve a list of accounts.
813
+
814
+ Args:
815
+ fields (list):
816
+ The list of fields in the results. If None, all default
817
+ fields are returned.
818
+ Fields for Guidewire accounts:
819
+ - *all = return all fields
820
+ - *default = return just the default list of fields
821
+ - *summary = return the fields defined for giving a summary
822
+ - *detail = details
823
+ - accountNumber
824
+ - accountHolder
825
+ - accountStatus
826
+ - businessOperationsDescription
827
+ - createdDate
828
+ - frozen
829
+ - id
830
+ - industryCode
831
+ - organizationType
832
+ - preferredCoverageCurrency
833
+ - preferredSettlementCurrency
834
+ - primaryLanguage
835
+ - primaryLocale
836
+ - primaryLocation
837
+ - producerCodes
838
+ filters (list):
839
+ List of dictionaries with three keys each:
840
+ - "attribute" - name of the attribute to use for the filter (available attributes see above)
841
+ - "op" - operator:
842
+ * eq - equal
843
+ * ne - not equal
844
+ * lt - less than - also usable for dates (before)
845
+ * gt - greater than - also usable for dates (after)
846
+ * le - less or equal
847
+ * ge - greater or equal
848
+ * in - is in list
849
+ * ni - is NOT in list
850
+ * sw - starts with
851
+ * cn - contains
852
+ - "value": the filue to filter for. Either literal or list of values
853
+ page_size (int, optional):
854
+ The maximum number of groups to return.
855
+ next_page_url (str, optional):
856
+ The Guidewire URL to retrieve the next page of Guidewire groups (pagination).
857
+ This is used for the iterator get_groups_iterator() below.
858
+
859
+ Returns:
860
+ dict:
861
+ JSON response containing account data. None in case of an error.
862
+
863
+ """
864
+
865
+ if not next_page_url:
866
+ request_url = self.config()["accountUrl"] + "/accounts"
867
+
868
+ encoded_query = self.process_parameters(fields=fields, filters=filters, page_size=page_size)
869
+ if encoded_query:
870
+ request_url += "?" + encoded_query
871
+ else:
872
+ request_url = self.config()["restUrl"] + next_page_url
873
+
874
+ return self.do_request(method="GET", url=request_url)
875
+
876
+ # end method definition
877
+
878
+ def get_accounts_iterator(
879
+ self, fields: list | None = None, filters: list | None = None, page_size: int = 25
880
+ ) -> iter:
881
+ """Get an iterator object that can be used to traverse all Guidewire accounts.
882
+
883
+ Returning a generator avoids loading a large number of nodes into memory at once. Instead you
884
+ can iterate over the potential large list of groups.
885
+
886
+ Example usage:
887
+ accounts = guidewire_object.get_accounts_iterator()
888
+ for account in accounts:
889
+ logger.info("Traversing Guidewire account -> '%s'...", account.get("attributes", {}).get("displayName"))
890
+
891
+ Returns:
892
+ iter:
893
+ A generator yielding one Guidewire account per iteration.
894
+ If the REST API fails, returns no value.
895
+
896
+ """
897
+
898
+ next_page_url = None
899
+
900
+ while True:
901
+ response = self.get_accounts(
902
+ fields=fields, filters=filters, page_size=page_size, next_page_url=next_page_url
903
+ )
904
+ if not response or "data" not in response:
905
+ # Don't return None! Plain return is what we need for iterators.
906
+ # Natural Termination: If the generator does not yield, it behaves
907
+ # like an empty iterable when used in a loop or converted to a list:
908
+ return
909
+
910
+ # Yield users one at a time:
911
+ yield from response["data"]
912
+
913
+ # See if we have an additional result page.
914
+ # If not terminate the iterator and return
915
+ # no value.
916
+ next_page_url = response.get("links", {}).get("next", {}).get("href")
917
+ if not next_page_url:
918
+ # Don't return None! Plain return is what we need for iterators.
919
+ # Natural Termination: If the generator does not yield, it behaves
920
+ # like an empty iterable when used in a loop or converted to a list:
921
+ return
922
+
923
+ # end method definition
924
+
925
+ def get_account(self, account_id: str) -> dict:
926
+ """Retrieve details of a specific account.
927
+
928
+ Args:
929
+ account_id:
930
+ The unique identifier of the account.
931
+
932
+ Returns:
933
+ dict:
934
+ JSON response containing account details.
935
+
936
+ """
937
+
938
+ request_url = self.config()["accountUrl"] + "/accounts/" + str(account_id)
939
+
940
+ return self.do_request(method="GET", url=request_url)
941
+
942
+ # end method definition
943
+
944
+ def add_account(self, account_data: dict) -> dict:
945
+ """Create a new account.
946
+
947
+ Args:
948
+ account_data:
949
+ Dictionary containing account information.
950
+
951
+ Returns:
952
+ dict:
953
+ JSON response with created account details.
954
+
955
+ """
956
+
957
+ request_url = self.config()["accountUrl"] + "/accounts"
958
+
959
+ return self.do_request(method="POST", url=request_url, data=account_data)
960
+
961
+ # end method definition
962
+
963
+ def update_account(self, account_id: str, account_data: dict) -> dict:
964
+ """Update an existing account.
965
+
966
+ Args:
967
+ account_id:
968
+ The unique identifier of the account.
969
+ account_data:
970
+ Dictionary containing updated account information.
971
+
972
+ Returns:
973
+ dict:
974
+ JSON response with updated account details.
975
+
976
+ """
977
+
978
+ request_url = self.config()["accountUrl"] + "/accounts/" + str(account_id)
979
+
980
+ return self.do_request(method="PUT", url=request_url, data=account_data)
981
+
982
+ # end method definition
983
+
984
+ def delete_account(self, account_id: str) -> dict:
985
+ """Delete an account.
986
+
987
+ Args:
988
+ account_id: The unique identifier of the account to delete.
989
+
990
+ Returns:
991
+ dict: JSON response indicating deletion success.
992
+
993
+ """
994
+
995
+ request_url = self.config()["accountUrl"] + "/accounts/" + str(account_id)
996
+
997
+ return self.do_request(method="DELETE", url=request_url)
998
+
999
+ # end method definition
1000
+
1001
+ def get_claims(
1002
+ self,
1003
+ fields: list | None = None,
1004
+ filters: list | None = None,
1005
+ page_size: int = 25,
1006
+ next_page_url: str | None = None,
1007
+ ) -> dict | None:
1008
+ """Retrieve a list of claims.
1009
+
1010
+ fields (list):
1011
+ The list of fields in the results. If None, all default
1012
+ fields are returned.
1013
+ Fields for Guidewire accounts:
1014
+ - *all = return all fields
1015
+ - *default = return just the default list of fields
1016
+ - *summary = return the fields defined for giving a summary
1017
+ - *detail = details
1018
+ - displayName
1019
+ - groupType
1020
+ - id
1021
+ - loadFactor
1022
+ - name
1023
+ - organization
1024
+ - parent
1025
+ - securityZone
1026
+ - supervisor
1027
+ filters (list):
1028
+ List of dictionaries with three keys each:
1029
+ - "attribute" - name of the attribute to use for the filter (available attributes see above)
1030
+ - "op" - operator:
1031
+ * eq - equal
1032
+ * ne - not equal
1033
+ * lt - less than - also usable for dates (before)
1034
+ * gt - greater than - also usable for dates (after)
1035
+ * le - less or equal
1036
+ * ge - greater or equal
1037
+ * in - is in list
1038
+ * ni - is NOT in list
1039
+ * sw - starts with
1040
+ * cn - contains
1041
+ - "value": the value to filter for. Either literal or list of values
1042
+ page_size (int, optional):
1043
+ The maximum number of groups to return.
1044
+ next_page_url (str, optional):
1045
+ The Guidewire URL to retrieve the next page of Guidewire groups (pagination).
1046
+ This is used for the iterator get_groups_iterator() below.
1047
+
1048
+ Returns:
1049
+ dict | None:
1050
+ JSON response containing claim data.
1051
+
1052
+ """
1053
+
1054
+ if not next_page_url:
1055
+ request_url = self.config()["claimUrl"] + "/claim-infos"
1056
+
1057
+ encoded_query = self.process_parameters(fields=fields, filters=filters, page_size=page_size)
1058
+ if encoded_query:
1059
+ request_url += "?" + encoded_query
1060
+ else:
1061
+ request_url = self.config()["restUrl"] + next_page_url
1062
+
1063
+ return self.do_request(method="GET", url=request_url)
1064
+
1065
+ # end method definition
1066
+
1067
+ def get_claims_iterator(self, fields: list | None = None, filters: list | None = None, page_size: int = 25) -> iter:
1068
+ """Get an iterator object that can be used to traverse all Guidewire claims.
1069
+
1070
+ Returning a generator avoids loading a large number of nodes into memory at once. Instead you
1071
+ can iterate over the potential large list of groups.
1072
+
1073
+ Example usage:
1074
+ claims = guidewire_object.get_claims_iterator()
1075
+ for claim in claims:
1076
+ logger.info("Traversing Guidewire claim -> '%s'...", claim.get("attributes", {}).get("displayName"))
1077
+
1078
+ Returns:
1079
+ iter:
1080
+ A generator yielding one Guidewire claim per iteration.
1081
+ If the REST API fails, returns no value.
1082
+
1083
+ """
1084
+
1085
+ next_page_url = None
1086
+
1087
+ while True:
1088
+ response = self.get_claims(fields=fields, filters=filters, page_size=page_size, next_page_url=next_page_url)
1089
+ if not response or "data" not in response:
1090
+ # Don't return None! Plain return is what we need for iterators.
1091
+ # Natural Termination: If the generator does not yield, it behaves
1092
+ # like an empty iterable when used in a loop or converted to a list:
1093
+ return
1094
+
1095
+ # Yield users one at a time:
1096
+ yield from response["data"]
1097
+
1098
+ # See if we have an additional result page.
1099
+ # If not terminate the iterator and return
1100
+ # no value.
1101
+ next_page_url = response.get("links", {}).get("next", {}).get("href")
1102
+ if not next_page_url:
1103
+ # Don't return None! Plain return is what we need for iterators.
1104
+ # Natural Termination: If the generator does not yield, it behaves
1105
+ # like an empty iterable when used in a loop or converted to a list:
1106
+ return
1107
+
1108
+ # end method definition
1109
+
1110
+ def get_claim(self, claim_id: str) -> dict:
1111
+ """Retrieve details of a specific claim.
1112
+
1113
+ Args:
1114
+ claim_id:
1115
+ The unique identifier of the claim.
1116
+
1117
+ Returns:
1118
+ dict:
1119
+ JSON response containing claim details.
1120
+
1121
+ """
1122
+
1123
+ request_url = self.config()["claimUrl"] + "/claims/" + str(claim_id)
1124
+
1125
+ return self.do_request(method="GET", url=request_url)
1126
+
1127
+ # end method definition
1128
+
1129
+ def add_claim(self, claim_data: dict) -> dict:
1130
+ """Create a new claim.
1131
+
1132
+ Args:
1133
+ claim_data (dict):
1134
+ Dictionary containing claim information.
1135
+
1136
+ Returns:
1137
+ dict:
1138
+ JSON response with created claim details.
1139
+
1140
+ """
1141
+
1142
+ request_url = self.config()["claimUrl"] + "/claims"
1143
+
1144
+ return self.do_request(method="POST", url=request_url, data=claim_data)
1145
+
1146
+ # end method definition
1147
+
1148
+ def update_claim(self, claim_id: str, claim_data: dict) -> dict:
1149
+ """Update an existing claim.
1150
+
1151
+ Args:
1152
+ claim_id:
1153
+ The unique identifier of the claim.
1154
+ claim_data:
1155
+ Dictionary containing updated claim information.
1156
+
1157
+ Returns:
1158
+ dict:
1159
+ Response with updated claim details.
1160
+
1161
+ """
1162
+
1163
+ request_url = self.config()["claimUrl"] + "/claims/" + str(claim_id)
1164
+
1165
+ return self.do_request(method="PUT", url=request_url, data=claim_data)
1166
+
1167
+ # end method definition
1168
+
1169
+ def delete_claim(self, claim_id: str) -> dict:
1170
+ """Delete a claim.
1171
+
1172
+ Args:
1173
+ claim_id (str):
1174
+ The unique identifier of the claim to delete.
1175
+
1176
+ Returns:
1177
+ dict:
1178
+ Response indicating deletion success.
1179
+
1180
+ """
1181
+
1182
+ request_url = self.config()["claimUrl"] + "/claims/" + str(claim_id)
1183
+
1184
+ return self.do_request(method="DELETE", url=request_url)
1185
+
1186
+ # end method definition