pvw-cli 1.0.6__py3-none-any.whl → 1.0.8__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.

@@ -0,0 +1,766 @@
1
+ """
2
+ Microsoft Purview Unified Catalog CLI Commands
3
+ Replaces data_product functionality with comprehensive Unified Catalog operations
4
+ """
5
+
6
+ import click
7
+ import csv
8
+ import json
9
+ import tempfile
10
+ import os
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+ from rich.text import Text
14
+ from purviewcli.client._unified_catalog import UnifiedCatalogClient
15
+
16
+ console = Console()
17
+
18
+
19
+ @click.group()
20
+ def uc():
21
+ """Manage Unified Catalog in Microsoft Purview (domains, terms, data products, OKRs, CDEs)."""
22
+ pass
23
+
24
+
25
+ # ========================================
26
+ # GOVERNANCE DOMAINS
27
+ # ========================================
28
+
29
+
30
+ @uc.group()
31
+ def domain():
32
+ """Manage governance domains."""
33
+ pass
34
+
35
+
36
+ @domain.command()
37
+ @click.option("--name", required=True, help="Name of the governance domain")
38
+ @click.option(
39
+ "--description", required=False, default="", help="Description of the governance domain"
40
+ )
41
+ @click.option(
42
+ "--type",
43
+ required=False,
44
+ default="FunctionalUnit",
45
+ type=click.Choice(["FunctionalUnit", "BusinessUnit", "Department"]),
46
+ help="Type of governance domain",
47
+ )
48
+ @click.option(
49
+ "--owner-id",
50
+ required=False,
51
+ help="Owner Entra ID (can be specified multiple times)",
52
+ multiple=True,
53
+ )
54
+ @click.option(
55
+ "--status",
56
+ required=False,
57
+ default="Draft",
58
+ type=click.Choice(["Draft", "Published", "Archived"]),
59
+ help="Status of the governance domain",
60
+ )
61
+ def create(name, description, type, owner_id, status):
62
+ """Create a new governance domain."""
63
+ try:
64
+ client = UnifiedCatalogClient()
65
+
66
+ # Build args dictionary in Purview CLI format
67
+ args = {
68
+ "--name": [name],
69
+ "--description": [description],
70
+ "--type": [type],
71
+ "--status": [status],
72
+ }
73
+
74
+ result = client.create_governance_domain(args)
75
+
76
+ if not result:
77
+ console.print("[red]ERROR:[/red] No response received")
78
+ return
79
+ if isinstance(result, dict) and "error" in result:
80
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
81
+ return
82
+
83
+ console.print(f"[green]✅ SUCCESS:[/green] Created governance domain '{name}'")
84
+ console.print(json.dumps(result, indent=2))
85
+
86
+ except Exception as e:
87
+ console.print(f"[red]ERROR:[/red] {str(e)}")
88
+
89
+
90
+ @domain.command(name="list")
91
+ def list_domains():
92
+ """List all governance domains."""
93
+ try:
94
+ client = UnifiedCatalogClient()
95
+ args = {} # No arguments needed for list operation
96
+ result = client.get_governance_domains(args)
97
+
98
+ if not result:
99
+ console.print("[yellow]No governance domains found.[/yellow]")
100
+ return
101
+
102
+ # Handle both list and dict responses
103
+ if isinstance(result, (list, tuple)):
104
+ domains = result
105
+ elif isinstance(result, dict):
106
+ domains = result.get("value", [])
107
+ else:
108
+ domains = []
109
+
110
+ if not domains:
111
+ console.print("[yellow]No governance domains found.[/yellow]")
112
+ return
113
+
114
+ table = Table(title="Governance Domains")
115
+ table.add_column("ID", style="cyan")
116
+ table.add_column("Name", style="green")
117
+ table.add_column("Type", style="blue")
118
+ table.add_column("Status", style="yellow")
119
+ table.add_column("Owners", style="magenta")
120
+
121
+ for domain in domains:
122
+ owners = ", ".join(
123
+ [o.get("name", o.get("id", "Unknown")) for o in domain.get("owners", [])]
124
+ )
125
+ table.add_row(
126
+ domain.get("id", "N/A"),
127
+ domain.get("name", "N/A"),
128
+ domain.get("type", "N/A"),
129
+ domain.get("status", "N/A"),
130
+ owners or "None",
131
+ )
132
+
133
+ console.print(table)
134
+
135
+ except Exception as e:
136
+ console.print(f"[red]ERROR:[/red] {str(e)}")
137
+
138
+
139
+ @domain.command()
140
+ @click.option("--domain-id", required=True, help="ID of the governance domain")
141
+ def show(domain_id):
142
+ """Show details of a governance domain."""
143
+ try:
144
+ client = UnifiedCatalogClient()
145
+ args = {"--domain-id": [domain_id]}
146
+ result = client.get_governance_domain_by_id(args)
147
+
148
+ if not result:
149
+ console.print("[red]ERROR:[/red] No response received")
150
+ return
151
+ if isinstance(result, dict) and result.get("error"):
152
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Domain not found')}")
153
+ return
154
+
155
+ console.print(json.dumps(result, indent=2))
156
+ except Exception as e:
157
+ console.print(f"[red]ERROR:[/red] {str(e)}")
158
+
159
+
160
+ # ========================================
161
+ # DATA PRODUCTS (for backwards compatibility)
162
+ # ========================================
163
+
164
+
165
+ @uc.group()
166
+ def dataproduct():
167
+ """Manage data products."""
168
+ pass
169
+
170
+
171
+ @dataproduct.command()
172
+ @click.option("--name", required=True, help="Name of the data product")
173
+ @click.option("--description", required=False, default="", help="Description of the data product")
174
+ @click.option("--domain-id", required=True, help="Governance domain ID")
175
+ @click.option(
176
+ "--type",
177
+ required=False,
178
+ default="Operational",
179
+ type=click.Choice(["Operational", "Analytical", "Reference"]),
180
+ help="Type of data product",
181
+ )
182
+ @click.option(
183
+ "--owner-id",
184
+ required=False,
185
+ help="Owner Entra ID (can be specified multiple times)",
186
+ multiple=True,
187
+ )
188
+ @click.option("--business-use", required=False, default="", help="Business use description")
189
+ @click.option(
190
+ "--update-frequency",
191
+ required=False,
192
+ default="Weekly",
193
+ type=click.Choice(["Daily", "Weekly", "Monthly", "Quarterly", "Annually"]),
194
+ help="Update frequency",
195
+ )
196
+ @click.option("--endorsed", is_flag=True, help="Mark as endorsed")
197
+ @click.option(
198
+ "--status",
199
+ required=False,
200
+ default="Draft",
201
+ type=click.Choice(["Draft", "Published", "Archived"]),
202
+ help="Status of the data product",
203
+ )
204
+ def create(
205
+ name, description, domain_id, type, owner_id, business_use, update_frequency, endorsed, status
206
+ ):
207
+ """Create a new data product."""
208
+ try:
209
+ client = UnifiedCatalogClient()
210
+ owners = [{"id": oid} for oid in owner_id] if owner_id else []
211
+
212
+ # Build args dictionary in Purview CLI format
213
+ args = {
214
+ "--governance-domain-id": [domain_id],
215
+ "--name": [name],
216
+ "--description": [description],
217
+ "--type": [type],
218
+ "--status": [status],
219
+ "--business-use": [business_use],
220
+ "--update-frequency": [update_frequency],
221
+ }
222
+ if endorsed:
223
+ args["--endorsed"] = ["true"]
224
+ if owners:
225
+ args["--owner-id"] = [owner["id"] for owner in owners]
226
+
227
+ result = client.create_data_product(args)
228
+
229
+ if not result:
230
+ console.print("[red]ERROR:[/red] No response received")
231
+ return
232
+ if isinstance(result, dict) and "error" in result:
233
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
234
+ return
235
+
236
+ console.print(f"[green]✅ SUCCESS:[/green] Created data product '{name}'")
237
+ console.print(json.dumps(result, indent=2))
238
+
239
+ except Exception as e:
240
+ console.print(f"[red]ERROR:[/red] {str(e)}")
241
+
242
+
243
+ @dataproduct.command(name="list")
244
+ @click.option("--domain-id", required=False, help="Governance domain ID (optional filter)")
245
+ @click.option("--status", required=False, help="Status filter (Draft, Published, Archived)")
246
+ def list_data_products(domain_id, status):
247
+ """List all data products (optionally filtered by domain or status)."""
248
+ try:
249
+ client = UnifiedCatalogClient()
250
+
251
+ # Build args dictionary in Purview CLI format
252
+ args = {}
253
+ if domain_id:
254
+ args["--domain-id"] = [domain_id]
255
+ if status:
256
+ args["--status"] = [status]
257
+
258
+ result = client.get_data_products(args)
259
+
260
+ # Handle both list and dict responses
261
+ if isinstance(result, (list, tuple)):
262
+ products = result
263
+ elif isinstance(result, dict):
264
+ products = result.get("value", [])
265
+ else:
266
+ products = []
267
+
268
+ if not products:
269
+ filter_msg = ""
270
+ if domain_id:
271
+ filter_msg += f" in domain '{domain_id}'"
272
+ if status:
273
+ filter_msg += f" with status '{status}'"
274
+ console.print(f"[yellow]No data products found{filter_msg}.[/yellow]")
275
+ return
276
+
277
+ table = Table(title="Data Products")
278
+ table.add_column("ID", style="cyan")
279
+ table.add_column("Name", style="green")
280
+ table.add_column("Domain ID", style="blue")
281
+ table.add_column("Status", style="yellow")
282
+ table.add_column("Description", style="white")
283
+
284
+ for product in products:
285
+ table.add_row(
286
+ product.get("id", "N/A"),
287
+ product.get("name", "N/A"),
288
+ product.get("domainId", "N/A"),
289
+ product.get("status", "N/A"),
290
+ (
291
+ (product.get("description", "")[:50] + "...")
292
+ if len(product.get("description", "")) > 50
293
+ else product.get("description", "")
294
+ ),
295
+ )
296
+
297
+ console.print(table)
298
+
299
+ except Exception as e:
300
+ console.print(f"[red]ERROR:[/red] {str(e)}")
301
+
302
+
303
+ @dataproduct.command()
304
+ @click.option("--product-id", required=True, help="ID of the data product")
305
+ def show(product_id):
306
+ """Show details of a data product."""
307
+ try:
308
+ client = UnifiedCatalogClient()
309
+ args = {"--product-id": [product_id]}
310
+ result = client.get_data_product_by_id(args)
311
+
312
+ if not result:
313
+ console.print("[red]ERROR:[/red] No response received")
314
+ return
315
+ if isinstance(result, dict) and "error" in result:
316
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Data product not found')}")
317
+ return
318
+
319
+ console.print(json.dumps(result, indent=2))
320
+
321
+ except Exception as e:
322
+ console.print(f"[red]ERROR:[/red] {str(e)}")
323
+
324
+
325
+ # ========================================
326
+ # GLOSSARY TERMS
327
+ # ========================================
328
+
329
+ @uc.group()
330
+ def term():
331
+ """Manage glossary terms."""
332
+ pass
333
+
334
+
335
+ @term.command()
336
+ @click.option("--name", required=True, help="Name of the glossary term")
337
+ @click.option("--description", required=False, default="", help="Rich text description of the term")
338
+ @click.option("--domain-id", required=True, help="Governance domain ID")
339
+ @click.option("--status", required=False, default="Draft",
340
+ type=click.Choice(["Draft", "Published", "Archived"]),
341
+ help="Status of the term")
342
+ @click.option("--acronym", required=False, help="Acronyms for the term (can be specified multiple times)", multiple=True)
343
+ @click.option("--owner-id", required=False, help="Owner Entra ID (can be specified multiple times)", multiple=True)
344
+ @click.option("--resource-name", required=False, help="Resource name for additional reading")
345
+ @click.option("--resource-url", required=False, help="Resource URL for additional reading")
346
+ def create(name, description, domain_id, status, acronym, owner_id, resource_name, resource_url):
347
+ """Create a new glossary term."""
348
+ try:
349
+ client = UnifiedCatalogClient()
350
+
351
+ # Build args dictionary
352
+ args = {
353
+ "--name": [name],
354
+ "--description": [description],
355
+ "--governance-domain-id": [domain_id],
356
+ "--status": [status]
357
+ }
358
+
359
+ if acronym:
360
+ args["--acronyms"] = list(acronym)
361
+ if owner_id:
362
+ args["--owner-id"] = list(owner_id)
363
+ if resource_name and resource_url:
364
+ args["--resource-name"] = [resource_name]
365
+ args["--resource-url"] = [resource_url]
366
+
367
+ result = client.create_term(args)
368
+
369
+ if not result:
370
+ console.print("[red]ERROR:[/red] No response received")
371
+ return
372
+ if isinstance(result, dict) and "error" in result:
373
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
374
+ return
375
+
376
+ console.print(f"[green]✅ SUCCESS:[/green] Created glossary term '{name}'")
377
+ console.print(json.dumps(result, indent=2))
378
+
379
+ except Exception as e:
380
+ console.print(f"[red]ERROR:[/red] {str(e)}")
381
+
382
+
383
+ @term.command(name="list")
384
+ @click.option("--domain-id", required=True, help="Governance domain ID to list terms from")
385
+ def list_terms(domain_id):
386
+ """List all glossary terms in a governance domain."""
387
+ try:
388
+ client = UnifiedCatalogClient()
389
+ args = {"--governance-domain-id": [domain_id]}
390
+ result = client.get_terms(args)
391
+
392
+ if not result:
393
+ console.print("[yellow]No glossary terms found.[/yellow]")
394
+ return
395
+
396
+ # Handle both list and dict responses
397
+ if isinstance(result, (list, tuple)):
398
+ terms = result
399
+ elif isinstance(result, dict):
400
+ terms = result.get("value", [])
401
+ else:
402
+ terms = []
403
+
404
+ if not terms:
405
+ console.print("[yellow]No glossary terms found.[/yellow]")
406
+ return
407
+
408
+ table = Table(title="Glossary Terms")
409
+ table.add_column("ID", style="cyan")
410
+ table.add_column("Name", style="green")
411
+ table.add_column("Status", style="yellow")
412
+ table.add_column("Acronyms", style="blue")
413
+ table.add_column("Description", style="white")
414
+
415
+ for term in terms:
416
+ acronyms = ", ".join(term.get("acronyms", []))
417
+ desc = term.get("description", "")
418
+ if len(desc) > 50:
419
+ desc = desc[:50] + "..."
420
+
421
+ table.add_row(
422
+ term.get("id", "N/A"),
423
+ term.get("name", "N/A"),
424
+ term.get("status", "N/A"),
425
+ acronyms or "None",
426
+ desc
427
+ )
428
+
429
+ console.print(table)
430
+
431
+ except Exception as e:
432
+ console.print(f"[red]ERROR:[/red] {str(e)}")
433
+
434
+
435
+ @term.command()
436
+ @click.option("--term-id", required=True, help="ID of the glossary term")
437
+ def show(term_id):
438
+ """Show details of a glossary term."""
439
+ try:
440
+ client = UnifiedCatalogClient()
441
+ args = {"--term-id": [term_id]}
442
+ result = client.get_term_by_id(args)
443
+
444
+ if not result:
445
+ console.print("[red]ERROR:[/red] No response received")
446
+ return
447
+ if isinstance(result, dict) and "error" in result:
448
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Term not found')}")
449
+ return
450
+
451
+ console.print(json.dumps(result, indent=2))
452
+
453
+ except Exception as e:
454
+ console.print(f"[red]ERROR:[/red] {str(e)}")
455
+
456
+
457
+ # ========================================
458
+ # OBJECTIVES AND KEY RESULTS (OKRs)
459
+ # ========================================
460
+
461
+ @uc.group()
462
+ def objective():
463
+ """Manage objectives and key results (OKRs)."""
464
+ pass
465
+
466
+
467
+ @objective.command()
468
+ @click.option("--definition", required=True, help="Definition of the objective")
469
+ @click.option("--domain-id", required=True, help="Governance domain ID")
470
+ @click.option("--status", required=False, default="Draft",
471
+ type=click.Choice(["Draft", "Published", "Archived"]),
472
+ help="Status of the objective")
473
+ @click.option("--owner-id", required=False, help="Owner Entra ID (can be specified multiple times)", multiple=True)
474
+ @click.option("--target-date", required=False, help="Target date (ISO format: 2025-12-30T14:00:00.000Z)")
475
+ def create(definition, domain_id, status, owner_id, target_date):
476
+ """Create a new objective."""
477
+ try:
478
+ client = UnifiedCatalogClient()
479
+
480
+ args = {
481
+ "--definition": [definition],
482
+ "--governance-domain-id": [domain_id],
483
+ "--status": [status]
484
+ }
485
+
486
+ if owner_id:
487
+ args["--owner-id"] = list(owner_id)
488
+ if target_date:
489
+ args["--target-date"] = [target_date]
490
+
491
+ result = client.create_objective(args)
492
+
493
+ if not result:
494
+ console.print("[red]ERROR:[/red] No response received")
495
+ return
496
+ if isinstance(result, dict) and "error" in result:
497
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
498
+ return
499
+
500
+ console.print(f"[green]✅ SUCCESS:[/green] Created objective")
501
+ console.print(json.dumps(result, indent=2))
502
+
503
+ except Exception as e:
504
+ console.print(f"[red]ERROR:[/red] {str(e)}")
505
+
506
+
507
+ @objective.command(name="list")
508
+ @click.option("--domain-id", required=True, help="Governance domain ID to list objectives from")
509
+ def list_objectives(domain_id):
510
+ """List all objectives in a governance domain."""
511
+ try:
512
+ client = UnifiedCatalogClient()
513
+ args = {"--governance-domain-id": [domain_id]}
514
+ result = client.get_objectives(args)
515
+
516
+ if not result:
517
+ console.print("[yellow]No objectives found.[/yellow]")
518
+ return
519
+
520
+ # Handle response format
521
+ if isinstance(result, (list, tuple)):
522
+ objectives = result
523
+ elif isinstance(result, dict):
524
+ objectives = result.get("value", [])
525
+ else:
526
+ objectives = []
527
+
528
+ if not objectives:
529
+ console.print("[yellow]No objectives found.[/yellow]")
530
+ return
531
+
532
+ table = Table(title="Objectives")
533
+ table.add_column("ID", style="cyan")
534
+ table.add_column("Definition", style="green")
535
+ table.add_column("Status", style="yellow")
536
+ table.add_column("Target Date", style="blue")
537
+
538
+ for obj in objectives:
539
+ definition = obj.get("definition", "")
540
+ if len(definition) > 50:
541
+ definition = definition[:50] + "..."
542
+
543
+ table.add_row(
544
+ obj.get("id", "N/A"),
545
+ definition,
546
+ obj.get("status", "N/A"),
547
+ obj.get("targetDate", "N/A")
548
+ )
549
+
550
+ console.print(table)
551
+
552
+ except Exception as e:
553
+ console.print(f"[red]ERROR:[/red] {str(e)}")
554
+
555
+
556
+ @objective.command()
557
+ @click.option("--objective-id", required=True, help="ID of the objective")
558
+ def show(objective_id):
559
+ """Show details of an objective."""
560
+ try:
561
+ client = UnifiedCatalogClient()
562
+ args = {"--objective-id": [objective_id]}
563
+ result = client.get_objective_by_id(args)
564
+
565
+ if not result:
566
+ console.print("[red]ERROR:[/red] No response received")
567
+ return
568
+ if isinstance(result, dict) and "error" in result:
569
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Objective not found')}")
570
+ return
571
+
572
+ console.print(json.dumps(result, indent=2))
573
+
574
+ except Exception as e:
575
+ console.print(f"[red]ERROR:[/red] {str(e)}")
576
+
577
+
578
+ # ========================================
579
+ # CRITICAL DATA ELEMENTS (CDEs)
580
+ # ========================================
581
+
582
+ @uc.group()
583
+ def cde():
584
+ """Manage critical data elements."""
585
+ pass
586
+
587
+
588
+ @cde.command()
589
+ @click.option("--name", required=True, help="Name of the critical data element")
590
+ @click.option("--description", required=False, default="", help="Description of the CDE")
591
+ @click.option("--domain-id", required=True, help="Governance domain ID")
592
+ @click.option("--data-type", required=True,
593
+ type=click.Choice(["String", "Number", "Boolean", "Date", "DateTime"]),
594
+ help="Data type of the CDE")
595
+ @click.option("--status", required=False, default="Draft",
596
+ type=click.Choice(["Draft", "Published", "Archived"]),
597
+ help="Status of the CDE")
598
+ @click.option("--owner-id", required=False, help="Owner Entra ID (can be specified multiple times)", multiple=True)
599
+ def create(name, description, domain_id, data_type, status, owner_id):
600
+ """Create a new critical data element."""
601
+ try:
602
+ client = UnifiedCatalogClient()
603
+
604
+ args = {
605
+ "--name": [name],
606
+ "--description": [description],
607
+ "--governance-domain-id": [domain_id],
608
+ "--data-type": [data_type],
609
+ "--status": [status]
610
+ }
611
+
612
+ if owner_id:
613
+ args["--owner-id"] = list(owner_id)
614
+
615
+ result = client.create_critical_data_element(args)
616
+
617
+ if not result:
618
+ console.print("[red]ERROR:[/red] No response received")
619
+ return
620
+ if isinstance(result, dict) and "error" in result:
621
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
622
+ return
623
+
624
+ console.print(f"[green]✅ SUCCESS:[/green] Created critical data element '{name}'")
625
+ console.print(json.dumps(result, indent=2))
626
+
627
+ except Exception as e:
628
+ console.print(f"[red]ERROR:[/red] {str(e)}")
629
+
630
+
631
+ @cde.command(name="list")
632
+ @click.option("--domain-id", required=True, help="Governance domain ID to list CDEs from")
633
+ def list_cdes(domain_id):
634
+ """List all critical data elements in a governance domain."""
635
+ try:
636
+ client = UnifiedCatalogClient()
637
+ args = {"--governance-domain-id": [domain_id]}
638
+ result = client.get_critical_data_elements(args)
639
+
640
+ if not result:
641
+ console.print("[yellow]No critical data elements found.[/yellow]")
642
+ return
643
+
644
+ # Handle response format
645
+ if isinstance(result, (list, tuple)):
646
+ cdes = result
647
+ elif isinstance(result, dict):
648
+ cdes = result.get("value", [])
649
+ else:
650
+ cdes = []
651
+
652
+ if not cdes:
653
+ console.print("[yellow]No critical data elements found.[/yellow]")
654
+ return
655
+
656
+ table = Table(title="Critical Data Elements")
657
+ table.add_column("ID", style="cyan")
658
+ table.add_column("Name", style="green")
659
+ table.add_column("Data Type", style="blue")
660
+ table.add_column("Status", style="yellow")
661
+ table.add_column("Description", style="white")
662
+
663
+ for cde_item in cdes:
664
+ desc = cde_item.get("description", "")
665
+ if len(desc) > 30:
666
+ desc = desc[:30] + "..."
667
+
668
+ table.add_row(
669
+ cde_item.get("id", "N/A"),
670
+ cde_item.get("name", "N/A"),
671
+ cde_item.get("dataType", "N/A"),
672
+ cde_item.get("status", "N/A"),
673
+ desc
674
+ )
675
+
676
+ console.print(table)
677
+
678
+ except Exception as e:
679
+ console.print(f"[red]ERROR:[/red] {str(e)}")
680
+
681
+
682
+ @cde.command()
683
+ @click.option("--cde-id", required=True, help="ID of the critical data element")
684
+ def show(cde_id):
685
+ """Show details of a critical data element."""
686
+ try:
687
+ client = UnifiedCatalogClient()
688
+ args = {"--cde-id": [cde_id]}
689
+ result = client.get_critical_data_element_by_id(args)
690
+
691
+ if not result:
692
+ console.print("[red]ERROR:[/red] No response received")
693
+ return
694
+ if isinstance(result, dict) and "error" in result:
695
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'CDE not found')}")
696
+ return
697
+
698
+ console.print(json.dumps(result, indent=2))
699
+
700
+ except Exception as e:
701
+ console.print(f"[red]ERROR:[/red] {str(e)}")
702
+
703
+
704
+ # ========================================
705
+ # HEALTH MANAGEMENT (Preview)
706
+ # ========================================
707
+
708
+ @uc.group()
709
+ def health():
710
+ """Manage health controls and actions (preview)."""
711
+ pass
712
+
713
+
714
+ @health.command(name="controls")
715
+ def list_controls():
716
+ """List health controls (preview - not yet implemented)."""
717
+ console.print("[yellow]🚧 Health Controls are not yet implemented in the API[/yellow]")
718
+ console.print("This feature is coming soon to Microsoft Purview Unified Catalog")
719
+
720
+
721
+ @health.command(name="actions")
722
+ def list_actions():
723
+ """List health actions (preview - not yet implemented)."""
724
+ console.print("[yellow]🚧 Health Actions are not yet implemented in the API[/yellow]")
725
+ console.print("This feature is coming soon to Microsoft Purview Unified Catalog")
726
+
727
+
728
+ @health.command(name="quality")
729
+ def data_quality():
730
+ """Data quality management (not yet supported by API)."""
731
+ console.print("[yellow]🚧 Data Quality management is not yet supported by the API[/yellow]")
732
+ console.print("This feature requires complex API interactions not yet available")
733
+
734
+
735
+ # ========================================
736
+ # CUSTOM ATTRIBUTES (Coming Soon)
737
+ # ========================================
738
+
739
+ @uc.group()
740
+ def attribute():
741
+ """Manage custom attributes (coming soon)."""
742
+ pass
743
+
744
+
745
+ @attribute.command(name="list")
746
+ def list_attributes():
747
+ """List custom attributes (coming soon)."""
748
+ console.print("[yellow]🚧 Custom Attributes are coming soon[/yellow]")
749
+ console.print("This feature is under development in Microsoft Purview")
750
+
751
+
752
+ # ========================================
753
+ # REQUESTS (Coming Soon)
754
+ # ========================================
755
+
756
+ @uc.group()
757
+ def request():
758
+ """Manage access requests (coming soon)."""
759
+ pass
760
+
761
+
762
+ @request.command(name="list")
763
+ def list_requests():
764
+ """List access requests (coming soon)."""
765
+ console.print("[yellow]🚧 Access Requests are coming soon[/yellow]")
766
+ console.print("This feature is under development for data access workflows")