pvw-cli 1.0.8__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.

@@ -11,11 +11,20 @@ import os
11
11
  from rich.console import Console
12
12
  from rich.table import Table
13
13
  from rich.text import Text
14
+ from rich.syntax import Syntax
14
15
  from purviewcli.client._unified_catalog import UnifiedCatalogClient
15
16
 
16
17
  console = Console()
17
18
 
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
+
19
28
  @click.group()
20
29
  def uc():
21
30
  """Manage Unified Catalog in Microsoft Purview (domains, terms, data products, OKRs, CDEs)."""
@@ -88,7 +97,8 @@ def create(name, description, type, owner_id, status):
88
97
 
89
98
 
90
99
  @domain.command(name="list")
91
- def list_domains():
100
+ @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
101
+ def list_domains(output_json):
92
102
  """List all governance domains."""
93
103
  try:
94
104
  client = UnifiedCatalogClient()
@@ -111,6 +121,11 @@ def list_domains():
111
121
  console.print("[yellow]No governance domains found.[/yellow]")
112
122
  return
113
123
 
124
+ # Output in JSON format if requested
125
+ if output_json:
126
+ _format_json_output(domains)
127
+ return
128
+
114
129
  table = Table(title="Governance Domains")
115
130
  table.add_column("ID", style="cyan")
116
131
  table.add_column("Name", style="green")
@@ -243,7 +258,8 @@ def create(
243
258
  @dataproduct.command(name="list")
244
259
  @click.option("--domain-id", required=False, help="Governance domain ID (optional filter)")
245
260
  @click.option("--status", required=False, help="Status filter (Draft, Published, Archived)")
246
- def list_data_products(domain_id, status):
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):
247
263
  """List all data products (optionally filtered by domain or status)."""
248
264
  try:
249
265
  client = UnifiedCatalogClient()
@@ -274,6 +290,11 @@ def list_data_products(domain_id, status):
274
290
  console.print(f"[yellow]No data products found{filter_msg}.[/yellow]")
275
291
  return
276
292
 
293
+ # Output in JSON format if requested
294
+ if output_json:
295
+ _format_json_output(products)
296
+ return
297
+
277
298
  table = Table(title="Data Products")
278
299
  table.add_column("ID", style="cyan")
279
300
  table.add_column("Name", style="green")
@@ -326,6 +347,7 @@ def show(product_id):
326
347
  # GLOSSARY TERMS
327
348
  # ========================================
328
349
 
350
+
329
351
  @uc.group()
330
352
  def term():
331
353
  """Manage glossary terms."""
@@ -336,26 +358,40 @@ def term():
336
358
  @click.option("--name", required=True, help="Name of the glossary term")
337
359
  @click.option("--description", required=False, default="", help="Rich text description of the term")
338
360
  @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)
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
+ )
344
380
  @click.option("--resource-name", required=False, help="Resource name for additional reading")
345
381
  @click.option("--resource-url", required=False, help="Resource URL for additional reading")
346
382
  def create(name, description, domain_id, status, acronym, owner_id, resource_name, resource_url):
347
383
  """Create a new glossary term."""
348
384
  try:
349
385
  client = UnifiedCatalogClient()
350
-
386
+
351
387
  # Build args dictionary
352
388
  args = {
353
389
  "--name": [name],
354
390
  "--description": [description],
355
391
  "--governance-domain-id": [domain_id],
356
- "--status": [status]
392
+ "--status": [status],
357
393
  }
358
-
394
+
359
395
  if acronym:
360
396
  args["--acronyms"] = list(acronym)
361
397
  if owner_id:
