pyxecm 3.9.2__tar.gz → 3.9.4__tar.gz

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.
Files changed (88) hide show
  1. {pyxecm-3.9.2 → pyxecm-3.9.4}/PKG-INFO +1 -1
  2. {pyxecm-3.9.2 → pyxecm-3.9.4}/pyproject.toml +1 -1
  3. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/errors.py +37 -0
  4. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/helper/data.py +49 -9
  5. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otcs.py +325 -132
  6. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/manager_push.py +27 -0
  7. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/payload.py +211 -46
  8. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/sap.py +1185 -310
  9. pyxecm-3.9.4/src/pyxecm_customizer/sap_metadata.py +345 -0
  10. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/sap_models.py +125 -2
  11. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/settings.py +4 -0
  12. {pyxecm-3.9.2 → pyxecm-3.9.4}/.gitignore +0 -0
  13. {pyxecm-3.9.2 → pyxecm-3.9.4}/README.md +0 -0
  14. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/__init__.py +0 -0
  15. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/avts.py +0 -0
  16. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/coreshare.py +0 -0
  17. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/helper/__init__.py +0 -0
  18. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/helper/assoc.py +0 -0
  19. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/helper/logadapter.py +0 -0
  20. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/helper/web.py +0 -0
  21. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/helper/xml.py +0 -0
  22. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otac.py +0 -0
  23. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otawp.py +0 -0
  24. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otca.py +0 -0
  25. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otds.py +0 -0
  26. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otiv.py +0 -0
  27. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otkd.py +0 -0
  28. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otmm.py +0 -0
  29. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/otpd.py +0 -0
  30. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm/py.typed +0 -0
  31. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/__init__.py +0 -0
  32. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/__main__.py +0 -0
  33. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/app.py +0 -0
  34. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/auth/__init__.py +0 -0
  35. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/auth/functions.py +0 -0
  36. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/auth/models.py +0 -0
  37. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/auth/router.py +0 -0
  38. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/common/__init__.py +0 -0
  39. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/common/functions.py +0 -0
  40. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/common/metrics.py +0 -0
  41. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/common/models.py +0 -0
  42. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/common/router.py +0 -0
  43. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/config/__init__.py +0 -0
  44. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/config/functions.py +0 -0
  45. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/config/models.py +0 -0
  46. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/config/router.py +0 -0
  47. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/pods/__init__.py +0 -0
  48. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/pods/functions.py +0 -0
  49. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/pods/log_spool.py +0 -0
  50. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/pods/models.py +0 -0
  51. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/pods/router.py +0 -0
  52. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/settings.py +0 -0
  53. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/terminal/__init__.py +0 -0
  54. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/terminal/router.py +0 -0
  55. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_maintenance/__init__.py +0 -0
  56. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_maintenance/functions.py +0 -0
  57. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_maintenance/models.py +0 -0
  58. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_maintenance/router.py +0 -0
  59. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_otcs/__init__.py +0 -0
  60. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_otcs/functions.py +0 -0
  61. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_otcs/router.py +0 -0
  62. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_payload/__init__.py +0 -0
  63. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_payload/functions.py +0 -0
  64. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_payload/log_reader.py +0 -0
  65. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_payload/models.py +0 -0
  66. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_api/v1_payload/router.py +0 -0
  67. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/__init__.py +0 -0
  68. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/__main__.py +0 -0
  69. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/browser_automation.py +0 -0
  70. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/customizer.py +0 -0
  71. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/exceptions.py +0 -0
  72. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/guidewire.py +0 -0
  73. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/k8s.py +0 -0
  74. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/log.py +0 -0
  75. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/m365.py +0 -0
  76. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/payload_list.py +0 -0
  77. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/salesforce.py +0 -0
  78. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/sap_forms.py +0 -0
  79. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/servicenow.py +0 -0
  80. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/successfactors.py +0 -0
  81. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/translate.py +0 -0
  82. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_customizer/version.py +0 -0
  83. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_maintenance_page/__init__.py +0 -0
  84. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_maintenance_page/__main__.py +0 -0
  85. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_maintenance_page/app.py +0 -0
  86. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_maintenance_page/settings.py +0 -0
  87. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_maintenance_page/static/favicon.avif +0 -0
  88. {pyxecm-3.9.2 → pyxecm-3.9.4}/src/pyxecm_maintenance_page/templates/maintenance.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyxecm
3
- Version: 3.9.2
3
+ Version: 3.9.4
4
4
  Summary: A Python library to interact with Opentext Content Management Rest API
5
5
  Project-URL: Homepage, https://github.com/opentext/pyxecm
6
6
  Author-email: Kai Gatzweiler <kgatzweiler@opentext.com>, "Dr. Marc Diefenbruch" <mdiefenb@opentext.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyxecm"
