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

@@ -296,23 +296,29 @@ def list_data_products(domain_id, status, output_json):
296
296
  return
297
297
 
298
298
  table = Table(title="Data Products")
299
- table.add_column("ID", style="cyan")
299
+ table.add_column("ID", style="cyan", no_wrap=True)
300
300
  table.add_column("Name", style="green")
301
- table.add_column("Domain ID", style="blue")
301
+ table.add_column("Domain ID", style="blue", no_wrap=True)
302
302
  table.add_column("Status", style="yellow")
303
- table.add_column("Description", style="white")
303
+ table.add_column("Description", style="white", max_width=50)
304
304
 
305
305
  for product in products:
306
+ # Get domain ID and handle "N/A" display
307
+ domain_id = product.get("domain") or product.get("domainId", "")
308
+ domain_display = domain_id if domain_id else "N/A"
309
+
310
+ # Clean HTML tags from description
311
+ description = product.get("description", "")
312
+ import re
313
+ description = re.sub(r'<[^>]+>', '', description)
314
+ description = description.strip()
315
+
306
316
  table.add_row(
307
317
  product.get("id", "N/A"),
308
318
  product.get("name", "N/A"),
309
- product.get("domainId", "N/A"),
319
+ domain_display,
310
320
  product.get("status", "N/A"),
311
- (
312
- (product.get("description", "")[:50] + "...")
313
- if len(product.get("description", "")) > 50
314
- else product.get("description", "")
315
- ),
321
+ (description[:50] + "...") if len(description) > 50 else description,
316
322
  )
317
323
 
318
324
  console.print(table)
@@ -343,6 +349,412 @@ def show(product_id):
343
349
  console.print(f"[red]ERROR:[/red] {str(e)}")
344
350
 
345
351
 
