pyxecm 2.0.1__py3-none-any.whl → 2.0.3__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/otmm.py CHANGED
@@ -55,6 +55,13 @@ default_logger = logging.getLogger(MODULE_NAME)
55
55
  class OTMM:
56
56
  """Class OTMM is used to automate data extraction from OTMM."""
57
57
 
58
+ PRODUCT_LOOKUP_DOMAIN = "OTMM.DOMAIN.OTM_PRODUCT"
59
+ PRODUCT_METADATA_TABLE = "OTM.TABLE.PRODUCT_TABLE_FIELD"
60
+ PRODUCT_METADATA_FIELD = "PRODUCT_CHAR_ID"
61
+ PRODUCT_NEW_LOOKUP_DOMAIN = "OTMM.DOMAIN.OTM_PRODUCT_NEW.LU"
62
+ PRODUCT_NEW_METADATA_TABLE = "OTMM.FIELD.PRODUCT_NEW.TAB"
63
+ PRODUCT_NEW_METADATA_FIELD = "OTMM.COLUMN.PRODUCT_NEW.TAB"
64
+
58
65
  logger: logging.Logger = default_logger
59
66
 
60
67
  _config: dict
@@ -69,6 +76,9 @@ class OTMM:
69
76
  _asset_exclusions = None
70
77
  _asset_inclusions = None
71
78
 
79
+ _asset_download_locks: dict = {}
80
+ _asset_download_locks_lock = threading.Lock()
81
+
72
82
  def __init__(
73
83
  self,
74
84
  base_url: str,
@@ -730,6 +740,8 @@ class OTMM:
730
740
  product_id: str,
731
741
  offset: int = 0,
732
742
  limit: int = 200,
743
+ metadata_table_id: str = "OTM.TABLE.PRODUCT_TABLE_FIELD",
744
+ metadata_field_id: str = "PRODUCT_CHAR_ID",
733
745
  ) -> list | None:
734
746
  """Get all Media Assets for a given product (ID).
735
747
 
@@ -744,6 +756,10 @@ class OTMM:
744
756
  Result pagination. Starting ID. Defaults to 0.
745
757
  limit (int, optional):
746
758
  Result pagination. Page length. Defaults to 200.
759
+ metadata_table_id (str, optional):
760
+ Specific descriptor for the metadata table ID in OTMM.
761
+ metadata_field_id (str, optional):
762
+ Specific descriptor for the metadata field ID in OTMM.
747
763
 
748
764
  Returns:
749
765
  dict:
@@ -768,15 +784,16 @@ class OTMM:
768
784
  "search_condition": [
769
785
  {
770
786
  "type": "com.artesia.search.SearchTabularCondition",
771
- "metadata_table_id": "OTM.TABLE.PRODUCT_TABLE_FIELD",
787
+ "metadata_table_id": str(metadata_table_id),
772
788
  "tabular_field_list": [
773
789
  {
774
790
  "type": "com.artesia.search.SearchTabularFieldCondition",
775
- "metadata_field_id": "PRODUCT_CHAR_ID",
791
+ "metadata_field_id": str(metadata_field_id),
776
792
  "relational_operator_id": "ARTESIA.OPERATOR.CHAR.CONTAINS",
777
793
  "value": str(product_id),
778
794
  "left_paren": "(",
779
795
  "right_paren": ")",
796
+ # "relational_operator": "or"
780
797
  },
781
798
  ],
782
799
  },
@@ -807,6 +824,7 @@ class OTMM:
807
824
 
808
825
  # Iterate through all result pages:
809
826
  while hits_remaining > 0:
827
+ # Calculate offset for next page:
810
828
  flattened_data["after"] += hits
811
829
  search_result = self.search_assets(payload=flattened_data)
812
830
 
@@ -849,66 +867,101 @@ class OTMM:
849
867
 
850
868
  """
851
869
 
870
+ # Acquire per-asset lock
871
+ with self._asset_download_locks_lock:
872
+ if asset_id not in self._asset_download_locks:
873
+ self._asset_download_locks[asset_id] = threading.Lock()
874
+ asset_lock = self._asset_download_locks[asset_id]
875
+
852
876
  request_url = download_url if download_url else self.config()["assetsUrl"] + "/" + asset_id + "/contents"
853
877
 
854
878
  # We use the Asset ID as the filename to avoid name collisions:
855
879
  file_name = os.path.join(self._download_dir, asset_id)
