pvw-cli 1.2.3__py3-none-any.whl → 1.2.5__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 pvw-cli might be problematic. Click here for more details.

@@ -4,6 +4,7 @@ Implements comprehensive Unified Catalog functionality
4
4
  """
5
5
 
6
6
  from .endpoint import Endpoint, decorator, get_json, no_api_call_decorator
7
+ from .endpoints import ENDPOINTS, get_api_version_params
7
8
  import os
8
9
  import json
9
10
 
@@ -23,7 +24,7 @@ class UnifiedCatalogClient(Endpoint):
23
24
  def get_governance_domains(self, args):
24
25
  """Get all governance domains."""
25
26
  self.method = "GET"
26
- self.endpoint = "/datagovernance/catalog/businessdomains"
27
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_domains"]
27
28
  self.params = {}
28
29
 
29
30
  @decorator
@@ -31,14 +32,14 @@ class UnifiedCatalogClient(Endpoint):
31
32
  """Get a governance domain by ID."""
32
33
  domain_id = args.get("--domain-id", [""])[0]
33
34
  self.method = "GET"
34
- self.endpoint = f"/datagovernance/catalog/businessdomains/{domain_id}"
35
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_domain"].format(domainId=domain_id)
35
36
  self.params = {}
36
37
 
37
38
  @decorator
38
39
  def create_governance_domain(self, args):
39
40
  """Create a new governance domain."""
40
41
  self.method = "POST"
41
- self.endpoint = "/datagovernance/catalog/businessdomains"
42
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_domains"]
42
43
  # Allow payload file to fully control creation; otherwise build payload from flags
43
44
  payload = get_json(args, "--payloadFile")
44
45
  if not payload:
@@ -61,7 +62,7 @@ class UnifiedCatalogClient(Endpoint):
61
62
  """Update a governance domain."""
62
63
  domain_id = args.get("--domain-id", [""])[0]
63
64
  self.method = "PUT"
64
- self.endpoint = f"/datagovernance/catalog/businessdomains/{domain_id}"
65
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_domain"].format(domainId=domain_id)
65
66
  self.payload = get_json(args, "--payloadFile") or {
66
67
  "name": args.get("--name", [""])[0],
67
68
  "description": args.get("--description", [""])[0],
@@ -74,7 +75,7 @@ class UnifiedCatalogClient(Endpoint):
74
75
  """Delete a governance domain."""
75
76
  domain_id = args.get("--domain-id", [""])[0]
76
77
  self.method = "DELETE"
77
- self.endpoint = f"/datagovernance/catalog/businessdomains/{domain_id}"
78
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_domain"].format(domainId=domain_id)
78
79
  self.params = {}
79
80
 
80
81
  # ========================================
@@ -84,7 +85,7 @@ class UnifiedCatalogClient(Endpoint):
84
85
  def get_data_products(self, args):
85
86
  """Get all data products."""
86
87
  self.method = "GET"
87
- self.endpoint = "/datagovernance/catalog/dataproducts"
88
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_data_products"]
88
89
 
89
90
  # Add optional filters
90
91
  domain_id = args.get("--governance-domain-id", [""])[0] or args.get("--domain-id", [""])[0]
@@ -98,14 +99,14 @@ class UnifiedCatalogClient(Endpoint):
98
99
  """Get a data product by ID."""
99
100
  product_id = args.get("--product-id", [""])[0]
100
101
  self.method = "GET"
101
- self.endpoint = f"/datagovernance/catalog/dataproducts/{product_id}"
102
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_data_product"].format(productId=product_id)
102
103
  self.params = {}
103
104
 
104
105
  @decorator
105
106
  def create_data_product(self, args):
106
107
  """Create a new data product."""
107
108
  self.method = "POST"
108
- self.endpoint = "/datagovernance/catalog/dataproducts"
109
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_data_products"]
109
110
 
110
111
  # Get domain ID
111
112
  domain_id = args.get("--governance-domain-id", [""])[0] or args.get("--domain-id", [""])[0]
@@ -193,7 +194,7 @@ class UnifiedCatalogClient(Endpoint):
193
194
 
194
195
  # Now perform the PUT request
195
196
  self.method = "PUT"
196
- self.endpoint = f"/datagovernance/catalog/dataproducts/{product_id}"
197
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_data_product"].format(productId=product_id)
197
198
  self.payload = payload
198
199
 
199
200
  @decorator
@@ -201,9 +202,145 @@ class UnifiedCatalogClient(Endpoint):
201
202
  """Delete a data product."""
202
203
  product_id = args.get("--product-id", [""])[0]
203
204
  self.method = "DELETE"
204
- self.endpoint = f"/datagovernance/catalog/dataproducts/{product_id}"
205
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_data_product"].format(productId=product_id)
205
206
  self.params = {}
206
207
 
208
+ @decorator
209
+ def create_data_product_relationship(self, args):
210
+ """Create a relationship for a data product.
211
+
212
+ Creates a relationship between a data product and another entity
213
+ (e.g., critical data column, term, asset).
214
+
215
+ API: POST /datagovernance/catalog/dataproducts/{productId}/relationships
216
+ API Version: 2025-09-15-preview
217
+ """
218
+ product_id = args.get("--product-id", [""])[0]
219
+ entity_type = args.get("--entity-type", [""])[0] # e.g., "CRITICALDATACOLUMN"
220
+ entity_id = args.get("--entity-id", [""])[0]
221
+ asset_id = args.get("--asset-id", [""])[0] if args.get("--asset-id") else entity_id
222
+ relationship_type = args.get("--relationship-type", ["Related"])[0]
223
+ description = args.get("--description", [""])[0]
224
+
225
+ # Build request body
226
+ payload = {
227
+ "relationship1": {
228
+ "description": description,
229
+ "relationshipType": relationship_type,
230
+ "assetId": asset_id,
231
+ "entityId": entity_id
232
+ }
233
+ }
234
+
235
+ self.method = "POST"
236
+ self.endpoint = ENDPOINTS["unified_catalog"]["create_data_product_relationship"].format(
237
+ productId=product_id
238
+ )
239
+ self.params = {"entityType": entity_type.upper()}
240
+ self.payload = payload
241
+
242
+ @decorator
243
+ def get_data_product_relationships(self, args):
244
+ """List relationships for a data product.
245
+
246
+ Lists all relationships for a data product, optionally filtered by entity type.
247
+
248
+ API: GET /datagovernance/catalog/dataproducts/{productId}/relationships
249
+ API Version: 2025-09-15-preview
250
+ """
251
+ product_id = args.get("--product-id", [""])[0]
252
+ entity_type = args.get("--entity-type", [""])[0] if args.get("--entity-type") else None
253
+
254
+ self.method = "GET"
255
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_data_product_relationships"].format(
256
+ productId=product_id
257
+ )
258
+
259
+ # Entity type is optional filter
260
+ if entity_type:
261
+ self.params = {"entityType": entity_type.upper()}
262
+ else:
263
+ self.params = {}
264
+
265
+ @decorator
266
+ def delete_data_product_relationship(self, args):
267
+ """Delete a relationship between a data product and an entity.
268
+
269
+ Deletes a specific relationship identified by entity type and entity ID.
270
+
271
+ API: DELETE /datagovernance/catalog/dataproducts/{productId}/relationships
272
+ API Version: 2025-09-15-preview
273
+ """
274
+ product_id = args.get("--product-id", [""])[0]
275
+ entity_type = args.get("--entity-type", [""])[0]
276
+ entity_id = args.get("--entity-id", [""])[0]
277
+
278
+ self.method = "DELETE"
279
+ self.endpoint = ENDPOINTS["unified_catalog"]["delete_data_product_relationship"].format(
280
+ productId=product_id
281
+ )
282
+ self.params = {
283
+ "entityType": entity_type.upper(),
284
+ "entityId": entity_id
285
+ }
286
+
287
+ @decorator
288
+ def query_data_products(self, args):
289
+ """Query data products with advanced filters.
290
+
291
+ Supports filtering by domain, owner, status, name keyword, type, and more.
292
+ Includes pagination (skip/top) and sorting (orderBy).
293
+
294
+ API: POST /datagovernance/catalog/dataproducts/query
295
+ API Version: 2025-09-15-preview
296
+ """
297
+ # Build query payload from args
298
+ payload = {}
299
+
300
+ # IDs and domain filters
301
+ if args.get("--ids"):
302
+ payload["ids"] = args["--ids"]
303
+ if args.get("--domain-ids"):
304
+ payload["domainIds"] = args["--domain-ids"]
305
+
306
+ # Name/keyword search
307
+ if args.get("--name-keyword"):
308
+ payload["nameKeyword"] = args["--name-keyword"][0]
309
+
310
+ # Owner filter
311
+ if args.get("--owners"):
312
+ payload["owners"] = args["--owners"]
313
+
314
+ # Status filters
315
+ if args.get("--status"):
316
+ payload["status"] = args["--status"][0]
317
+ if args.get("--multi-status"):
318
+ payload["multiStatus"] = args["--multi-status"]
319
+
320
+ # Type filters
321
+ if args.get("--type"):
322
+ payload["type"] = args["--type"][0]
323
+ if args.get("--types"):
324
+ payload["types"] = args["--types"]
325
+
326
+ # Pagination
327
+ if args.get("--skip"):
328
+ payload["skip"] = int(args["--skip"][0])
329
+ if args.get("--top"):
330
+ payload["top"] = int(args["--top"][0])
331
+
332
+ # Sorting
333
+ if args.get("--order-by-field"):
334
+ payload["orderby"] = [{
335
+ "field": args["--order-by-field"][0],
336
+ "direction": args.get("--order-by-direction", ["asc"])[0]
337
+ }]
338
+
339
+ self.method = "POST"
340
+ self.endpoint = ENDPOINTS["unified_catalog"]["query_data_products"]
341
+ self.params = {}
342
+ self.payload = payload
343
+
207
344
  # ========================================
208
345
  # GLOSSARY TERMS
209
346
  # ========================================
@@ -222,11 +359,11 @@ class UnifiedCatalogClient(Endpoint):
222
359
 
223
360
  if domain_id:
224
361
  # Use Unified Catalog terms API with domainId filter
225
- self.endpoint = "/datagovernance/catalog/terms"
362
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_terms"]
226
363
  self.params = {"domainId": domain_id}
227
364
  else:
228
365
  # List all UC terms
229
- self.endpoint = "/datagovernance/catalog/terms"
366
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_terms"]
230
367
  self.params = {}
231
368
 
232
369
  # Keeping old Data Map glossary-based implementation for reference/fallback
@@ -368,14 +505,14 @@ class UnifiedCatalogClient(Endpoint):
368
505
  """Get a Unified Catalog term by ID."""
369
506
  term_id = args.get("--term-id", [""])[0]
370
507
  self.method = "GET"
371
- self.endpoint = f"/datagovernance/catalog/terms/{term_id}"
508
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_term"].format(termId=term_id)
372
509
  self.params = {}
373
510
 
374
511
  @decorator
375
512
  def create_term(self, args):
376
513
  """Create a new Unified Catalog term (Governance Domain term)."""
377
514
  self.method = "POST"
378
- self.endpoint = "/datagovernance/catalog/terms"
515
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_terms"]
379
516
 
380
517
  # Build Unified Catalog term payload
381
518
  domain_id = args.get("--governance-domain-id", [""])[0]
@@ -530,9 +667,64 @@ class UnifiedCatalogClient(Endpoint):
530
667
  """Delete a Unified Catalog term."""
531
668
  term_id = args.get("--term-id", [""])[0]
532
669
  self.method = "DELETE"
533
- self.endpoint = f"/datagovernance/catalog/terms/{term_id}"
670
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_term"].format(termId=term_id)
534
671
  self.params = {}
535
672
 
673
+ @decorator
674
+ def query_terms(self, args):
675
+ """Query terms with advanced filters.
676
+
677
+ Supports filtering by domain, owner, status, name keyword, acronyms, and more.
678
+ Includes pagination (skip/top) and sorting (orderBy).
679
+
680
+ API: POST /datagovernance/catalog/terms/query
681
+ API Version: 2025-09-15-preview
682
+ """
683
+ # Build query payload from args
684
+ payload = {}
685
+
686
+ # IDs and domain filters
687
+ if args.get("--ids"):
688
+ payload["ids"] = args["--ids"]
689
+ if args.get("--domain-ids"):
690
+ payload["domainIds"] = args["--domain-ids"]
691
+
692
+ # Name/keyword search
693
+ if args.get("--name-keyword"):
694
+ payload["nameKeyword"] = args["--name-keyword"][0]
695
+
696
+ # Acronym filter (terms-specific)
697
+ if args.get("--acronyms"):
698
+ payload["acronyms"] = args["--acronyms"]
699
+
700
+ # Owner filter
701
+ if args.get("--owners"):
702
+ payload["owners"] = args["--owners"]
703
+
704
+ # Status filters
705
+ if args.get("--status"):
706
+ payload["status"] = args["--status"][0]
707
+ if args.get("--multi-status"):
708
+ payload["multiStatus"] = args["--multi-status"]
709
+
710
+ # Pagination
711
+ if args.get("--skip"):
712
+ payload["skip"] = int(args["--skip"][0])
713
+ if args.get("--top"):
714
+ payload["top"] = int(args["--top"][0])
715
+
716
+ # Sorting
717
+ if args.get("--order-by-field"):
718
+ payload["orderby"] = [{
719
+ "field": args["--order-by-field"][0],
720
+ "direction": args.get("--order-by-direction", ["asc"])[0]
721
+ }]
722
+
723
+ self.method = "POST"
724
+ self.endpoint = ENDPOINTS["unified_catalog"]["query_terms"]
725
+ self.params = {}
726
+ self.payload = payload
727
+
536
728
  def _get_or_create_glossary_for_domain(self, domain_id):
537
729
  """Get or create a default glossary for the domain."""
538
730
  # Improved implementation:
@@ -612,7 +804,7 @@ class UnifiedCatalogClient(Endpoint):
612
804
  """Get all objectives in a governance domain."""
613
805
  domain_id = args.get("--governance-domain-id", [""])[0]
614
806
  self.method = "GET"
615
- self.endpoint = "/datagovernance/catalog/objectives"
807
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_objectives"]
616
808
  self.params = {"domainId": domain_id} if domain_id else {}
617
809
 
618
810
  @decorator
@@ -620,14 +812,14 @@ class UnifiedCatalogClient(Endpoint):
620
812
  """Get an objective by ID."""
621
813
  objective_id = args.get("--objective-id", [""])[0]
622
814
  self.method = "GET"
623
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}"
815
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_objective"].format(objectiveId=objective_id)
624
816
  self.params = {}
625
817
 
626
818
  @decorator
627
819
  def create_objective(self, args):
628
820
  """Create a new objective."""
629
821
  self.method = "POST"
630
- self.endpoint = "/datagovernance/catalog/objectives"
822
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_objectives"]
631
823
 
632
824
  domain_id = args.get("--governance-domain-id", [""])[0]
633
825
  definition = args.get("--definition", [""])[0]
@@ -658,7 +850,7 @@ class UnifiedCatalogClient(Endpoint):
658
850
  """Update an existing objective."""
659
851
  objective_id = args.get("--objective-id", [""])[0]
660
852
  self.method = "PUT"
661
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}"
853
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_objective"].format(objectiveId=objective_id)
662
854
 
663
855
  domain_id = args.get("--governance-domain-id", [""])[0]
664
856
  definition = args.get("--definition", [""])[0]
@@ -690,8 +882,59 @@ class UnifiedCatalogClient(Endpoint):
690
882
  """Delete an objective."""
691
883
  objective_id = args.get("--objective-id", [""])[0]
692
884
  self.method = "DELETE"
693
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}"
885
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_objective"].format(objectiveId=objective_id)
886
+ self.params = {}
887
+
888
+ @decorator
889
+ def query_objectives(self, args):
890
+ """Query objectives with advanced filters.
891
+
892
+ Supports filtering by domain, owner, status, definition keyword, and more.
893
+ Includes pagination (skip/top) and sorting (orderBy).
894
+
895
+ API: POST /datagovernance/catalog/objectives/query
896
+ API Version: 2025-09-15-preview
897
+ """
898
+ # Build query payload from args
899
+ payload = {}
900
+
901
+ # IDs and domain filters
902
+ if args.get("--ids"):
903
+ payload["ids"] = args["--ids"]
904
+ if args.get("--domain-ids"):
905
+ payload["domainIds"] = args["--domain-ids"]
906
+
907
+ # Definition keyword search (objectives-specific)
908
+ if args.get("--definition"):
909
+ payload["definition"] = args["--definition"][0]
910
+
911
+ # Owner filter
912
+ if args.get("--owners"):
913
+ payload["owners"] = args["--owners"]
914
+
915
+ # Status filters
916
+ if args.get("--status"):
917
+ payload["status"] = args["--status"][0]
918
+ if args.get("--multi-status"):
919
+ payload["multiStatus"] = args["--multi-status"]
920
+
921
+ # Pagination
922
+ if args.get("--skip"):
923
+ payload["skip"] = int(args["--skip"][0])
924
+ if args.get("--top"):
925
+ payload["top"] = int(args["--top"][0])
926
+
927
+ # Sorting
928
+ if args.get("--order-by-field"):
929
+ payload["orderby"] = [{
930
+ "field": args["--order-by-field"][0],
931
+ "direction": args.get("--order-by-direction", ["asc"])[0]
932
+ }]
933
+
934
+ self.method = "POST"
935
+ self.endpoint = ENDPOINTS["unified_catalog"]["query_objectives"]
694
936
  self.params = {}
937
+ self.payload = payload
695
938
 
696
939
  # ========================================
697
940
  # KEY RESULTS (Part of OKRs)
@@ -702,7 +945,7 @@ class UnifiedCatalogClient(Endpoint):
702
945
  """Get all key results for an objective."""
703
946
  objective_id = args.get("--objective-id", [""])[0]
704
947
  self.method = "GET"
705
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}/keyResults"
948
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_key_results"].format(objectiveId=objective_id)
706
949
  self.params = {}
707
950
 
708
951
  @decorator
@@ -711,7 +954,7 @@ class UnifiedCatalogClient(Endpoint):
711
954
  objective_id = args.get("--objective-id", [""])[0]
712
955
  key_result_id = args.get("--key-result-id", [""])[0]
713
956
  self.method = "GET"
714
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}/keyResults/{key_result_id}"
957
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_key_result"].format(objectiveId=objective_id, keyResultId=key_result_id)
715
958
  self.params = {}
716
959
 
717
960
  @decorator
@@ -719,7 +962,7 @@ class UnifiedCatalogClient(Endpoint):
719
962
  """Create a new key result."""
720
963
  objective_id = args.get("--objective-id", [""])[0]
721
964
  self.method = "POST"
722
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}/keyResults"
965
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_key_results"].format(objectiveId=objective_id)
723
966
 
724
967
  domain_id = args.get("--governance-domain-id", [""])[0]
725
968
  progress = int(args.get("--progress", ["0"])[0])
@@ -745,7 +988,7 @@ class UnifiedCatalogClient(Endpoint):
745
988
  objective_id = args.get("--objective-id", [""])[0]
746
989
  key_result_id = args.get("--key-result-id", [""])[0]
747
990
  self.method = "PUT"
748
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}/keyResults/{key_result_id}"
991
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_key_result"].format(objectiveId=objective_id, keyResultId=key_result_id)
749
992
 
750
993
  domain_id = args.get("--governance-domain-id", [""])[0]
751
994
  progress = int(args.get("--progress", ["0"])[0])
@@ -772,7 +1015,7 @@ class UnifiedCatalogClient(Endpoint):
772
1015
  objective_id = args.get("--objective-id", [""])[0]
773
1016
  key_result_id = args.get("--key-result-id", [""])[0]
774
1017
  self.method = "DELETE"
775
- self.endpoint = f"/datagovernance/catalog/objectives/{objective_id}/keyResults/{key_result_id}"
1018
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_key_result"].format(objectiveId=objective_id, keyResultId=key_result_id)
776
1019
  self.params = {}
777
1020
 
778
1021
  # ========================================
@@ -784,7 +1027,7 @@ class UnifiedCatalogClient(Endpoint):
784
1027
  """Get all critical data elements in a governance domain."""
785
1028
  domain_id = args.get("--governance-domain-id", [""])[0]
786
1029
  self.method = "GET"
787
- self.endpoint = "/datagovernance/catalog/criticalDataElements"
1030
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_cdes"]
788
1031
  self.params = {"domainId": domain_id} if domain_id else {}
789
1032
 
790
1033
  @decorator
@@ -792,14 +1035,14 @@ class UnifiedCatalogClient(Endpoint):
792
1035
  """Get a critical data element by ID."""
793
1036
  cde_id = args.get("--cde-id", [""])[0]
794
1037
  self.method = "GET"
795
- self.endpoint = f"/datagovernance/catalog/criticalDataElements/{cde_id}"
1038
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_cde"].format(cdeId=cde_id)
796
1039
  self.params = {}
797
1040
 
798
1041
  @decorator
799
1042
  def create_critical_data_element(self, args):
800
1043
  """Create a new critical data element."""
801
1044
  self.method = "POST"
802
- self.endpoint = "/datagovernance/catalog/criticalDataElements"
1045
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_cdes"]
803
1046
 
804
1047
  domain_id = args.get("--governance-domain-id", [""])[0]
805
1048
  name = args.get("--name", [""])[0]
@@ -832,7 +1075,7 @@ class UnifiedCatalogClient(Endpoint):
832
1075
  """Update an existing critical data element."""
833
1076
  cde_id = args.get("--cde-id", [""])[0]
834
1077
  self.method = "PUT"
835
- self.endpoint = f"/datagovernance/catalog/criticalDataElements/{cde_id}"
1078
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_cde"].format(cdeId=cde_id)
836
1079
 
837
1080
  domain_id = args.get("--governance-domain-id", [""])[0]
838
1081
  name = args.get("--name", [""])[0]
@@ -866,8 +1109,132 @@ class UnifiedCatalogClient(Endpoint):
866
1109
  """Delete a critical data element."""
867
1110
  cde_id = args.get("--cde-id", [""])[0]
868
1111
  self.method = "DELETE"
869
- self.endpoint = f"/datagovernance/catalog/criticalDataElements/{cde_id}"
1112
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_cde"].format(cdeId=cde_id)
1113
+ self.params = {}
1114
+
1115
+ @decorator
1116
+ def query_critical_data_elements(self, args):
1117
+ """Query critical data elements with advanced filters.
1118
+
1119
+ Supports filtering by domain, owner, status, name keyword, and more.
1120
+ Includes pagination (skip/top) and sorting (orderBy).
1121
+
1122
+ API: POST /datagovernance/catalog/criticalDataElements/query
1123
+ API Version: 2025-09-15-preview
1124
+ """
1125
+ # Build query payload from args
1126
+ payload = {}
1127
+
1128
+ # IDs and domain filters
1129
+ if args.get("--ids"):
1130
+ payload["ids"] = args["--ids"]
1131
+ if args.get("--domain-ids"):
1132
+ payload["domainIds"] = args["--domain-ids"]
1133
+
1134
+ # Name/keyword search
1135
+ if args.get("--name-keyword"):
1136
+ payload["nameKeyword"] = args["--name-keyword"][0]
1137
+
1138
+ # Owner filter
1139
+ if args.get("--owners"):
1140
+ payload["owners"] = args["--owners"]
1141
+
1142
+ # Status filters
1143
+ if args.get("--status"):
1144
+ payload["status"] = args["--status"][0]
1145
+ if args.get("--multi-status"):
1146
+ payload["multiStatus"] = args["--multi-status"]
1147
+
1148
+ # Pagination
1149
+ if args.get("--skip"):
1150
+ payload["skip"] = int(args["--skip"][0])
1151
+ if args.get("--top"):
1152
+ payload["top"] = int(args["--top"][0])
1153
+
1154
+ # Sorting
1155
+ if args.get("--order-by-field"):
1156
+ payload["orderby"] = [{
1157
+ "field": args["--order-by-field"][0],
1158
+ "direction": args.get("--order-by-direction", ["asc"])[0]
1159
+ }]
1160
+
1161
+ self.method = "POST"
1162
+ self.endpoint = ENDPOINTS["unified_catalog"]["query_critical_data_elements"]
870
1163
  self.params = {}
1164
+ self.payload = payload
1165
+
1166
+ @decorator
1167
+ def create_cde_relationship(self, args):
1168
+ """Create a relationship for a critical data element.
1169
+
1170
+ Creates a relationship between a CDE and another entity
1171
+ (e.g., critical data column, term, asset).
1172
+
1173
+ API: POST /datagovernance/catalog/criticalDataElements/{cdeId}/relationships
1174
+ API Version: 2025-09-15-preview
1175
+ """
1176
+ cde_id = args.get("--cde-id", [""])[0]
1177
+ entity_type = args.get("--entity-type", [""])[0] # e.g., "CRITICALDATACOLUMN"
1178
+ entity_id = args.get("--entity-id", [""])[0]
1179
+ asset_id = args.get("--asset-id", [""])[0] if args.get("--asset-id") else entity_id
1180
+ relationship_type = args.get("--relationship-type", ["Related"])[0]
1181
+ description = args.get("--description", [""])[0]
1182
+
1183
+ # Build request body (same structure as data product relationships)
1184
+ payload = {
1185
+ "relationship1": {
1186
+ "description": description,
1187
+ "relationshipType": relationship_type,
1188
+ "assetId": asset_id,
1189
+ "entityId": entity_id
1190
+ }
1191
+ }
1192
+
1193
+ self.method = "POST"
1194
+ self.endpoint = ENDPOINTS["unified_catalog"]["create_cde_relationship"].format(cdeId=cde_id)
1195
+ self.params = {"entityType": entity_type.upper()}
1196
+ self.payload = payload
1197
+
1198
+ @decorator
1199
+ def get_cde_relationships(self, args):
1200
+ """List relationships for a critical data element.
1201
+
1202
+ Lists all relationships for a CDE, optionally filtered by entity type.
1203
+
1204
+ API: GET /datagovernance/catalog/criticalDataElements/{cdeId}/relationships
1205
+ API Version: 2025-09-15-preview
1206
+ """
1207
+ cde_id = args.get("--cde-id", [""])[0]
1208
+ entity_type = args.get("--entity-type", [""])[0] if args.get("--entity-type") else None
1209
+
1210
+ self.method = "GET"
1211
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_cde_relationships"].format(cdeId=cde_id)
1212
+
1213
+ # Entity type is optional filter
1214
+ if entity_type:
1215
+ self.params = {"entityType": entity_type.upper()}
1216
+ else:
1217
+ self.params = {}
1218
+
1219
+ @decorator
1220
+ def delete_cde_relationship(self, args):
1221
+ """Delete a relationship between a CDE and an entity.
1222
+
1223
+ Deletes a specific relationship identified by entity type and entity ID.
1224
+
1225
+ API: DELETE /datagovernance/catalog/criticalDataElements/{cdeId}/relationships
1226
+ API Version: 2025-09-15-preview
1227
+ """
1228
+ cde_id = args.get("--cde-id", [""])[0]
1229
+ entity_type = args.get("--entity-type", [""])[0]
1230
+ entity_id = args.get("--entity-id", [""])[0]
1231
+
1232
+ self.method = "DELETE"
1233
+ self.endpoint = ENDPOINTS["unified_catalog"]["delete_cde_relationship"].format(cdeId=cde_id)
1234
+ self.params = {
1235
+ "entityType": entity_type.upper(),
1236
+ "entityId": entity_id
1237
+ }
871
1238
 
872
1239
  # ========================================
873
1240
  # RELATIONSHIPS
@@ -969,6 +1336,252 @@ class UnifiedCatalogClient(Endpoint):
969
1336
  "relationshipType": relationship_type,
970
1337
  }
971
1338
 
1339
+ # ========================================
1340
+ # DATA POLICIES (NEW)
1341
+ # ========================================
1342
+
1343
+ @decorator
1344
+ @decorator
1345
+ def list_policies(self, args):
1346
+ """List all data policies."""
1347
+ self.method = "GET"
1348
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_policies"]
1349
+ self.params = {}
1350
+
1351
+ @decorator
1352
+ def get_policy(self, args):
1353
+ """Get a specific data policy by ID."""
1354
+ policy_id = args.get("--policy-id", [""])[0]
1355
+ self.method = "GET"
1356
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_policy"].format(policyId=policy_id)
1357
+ self.params = {}
1358
+
1359
+ @decorator
1360
+ def create_policy(self, args):
1361
+ """Create a new data policy."""
1362
+ self.method = "POST"
1363
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_policies"]
1364
+
1365
+ name = args.get("--name", [""])[0]
1366
+ description = args.get("--description", [""])[0]
1367
+ policy_type = args.get("--type", ["Access"])[0]
1368
+ status = args.get("--status", ["Draft"])[0]
1369
+
1370
+ payload = {
1371
+ "name": name,
1372
+ "description": description,
1373
+ "policyType": policy_type,
1374
+ "status": status,
1375
+ }
1376
+
1377
+ self.payload = payload
1378
+
1379
+ @decorator
1380
+ def update_policy(self, args):
1381
+ """Update an existing data policy."""
1382
+ policy_id = args.get("--policy-id", [""])[0]
1383
+ self.method = "PUT"
1384
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_policy"].format(policyId=policy_id)
1385
+
1386
+ name = args.get("--name", [""])[0]
1387
+ description = args.get("--description", [""])[0]
1388
+ policy_type = args.get("--type", ["Access"])[0]
1389
+ status = args.get("--status", ["Draft"])[0]
1390
+
1391
+ payload = {
1392
+ "name": name,
1393
+ "description": description,
1394
+ "policyType": policy_type,
1395
+ "status": status,
1396
+ }
1397
+
1398
+ self.payload = payload
1399
+
1400
+ @decorator
1401
+ def delete_policy(self, args):
1402
+ """Delete a data policy."""
1403
+ policy_id = args.get("--policy-id", [""])[0]
1404
+ self.method = "DELETE"
1405
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_policy"].format(policyId=policy_id)
1406
+ self.params = {}
1407
+
1408
+ # ========================================
1409
+ # CUSTOM METADATA (NEW)
1410
+ # ========================================
1411
+
1412
+ @decorator
1413
+ def list_custom_metadata(self, args):
1414
+ """List all custom metadata definitions with UC term template filter.
1415
+
1416
+ Uses Atlas API with includeTermTemplate=true to get UC Custom Metadata.
1417
+ This is the same API used by Purview UI for UC metadata.
1418
+ """
1419
+ self.method = "GET"
1420
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_custom_metadata"]
1421
+ self.params = {
1422
+ "type": "business_metadata",
1423
+ "includeTermTemplate": "true",
1424
+ "api-version": "2022-11-03"
1425
+ }
1426
+
1427
+ @decorator
1428
+ def get_custom_metadata(self, args):
1429
+ """Get custom metadata (business metadata) for a specific asset.
1430
+
1431
+ Uses Entity API to get business metadata attached to a GUID.
1432
+ This returns all business metadata groups attached to the entity.
1433
+ """
1434
+ asset_id = args.get("--asset-id", [""])[0]
1435
+ self.method = "GET"
1436
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_custom_metadata"].format(guid=asset_id)
1437
+ self.params = {
1438
+ "api-version": "2023-09-01",
1439
+ "minExtInfo": "false",
1440
+ "ignoreRelationships": "true"
1441
+ }
1442
+
1443
+ @decorator
1444
+ def add_custom_metadata(self, args):
1445
+ """Add custom metadata (business metadata) to an asset.
1446
+
1447
+ Uses Entity API to add business metadata to a GUID.
1448
+ Format: { "GroupName": { "attribute1": "value1", "attribute2": "value2" } }
1449
+ """
1450
+ asset_id = args.get("--asset-id", [""])[0]
1451
+ self.method = "POST"
1452
+ self.endpoint = ENDPOINTS["unified_catalog"]["add_custom_metadata"].format(guid=asset_id)
1453
+ self.params = {"api-version": "2023-09-01"}
1454
+
1455
+ # Build payload based on parameters
1456
+ key = args.get("--key", [""])[0]
1457
+ value = args.get("--value", [""])[0]
1458
+ group = args.get("--group", ["Custom"])[0] # Default group name
1459
+
1460
+ # Format: { "GroupName": { "attributeName": "value" } }
1461
+ payload = {
1462
+ group: {
1463
+ key: value
1464
+ }
1465
+ }
1466
+
1467
+ self.payload = payload
1468
+
1469
+ @decorator
1470
+ def update_custom_metadata(self, args):
1471
+ """Update custom metadata (business metadata) for an asset.
1472
+
1473
+ Uses Entity API to update business metadata on a GUID.
1474
+ POST method overwrites existing values for the specified attributes.
1475
+ Format: { "GroupName": { "attribute1": "newValue" } }
1476
+ """
1477
+ asset_id = args.get("--asset-id", [""])[0]
1478
+ self.method = "POST"
1479
+ self.endpoint = ENDPOINTS["unified_catalog"]["update_custom_metadata"].format(guid=asset_id)
1480
+ self.params = {"api-version": "2023-09-01"}
1481
+
1482
+ key = args.get("--key", [""])[0]
1483
+ value = args.get("--value", [""])[0]
1484
+ group = args.get("--group", ["Custom"])[0]
1485
+
1486
+ payload = {
1487
+ group: {
1488
+ key: value
1489
+ }
1490
+ }
1491
+
1492
+ self.payload = payload
1493
+
1494
+ @decorator
1495
+ def delete_custom_metadata(self, args):
1496
+ """Delete custom metadata (business metadata) from an asset.
1497
+
1498
+ Uses Entity API to remove business metadata from a GUID.
1499
+ Removes entire business metadata group by passing it in params + empty payload.
1500
+ """
1501
+ asset_id = args.get("--asset-id", [""])[0]
1502
+ group = args.get("--group", [""])[0]
1503
+
1504
+ if not group:
1505
+ raise ValueError("--group parameter is required to delete business metadata")
1506
+
1507
+ self.method = "DELETE"
1508
+ self.endpoint = ENDPOINTS["unified_catalog"]["delete_custom_metadata"].format(guid=asset_id)
1509
+ self.params = {
1510
+ "api-version": "2023-09-01"
1511
+ }
1512
+ # Payload must contain the group with empty dict to delete entire group
1513
+ self.payload = {
1514
+ group: {}
1515
+ }
1516
+
1517
+ # ========================================
1518
+ # CUSTOM ATTRIBUTES (NEW)
1519
+ # ========================================
1520
+
1521
+ @decorator
1522
+ def list_custom_attributes(self, args):
1523
+ """List all custom attribute definitions."""
1524
+ self.method = "GET"
1525
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_custom_attributes"]
1526
+ self.params = {}
1527
+
1528
+ @decorator
1529
+ def get_custom_attribute(self, args):
1530
+ """Get a specific custom attribute definition."""
1531
+ attribute_id = args.get("--attribute-id", [""])[0]
1532
+ self.method = "GET"
1533
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_custom_attribute"].format(attributeId=attribute_id)
1534
+ self.params = {}
1535
+
1536
+ @decorator
1537
+ def create_custom_attribute(self, args):
1538
+ """Create a new custom attribute definition."""
1539
+ self.method = "POST"
1540
+ self.endpoint = ENDPOINTS["unified_catalog"]["list_custom_attributes"]
1541
+
1542
+ name = args.get("--name", [""])[0]
1543
+ description = args.get("--description", [""])[0]
1544
+ data_type = args.get("--type", ["string"])[0]
1545
+ required = args.get("--required", ["false"])[0].lower() == "true"
1546
+
1547
+ payload = {
1548
+ "name": name,
1549
+ "description": description,
1550
+ "dataType": data_type,
1551
+ "required": required,
1552
+ }
1553
+
1554
+ self.payload = payload
1555
+
1556
+ @decorator
1557
+ def update_custom_attribute(self, args):
1558
+ """Update a custom attribute definition."""
1559
+ attribute_id = args.get("--attribute-id", [""])[0]
1560
+ self.method = "PUT"
1561
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_custom_attribute"].format(attributeId=attribute_id)
1562
+
1563
+ name = args.get("--name", [""])[0]
1564
+ description = args.get("--description", [""])[0]
1565
+ data_type = args.get("--type", ["string"])[0]
1566
+ required = args.get("--required", ["false"])[0].lower() == "true"
1567
+
1568
+ payload = {
1569
+ "name": name,
1570
+ "description": description,
1571
+ "dataType": data_type,
1572
+ "required": required,
1573
+ }
1574
+
1575
+ self.payload = payload
1576
+
1577
+ @decorator
1578
+ def delete_custom_attribute(self, args):
1579
+ """Delete a custom attribute definition."""
1580
+ attribute_id = args.get("--attribute-id", [""])[0]
1581
+ self.method = "DELETE"
1582
+ self.endpoint = ENDPOINTS["unified_catalog"]["get_custom_attribute"].format(attributeId=attribute_id)
1583
+ self.params = {}
1584
+
972
1585
  # ========================================
973
1586
  # UTILITY METHODS
974
1587
  # ========================================
@@ -986,6 +1599,9 @@ Available Operations:
986
1599
  - Objectives (OKRs): list, get, create, update, delete
987
1600
  - Key Results: list, get, create, update, delete
988
1601
  - Critical Data Elements: list, get, create, update, delete
1602
+ - Data Policies: list, get, create, update, delete (NEW)
1603
+ - Custom Metadata: list, get, add, update, delete (NEW)
1604
+ - Custom Attributes: list, get, create, update, delete (NEW)
989
1605
  - Relationships: create, delete (between terms, data products, CDEs)
990
1606
 
991
1607
  Use --payloadFile to provide JSON payload for create/update operations.