352
+ @dataproduct.command()
353
+ @click.option("--product-id", required=True, help="ID of the data product to update")
354
+ @click.option("--name", required=False, help="Name of the data product")
355
+ @click.option("--description", required=False, help="Description of the data product")
356
+ @click.option("--domain-id", required=False, help="Governance domain ID")
357
+ @click.option(
358
+ "--type",
359
+ required=False,
360
+ type=click.Choice(["Operational", "Analytical", "Reference"]),
361
+ help="Type of data product",
362
+ )
363
+ @click.option(
364
+ "--owner-id",
365
+ required=False,
366
+ help="Owner Entra ID (can be specified multiple times)",
367
+ multiple=True,
368
+ )
369
+ @click.option("--business-use", required=False, help="Business use description")
370
+ @click.option(
371
+ "--update-frequency",
372
+ required=False,
373
+ type=click.Choice(["Daily", "Weekly", "Monthly", "Quarterly", "Annually"]),
374
+ help="Update frequency",
375
+ )
376
+ @click.option("--endorsed", is_flag=True, help="Mark as endorsed")
377
+ @click.option(
378
+ "--status",
379
+ required=False,
380
+ type=click.Choice(["Draft", "Published", "Archived"]),
381
+ help="Status of the data product",
382
+ )
383
+ def update(
384
+ product_id, name, description, domain_id, type, owner_id, business_use, update_frequency, endorsed, status
385
+ ):
386
+ """Update an existing data product."""
387
+ try:
388
+ client = UnifiedCatalogClient()
389
+
390
+ # Build args dictionary - only include provided values
391
+ args = {"--product-id": [product_id]}
392
+
393
+ if name:
394
+ args["--name"] = [name]
395
+ if description is not None: # Allow empty string
396
+ args["--description"] = [description]
397
+ if domain_id:
398
+ args["--domain-id"] = [domain_id]
399
+ if type:
400
+ args["--type"] = [type]
401
+ if status:
402
+ args["--status"] = [status]
403
+ if business_use is not None:
404
+ args["--business-use"] = [business_use]
405
+ if update_frequency:
406
+ args["--update-frequency"] = [update_frequency]
407
+ if endorsed:
408
+ args["--endorsed"] = ["true"]
409
+ if owner_id:
410
+ args["--owner-id"] = list(owner_id)
411
+
412
+ result = client.update_data_product(args)
413
+
414
+ if not result:
415
+ console.print("[red]ERROR:[/red] No response received")
416
+ return
417
+ if isinstance(result, dict) and "error" in result:
418
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
419
+ return
420
+
421
+ console.print(f"[green]✅ SUCCESS:[/green] Updated data product '{product_id}'")
422
+ console.print(json.dumps(result, indent=2))
423
+
424
+ except Exception as e:
425
+ console.print(f"[red]ERROR:[/red] {str(e)}")
426
+
427
+
428
+ @dataproduct.command()
429
+ @click.option("--product-id", required=True, help="ID of the data product to delete")
430
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
431
+ def delete(product_id, yes):
432
+ """Delete a data product."""
433
+ try:
434
+ if not yes:
435
+ confirm = click.confirm(
436
+ f"Are you sure you want to delete data product '{product_id}'?",
437
+ default=False
438
+ )
439
+ if not confirm:
440
+ console.print("[yellow]Deletion cancelled.[/yellow]")
441
+ return
442
+
443
+ client = UnifiedCatalogClient()
444
+ args = {"--product-id": [product_id]}
445
+ result = client.delete_data_product(args)
446
+
447
+ # DELETE operations may return empty response on success
448
+ if result is None or (isinstance(result, dict) and not result.get("error")):
449
+ console.print(f"[green]✅ SUCCESS:[/green] Deleted data product '{product_id}'")
450
+ elif isinstance(result, dict) and "error" in result:
451
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
452
+ else:
453
+ console.print(f"[green]✅ SUCCESS:[/green] Deleted data product '{product_id}'")
454
+ if result:
455
+ console.print(json.dumps(result, indent=2))
456
+
457
+ except Exception as e:
458
+ console.print(f"[red]ERROR:[/red] {str(e)}")
459
+
460
+
461
+ # ========================================
462
+ # GLOSSARIES
463
+ # ========================================
464
+
465
+
466
+ @uc.group()
467
+ def glossary():
468
+ """Manage glossaries (for finding glossary GUIDs)."""
469
+ pass
470
+
471
+
472
+ @glossary.command(name="list")
473
+ @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
474
+ def list_glossaries(output_json):
475
+ """List all glossaries with their GUIDs."""
476
+ try:
477
+ from purviewcli.client._glossary import Glossary
478
+
479
+ client = Glossary()
480
+ result = client.glossaryRead({})
481
+
482
+ # Normalize response
483
+ if isinstance(result, dict):
484
+ glossaries = result.get("value", []) or []
485
+ elif isinstance(result, (list, tuple)):
486
+ glossaries = result
487
+ else:
488
+ glossaries = []
489
+
490
+ if not glossaries:
491
+ console.print("[yellow]No glossaries found.[/yellow]")
492
+ return
493
+
494
+ # Output in JSON format if requested
495
+ if output_json:
496
+ _format_json_output(glossaries)
497
+ return
498
+
499
+ table = Table(title="Glossaries")
500
+ table.add_column("GUID", style="cyan", no_wrap=True)
501
+ table.add_column("Name", style="green")
502
+ table.add_column("Qualified Name", style="yellow")
503
+ table.add_column("Description", style="white")
504
+
505
+ for g in glossaries:
506
+ if not isinstance(g, dict):
507
+ continue
508
+ table.add_row(
509
+ g.get("guid", "N/A"),
510
+ g.get("name", "N/A"),
511
+ g.get("qualifiedName", "N/A"),
512
+ (g.get("shortDescription", "")[:60] + "...") if len(g.get("shortDescription", "")) > 60 else g.get("shortDescription", ""),
513
+ )
514
+
515
+ console.print(table)
516
+ console.print("\n[dim]Tip: Use the GUID with --glossary-guid option when listing/creating terms[/dim]")
517
+
518
+ except Exception as e:
519
+ console.print(f"[red]ERROR:[/red] {str(e)}")
520
+
521
+
522
+ @glossary.command(name="create")
523
+ @click.option("--name", required=True, help="Name of the glossary")
524
+ @click.option("--description", required=False, default="", help="Description of the glossary")
525
+ @click.option("--domain-id", required=False, help="Associate with governance domain ID (optional)")
526
+ def create_glossary(name, description, domain_id):
527
+ """Create a new glossary."""
528
+ try:
529
+ from purviewcli.client._glossary import Glossary
530
+
531
+ client = Glossary()
532
+
533
+ # Build qualified name - include domain_id if provided
534
+ if domain_id:
535
+ qualified_name = f"{name}@{domain_id}"
536
+ else:
537
+ qualified_name = name
538
+
539
+ payload = {
540
+ "name": name,
541
+ "qualifiedName": qualified_name,
542
+ "shortDescription": description,
543
+ "longDescription": description,
544
+ }
545
+
546
+ result = client.glossaryCreate({"--payloadFile": payload})
547
+
548
+ if not result:
549
+ console.print("[red]ERROR:[/red] No response received")
550
+ return
551
+ if isinstance(result, dict) and "error" in result:
552
+ console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
553
+ return
554
+
555
+ guid = result.get("guid") if isinstance(result, dict) else None
556
+ console.print(f"[green]✅ SUCCESS:[/green] Created glossary '{name}'")
557
+ if guid:
558
+ console.print(f"[cyan]GUID:[/cyan] {guid}")
559
+ console.print(f"\n[dim]Use this GUID: --glossary-guid {guid}[/dim]")
560
+ console.print(json.dumps(result, indent=2))
561
+
562
+ except Exception as e:
563
+ console.print(f"[red]ERROR:[/red] {str(e)}")
564
+
565
+
566
+ @glossary.command(name="create-for-domains")
567
+ def create_glossaries_for_domains():
568
+ """Create glossaries for all governance domains that don't have one."""
569
+ try:
570
+ from purviewcli.client._glossary import Glossary
571
+
572
+ uc_client = UnifiedCatalogClient()
573
+ glossary_client = Glossary()
574
+
575
+ # Get all domains
576
+ domains_result = uc_client.get_governance_domains({})
577
+ if isinstance(domains_result, dict):
578
+ domains = domains_result.get("value", [])
579
+ elif isinstance(domains_result, (list, tuple)):
580
+ domains = domains_result
581
+ else:
582
+ domains = []
583
+
584
+ if not domains:
585
+ console.print("[yellow]No governance domains found.[/yellow]")
586
+ return
587
+
588
+ # Get existing glossaries
589
+ glossaries_result = glossary_client.glossaryRead({})
590
+ if isinstance(glossaries_result, dict):
591
+ existing_glossaries = glossaries_result.get("value", [])
592
+ elif isinstance(glossaries_result, (list, tuple)):
593
+ existing_glossaries = glossaries_result
594
+ else:
595
+ existing_glossaries = []
596
+
597
+ # Build set of domain IDs that already have glossaries (check qualifiedName)
598
+ existing_domain_ids = set()
599
+ for g in existing_glossaries:
600
+ if isinstance(g, dict):
601
+ qn = g.get("qualifiedName", "")
602
+ # Extract domain_id from qualifiedName if it contains @domain_id pattern
603
+ if "@" in qn:
604
+ domain_id_part = qn.split("@")[-1]
605
+ existing_domain_ids.add(domain_id_part)
606
+
607
+ console.print(f"[cyan]Found {len(domains)} governance domains and {len(existing_glossaries)} existing glossaries[/cyan]\n")
608
+
609
+ created_count = 0
610
+ for domain in domains:
611
+ if not isinstance(domain, dict):
612
+ continue
613
+
614
+ domain_id = domain.get("id")
615
+ domain_name = domain.get("name")
616
+
617
+ if not domain_id or not domain_name:
618
+ continue
619
+
620
+ # Check if glossary already exists for this domain
621
+ if domain_id in existing_domain_ids:
622
+ console.print(f"[dim]⏭ Skipping {domain_name} - glossary already exists[/dim]")
623
+ continue
624
+
625
+ # Create glossary for this domain
626
+ glossary_name = f"{domain_name} Glossary"
627
+ qualified_name = f"{glossary_name}@{domain_id}"
628
+
629
+ payload = {
630
+ "name": glossary_name,
631
+ "qualifiedName": qualified_name,
632
+ "shortDescription": f"Glossary for {domain_name} domain",
633
+ "longDescription": f"This glossary contains business terms for the {domain_name} governance domain.",
634
+ }
635
+
636
+ try:
637
+ result = glossary_client.glossaryCreate({"--payloadFile": payload})
638
+ guid = result.get("guid") if isinstance(result, dict) else None
639
+
640
+ if guid:
641
+ console.print(f"[green]✅ Created:[/green] {glossary_name} (GUID: {guid})")
642
+ created_count += 1
643
+ else:
644
+ console.print(f"[yellow]⚠ Created {glossary_name} but no GUID returned[/yellow]")
645
+
646
+ except Exception as e:
647
+ console.print(f"[red]❌ Failed to create {glossary_name}:[/red] {str(e)}")
648
+
649
+ console.print(f"\n[cyan]Created {created_count} new glossaries[/cyan]")
650
+ console.print("[dim]Run 'pvw uc glossary list' to see all glossaries[/dim]")
651
+
652
+ except Exception as e:
653
+ console.print(f"[red]ERROR:[/red] {str(e)}")
654
+
655
+
656
+ @glossary.command(name="verify-links")
657
+ def verify_glossary_links():
658
+ """Verify which domains have properly linked glossaries."""
659
+ try:
660
+ from purviewcli.client._glossary import Glossary
661
+
662
+ uc_client = UnifiedCatalogClient()
663
+ glossary_client = Glossary()
664
+
665
+ # Get all domains
666
+ domains_result = uc_client.get_governance_domains({})
667
+ if isinstance(domains_result, dict):
668
+ domains = domains_result.get("value", [])
669
+ elif isinstance(domains_result, (list, tuple)):
670
+ domains = domains_result
671
+ else:
672
+ domains = []
673
+
674
+ # Get all glossaries
675
+ glossaries_result = glossary_client.glossaryRead({})
676
+ if isinstance(glossaries_result, dict):
677
+ glossaries = glossaries_result.get("value", [])
678
+ elif isinstance(glossaries_result, (list, tuple)):
679
+ glossaries = glossaries_result
680
+ else:
681
+ glossaries = []
682
+
683
+ console.print(f"[bold cyan]Governance Domain → Glossary Link Verification[/bold cyan]\n")
684
+
685
+ table = Table(title="Domain-Glossary Associations")
686
+ table.add_column("Domain Name", style="green")
687
+ table.add_column("Domain ID", style="cyan", no_wrap=True)
688
+ table.add_column("Linked Glossary", style="yellow")
689
+ table.add_column("Glossary GUID", style="magenta", no_wrap=True)
690
+ table.add_column("Status", style="white")
691
+
692
+ # Build a map of domain_id -> glossary info
693
+ domain_glossary_map = {}
694
+ for g in glossaries:
695
+ if not isinstance(g, dict):
696
+ continue
697
+ qn = g.get("qualifiedName", "")
698
+ # Check if qualifiedName contains @domain_id pattern
699
+ if "@" in qn:
700
+ domain_id_part = qn.split("@")[-1]
701
+ domain_glossary_map[domain_id_part] = {
702
+ "name": g.get("name"),
703
+ "guid": g.get("guid"),
704
+ "qualifiedName": qn,
705
+ }
706
+
707
+ linked_count = 0
708
+ unlinked_count = 0
709
+
710
+ for domain in domains:
711
+ if not isinstance(domain, dict):
712
+ continue
713
+
714
+ domain_id = domain.get("id")
715
+ domain_name = domain.get("name", "N/A")
716
+ parent_id = domain.get("parentDomainId")
717
+
718
+ # Skip if no domain_id
719
+ if not domain_id:
720
+ continue
721
+
722
+ # Show if it's a nested domain
723
+ nested_indicator = " (nested)" if parent_id else ""
724
+ domain_display = f"{domain_name}{nested_indicator}"
725
+
726
+ if domain_id in domain_glossary_map:
727
+ glossary_info = domain_glossary_map[domain_id]
728
+ table.add_row(
729
+ domain_display,
730
+ domain_id[:8] + "...",
731
+ glossary_info["name"],
732
+ glossary_info["guid"][:8] + "...",
733
+ "[green]✅ Linked[/green]"
734
+ )
735
+ linked_count += 1
736
+ else:
737
+ table.add_row(
738
+ domain_display,
739
+ domain_id[:8] + "...",
740
+ "[dim]No glossary[/dim]",
741
+ "[dim]N/A[/dim]",
742
+ "[yellow]⚠ Not Linked[/yellow]"
743
+ )
744
+ unlinked_count += 1
745
+
746
+ console.print(table)
747
+ console.print(f"\n[cyan]Summary:[/cyan]")
748
+ console.print(f" • Linked domains: [green]{linked_count}[/green]")
749
+ console.print(f" • Unlinked domains: [yellow]{unlinked_count}[/yellow]")
750
+
751
+ if unlinked_count > 0:
752
+ console.print(f"\n[dim]💡 Tip: Run 'pvw uc glossary create-for-domains' to create glossaries for unlinked domains[/dim]")
753
+
754
+ except Exception as e:
755
+ console.print(f"[red]ERROR:[/red] {str(e)}")
756
+
757
+
346
758
  # ========================================