856
880
 
857
- if os.path.exists(file_name):
858
- if asset_modification_date:
859
- file_mod_time = datetime.fromtimestamp(os.path.getmtime(file_name), tz=timezone.utc)
860
- date_last_updated = datetime.strptime(
861
- asset_modification_date,
862
- "%Y-%m-%dT%H:%M:%SZ",
863
- ).replace(tzinfo=timezone.utc)
864
- download_up_to_date: bool = file_mod_time >= date_last_updated
865
- else:
866
- download_up_to_date = True
881
+ success = False
882
+
883
+ with asset_lock:
884
+ try:
885
+ if os.path.exists(file_name):
886
+ if asset_modification_date:
887
+ file_mod_time = datetime.fromtimestamp(os.path.getmtime(file_name), tz=timezone.utc)
888
+ date_last_updated = datetime.strptime(
889
+ asset_modification_date,
890
+ "%Y-%m-%dT%H:%M:%SZ",
891
+ ).replace(tzinfo=timezone.utc)
892
+ download_up_to_date: bool = file_mod_time >= date_last_updated
893
+ else:
894
+ download_up_to_date = True
895
+
896
+ if download_up_to_date:
897
+ self.logger.debug(
898
+ "Asset -> '%s' (%s) has been downloaded before and is up to date. Skipping download to -> %s...",
899
+ asset_name,
900
+ asset_id,
901
+ file_name,
902
+ )
903
+ success = True
904
+ else:
905
+ self.logger.debug(
906
+ "Asset -> '%s' (%s) has been downloaded before, but it is outdated. Updating download to -> %s...",
907
+ asset_name,
908
+ asset_id,
909
+ file_name,
910
+ )
911
+ os.remove(file_name)
912
+
913
+ # We only download if we have no success yet.
914
+ # Success means the file is already there and we don't
915
+ # need to update it.
916
+ if not success:
917
+ if not os.path.exists(self._download_dir):
918
+ # Create the directory
919
+ os.makedirs(self._download_dir)
867
920
 
