pvw-cli 1.2.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.
- purviewcli/__init__.py +27 -0
- purviewcli/__main__.py +15 -0
- purviewcli/cli/__init__.py +5 -0
- purviewcli/cli/account.py +199 -0
- purviewcli/cli/cli.py +170 -0
- purviewcli/cli/collections.py +502 -0
- purviewcli/cli/domain.py +361 -0
- purviewcli/cli/entity.py +2436 -0
- purviewcli/cli/glossary.py +533 -0
- purviewcli/cli/health.py +250 -0
- purviewcli/cli/insight.py +113 -0
- purviewcli/cli/lineage.py +1103 -0
- purviewcli/cli/management.py +141 -0
- purviewcli/cli/policystore.py +103 -0
- purviewcli/cli/relationship.py +75 -0
- purviewcli/cli/scan.py +357 -0
- purviewcli/cli/search.py +527 -0
- purviewcli/cli/share.py +478 -0
- purviewcli/cli/types.py +831 -0
- purviewcli/cli/unified_catalog.py +3540 -0
- purviewcli/cli/workflow.py +402 -0
- purviewcli/client/__init__.py +21 -0
- purviewcli/client/_account.py +1877 -0
- purviewcli/client/_collections.py +1761 -0
- purviewcli/client/_domain.py +414 -0
- purviewcli/client/_entity.py +3545 -0
- purviewcli/client/_glossary.py +3233 -0
- purviewcli/client/_health.py +501 -0
- purviewcli/client/_insight.py +2873 -0
- purviewcli/client/_lineage.py +2138 -0
- purviewcli/client/_management.py +2202 -0
- purviewcli/client/_policystore.py +2915 -0
- purviewcli/client/_relationship.py +1351 -0
- purviewcli/client/_scan.py +2607 -0
- purviewcli/client/_search.py +1472 -0
- purviewcli/client/_share.py +272 -0
- purviewcli/client/_types.py +2708 -0
- purviewcli/client/_unified_catalog.py +5112 -0
- purviewcli/client/_workflow.py +2734 -0
- purviewcli/client/api_client.py +1295 -0
- purviewcli/client/business_rules.py +675 -0
- purviewcli/client/config.py +231 -0
- purviewcli/client/data_quality.py +433 -0
- purviewcli/client/endpoint.py +123 -0
- purviewcli/client/endpoints.py +554 -0
- purviewcli/client/exceptions.py +38 -0
- purviewcli/client/lineage_visualization.py +797 -0
- purviewcli/client/monitoring_dashboard.py +712 -0
- purviewcli/client/rate_limiter.py +30 -0
- purviewcli/client/retry_handler.py +125 -0
- purviewcli/client/scanning_operations.py +523 -0
- purviewcli/client/settings.py +1 -0
- purviewcli/client/sync_client.py +250 -0
- purviewcli/plugins/__init__.py +1 -0
- purviewcli/plugins/plugin_system.py +709 -0
- pvw_cli-1.2.8.dist-info/METADATA +1618 -0
- pvw_cli-1.2.8.dist-info/RECORD +60 -0
- pvw_cli-1.2.8.dist-info/WHEEL +5 -0
- pvw_cli-1.2.8.dist-info/entry_points.txt +3 -0
- pvw_cli-1.2.8.dist-info/top_level.txt +1 -0
purviewcli/cli/health.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Health CLI commands for Microsoft Purview Unified Catalog
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from purviewcli.client._health import Health
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def health():
|
|
16
|
+
"""Health monitoring and governance recommendations."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@health.command()
|
|
21
|
+
@click.option("--domain-id", help="Filter by governance domain ID")
|
|
22
|
+
@click.option("--severity", help="Filter by severity: High, Medium, Low")
|
|
23
|
+
@click.option("--status", help="Filter by status: NotStarted, InProgress, Resolved, Dismissed")
|
|
24
|
+
@click.option("--finding-type", help="Filter by finding type (e.g., 'Estate Curation')")
|
|
25
|
+
@click.option("--target-entity-type", help="Filter by target entity type (e.g., DataProduct, Term)")
|
|
26
|
+
def query(domain_id, severity, status, finding_type, target_entity_type):
|
|
27
|
+
"""Query health actions (findings and recommendations)."""
|
|
28
|
+
client = Health()
|
|
29
|
+
|
|
30
|
+
args = {
|
|
31
|
+
"--domain-id": [domain_id] if domain_id else [""],
|
|
32
|
+
"--severity": [severity] if severity else [""],
|
|
33
|
+
"--status": [status] if status else [""],
|
|
34
|
+
"--finding-type": [finding_type] if finding_type else [""],
|
|
35
|
+
"--target-entity-type": [target_entity_type] if target_entity_type else [""]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
result = client.query_health_actions(args)
|
|
39
|
+
|
|
40
|
+
# get_data() in endpoint.py returns just the data part
|
|
41
|
+
if result and isinstance(result, dict):
|
|
42
|
+
actions = result.get("value", [])
|
|
43
|
+
|
|
44
|
+
if not actions:
|
|
45
|
+
console.print("[yellow]No health actions found matching the filters.[/yellow]")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Create summary table
|
|
49
|
+
table = Table(title=f"Health Actions ({len(actions)} found)", show_lines=True)
|
|
50
|
+
table.add_column("ID", style="cyan", no_wrap=False)
|
|
51
|
+
table.add_column("Finding", style="white", no_wrap=False)
|
|
52
|
+
table.add_column("Severity", style="yellow")
|
|
53
|
+
table.add_column("Status", style="green")
|
|
54
|
+
table.add_column("Target", style="magenta", no_wrap=False)
|
|
55
|
+
table.add_column("Domain", style="blue", no_wrap=False)
|
|
56
|
+
|
|
57
|
+
for action in actions:
|
|
58
|
+
# Color severity
|
|
59
|
+
severity_text = action.get("severity", "N/A")
|
|
60
|
+
if severity_text == "High":
|
|
61
|
+
severity_style = "[red]High[/red]"
|
|
62
|
+
elif severity_text == "Medium":
|
|
63
|
+
severity_style = "[yellow]Medium[/yellow]"
|
|
64
|
+
else:
|
|
65
|
+
severity_style = "[green]Low[/green]"
|
|
66
|
+
|
|
67
|
+
# Color status
|
|
68
|
+
status_text = action.get("status", "N/A")
|
|
69
|
+
if status_text == "NotStarted":
|
|
70
|
+
status_style = "[red]Not Started[/red]"
|
|
71
|
+
elif status_text == "InProgress":
|
|
72
|
+
status_style = "[yellow]In Progress[/yellow]"
|
|
73
|
+
elif status_text == "Resolved":
|
|
74
|
+
status_style = "[green]Resolved[/green]"
|
|
75
|
+
else:
|
|
76
|
+
status_style = status_text
|
|
77
|
+
|
|
78
|
+
# Truncate IDs for display
|
|
79
|
+
action_id = action.get("id", "N/A")
|
|
80
|
+
short_id = action_id[:13] + "..." if len(action_id) > 16 else action_id
|
|
81
|
+
|
|
82
|
+
domain_id_val = action.get("domainId", "N/A")
|
|
83
|
+
short_domain = domain_id_val[:13] + "..." if len(domain_id_val) > 16 else domain_id_val
|
|
84
|
+
|
|
85
|
+
table.add_row(
|
|
86
|
+
short_id,
|
|
87
|
+
action.get("findingName", "N/A"),
|
|
88
|
+
severity_style,
|
|
89
|
+
status_style,
|
|
90
|
+
action.get("targetEntityType", "N/A"),
|
|
91
|
+
short_domain
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
console.print(table)
|
|
95
|
+
console.print(f"\n[dim]Showing {len(actions)} health action(s)[/dim]")
|
|
96
|
+
console.print("[dim]Use 'pvcli uc health show --action-id <id>' for details[/dim]")
|
|
97
|
+
else:
|
|
98
|
+
console.print("[red]Failed to retrieve health actions.[/red]")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@health.command()
|
|
102
|
+
@click.option("--action-id", required=True, help="Health action ID")
|
|
103
|
+
def show(action_id):
|
|
104
|
+
"""Show detailed information about a health action."""
|
|
105
|
+
client = Health()
|
|
106
|
+
args = {"--action-id": [action_id]}
|
|
107
|
+
|
|
108
|
+
result = client.get_health_action(args)
|
|
109
|
+
|
|
110
|
+
# get_data() returns just the data part
|
|
111
|
+
if result and isinstance(result, dict) and "id" in result:
|
|
112
|
+
action = result
|
|
113
|
+
|
|
114
|
+
console.print(f"\n[bold cyan]Health Action Details[/bold cyan]\n")
|
|
115
|
+
|
|
116
|
+
# Basic info
|
|
117
|
+
console.print(f"[bold]ID:[/bold] {action.get('id', 'N/A')}")
|
|
118
|
+
console.print(f"[bold]Finding ID:[/bold] {action.get('findingId', 'N/A')}")
|
|
119
|
+
console.print(f"[bold]Name:[/bold] {action.get('findingName', 'N/A')}")
|
|
120
|
+
|
|
121
|
+
# Severity with color
|
|
122
|
+
severity = action.get("severity", "N/A")
|
|
123
|
+
severity_color = "red" if severity == "High" else "yellow" if severity == "Medium" else "green"
|
|
124
|
+
console.print(f"[bold]Severity:[/bold] [{severity_color}]{severity}[/{severity_color}]")
|
|
125
|
+
|
|
126
|
+
# Status with color
|
|
127
|
+
status = action.get("status", "N/A")
|
|
128
|
+
status_color = "red" if status == "NotStarted" else "yellow" if status == "InProgress" else "green"
|
|
129
|
+
console.print(f"[bold]Status:[/bold] [{status_color}]{status}[/{status_color}]")
|
|
130
|
+
|
|
131
|
+
# Category and types
|
|
132
|
+
console.print(f"\n[bold]Category:[/bold] {action.get('category', 'N/A')}")
|
|
133
|
+
console.print(f"[bold]Finding Type:[/bold] {action.get('findingType', 'N/A')}")
|
|
134
|
+
console.print(f"[bold]Finding SubType:[/bold] {action.get('findingSubType', 'N/A')}")
|
|
135
|
+
|
|
136
|
+
# Target
|
|
137
|
+
console.print(f"\n[bold]Target Entity Type:[/bold] {action.get('targetEntityType', 'N/A')}")
|
|
138
|
+
console.print(f"[bold]Target Entity ID:[/bold] {action.get('targetEntityId', 'N/A')}")
|
|
139
|
+
console.print(f"[bold]Domain ID:[/bold] {action.get('domainId', 'N/A')}")
|
|
140
|
+
|
|
141
|
+
# Recommendation
|
|
142
|
+
recommendation = action.get("recommendation", "")
|
|
143
|
+
if recommendation:
|
|
144
|
+
console.print(f"\n[bold]Recommendation:[/bold]\n{recommendation}")
|
|
145
|
+
|
|
146
|
+
# Reason
|
|
147
|
+
reason = action.get("reason", "")
|
|
148
|
+
if reason:
|
|
149
|
+
console.print(f"\n[bold]Reason:[/bold]\n{reason}")
|
|
150
|
+
|
|
151
|
+
# Assignment
|
|
152
|
+
assigned_to = action.get("assignedTo", [])
|
|
153
|
+
if assigned_to:
|
|
154
|
+
console.print(f"\n[bold]Assigned To:[/bold]")
|
|
155
|
+
for user_id in assigned_to:
|
|
156
|
+
console.print(f" • {user_id}")
|
|
157
|
+
else:
|
|
158
|
+
console.print(f"\n[bold]Assigned To:[/bold] [yellow]Not assigned[/yellow]")
|
|
159
|
+
|
|
160
|
+
# System data
|
|
161
|
+
system_data = action.get("systemData", {})
|
|
162
|
+
if system_data:
|
|
163
|
+
console.print(f"\n[bold]System Information:[/bold]")
|
|
164
|
+
console.print(f" Created At: {system_data.get('createdAt', 'N/A')}")
|
|
165
|
+
console.print(f" Created By: {system_data.get('createdBy', 'N/A')}")
|
|
166
|
+
console.print(f" Last Modified: {system_data.get('lastModifiedAt', 'N/A')}")
|
|
167
|
+
console.print(f" Last Modified By: {system_data.get('lastModifiedBy', 'N/A')}")
|
|
168
|
+
console.print(f" Last Hint At: {system_data.get('lastHintAt', 'N/A')}")
|
|
169
|
+
|
|
170
|
+
console.print()
|
|
171
|
+
else:
|
|
172
|
+
console.print(f"[red]Failed to retrieve health action: {action_id}[/red]")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@health.command()
|
|
176
|
+
@click.option("--action-id", required=True, help="Health action ID")
|
|
177
|
+
@click.option("--status", help="New status: NotStarted, InProgress, Resolved, Dismissed")
|
|
178
|
+
@click.option("--assigned-to", help="User ID or email to assign to")
|
|
179
|
+
@click.option("--reason", help="Reason for the update")
|
|
180
|
+
def update(action_id, status, assigned_to, reason):
|
|
181
|
+
"""Update a health action (status, assignment, etc.)."""
|
|
182
|
+
if not status and not assigned_to and not reason:
|
|
183
|
+
console.print("[red]Error: At least one of --status, --assigned-to, or --reason must be provided.[/red]")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
client = Health()
|
|
187
|
+
args = {
|
|
188
|
+
"--action-id": [action_id],
|
|
189
|
+
"--status": [status] if status else [""],
|
|
190
|
+
"--assigned-to": [assigned_to] if assigned_to else [""],
|
|
191
|
+
"--reason": [reason] if reason else [""]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
result = client.update_health_action(args)
|
|
195
|
+
|
|
196
|
+
if result and result.get("status") == "success":
|
|
197
|
+
console.print(f"[green][OK][/green] Health action updated successfully: {action_id}")
|
|
198
|
+
if status:
|
|
199
|
+
console.print(f" Status: {status}")
|
|
200
|
+
if assigned_to:
|
|
201
|
+
console.print(f" Assigned to: {assigned_to}")
|
|
202
|
+
if reason:
|
|
203
|
+
console.print(f" Reason: {reason}")
|
|
204
|
+
else:
|
|
205
|
+
console.print(f"[red]Failed to update health action: {action_id}[/red]")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@health.command()
|
|
209
|
+
@click.option("--action-id", required=True, help="Health action ID")
|
|
210
|
+
@click.confirmation_option(prompt="Are you sure you want to delete this health action?")
|
|
211
|
+
def delete(action_id):
|
|
212
|
+
"""Delete a health action."""
|
|
213
|
+
client = Health()
|
|
214
|
+
args = {"--action-id": [action_id]}
|
|
215
|
+
|
|
216
|
+
result = client.delete_health_action(args)
|
|
217
|
+
|
|
218
|
+
if result and result.get("status") == "success":
|
|
219
|
+
console.print(f"[green][OK][/green] Health action deleted successfully: {action_id}")
|
|
220
|
+
else:
|
|
221
|
+
console.print(f"[red]Failed to delete health action: {action_id}[/red]")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@health.command()
|
|
225
|
+
@click.option("--domain-id", help="Get summary for specific domain")
|
|
226
|
+
def summary(domain_id):
|
|
227
|
+
"""Get health summary statistics."""
|
|
228
|
+
client = Health()
|
|
229
|
+
args = {"--domain-id": [domain_id] if domain_id else [""]}
|
|
230
|
+
|
|
231
|
+
result = client.get_health_summary(args)
|
|
232
|
+
|
|
233
|
+
if result and result.get("data"):
|
|
234
|
+
summary_data = result["data"]
|
|
235
|
+
|
|
236
|
+
console.print("\n[bold cyan]Health Summary[/bold cyan]\n")
|
|
237
|
+
|
|
238
|
+
# Display summary statistics
|
|
239
|
+
console.print(f"Total Actions: {summary_data.get('total', 'N/A')}")
|
|
240
|
+
console.print(f"High Severity: [red]{summary_data.get('high', 'N/A')}[/red]")
|
|
241
|
+
console.print(f"Medium Severity: [yellow]{summary_data.get('medium', 'N/A')}[/yellow]")
|
|
242
|
+
console.print(f"Low Severity: [green]{summary_data.get('low', 'N/A')}[/green]")
|
|
243
|
+
console.print(f"\nNot Started: [red]{summary_data.get('notStarted', 'N/A')}[/red]")
|
|
244
|
+
console.print(f"In Progress: [yellow]{summary_data.get('inProgress', 'N/A')}[/yellow]")
|
|
245
|
+
console.print(f"Resolved: [green]{summary_data.get('resolved', 'N/A')}[/green]")
|
|
246
|
+
|
|
247
|
+
console.print()
|
|
248
|
+
else:
|
|
249
|
+
console.print("[yellow]Summary endpoint may not be available or no data returned.[/yellow]")
|
|
250
|
+
console.print("[dim]Try using 'pvcli uc health query' to see all actions.[/dim]")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Access Purview insight reports and analytics using modular Click-based commands.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
insight asset-distribution Show asset distribution insight
|
|
6
|
+
insight files-aggregation Show files aggregation insight
|
|
7
|
+
insight files-without-resource-set Show files without resource set insight
|
|
8
|
+
insight scan-status-summary Show scan status summary insight
|
|
9
|
+
insight scan-status-summary-by-ts Show scan status summary by timestamp insight
|
|
10
|
+
insight tags Show tags insight
|
|
11
|
+
insight tags-time-series Show tags time series insight
|
|
12
|
+
insight --help Show this help message and exit
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
-h --help Show this help message and exit
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
import json
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
def insight():
|
|
26
|
+
"""
|
|
27
|
+
Access Purview insight reports and analytics.
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@insight.command()
|
|
32
|
+
def asset_distribution():
|
|
33
|
+
"""Show asset distribution insight"""
|
|
34
|
+
try:
|
|
35
|
+
from purviewcli.client._insight import Insight
|
|
36
|
+
client = Insight()
|
|
37
|
+
result = client.assetDistribution({})
|
|
38
|
+
console.print(json.dumps(result, indent=2))
|
|
39
|
+
except Exception as e:
|
|
40
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
41
|
+
|
|
42
|
+
@insight.command()
|
|
43
|
+
def files_aggregation():
|
|
44
|
+
"""Show files aggregation insight"""
|
|
45
|
+
try:
|
|
46
|
+
from purviewcli.client._insight import Insight
|
|
47
|
+
client = Insight()
|
|
48
|
+
result = client.filesAggregation({})
|
|
49
|
+
console.print(json.dumps(result, indent=2))
|
|
50
|
+
except Exception as e:
|
|
51
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
52
|
+
|
|
53
|
+
@insight.command()
|
|
54
|
+
def files_without_resource_set():
|
|
55
|
+
"""Show files without resource set insight"""
|
|
56
|
+
try:
|
|
57
|
+
from purviewcli.client._insight import Insight
|
|
58
|
+
client = Insight()
|
|
59
|
+
result = client.filesWithoutResourceSet({})
|
|
60
|
+
console.print(json.dumps(result, indent=2))
|
|
61
|
+
except Exception as e:
|
|
62
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
63
|
+
|
|
64
|
+
@insight.command()
|
|
65
|
+
@click.option('--number-of-days', default=30, show_default=True, type=int, help='Trailing time period in days')
|
|
66
|
+
def scan_status_summary(number_of_days):
|
|
67
|
+
"""Show scan status summary insight"""
|
|
68
|
+
try:
|
|
69
|
+
from purviewcli.client._insight import Insight
|
|
70
|
+
client = Insight()
|
|
71
|
+
args = {'--numberOfDays': number_of_days}
|
|
72
|
+
result = client.scanStatusSummary(args)
|
|
73
|
+
console.print(json.dumps(result, indent=2))
|
|
74
|
+
except Exception as e:
|
|
75
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
76
|
+
|
|
77
|
+
@insight.command()
|
|
78
|
+
@click.option('--number-of-days', default=30, show_default=True, type=int, help='Trailing time period in days')
|
|
79
|
+
def scan_status_summary_by_ts(number_of_days):
|
|
80
|
+
"""Show scan status summary by timestamp insight"""
|
|
81
|
+
try:
|
|
82
|
+
from purviewcli.client._insight import Insight
|
|
83
|
+
client = Insight()
|
|
84
|
+
args = {'--numberOfDays': number_of_days}
|
|
85
|
+
result = client.scanStatusSummaryByTs(args)
|
|
86
|
+
console.print(json.dumps(result, indent=2))
|
|
87
|
+
except Exception as e:
|
|
88
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
89
|
+
|
|
90
|
+
@insight.command()
|
|
91
|
+
def tags():
|
|
92
|
+
"""Show tags insight"""
|
|
93
|
+
try:
|
|
94
|
+
from purviewcli.client._insight import Insight
|
|
95
|
+
client = Insight()
|
|
96
|
+
result = client.tags({})
|
|
97
|
+
console.print(json.dumps(result, indent=2))
|
|
98
|
+
except Exception as e:
|
|
99
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
100
|
+
|
|
101
|
+
@insight.command()
|
|
102
|
+
def tags_time_series():
|
|
103
|
+
"""Show tags time series insight"""
|
|
104
|
+
try:
|
|
105
|
+
from purviewcli.client._insight import Insight
|
|
106
|
+
client = Insight()
|
|
107
|
+
result = client.tagsTimeSeries({})
|
|
108
|
+
console.print(json.dumps(result, indent=2))
|
|
109
|
+
except Exception as e:
|
|
110
|
+
console.print(f"[red][X] Error: {e}[/red]")
|
|
111
|
+
|
|
112
|
+
# Make the insight group available for import
|
|
113
|
+
__all__ = ['insight']
|