347
759
  # GLOSSARY TERMS
348
760
  # ========================================
@@ -377,10 +789,10 @@ def term():
377
789
  help="Owner Entra ID (can be specified multiple times)",
378
790
  multiple=True,
379
791
  )
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")
792
+ @click.option("--resource-name", required=False, help="Resource name for additional reading (can be specified multiple times)", multiple=True)
793
+ @click.option("--resource-url", required=False, help="Resource URL for additional reading (can be specified multiple times)", multiple=True)
382
794
  def create(name, description, domain_id, status, acronym, owner_id, resource_name, resource_url):
383
- """Create a new glossary term."""
795
+ """Create a new Unified Catalog term (Governance Domain term)."""
384
796
  try:
385
797
  client = UnifiedCatalogClient()
386
798
 
@@ -393,12 +805,13 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
393
805
  }
394
806
 
395
807
  if acronym:
396
- args["--acronyms"] = list(acronym)
808
+ args["--acronym"] = list(acronym)
397
809
  if owner_id:
398
810
  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]
811
+ if resource_name:
812
+ args["--resource-name"] = list(resource_name)
813
+ if resource_url:
814
+ args["--resource-url"] = list(resource_url)
402
815
 
403
816
  result = client.create_term(args)
404
817
 
@@ -420,42 +833,29 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
420
833
  @click.option("--domain-id", required=True, help="Governance domain ID to list terms from")
