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.
- purviewcli/__init__.py +2 -2
- purviewcli/cli/__init__.py +2 -2
- purviewcli/cli/cli.py +11 -13
- purviewcli/cli/collections.py +363 -0
- purviewcli/cli/entity.py +464 -0
- purviewcli/cli/search.py +201 -10
- purviewcli/cli/unified_catalog.py +857 -0
- purviewcli/client/_search.py +7 -2
- purviewcli/client/_unified_catalog.py +292 -295
- purviewcli/client/endpoint.py +13 -1
- purviewcli/client/sync_client.py +72 -15
- pvw_cli-1.0.9.dist-info/METADATA +800 -0
- {pvw_cli-1.0.6.dist-info → pvw_cli-1.0.9.dist-info}/RECORD +16 -17
- {pvw_cli-1.0.6.dist-info → pvw_cli-1.0.9.dist-info}/WHEEL +1 -1
- purviewcli/cli/data_product.py +0 -278
- purviewcli/client/_data_product.py +0 -168
- pvw_cli-1.0.6.dist-info/METADATA +0 -399
- {pvw_cli-1.0.6.dist-info → pvw_cli-1.0.9.dist-info}/entry_points.txt +0 -0
- {pvw_cli-1.0.6.dist-info → pvw_cli-1.0.9.dist-info}/top_level.txt +0 -0
purviewcli/cli/search.py
CHANGED
|
@@ -21,39 +21,226 @@ options:
|
|
|
21
21
|
CLI for advanced search and discovery
|
|
22
22
|
"""
|
|
23
23
|
import click
|
|
24
|
+
import json
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.table import Table
|
|
24
27
|
from purviewcli.client._search import Search
|
|
25
28
|
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
26
31
|
@click.group()
|
|
27
32
|
def search():
|
|
28
33
|
"""Search and discover assets"""
|
|
29
34
|
pass
|
|
30
35
|
|
|
36
|
+
def _format_json_output(data):
|
|
37
|
+
"""Format JSON output with syntax highlighting using Rich"""
|
|
38
|
+
from rich.console import Console
|
|
39
|
+
from rich.syntax import Syntax
|
|
40
|
+
import json
|
|
41
|
+
|
|
42
|
+
console = Console()
|
|
43
|
+
|
|
44
|
+
# Pretty print JSON with syntax highlighting
|
|
45
|
+
json_str = json.dumps(data, indent=2)
|
|
46
|
+
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=True)
|
|
47
|
+
console.print(syntax)
|
|
48
|
+
|
|
49
|
+
def _format_detailed_output(data):
|
|
50
|
+
"""Format search results with detailed information in readable format"""
|
|
51
|
+
from rich.console import Console
|
|
52
|
+
from rich.panel import Panel
|
|
53
|
+
from rich.text import Text
|
|
54
|
+
|
|
55
|
+
console = Console()
|
|
56
|
+
|
|
57
|
+
# Extract results data
|
|
58
|
+
count = data.get('@search.count', 0)
|
|
59
|
+
items = data.get('value', [])
|
|
60
|
+
|
|
61
|
+
if not items:
|
|
62
|
+
console.print("[yellow]No results found[/yellow]")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
console.print(f"\n[bold cyan]Search Results: {len(items)} of {count} total[/bold cyan]\n")
|
|
66
|
+
|
|
67
|
+
for i, item in enumerate(items, 1):
|
|
68
|
+
# Create a panel for each result
|
|
69
|
+
details = []
|
|
70
|
+
|
|
71
|
+
# Basic information
|
|
72
|
+
details.append(f"[bold cyan]Name:[/bold cyan] {item.get('name', 'N/A')}")
|
|
73
|
+
details.append(f"[bold green]Type:[/bold green] {item.get('entityType', 'N/A')}")
|
|
74
|
+
details.append(f"[bold yellow]ID:[/bold yellow] {item.get('id', 'N/A')}")
|
|
75
|
+
|
|
76
|
+
# Collection
|
|
77
|
+
if 'collection' in item and item['collection']:
|
|
78
|
+
collection_name = item['collection'].get('name', 'N/A')
|
|
79
|
+
else:
|
|
80
|
+
collection_name = item.get('collectionId', 'N/A')
|
|
81
|
+
details.append(f"[bold blue]Collection:[/bold blue] {collection_name}")
|
|
82
|
+
|
|
83
|
+
# Qualified Name
|
|
84
|
+
details.append(f"[bold white]Qualified Name:[/bold white] {item.get('qualifiedName', 'N/A')}")
|
|
85
|
+
|
|
86
|
+
# Classifications
|
|
87
|
+
if 'classification' in item and item['classification']:
|
|
88
|
+
classifications = []
|
|
89
|
+
for cls in item['classification']:
|
|
90
|
+
if isinstance(cls, dict):
|
|
91
|
+
classifications.append(cls.get('typeName', str(cls)))
|
|
92
|
+
else:
|
|
93
|
+
classifications.append(str(cls))
|
|
94
|
+
details.append(f"[bold magenta]Classifications:[/bold magenta] {', '.join(classifications)}")
|
|
95
|
+
|
|
96
|
+
# Additional metadata
|
|
97
|
+
if 'updateTime' in item:
|
|
98
|
+
details.append(f"[bold dim]Last Updated:[/bold dim] {item.get('updateTime')}")
|
|
99
|
+
if 'createTime' in item:
|
|
100
|
+
details.append(f"[bold dim]Created:[/bold dim] {item.get('createTime')}")
|
|
101
|
+
if 'updateBy' in item:
|
|
102
|
+
details.append(f"[bold dim]Updated By:[/bold dim] {item.get('updateBy')}")
|
|
103
|
+
|
|
104
|
+
# Search score
|
|
105
|
+
if '@search.score' in item:
|
|
106
|
+
details.append(f"[bold dim]Search Score:[/bold dim] {item.get('@search.score'):.2f}")
|
|
107
|
+
|
|
108
|
+
# Create panel
|
|
109
|
+
panel_content = "\n".join(details)
|
|
110
|
+
panel = Panel(
|
|
111
|
+
panel_content,
|
|
112
|
+
title=f"[bold]{i}. {item.get('name', 'Unknown')}[/bold]",
|
|
113
|
+
border_style="blue"
|
|
114
|
+
)
|
|
115
|
+
console.print(panel)
|
|
116
|
+
|
|
117
|
+
# Add pagination hint if there are more results
|
|
118
|
+
if len(items) < count:
|
|
119
|
+
console.print(f"\n💡 [dim]More results available. Use --offset to paginate.[/dim]")
|
|
120
|
+
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
def _format_search_results(data, show_ids=False):
|
|
124
|
+
"""Format search results as a nice table using Rich"""
|
|
125
|
+
from rich.console import Console
|
|
126
|
+
from rich.table import Table
|
|
127
|
+
|
|
128
|
+
console = Console()
|
|
129
|
+
|
|
130
|
+
# Extract results data
|
|
131
|
+
count = data.get('@search.count', 0)
|
|
132
|
+
items = data.get('value', [])
|
|
133
|
+
|
|
134
|
+
if not items:
|
|
135
|
+
console.print("[yellow]No results found[/yellow]")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Create table
|
|
139
|
+
table = Table(title=f"Search Results ({len(items)} of {count} total)")
|
|
140
|
+
table.add_column("Name", style="cyan", min_width=15, max_width=25)
|
|
141
|
+
table.add_column("Type", style="green", min_width=15, max_width=20)
|
|
142
|
+
table.add_column("Collection", style="blue", min_width=12, max_width=20)
|
|
143
|
+
table.add_column("Classifications", style="magenta", min_width=15, max_width=30)
|
|
144
|
+
|
|
145
|
+
if show_ids:
|
|
146
|
+
table.add_column("ID", style="yellow", min_width=36, max_width=36)
|
|
147
|
+
|
|
148
|
+
table.add_column("Qualified Name", style="white", min_width=30)
|
|
149
|
+
|
|
150
|
+
for item in items:
|
|
151
|
+
# Extract entity information
|
|
152
|
+
name = item.get('name', 'N/A')
|
|
153
|
+
entity_type = item.get('entityType', 'N/A')
|
|
154
|
+
entity_id = item.get('id', 'N/A')
|
|
155
|
+
qualified_name = item.get('qualifiedName', 'N/A')
|
|
156
|
+
|
|
157
|
+
# Truncate long qualified names for better display
|
|
158
|
+
if len(qualified_name) > 60:
|
|
159
|
+
qualified_name = qualified_name[:57] + "..."
|
|
160
|
+
|
|
161
|
+
# Handle collection
|
|
162
|
+
collection = 'N/A'
|
|
163
|
+
if 'collection' in item and item['collection']:
|
|
164
|
+
collection = item['collection'].get('name', 'N/A')
|
|
165
|
+
|
|
166
|
+
# Handle classifications - truncate long classification lists
|
|
167
|
+
classifications = []
|
|
168
|
+
if 'classification' in item and item['classification']:
|
|
169
|
+
for cls in item['classification']:
|
|
170
|
+
if isinstance(cls, dict):
|
|
171
|
+
cls_name = cls.get('typeName', str(cls))
|
|
172
|
+
# Simplify Microsoft classifications for display
|
|
173
|
+
if cls_name.startswith('MICROSOFT.'):
|
|
174
|
+
cls_name = cls_name.replace('MICROSOFT.', 'MS.')
|
|
175
|
+
classifications.append(cls_name)
|
|
176
|
+
else:
|
|
177
|
+
classifications.append(str(cls))
|
|
178
|
+
|
|
179
|
+
# Truncate classifications if too long
|
|
180
|
+
cls_display = ", ".join(classifications) if classifications else ""
|
|
181
|
+
if len(cls_display) > 40:
|
|
182
|
+
cls_display = cls_display[:37] + "..."
|
|
183
|
+
|
|
184
|
+
# Build row data
|
|
185
|
+
row_data = [name, entity_type, collection, cls_display]
|
|
186
|
+
if show_ids:
|
|
187
|
+
row_data.append(entity_id)
|
|
188
|
+
row_data.append(qualified_name)
|
|
189
|
+
|
|
190
|
+
# Add row to table
|
|
191
|
+
table.add_row(*row_data)
|
|
192
|
+
|
|
193
|
+
# Print the table
|
|
194
|
+
console.print(table)
|
|
195
|
+
|
|
196
|
+
# Add pagination hint if there are more results
|
|
197
|
+
if len(items) < count:
|
|
198
|
+
console.print(f"\n💡 More results available. Use --offset to paginate.")
|
|
199
|
+
|
|
200
|
+
return
|
|
201
|
+
|
|
31
202
|
def _invoke_search_method(method_name, **kwargs):
|
|
32
203
|
search_client = Search()
|
|
33
204
|
method = getattr(search_client, method_name)
|
|
205
|
+
|
|
206
|
+
# Extract formatting options, don't pass to API
|
|
207
|
+
show_ids = kwargs.pop('show_ids', False)
|
|
208
|
+
output_json = kwargs.pop('output_json', False)
|
|
209
|
+
detailed = kwargs.pop('detailed', False)
|
|
210
|
+
|
|
34
211
|
args = {f'--{k}': v for k, v in kwargs.items() if v is not None}
|
|
35
212
|
try:
|
|
36
213
|
result = method(args)
|
|
37
|
-
|
|
214
|
+
# Choose output format
|
|
215
|
+
if output_json:
|
|
216
|
+
_format_json_output(result)
|
|
217
|
+
elif detailed and method_name in ['searchQuery', 'searchBrowse', 'searchSuggest', 'searchAutoComplete']:
|
|
218
|
+
_format_detailed_output(result)
|
|
219
|
+
elif method_name in ['searchQuery', 'searchBrowse', 'searchSuggest', 'searchAutoComplete']:
|
|
220
|
+
_format_search_results(result, show_ids=show_ids)
|
|
221
|
+
else:
|
|
222
|
+
_format_json_output(result)
|
|
38
223
|
except Exception as e:
|
|
39
|
-
|
|
224
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
40
225
|
|
|
41
226
|
@search.command()
|
|
42
227
|
@click.option('--keywords', required=False)
|
|
43
228
|
@click.option('--limit', required=False, type=int, default=25)
|
|
44
229
|
@click.option('--filterFile', required=False, type=click.Path(exists=True))
|
|
45
|
-
|
|
230
|
+
@click.option('--json', 'output_json', is_flag=True, help='Show full JSON details instead of table')
|
|
231
|
+
def autocomplete(keywords, limit, filterfile, output_json):
|
|
46
232
|
"""Autocomplete search suggestions"""
|
|
47
|
-
_invoke_search_method('searchAutoComplete', keywords=keywords, limit=limit, filterFile=filterfile)
|
|
233
|
+
_invoke_search_method('searchAutoComplete', keywords=keywords, limit=limit, filterFile=filterfile, output_json=output_json)
|
|
48
234
|
|
|
49
235
|
@search.command()
|
|
50
236
|
@click.option('--entityType', required=False)
|
|
51
237
|
@click.option('--path', required=False)
|
|
52
238
|
@click.option('--limit', required=False, type=int, default=25)
|
|
53
239
|
@click.option('--offset', required=False, type=int, default=0)
|
|
54
|
-
|
|
240
|
+
@click.option('--json', 'output_json', is_flag=True, help='Show full JSON details instead of table')
|
|
241
|
+
def browse(entitytype, path, limit, offset, output_json):
|
|
55
242
|
"""Browse entities by type or path"""
|
|
56
|
-
_invoke_search_method('searchBrowse', entityType=entitytype, path=path, limit=limit, offset=offset)
|
|
243
|
+
_invoke_search_method('searchBrowse', entityType=entitytype, path=path, limit=limit, offset=offset, output_json=output_json)
|
|
57
244
|
|
|
58
245
|
@search.command()
|
|
59
246
|
@click.option('--keywords', required=False)
|
|
@@ -61,17 +248,21 @@ def browse(entitytype, path, limit, offset):
|
|
|
61
248
|
@click.option('--offset', required=False, type=int, default=0)
|
|
62
249
|
@click.option('--filterFile', required=False, type=click.Path(exists=True))
|
|
63
250
|
@click.option('--facets-file', required=False, type=click.Path(exists=True))
|
|
64
|
-
|
|
251
|
+
@click.option('--show-ids', is_flag=True, help='Show entity IDs in the results')
|
|
252
|
+
@click.option('--json', 'output_json', is_flag=True, help='Show full JSON details instead of table')
|
|
253
|
+
@click.option('--detailed', is_flag=True, help='Show detailed information in readable format')
|
|
254
|
+
def query(keywords, limit, offset, filterfile, facets_file, show_ids, output_json, detailed):
|
|
65
255
|
"""Run a search query"""
|
|
66
|
-
_invoke_search_method('searchQuery', keywords=keywords, limit=limit, offset=offset, filterFile=filterfile, facets_file=facets_file)
|
|
256
|
+
_invoke_search_method('searchQuery', keywords=keywords, limit=limit, offset=offset, filterFile=filterfile, facets_file=facets_file, show_ids=show_ids, output_json=output_json, detailed=detailed)
|
|
67
257
|
|
|
68
258
|
@search.command()
|
|
69
259
|
@click.option('--keywords', required=False)
|
|
70
260
|
@click.option('--limit', required=False, type=int, default=25)
|
|
71
261
|
@click.option('--filterFile', required=False, type=click.Path(exists=True))
|
|
72
|
-
|
|
262
|
+
@click.option('--json', 'output_json', is_flag=True, help='Show full JSON details instead of table')
|
|
263
|
+
def suggest(keywords, limit, filterfile, output_json):
|
|
73
264
|
"""Get search suggestions"""
|
|
74
|
-
_invoke_search_method('searchSuggest', keywords=keywords, limit=limit, filterFile=filterfile)
|
|
265
|
+
_invoke_search_method('searchSuggest', keywords=keywords, limit=limit, filterFile=filterfile, output_json=output_json)
|
|
75
266
|
|
|
76
267
|
@search.command()
|
|
77
268
|
@click.option('--keywords', required=False)
|