@@ -363,71 +399,86 @@ def create(name, description, domain_id, status, acronym, owner_id, resource_nam
363
399
  if resource_name and resource_url:
364
400
  args["--resource-name"] = [resource_name]
365
401
  args["--resource-url"] = [resource_url]
366
-
402
+
367
403
  result = client.create_term(args)
368
-
404
+
369
405
  if not result:
370
406
  console.print("[red]ERROR:[/red] No response received")
371
407
  return
372
408
  if isinstance(result, dict) and "error" in result:
373
409
  console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
374
410
  return
375
-
411
+
376
412
  console.print(f"[green]✅ SUCCESS:[/green] Created glossary term '{name}'")
377
413
  console.print(json.dumps(result, indent=2))
378
-
414
+
379
415
  except Exception as e:
380
416
  console.print(f"[red]ERROR:[/red] {str(e)}")
381
417
 
382
418
 
383
419
  @term.command(name="list")
384
420
  @click.option("--domain-id", required=True, help="Governance domain ID to list terms from")
385
- def list_terms(domain_id):
421
+ @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
422
+ def list_terms(domain_id, output_json):
386
423
  """List all glossary terms in a governance domain."""
387
424
  try:
388
425
  client = UnifiedCatalogClient()
389
426
  args = {"--governance-domain-id": [domain_id]}
390
427
  result = client.get_terms(args)
391
-
428
+
392
429
  if not result:
393
430
  console.print("[yellow]No glossary terms found.[/yellow]")
394
431
  return
395
-
396
- # Handle both list and dict responses
432
+
433
+ # The API returns glossaries with terms nested inside
434
+ # Extract all terms from all glossaries
435
+ all_terms = []
436
+
397
437
  if isinstance(result, (list, tuple)):
398
- terms = result
438
+ glossaries = result
399
439
  elif isinstance(result, dict):
400
- terms = result.get("value", [])
440
+ glossaries = result.get("value", [])
401
441
  else:
402
- terms = []
403
-
404
- if not terms:
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:
405
458
  console.print("[yellow]No glossary terms found.[/yellow]")
406
459
  return
407
-
460
+
461
+ # Output in JSON format if requested
462
+ if output_json:
463
+ _format_json_output(all_terms)
464
+ return
465
+
408
466
  table = Table(title="Glossary Terms")
409
- table.add_column("ID", style="cyan")
467
+ table.add_column("Term ID", style="cyan")
410
468
  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
-
469
+ table.add_column("Glossary", style="yellow")
470
+ table.add_column("Glossary ID", style="blue")
471
+
472
+ for term in all_terms:
421
473
  table.add_row(
422
474
  term.get("id", "N/A"),
423
475
  term.get("name", "N/A"),
424
- term.get("status", "N/A"),
425
- acronyms or "None",
426
- desc
476
+ term.get("glossary", "N/A"),
477
+ term.get("glossary_id", "N/A"),
427
478
  )
428
-
479
+
429
480
  console.print(table)
430
-
481
+
431
482
  except Exception as e:
432
483
  console.print(f"[red]ERROR:[/red] {str(e)}")
433
484
 
@@ -440,16 +491,16 @@ def show(term_id):
440
491
  client = UnifiedCatalogClient()
441
492
  args = {"--term-id": [term_id]}
442
493
  result = client.get_term_by_id(args)
443
-
494
+
444
495
  if not result:
445
496
  console.print("[red]ERROR:[/red] No response received")
446
497
  return
447
498
  if isinstance(result, dict) and "error" in result:
448
499
  console.print(f"[red]ERROR:[/red] {result.get('error', 'Term not found')}")
449
500
  return
450
-
501
+
451
502
  console.print(json.dumps(result, indent=2))
452
-
503
+
453
504
  except Exception as e:
454
505
  console.print(f"[red]ERROR:[/red] {str(e)}")
455
506
 
@@ -458,6 +509,7 @@ def show(term_id):
458
509
  # OBJECTIVES AND KEY RESULTS (OKRs)
459
510
  # ========================================
460
511
 
512
+
461
513
  @uc.group()
462
514
  def objective():
463
515
  """Manage objectives and key results (OKRs)."""
@@ -467,56 +519,68 @@ def objective():
467
519
  @objective.command()
468
520
  @click.option("--definition", required=True, help="Definition of the objective")
469
521
  @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)")
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
+ )
475
538
  def create(definition, domain_id, status, owner_id, target_date):
476
539
  """Create a new objective."""
477
540
  try:
478
541
  client = UnifiedCatalogClient()
479
-
542
+
480
543
  args = {
481
544
  "--definition": [definition],
482
545
  "--governance-domain-id": [domain_id],
483
- "--status": [status]
546
+ "--status": [status],
484
547
  }
485
-
548
+
486
549
  if owner_id:
487
550
  args["--owner-id"] = list(owner_id)
488
551
  if target_date:
489
552
  args["--target-date"] = [target_date]
490
-
553
+
491
554
  result = client.create_objective(args)
492
-
555
+
493
556
  if not result:
494
557
  console.print("[red]ERROR:[/red] No response received")
495
558
  return
496
559
  if isinstance(result, dict) and "error" in result:
497
560
  console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
498
561
  return
499
-
562
+
500
563
  console.print(f"[green]✅ SUCCESS:[/green] Created objective")
501
564
  console.print(json.dumps(result, indent=2))
502
-
565
+
503
566
  except Exception as e:
504
567
  console.print(f"[red]ERROR:[/red] {str(e)}")
505
568
 
506
569
 
507
570
  @objective.command(name="list")
508
571
  @click.option("--domain-id", required=True, help="Governance domain ID to list objectives from")