421
834
  @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
422
835
  def list_terms(domain_id, output_json):
423
- """List all glossary terms in a governance domain."""
836
+ """List all Unified Catalog terms in a governance domain."""
424
837
  try:
425
838
  client = UnifiedCatalogClient()
426
839
  args = {"--governance-domain-id": [domain_id]}
427
840
  result = client.get_terms(args)
428
841
 
429
842
  if not result:
430
- console.print("[yellow]No glossary terms found.[/yellow]")
843
+ console.print("[yellow]No terms found.[/yellow]")
431
844
  return
432
845
 
433
- # The API returns glossaries with terms nested inside
434
- # Extract all terms from all glossaries
846
+ # Unified Catalog API returns terms directly in value array
435
847
  all_terms = []
436
848
 
437
- if isinstance(result, (list, tuple)):
438
- glossaries = result
439
- elif isinstance(result, dict):
440
- glossaries = result.get("value", [])
849
+ if isinstance(result, dict):
850
+ all_terms = result.get("value", [])
851
+ elif isinstance(result, (list, tuple)):
852
+ all_terms = result
441
853
  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
- )
854
+ console.print("[yellow]Unexpected response format.[/yellow]")
855
+ return
456
856
 
457
857
  if not all_terms:
458
- console.print("[yellow]No glossary terms found.[/yellow]")
858
+ console.print("[yellow]No terms found.[/yellow]")
459
859
  return