3
- version = "3.9.2"
3
+ version = "3.9.4"
4
4
  description = 'A Python library to interact with Opentext Content Management Rest API'
5
5
  keywords = [
6
6
  'opentext',
@@ -30,10 +30,13 @@ __credits__ = ["Kai-Philip Gatzweiler"]
30
30
  __maintainer__ = "Dr. Marc Diefenbruch"
31
31
  __email__ = "mdiefenb@opentext.com"
32
32
 
33
+ import contextlib
33
34
  import json
34
35
  import logging
36
+ import os
35
37
  import threading
36
38
  import urllib.parse
39
+ import warnings
37
40
  from collections import deque
38
41
  from collections.abc import Generator, Iterator
39
42
  from contextlib import contextmanager, suppress
@@ -719,6 +722,40 @@ def record_error(
719
722
  # end function definition
720
723
 
721
724
 
725
+ # The warning should point to the code that called pyxecm - not to the pyxecm
726
+ # method that passes the parameter on (e.g. OTCS.create_item() -> OTCS.do_request())
727
+ # or to the wrapper of the @tracer.start_as_current_span decorator (in contextlib).
728
+ # The contextlib entry has no ".py" extension: CPython does not compare the last
729
+ # character of the file name, so a prefix that is the complete file name never matches.
730
+ _PYXECM_PACKAGE_DIRS = (
731
+ *(
732
+ os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), package) + os.sep
733
+ for package in ("pyxecm", "pyxecm_customizer")
734
+ ),
735
+ os.path.splitext(contextlib.__file__)[0],
736
+ )
737
+
738
+
739
+ def warn_parse_error_response_deprecated() -> None:
740
+ """Warn that the parse_error_response parameter is deprecated.
741
+
742
+ The error details are available via capture_errors() for all pyxecm calls,
743
+ so the parameter will be removed in a future version.
744
+ """
745
+
746
+ warnings.warn(
747
+ message=(
748
+ "The parameter parse_error_response is deprecated and will be removed in a future version. "
749
+ "Use capture_errors() to get the error details instead, e.g. errors.root_cause.server_message."
750
+ ),
751
+ category=DeprecationWarning,
752
+ skip_file_prefixes=_PYXECM_PACKAGE_DIRS,
753
+ )
754
+
755
+
756
+ # end function definition
757
+
758
+
722
759
  def request_error_severity(show_error: bool, show_warning: bool = True) -> ErrorSeverity:
723
760
  """Return the severity of a failed REST call that matches its log level.
724
761
 
@@ -705,18 +705,30 @@ class Data:
705
705
  convert_dates: bool = False,
706
706
  index_column: str | None = None,
707
707
  compression: str | None = None,
708
+ replace: bool = False,
708
709
  ) -> bool:
709
710
  """Load JSON data into a Pandas data frame.
710
711
 
712
+ Without a schema (no `dtypes` given when the Data object was created), pandas infers the
713
+ data types.
714
+
715
+ With a schema, the JSON file is loaded with the schema (e.g. data saved with save_json_data()):
716
+ pandas does not infer the data types (it would e.g. turn the string "1e3" into the number
717
+ 1000.0), and the schema is applied after loading (see apply_schema()): the columns of the
718
+ schema with their data types (also datetimes, if saved in ISO format) and the index
719
+ (`index_columns`). If the schema cannot be applied, the data frame is left unchanged.
720
+
711
721
  Args:
712
722
  json_path (str):
713
723
  The path to the JSON file.
714
724
  convert_dates (bool, optional):
715
725
  Defines whether or not dates should be converted.
716
- The default is False = dates are NOT converted.
726
+ The default is False = dates are NOT converted. With a schema, the datetime
727
+ columns of the schema are converted anyway.
717
728
  index_column (str | None, optional):
718
729
  The Name of the column (i.e. JSON data field) that should
719
730
  become the index in the loaded data frame. Defaults to None.
731
+ Not to be combined with a schema - its index (`index_columns`) is applied.
720
732
  compression (str | None, optional):
721
733
  The compression of the JSON file that is to be decompressed:
722
734
  * gzip (.gz)
@@ -725,6 +737,9 @@ class Data:
725
737
  * xz (.xz)
726
738
  The value for compression should not include the dot.
727
739
  Default is None = no compression.
740
+ replace (bool, optional):
741
+ If True, the loaded data replaces the content of the data frame. If False (default),
742
+ it is appended to the data frame (with a new row index).
728
743
 
729
744
  Returns:
730
745
  bool: False in case an error occurred, True otherwise.
@@ -758,6 +773,8 @@ class Data:
758
773
  path_or_buf=json_path,
759
774
  convert_dates=convert_dates,
760
775
  compression=compression,
