pyxecm 2.0.3__py3-none-any.whl → 3.0.0__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 (94) hide show
  1. pyxecm/coreshare.py +76 -8
  2. pyxecm/helper/data.py +16 -24
  3. pyxecm/helper/otel_config.py +26 -0
  4. pyxecm/helper/web.py +1 -2
  5. pyxecm/otca.py +1356 -16
  6. pyxecm/otcs.py +4238 -758
  7. pyxecm/otds.py +4 -12
  8. pyxecm/otmm.py +4 -5
  9. pyxecm/py.typed +0 -0
  10. pyxecm-3.0.0.dist-info/METADATA +48 -0
  11. pyxecm-3.0.0.dist-info/RECORD +96 -0
  12. {pyxecm-2.0.3.dist-info → pyxecm-3.0.0.dist-info}/WHEEL +1 -2
  13. pyxecm-3.0.0.dist-info/entry_points.txt +4 -0
  14. {pyxecm/customizer/api → pyxecm_api}/__main__.py +1 -1
  15. pyxecm_api/agents/__init__.py +7 -0
  16. pyxecm_api/agents/app.py +13 -0
  17. pyxecm_api/agents/functions.py +119 -0
  18. pyxecm_api/agents/models.py +10 -0
  19. pyxecm_api/agents/otcm_knowledgegraph/functions.py +85 -0
  20. pyxecm_api/agents/otcm_knowledgegraph/models.py +61 -0
  21. pyxecm_api/agents/otcm_knowledgegraph/router.py +74 -0
  22. pyxecm_api/agents/otcm_user_agent/models.py +20 -0
  23. pyxecm_api/agents/otcm_user_agent/router.py +65 -0
  24. pyxecm_api/agents/otcm_workspace_agent/models.py +40 -0
  25. pyxecm_api/agents/otcm_workspace_agent/router.py +200 -0
  26. pyxecm_api/app.py +221 -0
  27. {pyxecm/customizer/api → pyxecm_api}/auth/functions.py +10 -2
  28. {pyxecm/customizer/api → pyxecm_api}/auth/router.py +4 -3
  29. {pyxecm/customizer/api → pyxecm_api}/common/functions.py +39 -9
  30. {pyxecm/customizer/api → pyxecm_api}/common/metrics.py +1 -2
  31. {pyxecm/customizer/api → pyxecm_api}/common/router.py +12 -11
  32. {pyxecm/customizer/api → pyxecm_api}/settings.py +30 -6
  33. {pyxecm/customizer/api → pyxecm_api}/terminal/router.py +1 -1
  34. {pyxecm/customizer/api → pyxecm_api}/v1_csai/router.py +39 -10
  35. pyxecm_api/v1_csai/statics/bindings/utils.js +189 -0
  36. pyxecm_api/v1_csai/statics/tom-select/tom-select.complete.min.js +356 -0
  37. pyxecm_api/v1_csai/statics/tom-select/tom-select.css +334 -0
  38. pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.css +1 -0
  39. pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.min.js +27 -0
  40. pyxecm_api/v1_maintenance/__init__.py +1 -0
  41. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/functions.py +3 -3
  42. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/router.py +8 -8
  43. pyxecm_api/v1_otcs/__init__.py +1 -0
  44. {pyxecm/customizer/api → pyxecm_api}/v1_otcs/functions.py +7 -5
  45. {pyxecm/customizer/api → pyxecm_api}/v1_otcs/router.py +24 -13
  46. pyxecm_api/v1_payload/__init__.py +1 -0
  47. {pyxecm/customizer/api → pyxecm_api}/v1_payload/functions.py +10 -7
  48. {pyxecm/customizer/api → pyxecm_api}/v1_payload/router.py +11 -10
  49. {pyxecm/customizer → pyxecm_customizer}/__init__.py +8 -0
  50. {pyxecm/customizer → pyxecm_customizer}/__main__.py +15 -21
  51. {pyxecm/customizer → pyxecm_customizer}/browser_automation.py +414 -103
  52. {pyxecm/customizer → pyxecm_customizer}/customizer.py +178 -116
  53. {pyxecm/customizer → pyxecm_customizer}/guidewire.py +60 -20
  54. {pyxecm/customizer → pyxecm_customizer}/k8s.py +4 -4
  55. pyxecm_customizer/knowledge_graph.py +719 -0
  56. pyxecm_customizer/log.py +35 -0
  57. {pyxecm/customizer → pyxecm_customizer}/m365.py +41 -33
  58. {pyxecm/customizer → pyxecm_customizer}/payload.py +2359 -1991
  59. {pyxecm/customizer/api/common → pyxecm_customizer}/payload_list.py +57 -65
  60. {pyxecm/customizer → pyxecm_customizer}/salesforce.py +1 -1
  61. {pyxecm/customizer → pyxecm_customizer}/sap.py +6 -2
  62. {pyxecm/customizer → pyxecm_customizer}/servicenow.py +2 -4
  63. {pyxecm/customizer → pyxecm_customizer}/settings.py +7 -6
  64. {pyxecm/customizer → pyxecm_customizer}/successfactors.py +40 -28
  65. {pyxecm/customizer → pyxecm_customizer}/translate.py +14 -10
  66. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__main__.py +1 -1
  67. {pyxecm/maintenance_page → pyxecm_maintenance_page}/app.py +16 -6
  68. pyxecm/customizer/api/app.py +0 -163
  69. pyxecm/customizer/log.py +0 -107
  70. pyxecm/customizer/nhc.py +0 -1169
  71. pyxecm/customizer/openapi.py +0 -258
  72. pyxecm/customizer/pht.py +0 -1357
  73. pyxecm-2.0.3.dist-info/METADATA +0 -119
  74. pyxecm-2.0.3.dist-info/RECORD +0 -78
  75. pyxecm-2.0.3.dist-info/licenses/LICENSE +0 -202
  76. pyxecm-2.0.3.dist-info/top_level.txt +0 -1
  77. {pyxecm/customizer/api → pyxecm_api}/__init__.py +0 -0
  78. {pyxecm/customizer/api/auth → pyxecm_api/agents/otcm_knowledgegraph}/__init__.py +0 -0
  79. {pyxecm/customizer/api/common → pyxecm_api/agents/otcm_user_agent}/__init__.py +0 -0
  80. {pyxecm/customizer/api/v1_csai → pyxecm_api/agents/otcm_workspace_agent}/__init__.py +0 -0
  81. {pyxecm/customizer/api/v1_maintenance → pyxecm_api/auth}/__init__.py +0 -0
  82. {pyxecm/customizer/api → pyxecm_api}/auth/models.py +0 -0
  83. {pyxecm/customizer/api/v1_otcs → pyxecm_api/common}/__init__.py +0 -0
  84. {pyxecm/customizer/api → pyxecm_api}/common/models.py +0 -0
  85. {pyxecm/customizer/api → pyxecm_api}/terminal/__init__.py +0 -0
  86. {pyxecm/customizer/api/v1_payload → pyxecm_api/v1_csai}/__init__.py +0 -0
  87. {pyxecm/customizer/api → pyxecm_api}/v1_csai/models.py +0 -0
  88. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/models.py +0 -0
  89. {pyxecm/customizer/api → pyxecm_api}/v1_payload/models.py +0 -0
  90. {pyxecm/customizer → pyxecm_customizer}/exceptions.py +0 -0
  91. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__init__.py +0 -0
  92. {pyxecm/maintenance_page → pyxecm_maintenance_page}/settings.py +0 -0
  93. {pyxecm/maintenance_page → pyxecm_maintenance_page}/static/favicon.avif +0 -0
  94. {pyxecm/maintenance_page → pyxecm_maintenance_page}/templates/maintenance.html +0 -0