460
860
 
461
861
  # Output in JSON format if requested
@@ -463,21 +863,30 @@ def list_terms(domain_id, output_json):
463
863
  _format_json_output(all_terms)
464
864
  return
465
865
 
466
- table = Table(title="Glossary Terms")
467
- table.add_column("Term ID", style="cyan")
866
+ table = Table(title="Unified Catalog Terms")
867
+ table.add_column("Term ID", style="cyan", no_wrap=False)
468
868
  table.add_column("Name", style="green")
469
- table.add_column("Glossary", style="yellow")
470
- table.add_column("Glossary ID", style="blue")
869
+ table.add_column("Status", style="yellow")
870
+ table.add_column("Description", style="white")
471
871
 
472
872
  for term in all_terms:
873
+ description = term.get("description", "")
874
+ # Strip HTML tags from description
875
+ import re
876
+ description = re.sub(r'<[^>]+>', '', description)
877
+ # Truncate long descriptions
878
+ if len(description) > 50:
879
+ description = description[:50] + "..."
880
+
473
881
  table.add_row(
474
882
  term.get("id", "N/A"),
475
883
  term.get("name", "N/A"),
476
- term.get("glossary", "N/A"),
477
- term.get("glossary_id", "N/A"),
884
+ term.get("status", "N/A"),
885
+ description.strip(),
478
886
  )
479
887
 
480
888
  console.print(table)
889
+ console.print(f"\n[dim]Found {len(all_terms)} term(s)[/dim]")
481
890
 
