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