509
- def list_objectives(domain_id):
572
+ @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
573
+ def list_objectives(domain_id, output_json):
510
574
  """List all objectives in a governance domain."""
511
575
  try:
512
576
  client = UnifiedCatalogClient()
513
577
  args = {"--governance-domain-id": [domain_id]}
514
578
  result = client.get_objectives(args)
515
-
579
+
516
580
  if not result:
517
581
  console.print("[yellow]No objectives found.[/yellow]")
518
582
  return
519
-
583
+
520
584
  # Handle response format
521
585
  if isinstance(result, (list, tuple)):
522
586
  objectives = result
@@ -524,31 +588,36 @@ def list_objectives(domain_id):
524
588
  objectives = result.get("value", [])
525
589
  else:
526
590
  objectives = []
527
-
591
+
528
592
  if not objectives:
529
593
  console.print("[yellow]No objectives found.[/yellow]")
530
594
  return
531
-
595
+
596
+ # Output in JSON format if requested
597
+ if output_json:
598
+ _format_json_output(objectives)
599
+ return
600
+
532
601
  table = Table(title="Objectives")
533
602
  table.add_column("ID", style="cyan")
534
603
  table.add_column("Definition", style="green")
535
604
  table.add_column("Status", style="yellow")
536
605
  table.add_column("Target Date", style="blue")
537
-
606
+
538
607
  for obj in objectives:
539
608
  definition = obj.get("definition", "")
540
609
  if len(definition) > 50:
541
610
  definition = definition[:50] + "..."
542
-
611
+
543
612
  table.add_row(
544
613
  obj.get("id", "N/A"),
545
614
  definition,
546
615
  obj.get("status", "N/A"),
547
- obj.get("targetDate", "N/A")
616
+ obj.get("targetDate", "N/A"),
548
617
  )
549
-
618
+
550
619
  console.print(table)
551
-
620
+
552
621
  except Exception as e:
553
622
  console.print(f"[red]ERROR:[/red] {str(e)}")
554
623
 
@@ -561,16 +630,16 @@ def show(objective_id):
561
630
  client = UnifiedCatalogClient()
562
631
  args = {"--objective-id": [objective_id]}
563
632
  result = client.get_objective_by_id(args)
564
-
633
+
565
634
  if not result:
566
635
  console.print("[red]ERROR:[/red] No response received")
567
636
  return
568
637
  if isinstance(result, dict) and "error" in result:
569
638
  console.print(f"[red]ERROR:[/red] {result.get('error', 'Objective not found')}")
570
639
  return
571
-
640
+
572
641
  console.print(json.dumps(result, indent=2))
573
-
642
+
574
643
  except Exception as e:
575
644
  console.print(f"[red]ERROR:[/red] {str(e)}")
576
645
 
@@ -579,6 +648,7 @@ def show(objective_id):
579
648
  # CRITICAL DATA ELEMENTS (CDEs)
580
649
  # ========================================
581
650
 
651
+
582
652
  @uc.group()
583
653
  def cde():
584
654
  """Manage critical data elements."""
@@ -589,58 +659,71 @@ def cde():
589
659
  @click.option("--name", required=True, help="Name of the critical data element")
590
660
  @click.option("--description", required=False, default="", help="Description of the CDE")
591
661
  @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)
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
+ )
599
681
  def create(name, description, domain_id, data_type, status, owner_id):
600
682
  """Create a new critical data element."""
601
683
  try:
602
684
  client = UnifiedCatalogClient()
603
-
685
+
604
686
  args = {
605
687
  "--name": [name],
606
688
  "--description": [description],
607
689
  "--governance-domain-id": [domain_id],
608
690
  "--data-type": [data_type],
609
- "--status": [status]
691
+ "--status": [status],
610
692
  }
611
-
693
+
612
694
  if owner_id:
613
695
  args["--owner-id"] = list(owner_id)
614
-
696
+
615
697
  result = client.create_critical_data_element(args)
616
-
698
+
617
699
  if not result:
618
700
  console.print("[red]ERROR:[/red] No response received")
619
701
  return
620
702
  if isinstance(result, dict) and "error" in result:
621
703
  console.print(f"[red]ERROR:[/red] {result.get('error', 'Unknown error')}")
622
704
  return
623
-
705
+
624
706
  console.print(f"[green]✅ SUCCESS:[/green] Created critical data element '{name}'")
625
707
  console.print(json.dumps(result, indent=2))
626
-
708
+
627
709
  except Exception as e:
628
710
  console.print(f"[red]ERROR:[/red] {str(e)}")
629
711
 
630
712
 
631
713
  @cde.command(name="list")