pyxecm/coreshare.py CHANGED
@@ -27,6 +27,7 @@ import time
27
27
  import urllib.parse
28
28
  from http import HTTPStatus
29
29
  from importlib.metadata import version
30
+ from urllib.parse import parse_qs, urlparse
30
31
 
31
32
  import requests
32
33
 
@@ -522,7 +523,7 @@ class CoreShare:
522
523
  Can be used to provide a more specific error message
523
524
  in case an error occurs.
524
525
  show_error (bool, optional):
525
- True: write an error to the log file
526
+ True: write an error to the log file (this is the default)
526
527
  False: write a warning to the log file
527
528
 
528
529
  Returns:
@@ -933,6 +934,71 @@ class CoreShare:
933
934
 
934
935
  # end method definition
935
936
 
937
+ def get_groups_iterator(
938
+ self,
939
+ count: int | None = None,
940
+ ) -> iter:
941
+ """Get an iterator object that can be used to traverse all Core Share groups.
942
+
943
+ Returning a generator avoids loading a large number of items into memory at once. Instead you
944
+ can iterate over the potential large list of Core Share groups.
945
+
946
+ Example usage:
947
+ groups = core_share_object.get_groups_iterator(page_size=10)
948
+ for group in groups:
949
+ logger.info("Traversing Core Share group -> %s", group["name"])
950
+
951
+ Args:
952
+ count (int | None, optional):
953
+ The chunk size for the number of groups returned by one
954
+ REST API call. If None, then a default of 250 is used.
955
+
956
+ Returns:
957
+ iter:
958
+ A generator yielding one OTDS group per iteration.
959
+ If the REST API fails, returns no value.
960
+
961
+ """
962
+
963
+ offset = 0
964
+
965
+ while True:
966
+ response = self.get_groups(
967
+ offset=offset,
968
+ count=count,
969
+ )
970
+ if not response or not response.get("results", []):
971
+ # Don't return None! Plain return is what we need for iterators.
972
+ # Natural Termination: If the generator does not yield, it behaves
973
+ # like an empty iterable when used in a loop or converted to a list:
974
+ return
975
+
976
+ # Yield users one at a time:
977
+ yield from response["results"]
978
+
979
+ # See if we have an additional result page.
980
+ # If not terminate the iterator and return
981
+ # no value.
982
+
983
+ next_page_url = response["_links"].get("next")
984
+ if not next_page_url:
985
+ # Don't return None! Plain return is what we need for iterators.
986
+ # Natural Termination: If the generator does not yield, it behaves
987
+ # like an empty iterable when used in a loop or converted to a list:
988
+ return
989
+ next_page_url = next_page_url.get("href")
990
+
991
+ # Extract the query string from the URL
992
+ query = urlparse(next_page_url).query
993
+
994
+ # Parse the query parameters into a dictionary
995
+ params = parse_qs(query)
996
+
997
+ # Get the 'offset' value as an integer (it's a list by default)
998
+ offset = int(params.get("offset", [0])[0])
999
+
1000
+ # end method definition
1001
+
936
1002
  def add_group(
937
1003
  self,
938
1004
  group_name: str,
@@ -1276,7 +1342,7 @@ class CoreShare:
1276
1342
  dict | None:
1277
1343
  Dictionary with the Core Share group data or None if the request fails.
1278
1344
 
1279
- Example result:
1345
+ Example Response:
1280
1346
  {
1281
1347
  'results': [
1282
1348
  {
@@ -1343,15 +1409,15 @@ class CoreShare:
1343
1409
 
1344
1410
  # end method definition
1345
1411
 
1346
- def get_users(self) -> dict | None:
1412
+ def get_users(self) -> list | None:
1347
1413
  """Get Core Share users.
1348
1414
 
1349
1415
  Args:
1350
1416
  None
1351
1417
 
1352
1418
  Returns:
1353
- dict | None:
1354
- Dictionary with the Core Share user data or None if the request fails.
1419
+ list | None:
1420
+ List with the Core Share user data or None if the request fails.
1355
1421
 
1356
1422
  Example response (it is a list!):
1357
1423
  [
@@ -2524,7 +2590,8 @@ class CoreShare:
2524
2590
  is_confirmed = self.get_result_value(response=user, key="isConfirmed")
2525
2591
  if not is_confirmed:
2526
2592
  self.logger.info(
2527
- "User -> %s is not yet confirmed - so it cannot have files to cleanup.",
2593
+ "User -> %s (%s) is not yet confirmed - so it cannot have files to cleanup.",
2594
+ user_login,
2528
2595
  user_id,
2529
2596
  )
2530
2597
  return True
@@ -2546,14 +2613,15 @@ class CoreShare:
2546
2613
  # Get all folders of the user:
2547
2614
  response = self.get_folders(parent_id=user_root_folder_id)
2548
2615
  if not response or not response["results"]:
2549
- self.logger.info("User -> %s has no items to cleanup!", user_id)
2616
+ self.logger.info("User -> %s (%s) has no items to cleanup!", user_login, user_id)
2550
2617
  else:
2551
2618
  items = response["results"]
2552
2619
  for item in items:
2553
2620
  if item["isShared"]:
2554
2621
  if item["owner"]["id"] == user_id:
2555
2622
  self.logger.info(
2556
- "User -> %s stops sharing item -> %s (%s)...",
2623
+ "User -> %s (%s) stops sharing item -> %s (%s)...",
2624
+ user_login,
2557
2625
  user_id,
2558
2626
  item["name"],
2559
2627
  item["id"],
pyxecm/helper/data.py CHANGED
@@ -857,6 +857,10 @@ class Data:
857
857
  columns = existing_columns
858
858
 
859
859
  # Attempt to save the data frame to Excel:
860
+ if self._df is None:
861
+ self.logger.error(
862
+ "Cannot write Excel file -> '%s' from empty / non-initialized data frame!", excel_path
863
+ )
860
864
  self._df.to_excel(
861
865
  excel_path,
862
866
  sheet_name=sheet_name,
@@ -868,29 +872,17 @@ class Data:
868
872
  excel_path,
869
873
  )
870
874
 
871
- except FileNotFoundError:
872
- self.logger.error(
873
- "Cannot write data frame to Excel file -> '%s'",
874
- excel_path,
875
- )
875
+ except FileNotFoundError as fnf_error:
876
+ self.logger.error("Cannot write data frame to Excel file -> '%s'; error -> %s", excel_path, str(fnf_error))
876
877
  return False
877
- except PermissionError:
878
- self.logger.error(
879
- "Cannot write data frame to Excel file -> '%s'",
880
- excel_path,
881
- )
878
+ except PermissionError as pe:
879
+ self.logger.error("Cannot write data frame to Excel file -> '%s'; error -> %s", excel_path, str(pe))
882
880
  return False
883
- except ValueError:
884
- self.logger.error(
885
- "Cannot write data frame to Excel file -> '%s'",
886
- excel_path,
887
- )
881
+ except ValueError as ve:
882
+ self.logger.error("Cannot write data frame to Excel file -> '%s'; error -> %s", excel_path, str(ve))
888
883
  return False
889
- except OSError:
890
- self.logger.error(
891
- "Cannot write data frame to Excel file -> '%s'",
892
- excel_path,
893
- )
884
+ except OSError as ose:
885
+ self.logger.error("Cannot write data frame to Excel file -> '%s'; error -> %s", excel_path, str(ose))
894
886
  return False
895
887
 
896
888
  return True
@@ -1418,7 +1410,7 @@ class Data:
1418
1410
  for value in values:
1419
1411
  # Do we want a special treatment for this value (e.g. the current year)
1420
1412
  if value in special_values:
1421
- self.logger.info("Processing special value -> '%s'...", value)
1413
+ self.logger.debug("Processing special value -> '%s'...", value)
1422
1414
  if value not in special_url_templates and str(value) not in special_url_templates:
1423
1415
  self.logger.error(
1424
1416
  "Cannot find key -> '%s' in special URL templates dictionary -> %s! Skipping...",
@@ -2135,7 +2127,7 @@ class Data:
2135
2127
  )
2136
2128
  continue
2137
2129
  # Apply cleansing to dictionary values in the main column
2138
- self.logger.info(
2130
+ self.logger.debug(
2139
2131
  "Cleansing for column -> '%s' has a subfield -> '%s' configured. Do cleansing for dictionary items with key -> '%s'...",
2140
2132
  column,
2141
2133
  dict_key,
@@ -2173,7 +2165,7 @@ class Data:
2173
2165
  # Handle string columns:
2174
2166
  if self.is_string_column(self._df[column]):
2175
2167
  # Apply cleansing operations on string column
2176
- self.logger.info(
2168
+ self.logger.debug(
2177
2169
  "Column -> '%s' has string values. Do cleansing for string values...",
2178
2170
  column,
2179
2171
  )
@@ -2203,7 +2195,7 @@ class Data:
2203
2195
  elif self.is_list_column(self._df[column]):
2204
2196
  # Handle list-like columns for this we iterate over each list item
2205
2197
  # and apply the cleansing by calling _apply_string_cleansing() for item:
2206
- self.logger.info(
2198
+ self.logger.debug(
2207
2199
  "Column -> '%s' has list values. Do cleansing for each list item...",
2208
2200
  column,
2209
2201
  )
@@ -0,0 +1,26 @@
1
+ """Define OpenTelemtry configuration."""
2
+
3
+ import os
4
+
5
+ from opentelemetry import trace
6
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
7
+ from opentelemetry.instrumentation.requests import RequestsInstrumentor
8
+ from opentelemetry.instrumentation.threading import ThreadingInstrumentor
9
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
10
+ from opentelemetry.sdk.trace import TracerProvider
11
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
12
+
13
+ resource = Resource.create(attributes={SERVICE_NAME: "pyxecm"})
14
+
15
+ trace.set_tracer_provider(TracerProvider(resource=resource))
16
+
17
+ if os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
18
+ trace.get_tracer_provider().add_span_processor(
19
+ BatchSpanProcessor(OTLPSpanExporter()),
20
+ )
21
+
22
+ # Auto-instrument requests
23
+ RequestsInstrumentor().instrument()
24
+ ThreadingInstrumentor().instrument()
25
+
26
+ tracer = trace.get_tracer("pyxecm")
pyxecm/helper/web.py CHANGED
@@ -326,8 +326,7 @@ class HTTP:
326
326
  )
327
327
  os.makedirs(directory)
328
328
  with open(filename, "wb") as download_file:
329
- for chunk in response.iter_content(chunk_size=chunk_size):
330
- download_file.write(chunk)
329
+ download_file.writelines(response.iter_content(chunk_size=chunk_size))
331
330
  self.logger.debug(
332
331
  "File downloaded successfully as -> '%s' (size -> %s).",
333
332
  filename,