482
891
  except Exception as e:
483
892
  console.print(f"[red]ERROR:[/red] {str(e)}")
@@ -485,7 +894,8 @@ def list_terms(domain_id, output_json):
485
894
 
486
895
  @term.command()
487
896
  @click.option("--term-id", required=True, help="ID of the glossary term")
488
- def show(term_id):
897
+ @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
898
+ def show(term_id, output_json):
489
899
  """Show details of a glossary term."""
490
900
  try:
491
901
  client = UnifiedCatalogClient()
@@ -499,12 +909,70 @@ def show(term_id):
499
909
  console.print(f"[red]ERROR:[/red] {result.get('error', 'Term not found')}")
500
910
  return
501
911
 
502
- console.print(json.dumps(result, indent=2))
912
+ if output_json:
913
+ _format_json_output(result)
914
+ else:
915
+ # Display key information in a readable format
916
+ if isinstance(result, dict):
917
+ console.print(f"[cyan]Term Name:[/cyan] {result.get('name', 'N/A')}")
918
+ console.print(f"[cyan]GUID:[/cyan] {result.get('guid', 'N/A')}")
919
+ console.print(f"[cyan]Status:[/cyan] {result.get('status', 'N/A')}")
920
+ console.print(f"[cyan]Qualified Name:[/cyan] {result.get('qualifiedName', 'N/A')}")
921
+
922
+ # Show glossary info
923
+ anchor = result.get('anchor', {})
924
+ if anchor:
925
+ console.print(f"[cyan]Glossary GUID:[/cyan] {anchor.get('glossaryGuid', 'N/A')}")
926
+
927
+ # Show description
928
+ desc = result.get('shortDescription') or result.get('longDescription', '')
929
+ if desc:
930
+ console.print(f"[cyan]Description:[/cyan] {desc}")
931
+
932
+ # Show full JSON if needed
933
+ console.print(f"\n[dim]Full details (JSON):[/dim]")
934
+ console.print(json.dumps(result, indent=2))
935
+ else:
936
+ console.print(json.dumps(result, indent=2))
503
937
 
504
938
  except Exception as e:
505
939
  console.print(f"[red]ERROR:[/red] {str(e)}")
506
940
 
507
941
 
942
+ @term.command()
943
+ @click.option("--term-id", required=True, help="ID of the glossary term to delete")
944
+ @click.option("--force", is_flag=True, help="Skip confirmation prompt")
945
+ def delete(term_id, force):
946
+ """Delete a glossary term."""
947
+ try:
948
+ if not force:
949
+ # Show term details first
950
+ client = UnifiedCatalogClient()
951
+ term_info = client.get_term_by_id({"--term-id": [term_id]})
952
+
953
+ if isinstance(term_info, dict) and term_info.get('name'):
954
+ console.print(f"[yellow]About to delete term:[/yellow]")
955
+ console.print(f" Name: {term_info.get('name')}")
956
+ console.print(f" GUID: {term_info.get('guid')}")
957
+ console.print(f" Status: {term_info.get('status')}")
958
+
959
+ confirm = click.confirm("Are you sure you want to delete this term?", default=False)
960
+ if not confirm:
961
+ console.print("[yellow]Deletion cancelled.[/yellow]")
962
+ return
963
+
964
+ # Import glossary client to delete term
965
+ from purviewcli.client._glossary import Glossary
966
+
967
+ gclient = Glossary()
968
+ result = gclient.glossaryDeleteTerm({"--termGuid": term_id})
969
+
970
+ console.print(f"[green]✅ SUCCESS:[/green] Deleted term with ID: {term_id}")
971
+
972
+ except Exception as e:
973
+ console.print(f"[red]ERROR:[/red] {str(e)}")
974
+
975
+
508
976
  # ========================================
509
977
  # OBJECTIVES AND KEY RESULTS (OKRs)
510
978
  # ========================================
@@ -790,35 +1258,12 @@ def show(cde_id):
790
1258
 
791
1259
 
792
1260
  # ========================================
793
- # HEALTH MANAGEMENT (Preview)
1261
+ # HEALTH MANAGEMENT - IMPLEMENTED! ✅
794
1262
  # ========================================
795
1263
 
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")
1264
+ # Import and register health commands from dedicated module
1265
+ from purviewcli.cli.health import health as health_commands
1266
+ uc.add_command(health_commands, name="health")
822
1267
 
823
1268
 
824
1269
  # ========================================