632
714
  @click.option("--domain-id", required=True, help="Governance domain ID to list CDEs from")
633
- def list_cdes(domain_id):
715
+ @click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
716
+ def list_cdes(domain_id, output_json):
634
717
  """List all critical data elements in a governance domain."""
635
718
  try:
636
719
  client = UnifiedCatalogClient()
637
720
  args = {"--governance-domain-id": [domain_id]}
638
721
  result = client.get_critical_data_elements(args)
639
-
722
+
640
723
  if not result:
641
724
  console.print("[yellow]No critical data elements found.[/yellow]")
642
725
  return
643
-
726
+
644
727
  # Handle response format
645
728
  if isinstance(result, (list, tuple)):
646
729
  cdes = result
@@ -648,33 +731,38 @@ def list_cdes(domain_id):
648
731
  cdes = result.get("value", [])
649
732
  else:
650
733
  cdes = []
651
-
734
+
652
735
  if not cdes:
653
736
  console.print("[yellow]No critical data elements found.[/yellow]")
654
737
  return
655
-
738
+
739
+ # Output in JSON format if requested
740
+ if output_json:
741
+ _format_json_output(cdes)
742
+ return
743
+
656
744
  table = Table(title="Critical Data Elements")
657
745
  table.add_column("ID", style="cyan")
658
746
  table.add_column("Name", style="green")
659
747
  table.add_column("Data Type", style="blue")
660
748
  table.add_column("Status", style="yellow")
661
749
  table.add_column("Description", style="white")
662
-
750
+
663
751
  for cde_item in cdes:
664
752
  desc = cde_item.get("description", "")
665
753
  if len(desc) > 30:
666
754
  desc = desc[:30] + "..."
667
-
755
+
668
756
  table.add_row(
669
757
  cde_item.get("id", "N/A"),
670
758
  cde_item.get("name", "N/A"),
671
759
  cde_item.get("dataType", "N/A"),
672
760
  cde_item.get("status", "N/A"),
673
- desc
761
+ desc,
674
762
  )
675
-
763
+
676
764
  console.print(table)
677
-
765
+
678
766
  except Exception as e:
679
767
  console.print(f"[red]ERROR:[/red] {str(e)}")
680
768
 
@@ -687,16 +775,16 @@ def show(cde_id):
687
775
  client = UnifiedCatalogClient()
688
776
  args = {"--cde-id": [cde_id]}
689
777
  result = client.get_critical_data_element_by_id(args)
690
-
778
+
691
779
  if not result:
692
780
  console.print("[red]ERROR:[/red] No response received")
693
781
  return
694
782
  if isinstance(result, dict) and "error" in result:
695
783
  console.print(f"[red]ERROR:[/red] {result.get('error', 'CDE not found')}")
696
784
  return
697
-
785
+
698
786
  console.print(json.dumps(result, indent=2))
699
-
787
+
700
788
  except Exception as e:
701
789
  console.print(f"[red]ERROR:[/red] {str(e)}")
702
790
 
@@ -705,6 +793,7 @@ def show(cde_id):
705
793
  # HEALTH MANAGEMENT (Preview)
706
794
  # ========================================
707
795
 
796
+
708
797
  @uc.group()
709
798
  def health():
710
799
  """Manage health controls and actions (preview)."""
@@ -718,7 +807,7 @@ def list_controls():
718
807
  console.print("This feature is coming soon to Microsoft Purview Unified Catalog")
719
808
 
720
809
 
721
- @health.command(name="actions")
810
+ @health.command(name="actions")
722
811
  def list_actions():
723
812
  """List health actions (preview - not yet implemented)."""
724
813
  console.print("[yellow]🚧 Health Actions are not yet implemented in the API[/yellow]")
@@ -736,6 +825,7 @@ def data_quality():
736
825
  # CUSTOM ATTRIBUTES (Coming Soon)
737
826
  # ========================================
738
827
 
828
+
739
829
  @uc.group()
740
830
  def attribute():
741
831
  """Manage custom attributes (coming soon)."""
@@ -753,6 +843,7 @@ def list_attributes():
753
843
  # REQUESTS (Coming Soon)
754
844
  # ========================================
755
845
 
846
+
756
847
  @uc.group()
757
848
  def request():
758
849
  """Manage access requests (coming soon)."""
@@ -763,4 +854,4 @@ def request():
763
854
  def list_requests():
764
855
  """List access requests (coming soon)."""
765
856
  console.print("[yellow]🚧 Access Requests are coming soon[/yellow]")
766
- console.print("This feature is under development for data access workflows")
857
+ console.print("This feature is under development for data access workflows")