776
+ # With a schema the data types are defined by it - pandas must not infer them:
777
+ dtype=not self._schema,
761
778
  )
762
779
 
763
780
  if index_column and index_column not in df.columns:
@@ -769,10 +786,18 @@ class Data:
769
786
 
770
787
  if index_column:
771
788
  df = df.set_index(keys=index_column)
772
- if self._df is None:
789
+ previous_df = self._df
790
+ if replace or self._df is None:
773
791
  self._df = df
774
792
  else:
775
793
  self._df = pd.concat([self._df, df], ignore_index=True)
794
+ if self._schema:
795
+ try:
796
+ self.apply_schema()
797
+ except Exception:
798
+ # apply_schema() has logged the error - keep the data frame as it was:
799
+ self._df = previous_df
800
+ raise
776
801
  self.logger.info(
777
802
  "After loading JSON file -> '%s', the data frame has %s row(s) and %s column(s)",
778
803
  json_path,
@@ -824,9 +849,14 @@ class Data:
824
849
  preserve_index: bool = False,
825
850
  index_column: str = "index",
826
851
  compression: str | None = None,
852
+ indent: int | None = 2,
827
853
  ) -> bool:
828
854
  """Save JSON data from data frame to file.
829
855
 
856
+ Datetimes are saved in ISO format (with millisecond precision). A data frame saved with
857
+ `orient="records"` is loaded again with its data types by load_json_data() of a Data object
858
+ with the same schema.
859
+
830
860
  Args:
831
861
  json_path (str):
832
862
  The path to where the JSON file should be saved.
@@ -851,6 +881,9 @@ class Data:
851
881
  * zip (.zip)
852
882
  * xz (.xz)
853
883
  Default is None = no compression.
884
+ indent (int | None, optional):
885
+ The number of spaces to indent the JSON. None writes compact JSON (smaller files).
886
+ Defaults to 2.
854
887
 
855
888
  Returns:
856
889
  bool:
@@ -874,8 +907,10 @@ class Data:
874
907
  # Save data to JSON file
875
908
  try:
876
909
  if self._df is not None:
877
- if not os.path.exists(os.path.dirname(json_path)):
878
- os.makedirs(os.path.dirname(json_path), exist_ok=True)
910
+ # A file name without directory is saved in the current directory:
911
+ json_directory = os.path.dirname(json_path)
912
+ if json_directory:
913
+ os.makedirs(json_directory, exist_ok=True)
879
914
 
880
915
  # index parameter is only allowed if orient has one of the following values:
881
916
  if orient in ("columns", "index", "table", "split"):
@@ -883,7 +918,7 @@ class Data:
883
918
  path_or_buf=json_path,
884
919
  index=preserve_index,
885
920
  orient=orient,
886
- indent=2,
921
+ indent=indent,
887
922
  compression=compression,
888
923
  date_format="iso",
889
924
  )
@@ -899,7 +934,7 @@ class Data:
899
934
  df_with_index.to_json(
900
935
  path_or_buf=json_path,
901
936
  orient=orient,
902
- indent=2,
937
+ indent=indent,
903
938
  compression=compression,
904
939
  date_format="iso",
905
940
  )
@@ -907,7 +942,7 @@ class Data:
907
942
  self._df.to_json(
908
943
  path_or_buf=json_path,
909
944
  orient=orient,
910
- indent=2,
945
+ indent=indent,
911
946
  compression=compression,
912
947
  date_format="iso",
913
948
  )
@@ -1819,6 +1854,9 @@ class Data:
1819
1854
  def deduplicate(self, unique_fields: list, inplace: bool = True) -> pd.DataFrame:
1820
1855
  """Remove duplicate rows that have all fields in unique_fields in common.
1821
1856
 
1857
+ The index defined for this Data object (`index_columns`) is kept - its rows are selected by it (e.g.
1858
+ with `loc[]`). Without one, the rows are numbered again (0, 1, 2, ...).
1859
+
1822
1860
  Args:
1823
1861
  unique_fields (list):
1824
1862
  Defines the fields for which we want a unique combination for.
@@ -1834,11 +1872,13 @@ class Data:
1834
1872
 
1835
1873
  if inplace:
1836
1874
  self._df.drop_duplicates(subset=unique_fields, inplace=True)
1837
- self._df.reset_index(drop=True, inplace=True)
1875
+ if not self._index_columns:
1876
+ self._df.reset_index(drop=True, inplace=True)
1838
1877
  return self._df
1839
1878
  else:
1840
1879
  df = self._df.drop_duplicates(subset=unique_fields, inplace=False)
1841
- df = df.reset_index(drop=True, inplace=False)
1880
+ if not self._index_columns:
1881
+ df = df.reset_index(drop=True, inplace=False)
1842
1882
  return df
1843
1883
 
1844
1884
  # end method definition