868
- if download_up_to_date:
869
- self.logger.debug(
870
- "Asset -> '%s' (%s) has been downloaded before and is up to date. Skipping download to -> %s...",
871
- asset_name,
872
- asset_id,
873
- file_name,
874
- )
875
- return True
876
- else:
877
- self.logger.debug(
878
- "Asset -> '%s' (%s) has been downloaded before, but it is outdated. Updating download to -> %s...",
879
- asset_name,
880
- asset_id,
881
- file_name,
921
+ self.logger.info(
922
+ "Downloading asset -> '%s' (%s) to -> %s...",
923
+ asset_name,
924
+ asset_id,
925
+ file_name,
926
+ )
927
+ response = self._session.get(request_url, stream=True)
928
+ response.raise_for_status()
929
+ with open(file_name, "wb") as f:
930
+ for chunk in response.iter_content(chunk_size=8192):
931
+ f.write(chunk)
932
+ success = True
933
+ # end try:
934
+
935
+ except HTTPError as http_error:
936
+ self.logger.error("HTTP error requesting -> %s; error -> %s", request_url, str(http_error))
937
+ except RequestException:
938
+ self.logger.error("Request error requesting -> %s!", request_url)
939
+ except OSError as os_error:
940
+ self.logger.error(
941
+ "File system error while writing to file -> '%s'; error -> %s", file_name, str(os_error)
882
942
  )
883
- os.remove(file_name)
884
-
885
- try:
886
- if not os.path.exists(self._download_dir):
887
- # Create the directory
888
- os.makedirs(self._download_dir)
889
-
890
- self.logger.info(
891
- "Downloading asset -> '%s' (%s) to -> %s...",
892
- asset_name,
893
- asset_id,
894
- file_name,
895
- )
896
- response = self._session.get(request_url, stream=True)
897
- response.raise_for_status()
898
- with open(file_name, "wb") as f:
899
- for chunk in response.iter_content(chunk_size=8192):
900
- f.write(chunk)
901
- except HTTPError as http_error:
902
- self.logger.error("HTTP error requesting -> %s; error -> %s", request_url, str(http_error))
903
- return False
904
- except RequestException:
905
- self.logger.error("Request error requesting -> %s!", request_url)
906
- return False
907
- except Exception:
908
- self.logger.error("Unexpected error requesting -> %s!", request_url)
909
- return False
910
-
911
- return True
943
+ except Exception:
944
+ self.logger.error("Unexpected error requesting -> %s!", request_url)
945
+ # end with asset_lock:
946
+
947
+ # Cleanup: Remove the lock for this asset if it's not currently in use by any thread.
948
+ with self._asset_download_locks_lock:
949
+ # Check if a lock exists for this asset_id. It is IMPORTANT to reassign the variable here!
950
+ asset_lock = self._asset_download_locks.get(asset_id)
951
+ if asset_lock and asset_lock.acquire(blocking=False):
952
+ # Try to acquire the lock immediately without waiting.
953
+ # If this succeeds, it means no thread currently holds or is waiting for this lock.
954
+ try:
955
+ # Safe to delete the lock now because no one else is using it
956
+ del self._asset_download_locks[asset_id]
957
+ finally:
958
+ # Release the lock we just acquired to leave system state consistent
959
+ asset_lock.release()
960
+ # If acquire() failed, some other thread is still using or waiting for the lock,
961
+ # so do not delete it yet.
962
+ # end self._asset_download_locks_lock:
963
+
964
+ return success
912
965
 
913
966
  # end method definition
914
967
 
@@ -932,14 +985,17 @@ class OTMM:
932
985
 
933
986
  file_name = os.path.join(self._download_dir, asset_id)
934
987
 
935
- if os.path.exists(file_name):
936
- self.logger.debug(
937
- "Deleting stale download file -> '%s' for asset %s...",
938
- file_name,
939
- "-> '{}' ({})".format(asset_name, asset_id) if asset_name else "-> {}".format(asset_id),
940
- )
941
- os.remove(file_name)
942
- return True
988
+ try:
989
+ if os.path.exists(file_name):
990
+ self.logger.debug(
991
+ "Deleting stale download file -> '%s' for asset %s...",
992
+ file_name,
993
+ "-> '{}' ({})".format(asset_name, asset_id) if asset_name else "-> {}".format(asset_id),
994
+ )
995
+ os.remove(file_name)
996
+ return True
997
+ except OSError as os_error:
998
+ self.logger.error("File system error while deleting file -> '%s'; error -> %s", file_name, str(os_error))
943
999
 
944
1000
  return False
945
1001
 
@@ -1505,7 +1561,10 @@ class OTMM:
1505
1561
  asset_list = []
1506
1562
 
1507
1563
  if load_products:
1508
- products = self.get_products() # dictionary with key = name and value = ID
1564
+ #
1565
+ # Collect assets for old (non-rebranded) products:
1566
+ #
1567
+ products = self.get_products(domain=OTMM.PRODUCT_LOOKUP_DOMAIN) # dictionary with key = name and value = ID
1509
1568
 
1510
1569
  if self._product_inclusions is not None:
1511
1570
  products_filtered = {}
@@ -1538,7 +1597,11 @@ class OTMM:
1538
1597
  product_name,
1539
1598
  )
1540
1599
 
1541
- assets = self.get_product_assets(product_id)
1600
+ assets = self.get_product_assets(
1601
+ product_id=product_id,
1602
+ metadata_table_id=OTMM.PRODUCT_METADATA_TABLE,
1603
+ metadata_field_id=OTMM.PRODUCT_METADATA_FIELD,
1604
+ )
1542
1605
 
1543
1606
  if not assets:
1544
1607
  self.logger.info(
@@ -1557,6 +1620,67 @@ class OTMM:
1557
1620
  # attribute for this:
1558
1621
  asset_list += [asset for asset in assets if "content_size" in asset]
1559
1622
 
1623
+ #
1624
+ # Collect assets for new (rebranded) products:
1625
+ #
1626
+ products = self.get_products(
1627
+ domain=OTMM.PRODUCT_NEW_LOOKUP_DOMAIN
1628
+ ) # dictionary with key = name and value = ID
1629
+
1630
+ if self._product_inclusions is not None:
1631
+ products_filtered = {}
1632
+ self.logger.info(
1633
+ "Apply include filter on products -> %s",
1634
+ str(self._product_inclusions),
1635
+ )
1636
+ for key in self._product_inclusions:
1637
+ if key in products:
1638
+ products_filtered[key] = products[key]
1639
+
1640
+ products = products_filtered
1641
+
1642
+ if self._product_exclusions:
1643
+ self.logger.info(
1644
+ "Excluding products -> %s",
1645
+ str(self._product_exclusions),
1646
+ )
1647
+ for key in self._product_exclusions:
1648
+ # pop(key, None) will remove the key if it exists,
1649
+ # and do nothing if it doesn't:
1650
+ products.pop(key, None)
1651
+
1652
+ for product_name, product_id in products.items():
1653
+ if "DO NOT USE" in product_name:
1654
+ continue
1655
+
1656
+ self.logger.info(
1657
+ "Processing assets for product (rebranded) -> '%s'...",
1658
+ product_name,
1659
+ )
1660
+
1661
+ assets = self.get_product_assets(
1662
+ product_id=product_id,
1663
+ metadata_table_id=OTMM.PRODUCT_NEW_METADATA_TABLE,
1664
+ metadata_field_id=OTMM.PRODUCT_NEW_METADATA_FIELD,
1665
+ )
1666
+
1667
+ if not assets:
1668
+ self.logger.info(
1669
+ "Found no assets for product (rebranded) -> '%s'. Skipping it...",
1670
+ product_name,
1671
+ )
1672
+ continue
1673
+
1674
+ # We enrich the dictionary with tags for workspace type and
1675
+ # workspace name for later bulk processing:
1676
+ for asset in assets:
1677
+ asset["workspace_type"] = "Product"
1678
+ asset["workspace_name"] = product_name
1679
+
1680
+ # Filter out assets that are not files - we use the content size
1681
+ # attribute for this:
1682
+ asset_list += [asset for asset in assets if "content_size" in asset]
1683
+
1560
1684
  if load_business_units:
1561
1685
  business_units = self.get_business_units()
1562
1686
 
@@ -1806,7 +1930,7 @@ class OTMM:
1806
1930
  asset_deleted = asset.get("deleted", False)
1807
1931
  asset_expired = asset.get("expired", False)
1808
1932
 
1809
- # We can skip the_download_ of deleted or expired assets,
1933
+ # We can skip the _download_ of deleted or expired assets,
1810
1934
  # but we still want to have them in the Data Frame for
1811
1935
  # bulk processing (to remove them from OTCS)
1812
1936
  if download_assets and asset.get("content_size", 0) > 0 and not asset_deleted and not asset_expired:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyxecm
3
- Version: 2.0.1
3
+ Version: 2.0.3
4
4
  Summary: A Python library to interact with Opentext Extended ECM REST API
5
5
  Author-email: Kai Gatzweiler <kgatzweiler@opentext.com>, "Dr. Marc Diefenbruch" <mdiefenb@opentext.com>
6
6
  Project-URL: Homepage, https://github.com/opentext/pyxecm
@@ -36,9 +36,7 @@ Requires-Dist: asyncio
36
36
  Requires-Dist: jinja2
37
37
  Requires-Dist: prometheus-fastapi-instrumentator
38
38
  Requires-Dist: psycopg[binary,pool]>=3.2.6
39
- Provides-Extra: browserautomation
40
- Requires-Dist: chromedriver_autoinstaller; extra == "browserautomation"
41
- Requires-Dist: playwright>=1.52.0; extra == "browserautomation"
39
+ Requires-Dist: playwright>=1.52.0
42
40
  Provides-Extra: dataloader
43
41
  Requires-Dist: pandas; extra == "dataloader"
44
42
  Requires-Dist: pyyaml; extra == "dataloader"
@@ -48,7 +46,7 @@ Requires-Dist: shapely; extra == "dataloader"
48
46
  Requires-Dist: cartopy; extra == "dataloader"
49
47
  Requires-Dist: psycopg; extra == "dataloader"
50
48
  Provides-Extra: sap
51
- Requires-Dist: pyrfc==2.8.3; extra == "sap"
49
+ Requires-Dist: pyrfc==3.3.1; extra == "sap"
52
50
  Provides-Extra: profiling
53
51
  Requires-Dist: pyinstrument; extra == "profiling"
54
52
  Dynamic: license-file
@@ -63,7 +61,6 @@ Detailed documentation of this package is available [here](https://opentext.gith
63
61
 
64
62
  Install pyxecm with the desired extras into your python environment, extra options are:
65
63
 
66
- - browserautomation
67
64
  - dataloader
68
65
  - sap
69
66
 
@@ -1,51 +1,53 @@
1
- pyxecm/__init__.py,sha256=2xgg6YUuDq-bmz6eAmdACOf2R1Pts25R4ezN8j_9KzI,422
2
- pyxecm/avts.py,sha256=GJZ_oMTmJUlJLHBHG3GhOBM2tShnAC_DxrDHJCct_Zc,56892
1
+ pyxecm/__init__.py,sha256=KNvTFbDVHylf8Ub28osm5Dw0W-ck-94y27lIBuE32RE,441
2
+ pyxecm/avts.py,sha256=J-W7JYrYv7JSI9whZ5iCHtim6vNrTZCXa1oizUWIc0Y,56952
3
3
  pyxecm/coreshare.py,sha256=wIwJk_t29I4oxwn0T67Cy8Pyl7XpjrNDwN8_udXldF4,93298
4
4
  pyxecm/otac.py,sha256=gXoZc-mkI1XoR_mPmDy3up58C1mTYf76rOaSsC4xOHg,22847
5
- pyxecm/otawp.py,sha256=CQ6o-XUEFAyOsLaAaibI4CbhC2MBe5pu1DJVhTFYfQg,112705
6
- pyxecm/otca.py,sha256=B3hdaNZMjz0gJygcTpQ1GBd8LjbS21bAWcQnZXin6dM,19804
7
- pyxecm/otcs.py,sha256=hs52ILgBCgTh-JWn-CzeRmlQ0G3cOgGGf_RnaxgefcU,588082
8
- pyxecm/otds.py,sha256=sO0b9FRdSLxghP74JQeWMofR2ekqjrRDNha6UvRoY44,184648
5
+ pyxecm/otawp.py,sha256=JA4LGvbqRvXxc7-7pE1YSLO5JxI3rr9rJdMmuUItrNM,112890
6
+ pyxecm/otca.py,sha256=BiX7CBkr8necC8LEFEAEcqsmtbDaXCiMHXBHfrIIJK0,25470
7
+ pyxecm/otcs.py,sha256=e7Jn89iKBps7DDBJONjPRhtQ0EgUAh7jnBv_IBOo1uk,598467
8
+ pyxecm/otds.py,sha256=I1lhL_7u446Eb2lAWrLeddGbXKLzm96rvcxbG2lVPQ4,184736
9
9
  pyxecm/otiv.py,sha256=I5lt4sz7TN3W7BCstCKQY2WQnei-t0tXdM4QjRQRmWI,2358
10
- pyxecm/otmm.py,sha256=ovjCfzoeuxH5R7v7f_GVSEn7zvaN1sSHsrtQ_PRLeP8,74819
10
+ pyxecm/otkd.py,sha256=JMpUa99vtTK7PfCnMJjt6lUyK6C6Aq1i1zjMROI9cvQ,47480
11
+ pyxecm/otmm.py,sha256=ryH1Q9yHDXlZ6ytBMe8Z0tJaT8S7S80JaZdNIsPHmO0,80797
11
12
  pyxecm/otpd.py,sha256=6rfCoO5Jm6pETCyp6aKYNTyvI1z44wP76zS-qRotyVk,14831
12
13
  pyxecm/customizer/__init__.py,sha256=T39r7ylBXhECDaCQeV2rmgIEO4WJzuC61FSK51cgzBI,512
13
14
  pyxecm/customizer/__main__.py,sha256=29JhUCUAcKPohCukMksS02n6J_a8HPkeReWvQ59_Hhs,1499
14
- pyxecm/customizer/browser_automation.py,sha256=TMKq-D9dh_3y-O365Gi9kHcjcPrHTgKBTo5BHOhOTrQ,38767
15
- pyxecm/customizer/customizer.py,sha256=Db8JbhZyvWGyoV8u_sroQmIb_Juh22vFY0v8hK5t5ms,77540
15
+ pyxecm/customizer/browser_automation.py,sha256=FMfYoDrq_fFWzqGBxgVu2M9LPr2K5TXNOhLRgY95ZG4,53679
16
+ pyxecm/customizer/customizer.py,sha256=E7wwo5VwiC8Sc920b-C0rzQos0eG2DQoBrEHwADrcu4,80967
16
17
  pyxecm/customizer/exceptions.py,sha256=YXX0in-UGXfJb6Fei01JQetOtAHqUiITb49OTSaZRkE,909
17
- pyxecm/customizer/guidewire.py,sha256=mD0zqq4HBS_E4jxqFody94I95bl51gGp4NSKkQ2OW1w,43241
18
+ pyxecm/customizer/guidewire.py,sha256=fwVrW05CP_oTuUhFG12BhjyDhOKugAqn_8cOAzEpvWk,49709
18
19
  pyxecm/customizer/k8s.py,sha256=SxnGXSqFtC4b5Cdd3wf3c5vGyhxbn1LDjE2MUlxeZcE,56187
19
20
  pyxecm/customizer/log.py,sha256=N-EZc822AYWniUb3PCQ8vPZ9ki61ZzMfSprdTDP-jKY,3015
20
21
  pyxecm/customizer/m365.py,sha256=nq-90A2gMn2BxeFfqHtXxRmI5FVc6Iv3GV-09izD__U,213366
21
22
  pyxecm/customizer/nhc.py,sha256=Yu2t0lc3CUqpDeZ4RWTNZCsNRz9hG4Bx8-p21ZLYRHM,45647
22
23
  pyxecm/customizer/openapi.py,sha256=dI5YQzxBVVeT0TWMzEfOxyPAnM4fAsBt3F1MrAK9jLg,7161
23
- pyxecm/customizer/payload.py,sha256=bAinqc5G-geK8GL1e3Futom3g1Ht6Nb9P0TmGvqoyGU,1289767
24
+ pyxecm/customizer/payload.py,sha256=neCrAyZQfKce0zg4_WFa2kgqPuwB_5WlMWdixFS2ROo,1307396
24
25
  pyxecm/customizer/pht.py,sha256=CSWHdmR--oNQ7fCRVK0XzAEUIelmmsyuzE5nr0_2-gI,48083
25
26
  pyxecm/customizer/salesforce.py,sha256=MB_8mDUikettUGQpcuG19QQyeNjxq1Z0BF6asQGkgZ8,63518
26
27
  pyxecm/customizer/sap.py,sha256=4smnL-epWe9tVrPI7AK1lVUqaEAbyphPWeExV7UfOfc,6388
27
28
  pyxecm/customizer/servicenow.py,sha256=3eUKtfPEYpT03Mg2phkG41jpJJsa6uiX3AcPyuw0tCI,65373
28
- pyxecm/customizer/settings.py,sha256=8m_ey-ujJufY1BzCjioyOm9UFDiWKZnBL75HdJ1PwAg,20219
29
+ pyxecm/customizer/settings.py,sha256=wzp5rud6o6s4s61RPkfRPXLlfdZmbi6WEQMk6eEwWZw,21284
29
30
  pyxecm/customizer/successfactors.py,sha256=iRoOCiZ9NXSGCzpxViavQKPrHAU1PxMvh7gK8mpRY8o,38299
30
31
  pyxecm/customizer/translate.py,sha256=O2I928YQx1F2rucI3eQM2k8oybVfsbUHFikrNt5PAQ8,4788
31
32
  pyxecm/customizer/api/__init__.py,sha256=8oXxEEFSu2afpyQURpxMA2qTZAB3MUdbBrndogDn5Oc,92
32
33
  pyxecm/customizer/api/__main__.py,sha256=0DvtXJUeOOvpRfcq67CZ0HhE5gI4vLD1UTCAfNXQrT4,128
33
- pyxecm/customizer/api/app.py,sha256=Cude5ZlPsCSqGRvXC4bjH3GFx4K5Se7gAYeqaWVW3Mg,5241
34
- pyxecm/customizer/api/settings.py,sha256=CghfsdpMRPmZizVb7VBpEELEEj1cwJnoB2HUtdq7Xvw,4608
34
+ pyxecm/customizer/api/app.py,sha256=PQ-f5xn4ntIDtOxvU6WwMxejEUk2dihJdn4uS-JIh94,5283
35
+ pyxecm/customizer/api/settings.py,sha256=KDXqCXILqZLhXk2a66z3owm-hMWzmHiP-gWx2--N5Z8,4782
35
36
  pyxecm/customizer/api/auth/__init__.py,sha256=ranS8DEliiC4Mlo-bVva9Maj5q08I0I6NJflUFIvOvw,19
36
- pyxecm/customizer/api/auth/functions.py,sha256=O-vIK56oX9NSlGHLgixNKOorqVqAN_RKQWFYV2SBQTc,2716
37
+ pyxecm/customizer/api/auth/functions.py,sha256=gHxGnm0ehF0ysyMLftPQZV6a79CxGtmaXryTJjF7Miw,3092
37
38
  pyxecm/customizer/api/auth/models.py,sha256=lKebaIHbALZ10quCCKQ3wf7w8V6k84tFXcPV1zbQsS0,271
38
39
  pyxecm/customizer/api/auth/router.py,sha256=-dxWfQn0KjL737j4zbJhc4eM-lg2RO2CIKERYz-WXBI,2227
39
40
  pyxecm/customizer/api/common/__init__.py,sha256=ranS8DEliiC4Mlo-bVva9Maj5q08I0I6NJflUFIvOvw,19
40
- pyxecm/customizer/api/common/functions.py,sha256=lqucCiSxUHKShDVFXS51AQZzRbLoKq0cecuxNZ-Egzo,971
41
+ pyxecm/customizer/api/common/functions.py,sha256=OUelyeN476ABu8nLL3hNsJ5qFm6kRpJb-2LLG8s1y_I,2644
41
42
  pyxecm/customizer/api/common/metrics.py,sha256=bJxXqExijgunlGj-WXkuaJKFNkewChL4j790U5q28ic,2719
42
43
  pyxecm/customizer/api/common/models.py,sha256=c76ysWUt40A875DirsFMXttxwjHvBYvjuOVEXePSZ9k,456
43
44
  pyxecm/customizer/api/common/payload_list.py,sha256=g87WMICfJ3CHJJNUBy37AuYGpdavgConD98ZGYaOBgg,28674
44
- pyxecm/customizer/api/common/router.py,sha256=QdvpERmaZmXa1NsqyaqjbpA-Du5Z1NDqqngmx7hlt3U,2148
45
+ pyxecm/customizer/api/common/router.py,sha256=mMokquT-_MVsKO2xzh4UvXUttCpZXXVxkWL1p1wP13E,3507
45
46
  pyxecm/customizer/api/terminal/__init__.py,sha256=RHlTzdGeOY0_dvvNZS_wq6uJcY1OatIUHwCxAUwklaE,43
46
- pyxecm/customizer/api/terminal/router.py,sha256=o6Mn6SQFrUSlNArOYgKbsbW9JECi98ACwKedd60tU1g,2663
47
+ pyxecm/customizer/api/terminal/router.py,sha256=sJG85uKGxcK2XH0oP0xPVAAEdB46Jr21WJ1ruaMsnGc,3592
47
48
  pyxecm/customizer/api/v1_csai/__init__.py,sha256=ranS8DEliiC4Mlo-bVva9Maj5q08I0I6NJflUFIvOvw,19
48
- pyxecm/customizer/api/v1_csai/router.py,sha256=sekkoPkkUgo9NBlAlw-ZH2Lw_LNI4UBRjJeW-grvhek,3037
49
+ pyxecm/customizer/api/v1_csai/models.py,sha256=c0VEJP09jsTW83dpHtZnooNvg3SZNuyMHEBCgWQDJ1s,489
50
+ pyxecm/customizer/api/v1_csai/router.py,sha256=24_BQMa9AV5M4cAFmMQEM8YCfVc6uIf4TWDv6ZX7kh8,3892
49
51
  pyxecm/customizer/api/v1_maintenance/__init__.py,sha256=ranS8DEliiC4Mlo-bVva9Maj5q08I0I6NJflUFIvOvw,19
50
52
  pyxecm/customizer/api/v1_maintenance/functions.py,sha256=k3-4axvU4sKSAVRyO8bhfm5-cT9uPi0mbNoMwzPJLBU,3012
51
53
  pyxecm/customizer/api/v1_maintenance/models.py,sha256=HcrhBg9hhRZg4Y6xuus9T8SCNIsL8ZxX1uuaRrBnFBw,271
@@ -54,7 +56,7 @@ pyxecm/customizer/api/v1_otcs/__init__.py,sha256=ranS8DEliiC4Mlo-bVva9Maj5q08I0I
54
56
  pyxecm/customizer/api/v1_otcs/functions.py,sha256=GipDzdz1xC_tMDXLcB40H9miL-OyQ-BAv4kGYbjb20s,1963
55
57
  pyxecm/customizer/api/v1_otcs/router.py,sha256=IPsA9Zt9V0zNPZVidRAONNjtdS7gcoZyVBrTc6PqiBw,6854
56
58
  pyxecm/customizer/api/v1_payload/__init__.py,sha256=ranS8DEliiC4Mlo-bVva9Maj5q08I0I6NJflUFIvOvw,19
57
- pyxecm/customizer/api/v1_payload/functions.py,sha256=3jcPiu4hN-UT6MFDSpOgUdhKxENE9ykG6Xtl3V0cGHM,5995
59
+ pyxecm/customizer/api/v1_payload/functions.py,sha256=lLu42v8lWT5BhogkreJI9UTHA7FnRWmFgk56_2RR2-Y,6162
58
60
  pyxecm/customizer/api/v1_payload/models.py,sha256=eD9A2K23L_cGhBDTO1FGVGJMQ1COaYWmcr-ELE66tOA,1006
59
61
  pyxecm/customizer/api/v1_payload/router.py,sha256=VQQff0KpZI3jgOkXi-z14GX5J_vZHMg3PF7wyCsnM0E,15573
60
62
  pyxecm/helper/__init__.py,sha256=B35HdIZC9wZ1m_sx4NOPggyFFJoXmFuA1T2tuPDU1W8,246
@@ -62,15 +64,15 @@ pyxecm/helper/assoc.py,sha256=yOh4GFZEkTfIaVVrHBr4bhMtIv-E76oDVWGqaUdVqyA,7321
62
64
  pyxecm/helper/data.py,sha256=Mit8qYWDU_YgK98UtLFPEr2ZNE5xk5lJS_wjyv63-jk,123178
63
65
  pyxecm/helper/logadapter.py,sha256=ExrIKpA2JKegkTIky0wDDTgPYSvVmwQLrDh5jko_6hQ,724
64
66
  pyxecm/helper/web.py,sha256=k010t9gzBflPxnxUdIGvulL69ASzdCEDY-UQ3JXQZ1w,13380
65
- pyxecm/helper/xml.py,sha256=SzuP1Y24om3Q-EpSMlwlX6FexmCVPOA4doPMANk7r50,45640
67
+ pyxecm/helper/xml.py,sha256=QhavLKGl_lwSasq1LF05ftj0OIVr04lyNE3bTqtemiA,45641
66
68
  pyxecm/maintenance_page/__init__.py,sha256=09to4a8rygOIN6Z1SCN9tLtW1qPUC78Z-scDbpt0E-Q,136
67
69
  pyxecm/maintenance_page/__main__.py,sha256=lFBgG3PkXz8ddYK_3wb376IoUAvpDEACVbrmx7xrFc8,158
68
70
  pyxecm/maintenance_page/app.py,sha256=aJUiZc2kNmtrl0sfg10rXBySgNBBSBCfyC6_G2Q17EU,1698
69
71
  pyxecm/maintenance_page/settings.py,sha256=VRReZeNdza7i7lgnQ3wVojzoPDGXZnzr5rsMJY1EnHk,955
70
72
  pyxecm/maintenance_page/static/favicon.avif,sha256=POuuPXKbjHVP3BjNLpFIx8MfkQg5z2LZA7sK6lejARg,1543
71
73
  pyxecm/maintenance_page/templates/maintenance.html,sha256=0OAinv7jmj3Aa7GNCIoBLDGEMW1-_HdJfwWmkmb6Cs4,5581
72
- pyxecm-2.0.1.dist-info/licenses/LICENSE,sha256=z5DWWd5cHmQYJnq4BDt1bmVQjuXY1Qsp6y0v5ETCw-s,11360
73
- pyxecm-2.0.1.dist-info/METADATA,sha256=oRv1LtX0vbWA_X5ZaAmpl3eolqDT1E-57mFapxjPSSo,4029
74
- pyxecm-2.0.1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
75
- pyxecm-2.0.1.dist-info/top_level.txt,sha256=TGak3_dYN67ugKFbmRxRG1leDyOt0T7dypjdX4Ij1WE,7
76
- pyxecm-2.0.1.dist-info/RECORD,,
74
+ pyxecm-2.0.3.dist-info/licenses/LICENSE,sha256=z5DWWd5cHmQYJnq4BDt1bmVQjuXY1Qsp6y0v5ETCw-s,11360
75
+ pyxecm-2.0.3.dist-info/METADATA,sha256=5pWNI1hGMw8gVBluaFikWxJCY8xqrWjnkHM_GbusoCU,3869
76
+ pyxecm-2.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ pyxecm-2.0.3.dist-info/top_level.txt,sha256=TGak3_dYN67ugKFbmRxRG1leDyOt0T7dypjdX4Ij1WE,7
78
+ pyxecm-2.0.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5