pvw-cli 1.3.3__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.
- 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 +3796 -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.3.3.dist-info/METADATA +1618 -0
- pvw_cli-1.3.3.dist-info/RECORD +60 -0
- pvw_cli-1.3.3.dist-info/WHEEL +5 -0
- pvw_cli-1.3.3.dist-info/entry_points.txt +3 -0
- pvw_cli-1.3.3.dist-info/top_level.txt +1 -0
purviewcli/cli/entity.py
ADDED
|
@@ -0,0 +1,2436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage entities in Microsoft Purview using modular Click-based commands.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
entity create Create a new entity
|
|
6
|
+
entity read Read an entity
|
|
7
|
+
entity update Update an entity
|
|
8
|
+
entity delete Delete an entity
|
|
9
|
+
entity --help Show this help message and exit
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
-h --help Show this help message and exit
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import click
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group()
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def entity(ctx):
|
|
25
|
+
"""
|
|
26
|
+
Manage entities in Microsoft Purview.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@entity.command()
|
|
32
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
33
|
+
@click.option(
|
|
34
|
+
"--ignore-relationships", is_flag=True, help="Whether to ignore relationship attributes"
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--min-ext-info",
|
|
38
|
+
is_flag=True,
|
|
39
|
+
help="Whether to return minimal information for referred entities",
|
|
40
|
+
)
|
|
41
|
+
@click.pass_context
|
|
42
|
+
def read(ctx, guid, ignore_relationships, min_ext_info):
|
|
43
|
+
"""Read entity information by GUID"""
|
|
44
|
+
try:
|
|
45
|
+
if ctx.obj.get("mock"):
|
|
46
|
+
console.print("[yellow]🎠Mock: entity read command[/yellow]")
|
|
47
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
48
|
+
console.print(f"[dim]Ignore Relationships: {ignore_relationships}[/dim]")
|
|
49
|
+
console.print(f"[dim]Min Ext Info: {min_ext_info}[/dim]")
|
|
50
|
+
console.print("[green][OK] Mock entity read completed successfully[/green]")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
args = {
|
|
54
|
+
"--guid": guid,
|
|
55
|
+
"--ignoreRelationships": ignore_relationships,
|
|
56
|
+
"--minExtInfo": min_ext_info,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
from purviewcli.client._entity import Entity
|
|
60
|
+
|
|
61
|
+
entity_client = Entity()
|
|
62
|
+
result = entity_client.entityRead(args)
|
|
63
|
+
|
|
64
|
+
if result:
|
|
65
|
+
console.print("[green][OK] Entity read completed successfully[/green]")
|
|
66
|
+
console.print(json.dumps(result, indent=2))
|
|
67
|
+
else:
|
|
68
|
+
console.print("[yellow][!] Entity read completed with no result[/yellow]")
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
console.print(f"[red][X] Error executing entity read: {str(e)}[/red]")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@entity.command()
|
|
75
|
+
@click.option(
|
|
76
|
+
"--payload-file",
|
|
77
|
+
required=True,
|
|
78
|
+
type=click.Path(exists=True),
|
|
79
|
+
help="File path to a valid JSON document containing entity data",
|
|
80
|
+
)
|
|
81
|
+
@click.pass_context
|
|
82
|
+
def create(ctx, payload_file):
|
|
83
|
+
"""Create a new entity"""
|
|
84
|
+
try:
|
|
85
|
+
if ctx.obj.get("mock"):
|
|
86
|
+
console.print("[yellow]🎠Mock: entity create command[/yellow]")
|
|
87
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
88
|
+
console.print("[green][OK] Mock entity create completed successfully[/green]")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
args = {"--payloadFile": payload_file}
|
|
92
|
+
|
|
93
|
+
from purviewcli.client._entity import Entity
|
|
94
|
+
|
|
95
|
+
entity_client = Entity()
|
|
96
|
+
result = entity_client.entityCreate(args)
|
|
97
|
+
|
|
98
|
+
if result:
|
|
99
|
+
console.print("[green][OK] Entity create completed successfully[/green]")
|
|
100
|
+
console.print(json.dumps(result, indent=2))
|
|
101
|
+
else:
|
|
102
|
+
console.print("[yellow][!] Entity create completed with no result[/yellow]")
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
console.print(f"[red][X] Error executing entity create: {str(e)}[/red]")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@entity.command()
|
|
109
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
110
|
+
@click.pass_context
|
|
111
|
+
def delete(ctx, guid):
|
|
112
|
+
"""Delete an entity by GUID"""
|
|
113
|
+
try:
|
|
114
|
+
if ctx.obj.get("mock"):
|
|
115
|
+
console.print("[yellow]🎠Mock: entity delete command[/yellow]")
|
|
116
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
117
|
+
console.print("[green][OK] Mock entity delete completed successfully[/green]")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
args = {"--guid": guid}
|
|
121
|
+
|
|
122
|
+
from purviewcli.client._entity import Entity
|
|
123
|
+
|
|
124
|
+
entity_client = Entity()
|
|
125
|
+
result = entity_client.entityDelete(args)
|
|
126
|
+
|
|
127
|
+
if result:
|
|
128
|
+
console.print("[green][OK] Entity delete completed successfully[/green]")
|
|
129
|
+
console.print(json.dumps(result, indent=2))
|
|
130
|
+
else:
|
|
131
|
+
console.print("[yellow][!] Entity delete completed with no result[/yellow]")
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
console.print(f"[red][X] Error executing entity delete: {str(e)}[/red]")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@entity.command()
|
|
138
|
+
@click.option(
|
|
139
|
+
"--payload-file",
|
|
140
|
+
required=True,
|
|
141
|
+
type=click.Path(exists=True),
|
|
142
|
+
help="File path to a valid JSON document containing bulk entity data",
|
|
143
|
+
)
|
|
144
|
+
@click.pass_context
|
|
145
|
+
def bulk_create(ctx, payload_file):
|
|
146
|
+
"""Create multiple entities in bulk"""
|
|
147
|
+
try:
|
|
148
|
+
if ctx.obj.get("mock"):
|
|
149
|
+
console.print("[yellow]🎠Mock: entity bulk-create command[/yellow]")
|
|
150
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
151
|
+
console.print("[green][OK] Mock entity bulk-create completed successfully[/green]")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
args = {"--payloadFile": payload_file}
|
|
155
|
+
|
|
156
|
+
from purviewcli.client._entity import Entity
|
|
157
|
+
|
|
158
|
+
entity_client = Entity()
|
|
159
|
+
result = entity_client.entityBulkCreateOrUpdate(args)
|
|
160
|
+
|
|
161
|
+
if result:
|
|
162
|
+
console.print("[green][OK] Entity bulk-create completed successfully[/green]")
|
|
163
|
+
console.print(json.dumps(result, indent=2))
|
|
164
|
+
else:
|
|
165
|
+
console.print("[yellow][!] Entity bulk-create completed with no result[/yellow]")
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
console.print(f"[red][X] Error executing entity bulk-create: {str(e)}[/red]")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@entity.command(name="bulk-update")
|
|
172
|
+
@click.option(
|
|
173
|
+
"--payload-file",
|
|
174
|
+
required=True,
|
|
175
|
+
type=click.Path(exists=True),
|
|
176
|
+
help="File path to a valid JSON document containing entities to update/create (same shape as bulk-create).",
|
|
177
|
+
)
|
|
178
|
+
@click.pass_context
|
|
179
|
+
def bulk_update(ctx, payload_file):
|
|
180
|
+
"""Bulk update/create entities from a JSON payload file (uses qualifiedName to match existing entities)."""
|
|
181
|
+
try:
|
|
182
|
+
if ctx.obj.get("mock"):
|
|
183
|
+
console.print("[yellow]🎠Mock: entity bulk-update command[/yellow]")
|
|
184
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
185
|
+
console.print("[green][OK] Mock entity bulk-update completed successfully[/green]")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
args = {"--payloadFile": payload_file}
|
|
189
|
+
|
|
190
|
+
from purviewcli.client._entity import Entity
|
|
191
|
+
|
|
192
|
+
entity_client = Entity()
|
|
193
|
+
result = entity_client.entityBulkCreateOrUpdate(args)
|
|
194
|
+
|
|
195
|
+
if result:
|
|
196
|
+
console.print("[green][OK] Entity bulk-update completed successfully[/green]")
|
|
197
|
+
console.print(json.dumps(result, indent=2))
|
|
198
|
+
else:
|
|
199
|
+
console.print("[yellow][!] Entity bulk-update completed with no result[/yellow]")
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
console.print(f"[red][X] Error executing entity bulk-update: {str(e)}[/red]")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# === BULK OPERATIONS ===
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@entity.command()
|
|
209
|
+
@click.option(
|
|
210
|
+
"--guid", required=True, multiple=True, help="Entity GUIDs to read (can specify multiple)"
|
|
211
|
+
)
|
|
212
|
+
@click.option(
|
|
213
|
+
"--ignore-relationships", is_flag=True, help="Whether to ignore relationship attributes"
|
|
214
|
+
)
|
|
215
|
+
@click.option(
|
|
216
|
+
"--min-ext-info",
|
|
217
|
+
is_flag=True,
|
|
218
|
+
help="Whether to return minimal information for referred entities",
|
|
219
|
+
)
|
|
220
|
+
@click.pass_context
|
|
221
|
+
def bulk_read(ctx, guid, ignore_relationships, min_ext_info):
|
|
222
|
+
"""Read multiple entities by their GUIDs"""
|
|
223
|
+
try:
|
|
224
|
+
if ctx.obj.get("mock"):
|
|
225
|
+
console.print("[yellow]🎠Mock: entity bulk-read command[/yellow]")
|
|
226
|
+
console.print(f"[dim]GUIDs: {', '.join(guid)}[/dim]")
|
|
227
|
+
console.print("[green][OK] Mock entity bulk-read completed successfully[/green]")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
args = {
|
|
231
|
+
"--guid": list(guid),
|
|
232
|
+
"--ignoreRelationships": ignore_relationships,
|
|
233
|
+
"--minExtInfo": min_ext_info,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
from purviewcli.client._entity import Entity
|
|
237
|
+
|
|
238
|
+
entity_client = Entity()
|
|
239
|
+
result = entity_client.entityReadBulk(args)
|
|
240
|
+
|
|
241
|
+
if result:
|
|
242
|
+
console.print("[green][OK] Entity bulk-read completed successfully[/green]")
|
|
243
|
+
console.print(json.dumps(result, indent=2))
|
|
244
|
+
else:
|
|
245
|
+
console.print("[yellow][!] Entity bulk-read completed with no result[/yellow]")
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
console.print(f"[red][X] Error executing entity bulk-read: {str(e)}[/red]")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@entity.command()
|
|
252
|
+
@click.option(
|
|
253
|
+
"--guid", required=True, multiple=True, help="Entity GUIDs to delete (can specify multiple)"
|
|
254
|
+
)
|
|
255
|
+
@click.pass_context
|
|
256
|
+
def bulk_delete(ctx, guid):
|
|
257
|
+
"""Delete multiple entities by their GUIDs"""
|
|
258
|
+
try:
|
|
259
|
+
if ctx.obj.get("mock"):
|
|
260
|
+
console.print("[yellow]🎠Mock: entity bulk-delete command[/yellow]")
|
|
261
|
+
console.print(f"[dim]GUIDs: {', '.join(guid)}[/dim]")
|
|
262
|
+
console.print("[green][OK] Mock entity bulk-delete completed successfully[/green]")
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
args = {"--guid": list(guid)}
|
|
266
|
+
|
|
267
|
+
from purviewcli.client._entity import Entity
|
|
268
|
+
|
|
269
|
+
entity_client = Entity()
|
|
270
|
+
result = entity_client.entityDeleteBulk(args)
|
|
271
|
+
|
|
272
|
+
if result:
|
|
273
|
+
console.print("[green][OK] Entity bulk-delete completed successfully[/green]")
|
|
274
|
+
console.print(json.dumps(result, indent=2))
|
|
275
|
+
else:
|
|
276
|
+
console.print("[yellow][!] Entity bulk-delete completed with no result[/yellow]")
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
console.print(f"[red][X] Error executing entity bulk-delete: {str(e)}[/red]")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# === UNIQUE ATTRIBUTE OPERATIONS ===
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@entity.command()
|
|
286
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
287
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
288
|
+
@click.option(
|
|
289
|
+
"--ignore-relationships", is_flag=True, help="Whether to ignore relationship attributes"
|
|
290
|
+
)
|
|
291
|
+
@click.option(
|
|
292
|
+
"--min-ext-info",
|
|
293
|
+
is_flag=True,
|
|
294
|
+
help="Whether to return minimal information for referred entities",
|
|
295
|
+
)
|
|
296
|
+
@click.pass_context
|
|
297
|
+
def read_by_attribute(ctx, type_name, qualified_name, ignore_relationships, min_ext_info):
|
|
298
|
+
"""Read entity by unique attributes"""
|
|
299
|
+
try:
|
|
300
|
+
if ctx.obj.get("mock"):
|
|
301
|
+
console.print("[yellow]🎠Mock: entity read-by-attribute command[/yellow]")
|
|
302
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
303
|
+
console.print("[green][OK] Mock entity read-by-attribute completed successfully[/green]")
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
args = {
|
|
307
|
+
"--typeName": type_name,
|
|
308
|
+
"--qualifiedName": qualified_name,
|
|
309
|
+
"--ignoreRelationships": ignore_relationships,
|
|
310
|
+
"--minExtInfo": min_ext_info,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
from purviewcli.client._entity import Entity
|
|
314
|
+
|
|
315
|
+
entity_client = Entity()
|
|
316
|
+
result = entity_client.entityReadUniqueAttribute(args)
|
|
317
|
+
|
|
318
|
+
if result:
|
|
319
|
+
console.print("[green][OK] Entity read-by-attribute completed successfully[/green]")
|
|
320
|
+
console.print(json.dumps(result, indent=2))
|
|
321
|
+
else:
|
|
322
|
+
console.print("[yellow][!] Entity read-by-attribute completed with no result[/yellow]")
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
console.print(f"[red][X] Error executing entity read-by-attribute: {str(e)}[/red]")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@entity.command()
|
|
329
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
330
|
+
@click.option(
|
|
331
|
+
"--qualified-name", required=True, multiple=True, help="Qualified names (can specify multiple)"
|
|
332
|
+
)
|
|
333
|
+
@click.option(
|
|
334
|
+
"--ignore-relationships", is_flag=True, help="Whether to ignore relationship attributes"
|
|
335
|
+
)
|
|
336
|
+
@click.option(
|
|
337
|
+
"--min-ext-info",
|
|
338
|
+
is_flag=True,
|
|
339
|
+
help="Whether to return minimal information for referred entities",
|
|
340
|
+
)
|
|
341
|
+
@click.pass_context
|
|
342
|
+
def bulk_read_by_attribute(ctx, type_name, qualified_name, ignore_relationships, min_ext_info):
|
|
343
|
+
"""Read multiple entities by unique attributes"""
|
|
344
|
+
try:
|
|
345
|
+
if ctx.obj.get("mock"):
|
|
346
|
+
console.print("[yellow]🎠Mock: entity bulk-read-by-attribute command[/yellow]")
|
|
347
|
+
console.print(
|
|
348
|
+
f"[dim]Type: {type_name}, Qualified Names: {', '.join(qualified_name)}[/dim]"
|
|
349
|
+
)
|
|
350
|
+
console.print(
|
|
351
|
+
"[green][OK] Mock entity bulk-read-by-attribute completed successfully[/green]"
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
args = {
|
|
356
|
+
"--typeName": type_name,
|
|
357
|
+
"--qualifiedName": list(qualified_name),
|
|
358
|
+
"--ignoreRelationships": ignore_relationships,
|
|
359
|
+
"--minExtInfo": min_ext_info,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
from purviewcli.client._entity import Entity
|
|
363
|
+
|
|
364
|
+
entity_client = Entity()
|
|
365
|
+
result = entity_client.entityReadBulkUniqueAttribute(args)
|
|
366
|
+
|
|
367
|
+
if result:
|
|
368
|
+
console.print("[green][OK] Entity bulk-read-by-attribute completed successfully[/green]")
|
|
369
|
+
console.print(json.dumps(result, indent=2))
|
|
370
|
+
else:
|
|
371
|
+
console.print(
|
|
372
|
+
"[yellow][!] Entity bulk-read-by-attribute completed with no result[/yellow]"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
console.print(f"[red][X] Error executing entity bulk-read-by-attribute: {str(e)}[/red]")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@entity.command()
|
|
380
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
381
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
382
|
+
@click.pass_context
|
|
383
|
+
def delete_by_attribute(ctx, type_name, qualified_name):
|
|
384
|
+
"""Delete entity by unique attributes"""
|
|
385
|
+
try:
|
|
386
|
+
if ctx.obj.get("mock"):
|
|
387
|
+
console.print("[yellow]🎠Mock: entity delete-by-attribute command[/yellow]")
|
|
388
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
389
|
+
console.print("[green][OK] Mock entity delete-by-attribute completed successfully[/green]")
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
args = {
|
|
393
|
+
"--typeName": type_name,
|
|
394
|
+
"--qualifiedName": qualified_name,
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
from purviewcli.client._entity import Entity
|
|
398
|
+
|
|
399
|
+
entity_client = Entity()
|
|
400
|
+
result = entity_client.entityDeleteUniqueAttribute(args)
|
|
401
|
+
|
|
402
|
+
if result:
|
|
403
|
+
console.print("[green][OK] Entity delete-by-attribute completed successfully[/green]")
|
|
404
|
+
console.print(json.dumps(result, indent=2))
|
|
405
|
+
else:
|
|
406
|
+
console.print("[yellow][!] Entity delete-by-attribute completed with no result[/yellow]")
|
|
407
|
+
|
|
408
|
+
except Exception as e:
|
|
409
|
+
console.print(f"[red][X] Error executing entity delete-by-attribute: {str(e)}[/red]")
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@entity.command()
|
|
413
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
414
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
415
|
+
@click.option(
|
|
416
|
+
"--payload-file",
|
|
417
|
+
required=True,
|
|
418
|
+
type=click.Path(exists=True),
|
|
419
|
+
help="File path to a valid JSON document containing entity data",
|
|
420
|
+
)
|
|
421
|
+
@click.pass_context
|
|
422
|
+
def update_by_attribute(ctx, type_name, qualified_name, payload_file):
|
|
423
|
+
"""Update entity by unique attributes"""
|
|
424
|
+
try:
|
|
425
|
+
if ctx.obj.get("mock"):
|
|
426
|
+
console.print("[yellow]🎠Mock: entity update-by-attribute command[/yellow]")
|
|
427
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
428
|
+
console.print("[green][OK] Mock entity update-by-attribute completed successfully[/green]")
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
args = {
|
|
432
|
+
"--typeName": type_name,
|
|
433
|
+
"--qualifiedName": qualified_name,
|
|
434
|
+
"--payloadFile": payload_file,
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
from purviewcli.client._entity import Entity
|
|
438
|
+
|
|
439
|
+
entity_client = Entity()
|
|
440
|
+
result = entity_client.entityPartialUpdateByUniqueAttribute(args)
|
|
441
|
+
|
|
442
|
+
if result:
|
|
443
|
+
console.print("[green][OK] Entity update-by-attribute completed successfully[/green]")
|
|
444
|
+
console.print(json.dumps(result, indent=2))
|
|
445
|
+
else:
|
|
446
|
+
console.print("[yellow][!] Entity update-by-attribute completed with no result[/yellow]")
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
console.print(f"[red][X] Error executing entity update-by-attribute: {str(e)}[/red]")
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
# === HEADER OPERATIONS ===
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@entity.command()
|
|
456
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
457
|
+
@click.pass_context
|
|
458
|
+
def read_header(ctx, guid):
|
|
459
|
+
"""Read entity header information by GUID"""
|
|
460
|
+
try:
|
|
461
|
+
if ctx.obj.get("mock"):
|
|
462
|
+
console.print("[yellow]🎠Mock: entity read-header command[/yellow]")
|
|
463
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
464
|
+
console.print("[green][OK] Mock entity read-header completed successfully[/green]")
|
|
465
|
+
return
|
|
466
|
+
|
|
467
|
+
args = {"--guid": [guid]}
|
|
468
|
+
|
|
469
|
+
from purviewcli.client._entity import Entity
|
|
470
|
+
|
|
471
|
+
entity_client = Entity()
|
|
472
|
+
result = entity_client.entityReadHeader(args)
|
|
473
|
+
|
|
474
|
+
if result:
|
|
475
|
+
console.print("[green][OK] Entity read-header completed successfully[/green]")
|
|
476
|
+
console.print(json.dumps(result, indent=2))
|
|
477
|
+
else:
|
|
478
|
+
console.print("[yellow][!] Entity read-header completed with no result[/yellow]")
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
console.print(f"[red][X] Error executing entity read-header: {str(e)}[/red]")
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@entity.command()
|
|
485
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
486
|
+
@click.option("--attr-name", required=True, help="The name of the attribute to update")
|
|
487
|
+
@click.option("--attr-value", required=True, help="The new value for the attribute")
|
|
488
|
+
@click.pass_context
|
|
489
|
+
def update_attribute(ctx, guid, attr_name, attr_value):
|
|
490
|
+
"""Update a specific attribute of an entity"""
|
|
491
|
+
try:
|
|
492
|
+
if ctx.obj.get("mock"):
|
|
493
|
+
console.print("[yellow]🎠Mock: entity update-attribute command[/yellow]")
|
|
494
|
+
console.print(f"[dim]GUID: {guid}, Attribute: {attr_name}, Value: {attr_value}[/dim]")
|
|
495
|
+
console.print("[green][OK] Mock entity update-attribute completed successfully[/green]")
|
|
496
|
+
return
|
|
497
|
+
|
|
498
|
+
args = {
|
|
499
|
+
"--guid": [guid],
|
|
500
|
+
"--attrName": attr_name,
|
|
501
|
+
"--attrValue": attr_value,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
from purviewcli.client._entity import Entity
|
|
505
|
+
|
|
506
|
+
entity_client = Entity()
|
|
507
|
+
result = entity_client.entityPartialUpdateAttribute(args)
|
|
508
|
+
|
|
509
|
+
if result:
|
|
510
|
+
console.print("[green][OK] Entity update-attribute completed successfully[/green]")
|
|
511
|
+
console.print(json.dumps(result, indent=2))
|
|
512
|
+
else:
|
|
513
|
+
console.print("[yellow][!] Entity update-attribute completed with no result[/yellow]")
|
|
514
|
+
|
|
515
|
+
except Exception as e:
|
|
516
|
+
console.print(f"[red][X] Error executing entity update-attribute: {str(e)}[/red]")
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
# === CLASSIFICATION OPERATIONS ===
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@entity.command()
|
|
523
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
524
|
+
@click.option("--classification-name", required=True, help="The name of the classification")
|
|
525
|
+
@click.pass_context
|
|
526
|
+
def read_classification(ctx, guid, classification_name):
|
|
527
|
+
"""Read specific classification for an entity"""
|
|
528
|
+
try:
|
|
529
|
+
if ctx.obj.get("mock"):
|
|
530
|
+
console.print("[yellow]🎠Mock: entity read-classification command[/yellow]")
|
|
531
|
+
console.print(f"[dim]GUID: {guid}, Classification: {classification_name}[/dim]")
|
|
532
|
+
console.print("[green][OK] Mock entity read-classification completed successfully[/green]")
|
|
533
|
+
return
|
|
534
|
+
|
|
535
|
+
args = {
|
|
536
|
+
"--guid": [guid],
|
|
537
|
+
"--classificationName": classification_name,
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
from purviewcli.client._entity import Entity
|
|
541
|
+
|
|
542
|
+
entity_client = Entity()
|
|
543
|
+
result = entity_client.entityReadClassification(args)
|
|
544
|
+
|
|
545
|
+
if result:
|
|
546
|
+
console.print("[green][OK] Entity read-classification completed successfully[/green]")
|
|
547
|
+
console.print(json.dumps(result, indent=2))
|
|
548
|
+
else:
|
|
549
|
+
console.print("[yellow][!] Entity read-classification completed with no result[/yellow]")
|
|
550
|
+
|
|
551
|
+
except Exception as e:
|
|
552
|
+
console.print(f"[red][X] Error executing entity read-classification: {str(e)}[/red]")
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@entity.command()
|
|
556
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
557
|
+
@click.pass_context
|
|
558
|
+
def read_classifications(ctx, guid):
|
|
559
|
+
"""Read all classifications for an entity"""
|
|
560
|
+
try:
|
|
561
|
+
if ctx.obj.get("mock"):
|
|
562
|
+
console.print("[yellow]🎠Mock: entity read-classifications command[/yellow]")
|
|
563
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
564
|
+
console.print(
|
|
565
|
+
"[green][OK] Mock entity read-classifications completed successfully[/green]"
|
|
566
|
+
)
|
|
567
|
+
return
|
|
568
|
+
|
|
569
|
+
args = {"--guid": [guid]}
|
|
570
|
+
|
|
571
|
+
from purviewcli.client._entity import Entity
|
|
572
|
+
|
|
573
|
+
entity_client = Entity()
|
|
574
|
+
result = entity_client.entityReadClassifications(args)
|
|
575
|
+
|
|
576
|
+
if result:
|
|
577
|
+
console.print("[green][OK] Entity read-classifications completed successfully[/green]")
|
|
578
|
+
console.print(json.dumps(result, indent=2))
|
|
579
|
+
else:
|
|
580
|
+
console.print("[yellow][!] Entity read-classifications completed with no result[/yellow]")
|
|
581
|
+
|
|
582
|
+
except Exception as e:
|
|
583
|
+
console.print(f"[red][X] Error executing entity read-classifications: {str(e)}[/red]")
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@entity.command()
|
|
587
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
588
|
+
@click.option(
|
|
589
|
+
"--payload-file",
|
|
590
|
+
required=True,
|
|
591
|
+
type=click.Path(exists=True),
|
|
592
|
+
help="File path to a valid JSON document containing classification data",
|
|
593
|
+
)
|
|
594
|
+
@click.pass_context
|
|
595
|
+
def add_classifications(ctx, guid, payload_file):
|
|
596
|
+
"""Add classifications to an entity"""
|
|
597
|
+
try:
|
|
598
|
+
if ctx.obj.get("mock"):
|
|
599
|
+
console.print("[yellow]🎠Mock: entity add-classifications command[/yellow]")
|
|
600
|
+
console.print(f"[dim]GUID: {guid}, Payload File: {payload_file}[/dim]")
|
|
601
|
+
console.print("[green][OK] Mock entity add-classifications completed successfully[/green]")
|
|
602
|
+
return
|
|
603
|
+
|
|
604
|
+
args = {
|
|
605
|
+
"--guid": [guid],
|
|
606
|
+
"--payloadFile": payload_file,
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
from purviewcli.client._entity import Entity
|
|
610
|
+
|
|
611
|
+
entity_client = Entity()
|
|
612
|
+
result = entity_client.entityAddClassifications(args)
|
|
613
|
+
|
|
614
|
+
if result:
|
|
615
|
+
console.print("[green][OK] Entity add-classifications completed successfully[/green]")
|
|
616
|
+
console.print(json.dumps(result, indent=2))
|
|
617
|
+
else:
|
|
618
|
+
console.print("[yellow][!] Entity add-classifications completed with no result[/yellow]")
|
|
619
|
+
|
|
620
|
+
except Exception as e:
|
|
621
|
+
console.print(f"[red][X] Error executing entity add-classifications: {str(e)}[/red]")
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
@entity.command()
|
|
625
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
626
|
+
@click.option(
|
|
627
|
+
"--payload-file",
|
|
628
|
+
required=True,
|
|
629
|
+
type=click.Path(exists=True),
|
|
630
|
+
help="File path to a valid JSON document containing classification data",
|
|
631
|
+
)
|
|
632
|
+
@click.pass_context
|
|
633
|
+
def update_classifications(ctx, guid, payload_file):
|
|
634
|
+
"""Update classifications on an entity"""
|
|
635
|
+
try:
|
|
636
|
+
if ctx.obj.get("mock"):
|
|
637
|
+
console.print("[yellow]🎠Mock: entity update-classifications command[/yellow]")
|
|
638
|
+
console.print(f"[dim]GUID: {guid}, Payload File: {payload_file}[/dim]")
|
|
639
|
+
console.print(
|
|
640
|
+
"[green][OK] Mock entity update-classifications completed successfully[/green]"
|
|
641
|
+
)
|
|
642
|
+
return
|
|
643
|
+
|
|
644
|
+
args = {
|
|
645
|
+
"--guid": [guid],
|
|
646
|
+
"--payloadFile": payload_file,
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
from purviewcli.client._entity import Entity
|
|
650
|
+
|
|
651
|
+
entity_client = Entity()
|
|
652
|
+
result = entity_client.entityUpdateClassifications(args)
|
|
653
|
+
|
|
654
|
+
if result:
|
|
655
|
+
console.print("[green][OK] Entity update-classifications completed successfully[/green]")
|
|
656
|
+
console.print(json.dumps(result, indent=2))
|
|
657
|
+
else:
|
|
658
|
+
console.print(
|
|
659
|
+
"[yellow][!] Entity update-classifications completed with no result[/yellow]"
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
except Exception as e:
|
|
663
|
+
console.print(f"[red][X] Error executing entity update-classifications: {str(e)}[/red]")
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
@entity.command()
|
|
667
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
668
|
+
@click.option(
|
|
669
|
+
"--classification-name", required=True, help="The name of the classification to remove"
|
|
670
|
+
)
|
|
671
|
+
@click.pass_context
|
|
672
|
+
def remove_classification(ctx, guid, classification_name):
|
|
673
|
+
"""Remove classification from an entity"""
|
|
674
|
+
try:
|
|
675
|
+
if ctx.obj.get("mock"):
|
|
676
|
+
console.print("[yellow]🎠Mock: entity remove-classification command[/yellow]")
|
|
677
|
+
console.print(f"[dim]GUID: {guid}, Classification: {classification_name}[/dim]")
|
|
678
|
+
console.print(
|
|
679
|
+
"[green][OK] Mock entity remove-classification completed successfully[/green]"
|
|
680
|
+
)
|
|
681
|
+
return
|
|
682
|
+
|
|
683
|
+
args = {
|
|
684
|
+
"--guid": [guid],
|
|
685
|
+
"--classificationName": classification_name,
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
from purviewcli.client._entity import Entity
|
|
689
|
+
|
|
690
|
+
entity_client = Entity()
|
|
691
|
+
result = entity_client.entityDeleteClassification(args)
|
|
692
|
+
|
|
693
|
+
if result:
|
|
694
|
+
console.print("[green][OK] Entity remove-classification completed successfully[/green]")
|
|
695
|
+
console.print(json.dumps(result, indent=2))
|
|
696
|
+
else:
|
|
697
|
+
console.print(
|
|
698
|
+
"[yellow][!] Entity remove-classification completed with no result[/yellow]"
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
except Exception as e:
|
|
702
|
+
console.print(f"[red][X] Error executing entity remove-classification: {str(e)}[/red]")
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
# === CLASSIFICATION OPERATIONS BY UNIQUE ATTRIBUTE ===
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
@entity.command()
|
|
709
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
710
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
711
|
+
@click.option(
|
|
712
|
+
"--payload-file",
|
|
713
|
+
required=True,
|
|
714
|
+
type=click.Path(exists=True),
|
|
715
|
+
help="File path to a valid JSON document containing classification data",
|
|
716
|
+
)
|
|
717
|
+
@click.pass_context
|
|
718
|
+
def add_classifications_by_attribute(ctx, type_name, qualified_name, payload_file):
|
|
719
|
+
"""Add classifications by unique attribute"""
|
|
720
|
+
try:
|
|
721
|
+
if ctx.obj.get("mock"):
|
|
722
|
+
console.print(
|
|
723
|
+
"[yellow]🎠Mock: entity add-classifications-by-attribute command[/yellow]"
|
|
724
|
+
)
|
|
725
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
726
|
+
console.print(
|
|
727
|
+
"[green][OK] Mock entity add-classifications-by-attribute completed successfully[/green]"
|
|
728
|
+
)
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
args = {
|
|
732
|
+
"--typeName": type_name,
|
|
733
|
+
"--qualifiedName": qualified_name,
|
|
734
|
+
"--payloadFile": payload_file,
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
from purviewcli.client._entity import Entity
|
|
738
|
+
|
|
739
|
+
entity_client = Entity()
|
|
740
|
+
result = entity_client.entityAddClassificationsByUniqueAttribute(args)
|
|
741
|
+
|
|
742
|
+
if result:
|
|
743
|
+
console.print(
|
|
744
|
+
"[green][OK] Entity add-classifications-by-attribute completed successfully[/green]"
|
|
745
|
+
)
|
|
746
|
+
console.print(json.dumps(result, indent=2))
|
|
747
|
+
else:
|
|
748
|
+
console.print(
|
|
749
|
+
"[yellow][!] Entity add-classifications-by-attribute completed with no result[/yellow]"
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
except Exception as e:
|
|
753
|
+
console.print(
|
|
754
|
+
f"[red][X] Error executing entity add-classifications-by-attribute: {str(e)}[/red]"
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
@entity.command()
|
|
759
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
760
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
761
|
+
@click.option(
|
|
762
|
+
"--payload-file",
|
|
763
|
+
required=True,
|
|
764
|
+
type=click.Path(exists=True),
|
|
765
|
+
help="File path to a valid JSON document containing classification data",
|
|
766
|
+
)
|
|
767
|
+
@click.pass_context
|
|
768
|
+
def update_classifications_by_attribute(ctx, type_name, qualified_name, payload_file):
|
|
769
|
+
"""Update classifications by unique attribute"""
|
|
770
|
+
try:
|
|
771
|
+
if ctx.obj.get("mock"):
|
|
772
|
+
console.print(
|
|
773
|
+
"[yellow]🎠Mock: entity update-classifications-by-attribute command[/yellow]"
|
|
774
|
+
)
|
|
775
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
776
|
+
console.print(
|
|
777
|
+
"[green][OK] Mock entity update-classifications-by-attribute completed successfully[/green]"
|
|
778
|
+
)
|
|
779
|
+
return
|
|
780
|
+
|
|
781
|
+
args = {
|
|
782
|
+
"--typeName": type_name,
|
|
783
|
+
"--qualifiedName": qualified_name,
|
|
784
|
+
"--payloadFile": payload_file,
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
from purviewcli.client._entity import Entity
|
|
788
|
+
|
|
789
|
+
entity_client = Entity()
|
|
790
|
+
result = entity_client.entityUpdateClassificationsByUniqueAttribute(args)
|
|
791
|
+
|
|
792
|
+
if result:
|
|
793
|
+
console.print(
|
|
794
|
+
"[green][OK] Entity update-classifications-by-attribute completed successfully[/green]"
|
|
795
|
+
)
|
|
796
|
+
console.print(json.dumps(result, indent=2))
|
|
797
|
+
else:
|
|
798
|
+
console.print(
|
|
799
|
+
"[yellow][!] Entity update-classifications-by-attribute completed with no result[/yellow]"
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
except Exception as e:
|
|
803
|
+
console.print(
|
|
804
|
+
f"[red][X] Error executing entity update-classifications-by-attribute: {str(e)}[/red]"
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@entity.command()
|
|
809
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
810
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
811
|
+
@click.option(
|
|
812
|
+
"--classification-name", required=True, help="The name of the classification to remove"
|
|
813
|
+
)
|
|
814
|
+
@click.pass_context
|
|
815
|
+
def remove_classification_by_attribute(ctx, type_name, qualified_name, classification_name):
|
|
816
|
+
"""Remove classification by unique attribute"""
|
|
817
|
+
try:
|
|
818
|
+
if ctx.obj.get("mock"):
|
|
819
|
+
console.print(
|
|
820
|
+
"[yellow]🎠Mock: entity remove-classification-by-attribute command[/yellow]"
|
|
821
|
+
)
|
|
822
|
+
console.print(
|
|
823
|
+
f"[dim]Type: {type_name}, Qualified Name: {qualified_name}, Classification: {classification_name}[/dim]"
|
|
824
|
+
)
|
|
825
|
+
console.print(
|
|
826
|
+
"[green][OK] Mock entity remove-classification-by-attribute completed successfully[/green]"
|
|
827
|
+
)
|
|
828
|
+
return
|
|
829
|
+
|
|
830
|
+
args = {
|
|
831
|
+
"--typeName": type_name,
|
|
832
|
+
"--qualifiedName": qualified_name,
|
|
833
|
+
"--classificationName": classification_name,
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
from purviewcli.client._entity import Entity
|
|
837
|
+
|
|
838
|
+
entity_client = Entity()
|
|
839
|
+
result = entity_client.entityDeleteClassificationByUniqueAttribute(args)
|
|
840
|
+
|
|
841
|
+
if result:
|
|
842
|
+
console.print(
|
|
843
|
+
"[green][OK] Entity remove-classification-by-attribute completed successfully[/green]"
|
|
844
|
+
)
|
|
845
|
+
console.print(json.dumps(result, indent=2))
|
|
846
|
+
else:
|
|
847
|
+
console.print(
|
|
848
|
+
"[yellow][!] Entity remove-classification-by-attribute completed with no result[/yellow]"
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
except Exception as e:
|
|
852
|
+
console.print(
|
|
853
|
+
f"[red][X] Error executing entity remove-classification-by-attribute: {str(e)}[/red]"
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
# === BULK CLASSIFICATION OPERATIONS ===
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
@entity.command()
|
|
861
|
+
@click.option(
|
|
862
|
+
"--payload-file",
|
|
863
|
+
required=True,
|
|
864
|
+
type=click.Path(exists=True),
|
|
865
|
+
help="File path to a valid JSON document containing bulk classification data",
|
|
866
|
+
)
|
|
867
|
+
@click.pass_context
|
|
868
|
+
def bulk_add_classification(ctx, payload_file):
|
|
869
|
+
"""Add classification to multiple entities in bulk"""
|
|
870
|
+
try:
|
|
871
|
+
if ctx.obj.get("mock"):
|
|
872
|
+
console.print("[yellow]🎠Mock: entity bulk-add-classification command[/yellow]")
|
|
873
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
874
|
+
console.print(
|
|
875
|
+
"[green][OK] Mock entity bulk-add-classification completed successfully[/green]"
|
|
876
|
+
)
|
|
877
|
+
return
|
|
878
|
+
|
|
879
|
+
args = {"--payloadFile": payload_file}
|
|
880
|
+
|
|
881
|
+
from purviewcli.client._entity import Entity
|
|
882
|
+
|
|
883
|
+
entity_client = Entity()
|
|
884
|
+
result = entity_client.entityAddClassification(args)
|
|
885
|
+
|
|
886
|
+
if result:
|
|
887
|
+
console.print("[green][OK] Entity bulk-add-classification completed successfully[/green]")
|
|
888
|
+
console.print(json.dumps(result, indent=2))
|
|
889
|
+
else:
|
|
890
|
+
console.print(
|
|
891
|
+
"[yellow][!] Entity bulk-add-classification completed with no result[/yellow]"
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
except Exception as e:
|
|
895
|
+
console.print(f"[red][X] Error executing entity bulk-add-classification: {str(e)}[/red]")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
@entity.command()
|
|
899
|
+
@click.option(
|
|
900
|
+
"--payload-file",
|
|
901
|
+
required=True,
|
|
902
|
+
type=click.Path(exists=True),
|
|
903
|
+
help="File path to a valid JSON document containing bulk classification data",
|
|
904
|
+
)
|
|
905
|
+
@click.pass_context
|
|
906
|
+
def bulk_set_classifications(ctx, payload_file):
|
|
907
|
+
"""Set classifications on multiple entities in bulk"""
|
|
908
|
+
try:
|
|
909
|
+
if ctx.obj.get("mock"):
|
|
910
|
+
console.print("[yellow]🎠Mock: entity bulk-set-classifications command[/yellow]")
|
|
911
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
912
|
+
console.print(
|
|
913
|
+
"[green][OK] Mock entity bulk-set-classifications completed successfully[/green]"
|
|
914
|
+
)
|
|
915
|
+
return
|
|
916
|
+
|
|
917
|
+
args = {"--payloadFile": payload_file}
|
|
918
|
+
|
|
919
|
+
from purviewcli.client._entity import Entity
|
|
920
|
+
|
|
921
|
+
entity_client = Entity()
|
|
922
|
+
result = entity_client.entityBulkSetClassifications(args)
|
|
923
|
+
|
|
924
|
+
if result:
|
|
925
|
+
console.print("[green][OK] Entity bulk-set-classifications completed successfully[/green]")
|
|
926
|
+
console.print(json.dumps(result, indent=2))
|
|
927
|
+
else:
|
|
928
|
+
console.print(
|
|
929
|
+
"[yellow][!] Entity bulk-set-classifications completed with no result[/yellow]"
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
except Exception as e:
|
|
933
|
+
console.print(f"[red][X] Error executing entity bulk-set-classifications: {str(e)}[/red]")
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
# === LABEL OPERATIONS ===
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
@entity.command()
|
|
940
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
941
|
+
@click.option(
|
|
942
|
+
"--payload-file",
|
|
943
|
+
required=True,
|
|
944
|
+
type=click.Path(exists=True),
|
|
945
|
+
help="File path to a valid JSON document containing label data",
|
|
946
|
+
)
|
|
947
|
+
@click.pass_context
|
|
948
|
+
def add_labels(ctx, guid, payload_file):
|
|
949
|
+
"""Add labels to an entity"""
|
|
950
|
+
try:
|
|
951
|
+
if ctx.obj.get("mock"):
|
|
952
|
+
console.print("[yellow]🎠Mock: entity add-labels command[/yellow]")
|
|
953
|
+
console.print(f"[dim]GUID: {guid}, Payload File: {payload_file}[/dim]")
|
|
954
|
+
console.print("[green][OK] Mock entity add-labels completed successfully[/green]")
|
|
955
|
+
return
|
|
956
|
+
|
|
957
|
+
args = {
|
|
958
|
+
"--guid": [guid],
|
|
959
|
+
"--payloadFile": payload_file,
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
from purviewcli.client._entity import Entity
|
|
963
|
+
|
|
964
|
+
entity_client = Entity()
|
|
965
|
+
result = entity_client.entityAddLabels(args)
|
|
966
|
+
|
|
967
|
+
if result:
|
|
968
|
+
console.print("[green][OK] Entity add-labels completed successfully[/green]")
|
|
969
|
+
console.print(json.dumps(result, indent=2))
|
|
970
|
+
else:
|
|
971
|
+
console.print("[yellow][!] Entity add-labels completed with no result[/yellow]")
|
|
972
|
+
|
|
973
|
+
except Exception as e:
|
|
974
|
+
console.print(f"[red][X] Error executing entity add-labels: {str(e)}[/red]")
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
@entity.command()
|
|
978
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
979
|
+
@click.option(
|
|
980
|
+
"--payload-file",
|
|
981
|
+
required=True,
|
|
982
|
+
type=click.Path(exists=True),
|
|
983
|
+
help="File path to a valid JSON document containing label data",
|
|
984
|
+
)
|
|
985
|
+
@click.pass_context
|
|
986
|
+
def set_labels(ctx, guid, payload_file):
|
|
987
|
+
"""Set labels on an entity"""
|
|
988
|
+
try:
|
|
989
|
+
if ctx.obj.get("mock"):
|
|
990
|
+
console.print("[yellow]🎠Mock: entity set-labels command[/yellow]")
|
|
991
|
+
console.print(f"[dim]GUID: {guid}, Payload File: {payload_file}[/dim]")
|
|
992
|
+
console.print("[green][OK] Mock entity set-labels completed successfully[/green]")
|
|
993
|
+
return
|
|
994
|
+
|
|
995
|
+
args = {
|
|
996
|
+
"--guid": [guid],
|
|
997
|
+
"--payloadFile": payload_file,
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
from purviewcli.client._entity import Entity
|
|
1001
|
+
|
|
1002
|
+
entity_client = Entity()
|
|
1003
|
+
result = entity_client.entitySetLabels(args)
|
|
1004
|
+
|
|
1005
|
+
if result:
|
|
1006
|
+
console.print("[green][OK] Entity set-labels completed successfully[/green]")
|
|
1007
|
+
console.print(json.dumps(result, indent=2))
|
|
1008
|
+
else:
|
|
1009
|
+
console.print("[yellow][!] Entity set-labels completed with no result[/yellow]")
|
|
1010
|
+
|
|
1011
|
+
except Exception as e:
|
|
1012
|
+
console.print(f"[red][X] Error executing entity set-labels: {str(e)}[/red]")
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
@entity.command()
|
|
1016
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1017
|
+
@click.option(
|
|
1018
|
+
"--payload-file",
|
|
1019
|
+
required=True,
|
|
1020
|
+
type=click.Path(exists=True),
|
|
1021
|
+
help="File path to a valid JSON document containing label data",
|
|
1022
|
+
)
|
|
1023
|
+
@click.pass_context
|
|
1024
|
+
def remove_labels(ctx, guid, payload_file):
|
|
1025
|
+
"""Remove labels from an entity"""
|
|
1026
|
+
try:
|
|
1027
|
+
if ctx.obj.get("mock"):
|
|
1028
|
+
console.print("[yellow]🎠Mock: entity remove-labels command[/yellow]")
|
|
1029
|
+
console.print(f"[dim]GUID: {guid}, Payload File: {payload_file}[/dim]")
|
|
1030
|
+
console.print("[green][OK] Mock entity remove-labels completed successfully[/green]")
|
|
1031
|
+
return
|
|
1032
|
+
|
|
1033
|
+
args = {
|
|
1034
|
+
"--guid": [guid],
|
|
1035
|
+
"--payloadFile": payload_file,
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
from purviewcli.client._entity import Entity
|
|
1039
|
+
|
|
1040
|
+
entity_client = Entity()
|
|
1041
|
+
result = entity_client.entityRemoveLabels(args)
|
|
1042
|
+
|
|
1043
|
+
if result:
|
|
1044
|
+
console.print("[green][OK] Entity remove-labels completed successfully[/green]")
|
|
1045
|
+
console.print(json.dumps(result, indent=2))
|
|
1046
|
+
else:
|
|
1047
|
+
console.print("[yellow][!] Entity remove-labels completed with no result[/yellow]")
|
|
1048
|
+
|
|
1049
|
+
except Exception as e:
|
|
1050
|
+
console.print(f"[red][X] Error executing entity remove-labels: {str(e)}[/red]")
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
# === LABEL OPERATIONS BY UNIQUE ATTRIBUTE ===
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
@entity.command()
|
|
1057
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
1058
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
1059
|
+
@click.option(
|
|
1060
|
+
"--payload-file",
|
|
1061
|
+
required=True,
|
|
1062
|
+
type=click.Path(exists=True),
|
|
1063
|
+
help="File path to a valid JSON document containing label data",
|
|
1064
|
+
)
|
|
1065
|
+
@click.pass_context
|
|
1066
|
+
def add_labels_by_attribute(ctx, type_name, qualified_name, payload_file):
|
|
1067
|
+
"""Add labels by unique attribute"""
|
|
1068
|
+
try:
|
|
1069
|
+
if ctx.obj.get("mock"):
|
|
1070
|
+
console.print("[yellow]🎠Mock: entity add-labels-by-attribute command[/yellow]")
|
|
1071
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
1072
|
+
console.print(
|
|
1073
|
+
"[green][OK] Mock entity add-labels-by-attribute completed successfully[/green]"
|
|
1074
|
+
)
|
|
1075
|
+
return
|
|
1076
|
+
|
|
1077
|
+
args = {
|
|
1078
|
+
"--typeName": type_name,
|
|
1079
|
+
"--qualifiedName": qualified_name,
|
|
1080
|
+
"--payloadFile": payload_file,
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
from purviewcli.client._entity import Entity
|
|
1084
|
+
|
|
1085
|
+
entity_client = Entity()
|
|
1086
|
+
result = entity_client.entityAddLabelsByUniqueAttribute(args)
|
|
1087
|
+
|
|
1088
|
+
if result:
|
|
1089
|
+
console.print("[green][OK] Entity add-labels-by-attribute completed successfully[/green]")
|
|
1090
|
+
console.print(json.dumps(result, indent=2))
|
|
1091
|
+
else:
|
|
1092
|
+
console.print(
|
|
1093
|
+
"[yellow][!] Entity add-labels-by-attribute completed with no result[/yellow]"
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
except Exception as e:
|
|
1097
|
+
console.print(f"[red][X] Error executing entity add-labels-by-attribute: {str(e)}[/red]")
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
@entity.command()
|
|
1101
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
1102
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
1103
|
+
@click.option(
|
|
1104
|
+
"--payload-file",
|
|
1105
|
+
required=True,
|
|
1106
|
+
type=click.Path(exists=True),
|
|
1107
|
+
help="File path to a valid JSON document containing label data",
|
|
1108
|
+
)
|
|
1109
|
+
@click.pass_context
|
|
1110
|
+
def set_labels_by_attribute(ctx, type_name, qualified_name, payload_file):
|
|
1111
|
+
"""Set labels by unique attribute"""
|
|
1112
|
+
try:
|
|
1113
|
+
if ctx.obj.get("mock"):
|
|
1114
|
+
console.print("[yellow]🎠Mock: entity set-labels-by-attribute command[/yellow]")
|
|
1115
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}[/dim]")
|
|
1116
|
+
console.print(
|
|
1117
|
+
"[green][OK] Mock entity set-labels-by-attribute completed successfully[/green]"
|
|
1118
|
+
)
|
|
1119
|
+
return
|
|
1120
|
+
|
|
1121
|
+
args = {
|
|
1122
|
+
"--typeName": type_name,
|
|
1123
|
+
"--qualifiedName": qualified_name,
|
|
1124
|
+
"--payloadFile": payload_file,
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
from purviewcli.client._entity import Entity
|
|
1128
|
+
|
|
1129
|
+
entity_client = Entity()
|
|
1130
|
+
result = entity_client.entitySetLabelsByUniqueAttribute(args)
|
|
1131
|
+
|
|
1132
|
+
if result:
|
|
1133
|
+
console.print("[green][OK] Entity set-labels-by-attribute completed successfully[/green]")
|
|
1134
|
+
console.print(json.dumps(result, indent=2))
|
|
1135
|
+
else:
|
|
1136
|
+
console.print(
|
|
1137
|
+
"[yellow][!] Entity set-labels-by-attribute completed with no result[/yellow]"
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
except Exception as e:
|
|
1141
|
+
console.print(f"[red][X] Error executing entity set-labels-by-attribute: {str(e)}[/red]")
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
@entity.command()
|
|
1145
|
+
@click.option(
|
|
1146
|
+
"--payload-file",
|
|
1147
|
+
required=True,
|
|
1148
|
+
type=click.Path(exists=True),
|
|
1149
|
+
help="File path to a valid JSON document containing bulk label data",
|
|
1150
|
+
)
|
|
1151
|
+
@click.pass_context
|
|
1152
|
+
def bulk_remove_labels(ctx, payload_file):
|
|
1153
|
+
"""Remove labels from multiple entities in bulk (by GUID)"""
|
|
1154
|
+
try:
|
|
1155
|
+
if ctx.obj.get("mock"):
|
|
1156
|
+
console.print("[yellow]🎠Mock: entity bulk-remove-labels command[/yellow]")
|
|
1157
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
1158
|
+
console.print("[green][OK] Mock entity bulk-remove-labels completed successfully[/green]")
|
|
1159
|
+
return
|
|
1160
|
+
args = {"--payloadFile": payload_file}
|
|
1161
|
+
from purviewcli.client._entity import Entity
|
|
1162
|
+
entity_client = Entity()
|
|
1163
|
+
result = entity_client.entityBulkRemoveLabels(args)
|
|
1164
|
+
if result:
|
|
1165
|
+
console.print("[green][OK] Entity bulk-remove-labels completed successfully[/green]")
|
|
1166
|
+
console.print(json.dumps(result, indent=2))
|
|
1167
|
+
else:
|
|
1168
|
+
console.print("[yellow][!] Entity bulk-remove-labels completed with no result[/yellow]")
|
|
1169
|
+
except Exception as e:
|
|
1170
|
+
console.print(f"[red][X] Error executing entity bulk-remove-labels: {str(e)}[/red]")
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
@entity.command()
|
|
1174
|
+
@click.option("--type-name", required=True, help="The name of the entity type")
|
|
1175
|
+
@click.option("--qualified-name", required=True, help="The qualified name of the entity")
|
|
1176
|
+
@click.option(
|
|
1177
|
+
"--payload-file",
|
|
1178
|
+
required=True,
|
|
1179
|
+
type=click.Path(exists=True),
|
|
1180
|
+
help="File path to a valid JSON document containing label data",
|
|
1181
|
+
)
|
|
1182
|
+
@click.pass_context
|
|
1183
|
+
def bulk_remove_labels_by_attribute(ctx, type_name, qualified_name, payload_file):
|
|
1184
|
+
"""Remove labels from multiple entities in bulk (by unique attribute)"""
|
|
1185
|
+
try:
|
|
1186
|
+
if ctx.obj.get("mock"):
|
|
1187
|
+
console.print("[yellow]🎠Mock: entity bulk-remove-labels-by-attribute command[/yellow]")
|
|
1188
|
+
console.print(f"[dim]Type: {type_name}, Qualified Name: {qualified_name}, Payload File: {payload_file}[/dim]")
|
|
1189
|
+
console.print("[green][OK] Mock entity bulk-remove-labels-by-attribute completed successfully[/green]")
|
|
1190
|
+
return
|
|
1191
|
+
args = {"--typeName": type_name, "--qualifiedName": qualified_name, "--payloadFile": payload_file}
|
|
1192
|
+
from purviewcli.client._entity import Entity
|
|
1193
|
+
entity_client = Entity()
|
|
1194
|
+
result = entity_client.entityBulkRemoveLabelsByUniqueAttribute(args)
|
|
1195
|
+
if result:
|
|
1196
|
+
console.print("[green][OK] Entity bulk-remove-labels-by-attribute completed successfully[/green]")
|
|
1197
|
+
console.print(json.dumps(result, indent=2))
|
|
1198
|
+
else:
|
|
1199
|
+
console.print("[yellow][!] Entity bulk-remove-labels-by-attribute completed with no result[/yellow]")
|
|
1200
|
+
except Exception as e:
|
|
1201
|
+
console.print(f"[red][X] Error executing entity bulk-remove-labels-by-attribute: {str(e)}[/red]")
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
# === BUSINESS METADATA OPERATIONS ===
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
@entity.command()
|
|
1208
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1209
|
+
@click.option(
|
|
1210
|
+
"--payload-file",
|
|
1211
|
+
required=True,
|
|
1212
|
+
type=click.Path(exists=True),
|
|
1213
|
+
help="File path to a valid JSON document containing business metadata",
|
|
1214
|
+
)
|
|
1215
|
+
@click.option(
|
|
1216
|
+
"--is-overwrite", is_flag=True, help="Whether to overwrite existing business metadata"
|
|
1217
|
+
)
|
|
1218
|
+
@click.pass_context
|
|
1219
|
+
def add_business_metadata(ctx, guid, payload_file, is_overwrite):
|
|
1220
|
+
"""Add or update business metadata to an entity"""
|
|
1221
|
+
try:
|
|
1222
|
+
if ctx.obj.get("mock"):
|
|
1223
|
+
console.print("[yellow]🎠Mock: entity add-business-metadata command[/yellow]")
|
|
1224
|
+
console.print(f"[dim]GUID: {guid}, Overwrite: {is_overwrite}[/dim]")
|
|
1225
|
+
console.print(
|
|
1226
|
+
"[green][OK] Mock entity add-business-metadata completed successfully[/green]"
|
|
1227
|
+
)
|
|
1228
|
+
return
|
|
1229
|
+
|
|
1230
|
+
args = {
|
|
1231
|
+
"--guid": [guid],
|
|
1232
|
+
"--payloadFile": payload_file,
|
|
1233
|
+
"--isOverwrite": is_overwrite,
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
from purviewcli.client._entity import Entity
|
|
1237
|
+
|
|
1238
|
+
entity_client = Entity()
|
|
1239
|
+
result = entity_client.entityAddOrUpdateBusinessMetadata(args)
|
|
1240
|
+
|
|
1241
|
+
if result:
|
|
1242
|
+
console.print("[green][OK] Entity add-business-metadata completed successfully[/green]")
|
|
1243
|
+
console.print(json.dumps(result, indent=2))
|
|
1244
|
+
else:
|
|
1245
|
+
console.print(
|
|
1246
|
+
"[yellow][!] Entity add-business-metadata completed with no result[/yellow]"
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
except Exception as e:
|
|
1250
|
+
console.print(f"[red][X] Error executing entity add-business-metadata: {str(e)}[/red]")
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
@entity.command()
|
|
1254
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1255
|
+
@click.option("--bm-name", required=True, help="The business metadata name")
|
|
1256
|
+
@click.option(
|
|
1257
|
+
"--payload-file",
|
|
1258
|
+
required=True,
|
|
1259
|
+
type=click.Path(exists=True),
|
|
1260
|
+
help="File path to a valid JSON document containing business metadata attributes",
|
|
1261
|
+
)
|
|
1262
|
+
@click.pass_context
|
|
1263
|
+
def add_business_metadata_attributes(ctx, guid, bm_name, payload_file):
|
|
1264
|
+
"""Add or update business metadata attributes"""
|
|
1265
|
+
try:
|
|
1266
|
+
if ctx.obj.get("mock"):
|
|
1267
|
+
console.print(
|
|
1268
|
+
"[yellow]🎠Mock: entity add-business-metadata-attributes command[/yellow]"
|
|
1269
|
+
)
|
|
1270
|
+
console.print(f"[dim]GUID: {guid}, BM Name: {bm_name}[/dim]")
|
|
1271
|
+
console.print(
|
|
1272
|
+
"[green][OK] Mock entity add-business-metadata-attributes completed successfully[/green]"
|
|
1273
|
+
)
|
|
1274
|
+
return
|
|
1275
|
+
|
|
1276
|
+
args = {
|
|
1277
|
+
"--guid": [guid],
|
|
1278
|
+
"--bmName": bm_name,
|
|
1279
|
+
"--payloadFile": payload_file,
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
from purviewcli.client._entity import Entity
|
|
1283
|
+
|
|
1284
|
+
entity_client = Entity()
|
|
1285
|
+
result = entity_client.entityAddOrUpdateBusinessMetadataAttributes(args)
|
|
1286
|
+
|
|
1287
|
+
if result:
|
|
1288
|
+
console.print(
|
|
1289
|
+
"[green][OK] Entity add-business-metadata-attributes completed successfully[/green]"
|
|
1290
|
+
)
|
|
1291
|
+
console.print(json.dumps(result, indent=2))
|
|
1292
|
+
else:
|
|
1293
|
+
console.print(
|
|
1294
|
+
"[yellow][!] Entity add-business-metadata-attributes completed with no result[/yellow]"
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
except Exception as e:
|
|
1298
|
+
console.print(
|
|
1299
|
+
f"[red][X] Error executing entity add-business-metadata-attributes: {str(e)}[/red]"
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
@entity.command()
|
|
1304
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1305
|
+
@click.option(
|
|
1306
|
+
"--payload-file",
|
|
1307
|
+
required=True,
|
|
1308
|
+
type=click.Path(exists=True),
|
|
1309
|
+
help="File path to a valid JSON document specifying business metadata to remove",
|
|
1310
|
+
)
|
|
1311
|
+
@click.pass_context
|
|
1312
|
+
def remove_business_metadata(ctx, guid, payload_file):
|
|
1313
|
+
"""Remove business metadata from an entity"""
|
|
1314
|
+
try:
|
|
1315
|
+
if ctx.obj.get("mock"):
|
|
1316
|
+
console.print("[yellow]🎠Mock: entity remove-business-metadata command[/yellow]")
|
|
1317
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
1318
|
+
console.print(
|
|
1319
|
+
"[green][OK] Mock entity remove-business-metadata completed successfully[/green]"
|
|
1320
|
+
)
|
|
1321
|
+
return
|
|
1322
|
+
|
|
1323
|
+
args = {
|
|
1324
|
+
"--guid": [guid],
|
|
1325
|
+
"--payloadFile": payload_file,
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
from purviewcli.client._entity import Entity
|
|
1329
|
+
|
|
1330
|
+
entity_client = Entity()
|
|
1331
|
+
result = entity_client.entityRemoveBusinessMetadata(args)
|
|
1332
|
+
|
|
1333
|
+
if result:
|
|
1334
|
+
console.print("[green][OK] Entity remove-business-metadata completed successfully[/green]")
|
|
1335
|
+
console.print(json.dumps(result, indent=2))
|
|
1336
|
+
else:
|
|
1337
|
+
console.print(
|
|
1338
|
+
"[yellow][!] Entity remove-business-metadata completed with no result[/yellow]"
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
except Exception as e:
|
|
1342
|
+
console.print(f"[red][X] Error executing entity remove-business-metadata: {str(e)}[/red]")
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
@entity.command()
|
|
1346
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1347
|
+
@click.option("--bm-name", required=True, help="The business metadata name")
|
|
1348
|
+
@click.option(
|
|
1349
|
+
"--payload-file",
|
|
1350
|
+
required=True,
|
|
1351
|
+
type=click.Path(exists=True),
|
|
1352
|
+
help="File path to a valid JSON document specifying attributes to remove",
|
|
1353
|
+
)
|
|
1354
|
+
@click.pass_context
|
|
1355
|
+
def remove_business_metadata_attributes(ctx, guid, bm_name, payload_file):
|
|
1356
|
+
"""Remove business metadata attributes"""
|
|
1357
|
+
try:
|
|
1358
|
+
if ctx.obj.get("mock"):
|
|
1359
|
+
console.print(
|
|
1360
|
+
"[yellow]🎠Mock: entity remove-business-metadata-attributes command[/yellow]"
|
|
1361
|
+
)
|
|
1362
|
+
console.print(f"[dim]GUID: {guid}, BM Name: {bm_name}[/dim]")
|
|
1363
|
+
console.print(
|
|
1364
|
+
"[green][OK] Mock entity remove-business-metadata-attributes completed successfully[/green]"
|
|
1365
|
+
)
|
|
1366
|
+
return
|
|
1367
|
+
|
|
1368
|
+
args = {
|
|
1369
|
+
"--guid": [guid],
|
|
1370
|
+
"--bmName": bm_name,
|
|
1371
|
+
"--payloadFile": payload_file,
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
from purviewcli.client._entity import Entity
|
|
1375
|
+
|
|
1376
|
+
entity_client = Entity()
|
|
1377
|
+
result = entity_client.entityRemoveBusinessMetadataAttributes(args)
|
|
1378
|
+
|
|
1379
|
+
if result:
|
|
1380
|
+
console.print(
|
|
1381
|
+
"[green][OK] Entity remove-business-metadata-attributes completed successfully[/green]"
|
|
1382
|
+
)
|
|
1383
|
+
console.print(json.dumps(result, indent=2))
|
|
1384
|
+
else:
|
|
1385
|
+
console.print(
|
|
1386
|
+
"[yellow][!] Entity remove-business-metadata-attributes completed with no result[/yellow]"
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
except Exception as e:
|
|
1390
|
+
console.print(
|
|
1391
|
+
f"[red][X] Error executing entity remove-business-metadata-attributes: {str(e)}[/red]"
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
@entity.command(name="import-business-metadata")
|
|
1396
|
+
@click.option(
|
|
1397
|
+
"--bm-file",
|
|
1398
|
+
required=True,
|
|
1399
|
+
type=click.Path(exists=True),
|
|
1400
|
+
help="File path to a valid business metadata CSV file",
|
|
1401
|
+
)
|
|
1402
|
+
@click.pass_context
|
|
1403
|
+
def import_business_metadata(ctx, bm_file):
|
|
1404
|
+
"""Import business metadata in bulk from CSV"""
|
|
1405
|
+
try:
|
|
1406
|
+
if ctx.obj.get("mock"):
|
|
1407
|
+
console.print("[yellow]🎠Mock: entity import-business-metadata command[/yellow]")
|
|
1408
|
+
console.print(f"[dim]BM File: {bm_file}[/dim]")
|
|
1409
|
+
console.print(
|
|
1410
|
+
"[green][OK] Mock entity import-business-metadata completed successfully[/green]"
|
|
1411
|
+
)
|
|
1412
|
+
return
|
|
1413
|
+
|
|
1414
|
+
args = {"--bmFile": bm_file}
|
|
1415
|
+
|
|
1416
|
+
from purviewcli.client._entity import Entity
|
|
1417
|
+
|
|
1418
|
+
entity_client = Entity()
|
|
1419
|
+
result = entity_client.entityImportBusinessMetadata(args)
|
|
1420
|
+
|
|
1421
|
+
if result:
|
|
1422
|
+
console.print("[green][OK] Entity import-business-metadata completed successfully[/green]")
|
|
1423
|
+
console.print(json.dumps(result, indent=2))
|
|
1424
|
+
else:
|
|
1425
|
+
console.print(
|
|
1426
|
+
"[yellow][!] Entity import-business-metadata completed with no result[/yellow]"
|
|
1427
|
+
)
|
|
1428
|
+
|
|
1429
|
+
except Exception as e:
|
|
1430
|
+
console.print(f"[red][X] Error executing entity import-business-metadata: {str(e)}[/red]")
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
@entity.command()
|
|
1434
|
+
@click.pass_context
|
|
1435
|
+
def get_business_metadata_template(ctx):
|
|
1436
|
+
"""Get sample template for business metadata"""
|
|
1437
|
+
try:
|
|
1438
|
+
if ctx.obj.get("mock"):
|
|
1439
|
+
console.print("[yellow]🎠Mock: entity get-business-metadata-template command[/yellow]")
|
|
1440
|
+
console.print(
|
|
1441
|
+
"[green][OK] Mock entity get-business-metadata-template completed successfully[/green]"
|
|
1442
|
+
)
|
|
1443
|
+
return
|
|
1444
|
+
|
|
1445
|
+
args = {}
|
|
1446
|
+
|
|
1447
|
+
from purviewcli.client._entity import Entity
|
|
1448
|
+
|
|
1449
|
+
entity_client = Entity()
|
|
1450
|
+
result = entity_client.entityGetBusinessMetadataTemplate(args)
|
|
1451
|
+
|
|
1452
|
+
if result:
|
|
1453
|
+
console.print(
|
|
1454
|
+
"[green][OK] Entity get-business-metadata-template completed successfully[/green]"
|
|
1455
|
+
)
|
|
1456
|
+
console.print(json.dumps(result, indent=2))
|
|
1457
|
+
else:
|
|
1458
|
+
console.print(
|
|
1459
|
+
"[yellow][!] Entity get-business-metadata-template completed with no result[/yellow]"
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
except Exception as e:
|
|
1463
|
+
console.print(
|
|
1464
|
+
f"[red][X] Error executing entity get-business-metadata-template: {str(e)}[/red]"
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
# === COLLECTION OPERATIONS ===
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
@entity.command()
|
|
1472
|
+
@click.option(
|
|
1473
|
+
"--payload-file",
|
|
1474
|
+
required=True,
|
|
1475
|
+
type=click.Path(exists=True),
|
|
1476
|
+
help="File path to a valid JSON document containing entities to move and target collection",
|
|
1477
|
+
)
|
|
1478
|
+
@click.pass_context
|
|
1479
|
+
def move_to_collection(ctx, payload_file):
|
|
1480
|
+
"""Move entities to a target collection"""
|
|
1481
|
+
try:
|
|
1482
|
+
if ctx.obj.get("mock"):
|
|
1483
|
+
console.print("[yellow]🎠Mock: entity move-to-collection command[/yellow]")
|
|
1484
|
+
console.print(f"[dim]Payload File: {payload_file}[/dim]")
|
|
1485
|
+
console.print("[green][OK] Mock entity move-to-collection completed successfully[/green]")
|
|
1486
|
+
return
|
|
1487
|
+
|
|
1488
|
+
args = {"--payloadFile": payload_file}
|
|
1489
|
+
|
|
1490
|
+
from purviewcli.client._entity import Entity
|
|
1491
|
+
|
|
1492
|
+
entity_client = Entity()
|
|
1493
|
+
result = entity_client.entityMoveEntitiesToCollection(args)
|
|
1494
|
+
|
|
1495
|
+
if result:
|
|
1496
|
+
console.print("[green][OK] Entity move-to-collection completed successfully[/green]")
|
|
1497
|
+
console.print(json.dumps(result, indent=2))
|
|
1498
|
+
else:
|
|
1499
|
+
console.print("[yellow][!] Entity move-to-collection completed with no result[/yellow]")
|
|
1500
|
+
|
|
1501
|
+
except Exception as e:
|
|
1502
|
+
console.print(f"[red][X] Error executing entity move-to-collection: {str(e)}[/red]")
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
# === SAMPLE OPERATIONS ===
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
@entity.command()
|
|
1509
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1510
|
+
@click.pass_context
|
|
1511
|
+
def read_sample(ctx, guid):
|
|
1512
|
+
"""Get sample data for an entity"""
|
|
1513
|
+
try:
|
|
1514
|
+
if ctx.obj.get("mock"):
|
|
1515
|
+
console.print("[yellow]🎠Mock: entity read-sample command[/yellow]")
|
|
1516
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
1517
|
+
console.print("[green][OK] Mock entity read-sample completed successfully[/green]")
|
|
1518
|
+
return
|
|
1519
|
+
|
|
1520
|
+
args = {"--guid": [guid]}
|
|
1521
|
+
|
|
1522
|
+
from purviewcli.client._entity import Entity
|
|
1523
|
+
|
|
1524
|
+
entity_client = Entity()
|
|
1525
|
+
result = entity_client.entityReadSample(args)
|
|
1526
|
+
|
|
1527
|
+
if result:
|
|
1528
|
+
console.print("[green][OK] Entity read-sample completed successfully[/green]")
|
|
1529
|
+
console.print(json.dumps(result, indent=2))
|
|
1530
|
+
else:
|
|
1531
|
+
console.print("[yellow][!] Entity read-sample completed with no result[/yellow]")
|
|
1532
|
+
|
|
1533
|
+
except Exception as e:
|
|
1534
|
+
console.print(f"[red][X] Error executing entity read-sample: {str(e)}[/red]")
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
@entity.command()
|
|
1538
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="CSV file with GUID and classificationName columns")
|
|
1539
|
+
@click.option("--batch-size", default=100, help="Batch size for API calls")
|
|
1540
|
+
@click.pass_context
|
|
1541
|
+
def bulk_classify_csv(ctx, csv_file, batch_size):
|
|
1542
|
+
"""Bulk classify entities from a CSV file (guid, classificationName columns)"""
|
|
1543
|
+
import pandas as pd
|
|
1544
|
+
import tempfile
|
|
1545
|
+
from purviewcli.client._entity import Entity
|
|
1546
|
+
try:
|
|
1547
|
+
if ctx.obj.get("mock"):
|
|
1548
|
+
console.print("[yellow]🎠Mock: entity bulk-classify-csv command[/yellow]")
|
|
1549
|
+
console.print(f"[dim]CSV File: {csv_file}[/dim]")
|
|
1550
|
+
console.print("[green][OK] Mock entity bulk-classify-csv completed successfully[/green]")
|
|
1551
|
+
return
|
|
1552
|
+
|
|
1553
|
+
df = pd.read_csv(csv_file)
|
|
1554
|
+
if "guid" not in df.columns or "classificationName" not in df.columns:
|
|
1555
|
+
console.print("[red][X] CSV must contain 'guid' and 'classificationName' columns[/red]")
|
|
1556
|
+
return
|
|
1557
|
+
entity_client = Entity()
|
|
1558
|
+
total = len(df)
|
|
1559
|
+
success, failed = 0, 0
|
|
1560
|
+
errors = []
|
|
1561
|
+
for i in range(0, total, batch_size):
|
|
1562
|
+
batch = df.iloc[i:i+batch_size]
|
|
1563
|
+
payload = {
|
|
1564
|
+
"entities": [
|
|
1565
|
+
{
|
|
1566
|
+
"guid": str(row["guid"]),
|
|
1567
|
+
"classifications": [{"typeName": str(row["classificationName"])}]
|
|
1568
|
+
}
|
|
1569
|
+
for _, row in batch.iterrows()
|
|
1570
|
+
]
|
|
1571
|
+
}
|
|
1572
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmpf:
|
|
1573
|
+
import json
|
|
1574
|
+
json.dump(payload, tmpf, indent=2)
|
|
1575
|
+
tmpf.flush()
|
|
1576
|
+
payload_file = tmpf.name
|
|
1577
|
+
try:
|
|
1578
|
+
args = {"--payloadFile": payload_file}
|
|
1579
|
+
result = entity_client.entityAddClassification(args)
|
|
1580
|
+
if result and (not isinstance(result, dict) or result.get("status") != "error"):
|
|
1581
|
+
success += len(batch)
|
|
1582
|
+
else:
|
|
1583
|
+
failed += len(batch)
|
|
1584
|
+
errors.append(f"Batch {i//batch_size+1}: {result}")
|
|
1585
|
+
except Exception as e:
|
|
1586
|
+
failed += len(batch)
|
|
1587
|
+
errors.append(f"Batch {i//batch_size+1}: {str(e)}")
|
|
1588
|
+
finally:
|
|
1589
|
+
import os
|
|
1590
|
+
os.remove(payload_file)
|
|
1591
|
+
console.print(f"[green][OK] Bulk classification completed. Success: {success}, Failed: {failed}[/green]")
|
|
1592
|
+
if errors:
|
|
1593
|
+
console.print("[red]Errors:[/red]")
|
|
1594
|
+
for err in errors:
|
|
1595
|
+
console.print(f"[red]- {err}[/red]")
|
|
1596
|
+
except Exception as e:
|
|
1597
|
+
console.print(f"[red][X] Error executing entity bulk-classify-csv: {str(e)}[/red]")
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
# === BULK ENTITY CSV OPERATIONS ===
|
|
1601
|
+
|
|
1602
|
+
@entity.command()
|
|
1603
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="CSV file with entity attributes (typeName, qualifiedName, ...)")
|
|
1604
|
+
@click.option("--batch-size", default=100, help="Batch size for API calls")
|
|
1605
|
+
@click.option("--dry-run", is_flag=True, help="Preview entities to be created without making changes")
|
|
1606
|
+
@click.option("--error-csv", type=click.Path(), help="CSV file to write failed rows (optional)")
|
|
1607
|
+
@click.pass_context
|
|
1608
|
+
def bulk_create_csv(ctx, csv_file, batch_size, dry_run, error_csv):
|
|
1609
|
+
"""Bulk create entities from a CSV file (typeName, qualifiedName, ... columns)"""
|
|
1610
|
+
import pandas as pd
|
|
1611
|
+
import tempfile
|
|
1612
|
+
import os
|
|
1613
|
+
from purviewcli.client._entity import Entity
|
|
1614
|
+
try:
|
|
1615
|
+
if ctx.obj.get("mock"):
|
|
1616
|
+
console.print("[yellow]🎠Mock: entity bulk-create-csv command[/yellow]")
|
|
1617
|
+
console.print(f"[dim]CSV File: {csv_file}[/dim]")
|
|
1618
|
+
console.print("[green][OK] Mock entity bulk-create-csv completed successfully[/green]")
|
|
1619
|
+
return
|
|
1620
|
+
|
|
1621
|
+
df = pd.read_csv(csv_file)
|
|
1622
|
+
if "typeName" not in df.columns or "qualifiedName" not in df.columns:
|
|
1623
|
+
console.print("[red][X] CSV must contain at least 'typeName' and 'qualifiedName' columns[/red]")
|
|
1624
|
+
return
|
|
1625
|
+
entity_client = Entity()
|
|
1626
|
+
total = len(df)
|
|
1627
|
+
success, failed = 0, 0
|
|
1628
|
+
errors = []
|
|
1629
|
+
failed_rows = []
|
|
1630
|
+
for i in range(0, total, batch_size):
|
|
1631
|
+
batch = df.iloc[i:i+batch_size]
|
|
1632
|
+
# Map each row to the correct Purview entity format
|
|
1633
|
+
from purviewcli.client._entity import map_flat_entity_to_purview_entity
|
|
1634
|
+
entities = [map_flat_entity_to_purview_entity(row) for _, row in batch.iterrows()]
|
|
1635
|
+
payload = {"entities": entities}
|
|
1636
|
+
if dry_run:
|
|
1637
|
+
console.print(f"[blue]DRY RUN: Would create batch {i//batch_size+1} with {len(batch)} entities[/blue]")
|
|
1638
|
+
continue
|
|
1639
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmpf:
|
|
1640
|
+
import json
|
|
1641
|
+
json.dump(payload, tmpf, indent=2)
|
|
1642
|
+
tmpf.flush()
|
|
1643
|
+
payload_file = tmpf.name
|
|
1644
|
+
try:
|
|
1645
|
+
args = {"--payloadFile": payload_file}
|
|
1646
|
+
result = entity_client.entityCreateBulk(args)
|
|
1647
|
+
if result and (not isinstance(result, dict) or result.get("status") != "error"):
|
|
1648
|
+
success += len(batch)
|
|
1649
|
+
else:
|
|
1650
|
+
failed += len(batch)
|
|
1651
|
+
errors.append(f"Batch {i//batch_size+1}: {result}")
|
|
1652
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1653
|
+
except Exception as e:
|
|
1654
|
+
failed += len(batch)
|
|
1655
|
+
errors.append(f"Batch {i//batch_size+1}: {str(e)}")
|
|
1656
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1657
|
+
finally:
|
|
1658
|
+
os.remove(payload_file)
|
|
1659
|
+
console.print(f"[green]SUCCESS: Bulk create completed. Success: {success}, Failed: {failed}[/green]")
|
|
1660
|
+
if errors:
|
|
1661
|
+
console.print("[red]Errors:[/red]")
|
|
1662
|
+
for err in errors:
|
|
1663
|
+
console.print(f"[red]- {err}[/red]")
|
|
1664
|
+
if error_csv and failed_rows:
|
|
1665
|
+
pd.DataFrame(failed_rows).to_csv(error_csv, index=False)
|
|
1666
|
+
console.print(f"[yellow]WARNING: Failed rows written to {error_csv}[/yellow]")
|
|
1667
|
+
except Exception as e:
|
|
1668
|
+
console.print(f"[red]ERROR: Error executing entity bulk-create-csv: {str(e)}[/red]")
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
@entity.command()
|
|
1672
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="CSV file with GUID and attributes to update")
|
|
1673
|
+
@click.option("--batch-size", default=100, help="Batch size for API calls")
|
|
1674
|
+
@click.option("--dry-run", is_flag=True, help="Preview entities to be updated without making changes")
|
|
1675
|
+
@click.option("--error-csv", type=click.Path(), help="CSV file to write failed rows (optional)")
|
|
1676
|
+
@click.pass_context
|
|
1677
|
+
def bulk_update_csv(ctx, csv_file, batch_size, dry_run, error_csv):
|
|
1678
|
+
"""Bulk update entities from a CSV file (guid, attributes...)"""
|
|
1679
|
+
import pandas as pd
|
|
1680
|
+
import tempfile
|
|
1681
|
+
import os
|
|
1682
|
+
import json
|
|
1683
|
+
from purviewcli.client._entity import Entity
|
|
1684
|
+
try:
|
|
1685
|
+
if ctx.obj.get("mock"):
|
|
1686
|
+
console.print("[yellow]🎠Mock: entity bulk-update-csv command[/yellow]")
|
|
1687
|
+
console.print(f"[dim]CSV File: {csv_file}[/dim]")
|
|
1688
|
+
console.print("[green][OK] Mock entity bulk-update-csv completed successfully[/green]")
|
|
1689
|
+
return
|
|
1690
|
+
|
|
1691
|
+
df = pd.read_csv(csv_file)
|
|
1692
|
+
if df.empty:
|
|
1693
|
+
console.print("[yellow]No rows found in CSV. Exiting.[/yellow]")
|
|
1694
|
+
return
|
|
1695
|
+
|
|
1696
|
+
entity_client = Entity()
|
|
1697
|
+
total = len(df)
|
|
1698
|
+
success, failed = 0, 0
|
|
1699
|
+
errors = []
|
|
1700
|
+
failed_rows = []
|
|
1701
|
+
|
|
1702
|
+
# Determine mode:
|
|
1703
|
+
# - If CSV has both 'typeName' and 'qualifiedName' -> map rows to Purview entities and call bulk create-or-update
|
|
1704
|
+
# - Else if CSV has 'guid' -> build guid-based payloads (preferred for partial attribute updates)
|
|
1705
|
+
has_type_qn = ("typeName" in df.columns and "qualifiedName" in df.columns)
|
|
1706
|
+
has_guid = "guid" in df.columns
|
|
1707
|
+
|
|
1708
|
+
for i in range(0, total, batch_size):
|
|
1709
|
+
batch = df.iloc[i : i + batch_size]
|
|
1710
|
+
|
|
1711
|
+
if has_type_qn:
|
|
1712
|
+
# Map flat rows to Purview entity objects using helper
|
|
1713
|
+
from purviewcli.client._entity import map_flat_entity_to_purview_entity
|
|
1714
|
+
|
|
1715
|
+
entities = [map_flat_entity_to_purview_entity(row) for _, row in batch.iterrows()]
|
|
1716
|
+
payload = {"entities": entities}
|
|
1717
|
+
|
|
1718
|
+
if dry_run:
|
|
1719
|
+
console.print(f"[blue]DRY RUN: Would bulk-create/update batch {i//batch_size+1} with {len(batch)} entities[/blue]")
|
|
1720
|
+
continue
|
|
1721
|
+
|
|
1722
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as tmpf:
|
|
1723
|
+
json.dump(payload, tmpf, indent=2)
|
|
1724
|
+
tmpf.flush()
|
|
1725
|
+
payload_file = tmpf.name
|
|
1726
|
+
|
|
1727
|
+
try:
|
|
1728
|
+
args = {"--payloadFile": payload_file}
|
|
1729
|
+
result = entity_client.entityCreateBulk(args)
|
|
1730
|
+
if result and (not isinstance(result, dict) or result.get("status") != "error"):
|
|
1731
|
+
success += len(batch)
|
|
1732
|
+
else:
|
|
1733
|
+
failed += len(batch)
|
|
1734
|
+
errors.append(f"Batch {i//batch_size+1}: {result}")
|
|
1735
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1736
|
+
except Exception as e:
|
|
1737
|
+
failed += len(batch)
|
|
1738
|
+
errors.append(f"Batch {i//batch_size+1}: {str(e)}")
|
|
1739
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1740
|
+
finally:
|
|
1741
|
+
try:
|
|
1742
|
+
os.remove(payload_file)
|
|
1743
|
+
except Exception:
|
|
1744
|
+
pass
|
|
1745
|
+
|
|
1746
|
+
elif has_guid:
|
|
1747
|
+
# Build guid-based updates. If the CSV contains only guid + attr columns, we'll attempt to perform
|
|
1748
|
+
# partial attribute updates by calling entityPartialUpdateAttribute where possible.
|
|
1749
|
+
# If a row contains multiple attributes, we will call entityCreateBulk with a payload containing
|
|
1750
|
+
# the guid and attributes (server supports bulk create-or-update by guid in some endpoints).
|
|
1751
|
+
|
|
1752
|
+
# Normalize rows into dicts
|
|
1753
|
+
rows = [row.to_dict() for _, row in batch.iterrows()]
|
|
1754
|
+
|
|
1755
|
+
# Attempt to detect single-attribute update pattern: columns [guid, attrName, attrValue]
|
|
1756
|
+
if set(["guid", "attrName", "attrValue"]).issubset(set(batch.columns)):
|
|
1757
|
+
# perform per-guid partial updates in batch
|
|
1758
|
+
for r in rows:
|
|
1759
|
+
guid = str(r.get("guid"))
|
|
1760
|
+
attr_name = r.get("attrName")
|
|
1761
|
+
attr_value = r.get("attrValue")
|
|
1762
|
+
if pd.isna(guid) or pd.isna(attr_name):
|
|
1763
|
+
failed += 1
|
|
1764
|
+
failed_rows.append(r)
|
|
1765
|
+
continue
|
|
1766
|
+
if dry_run:
|
|
1767
|
+
console.print(f"[blue]DRY RUN: Would update GUID {guid} set {attr_name}={attr_value}[/blue]")
|
|
1768
|
+
success += 1
|
|
1769
|
+
continue
|
|
1770
|
+
try:
|
|
1771
|
+
args = {"--guid": [guid], "--attrName": attr_name, "--attrValue": attr_value}
|
|
1772
|
+
result = entity_client.entityPartialUpdateAttribute(args)
|
|
1773
|
+
if result and (not isinstance(result, dict) or result.get("status") != "error"):
|
|
1774
|
+
success += 1
|
|
1775
|
+
else:
|
|
1776
|
+
failed += 1
|
|
1777
|
+
errors.append(f"GUID {guid}: {result}")
|
|
1778
|
+
failed_rows.append(r)
|
|
1779
|
+
except Exception as e:
|
|
1780
|
+
failed += 1
|
|
1781
|
+
errors.append(f"GUID {guid}: {str(e)}")
|
|
1782
|
+
failed_rows.append(r)
|
|
1783
|
+
|
|
1784
|
+
else:
|
|
1785
|
+
# Fallback: call bulk create-or-update with guid included in each entity object.
|
|
1786
|
+
# Map each row into an entity dict keeping non-null columns.
|
|
1787
|
+
entities = []
|
|
1788
|
+
for r in rows:
|
|
1789
|
+
if pd.isna(r.get("guid")):
|
|
1790
|
+
failed_rows.append(r)
|
|
1791
|
+
failed += 1
|
|
1792
|
+
continue
|
|
1793
|
+
ent = {k: v for k, v in r.items() if pd.notnull(v)}
|
|
1794
|
+
# ensure guid is string under top-level 'guid' field for server bulk endpoints
|
|
1795
|
+
ent["guid"] = str(ent.get("guid"))
|
|
1796
|
+
entities.append(ent)
|
|
1797
|
+
|
|
1798
|
+
if not entities:
|
|
1799
|
+
continue
|
|
1800
|
+
|
|
1801
|
+
payload = {"entities": entities}
|
|
1802
|
+
if dry_run:
|
|
1803
|
+
console.print(f"[blue]DRY RUN: Would bulk-update (by guid) batch {i//batch_size+1} with {len(entities)} entities[/blue]")
|
|
1804
|
+
success += len(entities)
|
|
1805
|
+
continue
|
|
1806
|
+
|
|
1807
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as tmpf:
|
|
1808
|
+
json.dump(payload, tmpf, indent=2)
|
|
1809
|
+
tmpf.flush()
|
|
1810
|
+
payload_file = tmpf.name
|
|
1811
|
+
|
|
1812
|
+
try:
|
|
1813
|
+
args = {"--payloadFile": payload_file}
|
|
1814
|
+
# Use the create-or-update bulk endpoint - server will use guid when present
|
|
1815
|
+
result = entity_client.entityCreateBulk(args)
|
|
1816
|
+
if result and (not isinstance(result, dict) or result.get("status") != "error"):
|
|
1817
|
+
success += len(entities)
|
|
1818
|
+
else:
|
|
1819
|
+
failed += len(entities)
|
|
1820
|
+
errors.append(f"Batch {i//batch_size+1}: {result}")
|
|
1821
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1822
|
+
except Exception as e:
|
|
1823
|
+
failed += len(entities)
|
|
1824
|
+
errors.append(f"Batch {i//batch_size+1}: {str(e)}")
|
|
1825
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1826
|
+
finally:
|
|
1827
|
+
try:
|
|
1828
|
+
os.remove(payload_file)
|
|
1829
|
+
except Exception:
|
|
1830
|
+
pass
|
|
1831
|
+
|
|
1832
|
+
else:
|
|
1833
|
+
console.print(f"[red][X] CSV must contain either (typeName and qualifiedName) or guid column[/red]")
|
|
1834
|
+
return
|
|
1835
|
+
|
|
1836
|
+
console.print(f"[green][OK] Bulk update completed. Success: {success}, Failed: {failed}[/green]")
|
|
1837
|
+
if errors:
|
|
1838
|
+
console.print("[red]Errors:[/red]")
|
|
1839
|
+
for err in errors:
|
|
1840
|
+
console.print(f"[red]- {err}[/red]")
|
|
1841
|
+
if error_csv and failed_rows:
|
|
1842
|
+
pd.DataFrame(failed_rows).to_csv(error_csv, index=False)
|
|
1843
|
+
console.print(f"[yellow]WARNING: Failed rows written to {error_csv}[/yellow]")
|
|
1844
|
+
except Exception as e:
|
|
1845
|
+
console.print(f"[red][X] Error executing entity bulk-update-csv: {str(e)}[/red]")
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
@entity.command()
|
|
1849
|
+
@click.option("--csv-file", required=True, type=click.Path(exists=True), help="CSV file with GUIDs to delete")
|
|
1850
|
+
@click.option("--batch-size", default=100, help="Batch size for API calls")
|
|
1851
|
+
@click.option("--dry-run", is_flag=True, help="Preview entities to be deleted without making changes")
|
|
1852
|
+
@click.option("--error-csv", type=click.Path(), help="CSV file to write failed rows (optional)")
|
|
1853
|
+
@click.pass_context
|
|
1854
|
+
def bulk_delete_csv(ctx, csv_file, batch_size, dry_run, error_csv):
|
|
1855
|
+
"""Bulk delete entities from a CSV file (guid column)"""
|
|
1856
|
+
import pandas as pd
|
|
1857
|
+
import os
|
|
1858
|
+
from purviewcli.client._entity import Entity
|
|
1859
|
+
try:
|
|
1860
|
+
if ctx.obj.get("mock"):
|
|
1861
|
+
console.print("[yellow]🎠Mock: entity bulk-delete-csv command[/yellow]")
|
|
1862
|
+
console.print(f"[dim]CSV File: {csv_file}[/dim]")
|
|
1863
|
+
console.print("[green][OK] Mock entity bulk-delete-csv completed successfully[/green]")
|
|
1864
|
+
return
|
|
1865
|
+
|
|
1866
|
+
df = pd.read_csv(csv_file)
|
|
1867
|
+
if "guid" not in df.columns:
|
|
1868
|
+
console.print("[red][X] CSV must contain 'guid' column[/red]")
|
|
1869
|
+
return
|
|
1870
|
+
entity_client = Entity()
|
|
1871
|
+
total = len(df)
|
|
1872
|
+
success, failed = 0, 0
|
|
1873
|
+
errors = []
|
|
1874
|
+
failed_rows = []
|
|
1875
|
+
for i in range(0, total, batch_size):
|
|
1876
|
+
batch = df.iloc[i:i+batch_size]
|
|
1877
|
+
guids = [str(row["guid"]) for _, row in batch.iterrows() if pd.notnull(row["guid"])]
|
|
1878
|
+
if dry_run:
|
|
1879
|
+
console.print(f"[blue]DRY RUN: Would delete batch {i//batch_size+1} with {len(guids)} entities[/blue]")
|
|
1880
|
+
continue
|
|
1881
|
+
try:
|
|
1882
|
+
args = {"--guid": guids}
|
|
1883
|
+
result = entity_client.entityDeleteBulk(args)
|
|
1884
|
+
if result and (not isinstance(result, dict) or result.get("status") != "error"):
|
|
1885
|
+
success += len(guids)
|
|
1886
|
+
else:
|
|
1887
|
+
failed += len(guids)
|
|
1888
|
+
errors.append(f"Batch {i//batch_size+1}: {result}")
|
|
1889
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1890
|
+
except Exception as e:
|
|
1891
|
+
failed += len(guids)
|
|
1892
|
+
errors.append(f"Batch {i//batch_size+1}: {str(e)}")
|
|
1893
|
+
failed_rows.extend(batch.to_dict(orient="records"))
|
|
1894
|
+
console.print(f"[green][OK] Bulk delete completed. Success: {success}, Failed: {failed}[/green]")
|
|
1895
|
+
if errors:
|
|
1896
|
+
console.print("[red]Errors:[/red]")
|
|
1897
|
+
for err in errors:
|
|
1898
|
+
console.print(f"[red]- {err}[/red]")
|
|
1899
|
+
if error_csv and failed_rows:
|
|
1900
|
+
pd.DataFrame(failed_rows).to_csv(error_csv, index=False)
|
|
1901
|
+
console.print(f"[yellow][X] Failed rows written to {error_csv}[/yellow]")
|
|
1902
|
+
except Exception as e:
|
|
1903
|
+
console.print(f"[red][X] Error executing entity bulk-delete-csv: {str(e)}[/red]")
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
# === AUDIT OPERATIONS ===
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
@entity.command()
|
|
1910
|
+
@click.option("--guid", required=True, help="The globally unique identifier of the entity")
|
|
1911
|
+
@click.pass_context
|
|
1912
|
+
def audit(ctx, guid):
|
|
1913
|
+
"""Get audit events for an entity by GUID"""
|
|
1914
|
+
try:
|
|
1915
|
+
if ctx.obj.get("mock"):
|
|
1916
|
+
console.print("[yellow]🎠Mock: entity audit command[/yellow]")
|
|
1917
|
+
console.print(f"[dim]GUID: {guid}[/dim]")
|
|
1918
|
+
console.print("[green][OK] Mock entity audit completed successfully[/green]")
|
|
1919
|
+
return
|
|
1920
|
+
args = {"--guid": guid}
|
|
1921
|
+
from purviewcli.client._entity import Entity
|
|
1922
|
+
entity_client = Entity()
|
|
1923
|
+
result = entity_client.entityReadAudit(args)
|
|
1924
|
+
if result:
|
|
1925
|
+
console.print("[green][OK] Entity audit events retrieved successfully[/green]")
|
|
1926
|
+
console.print(json.dumps(result, indent=2))
|
|
1927
|
+
else:
|
|
1928
|
+
console.print("[yellow][!] Entity audit completed with no result[/yellow]")
|
|
1929
|
+
except Exception as e:
|
|
1930
|
+
console.print(f"[red][X] Error executing entity audit: {str(e)}[/red]")
|
|
1931
|
+
|
|
1932
|
+
|
|
1933
|
+
@entity.command()
|
|
1934
|
+
@click.option('--type-name', required=False, help='Filter by entity typeName (e.g., DataSet, DataProduct)')
|
|
1935
|
+
@click.option('--limit', default=100, help='Maximum number of entities to return')
|
|
1936
|
+
def list(type_name, limit):
|
|
1937
|
+
"""List entities in Microsoft Purview."""
|
|
1938
|
+
try:
|
|
1939
|
+
from purviewcli.client._search import Search
|
|
1940
|
+
search_client = Search()
|
|
1941
|
+
|
|
1942
|
+
# Create search query payload with proper filter structure
|
|
1943
|
+
search_payload = {
|
|
1944
|
+
"keywords": "*",
|
|
1945
|
+
"limit": limit,
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
# Only add filter if type_name is specified
|
|
1949
|
+
if type_name:
|
|
1950
|
+
search_payload["filter"] = {
|
|
1951
|
+
"entityType": type_name # Send as string, not array
|
|
1952
|
+
}
|
|
1953
|
+
# If no type specified, don't include filter at all
|
|
1954
|
+
|
|
1955
|
+
# Convert to args format expected by searchQuery
|
|
1956
|
+
search_args = {
|
|
1957
|
+
"--payloadFile": None,
|
|
1958
|
+
"--payload": json.dumps(search_payload)
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
results = search_client.searchQuery(search_args)
|
|
1962
|
+
from rich.console import Console
|
|
1963
|
+
console = Console()
|
|
1964
|
+
console.print(json.dumps(results, indent=2))
|
|
1965
|
+
except Exception as e:
|
|
1966
|
+
from rich.console import Console
|
|
1967
|
+
console = Console()
|
|
1968
|
+
console.print(f"[red][X] Error executing entity list: {str(e)}[/red]")
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
@entity.command("bulk-delete-optimized")
|
|
1972
|
+
@click.argument("guids", nargs=-1, required=True)
|
|
1973
|
+
@click.option("--bulk-size", type=int, default=50,
|
|
1974
|
+
help="Assets per bulk delete request (Microsoft recommended: 50)")
|
|
1975
|
+
@click.option("--max-parallel", type=int, default=10,
|
|
1976
|
+
help="Maximum parallel deletion jobs")
|
|
1977
|
+
@click.option("--throttle-ms", type=int, default=200,
|
|
1978
|
+
help="Throttle delay between API calls (milliseconds)")
|
|
1979
|
+
@click.option("--batch-throttle-ms", type=int, default=800,
|
|
1980
|
+
help="Throttle delay between batches (milliseconds)")
|
|
1981
|
+
@click.option("--dry-run", is_flag=True,
|
|
1982
|
+
help="Show what would be deleted without actually deleting")
|
|
1983
|
+
@click.option("--continuous", is_flag=True,
|
|
1984
|
+
help="Continue until all assets in collection are deleted")
|
|
1985
|
+
@click.option("--collection-name",
|
|
1986
|
+
help="Collection name for continuous deletion mode")
|
|
1987
|
+
@click.pass_context
|
|
1988
|
+
def bulk_delete_optimized(ctx, guids, bulk_size, max_parallel, throttle_ms,
|
|
1989
|
+
batch_throttle_ms, dry_run, continuous, collection_name):
|
|
1990
|
+
"""
|
|
1991
|
+
Optimized bulk delete with mathematical precision (equivalent to Remove-PurviewAsset-Batch.ps1)
|
|
1992
|
+
|
|
1993
|
+
Features:
|
|
1994
|
+
- Mathematical optimization for perfect efficiency
|
|
1995
|
+
- Parallel processing with controlled throttling
|
|
1996
|
+
- Continuous deletion mode for large collections
|
|
1997
|
+
- Reliable counting and progress tracking
|
|
1998
|
+
- Microsoft's recommended 50 assets per bulk request
|
|
1999
|
+
"""
|
|
2000
|
+
try:
|
|
2001
|
+
from rich.console import Console
|
|
2002
|
+
import math
|
|
2003
|
+
|
|
2004
|
+
console = Console()
|
|
2005
|
+
|
|
2006
|
+
# Mathematical optimization display
|
|
2007
|
+
if len(guids) > 0:
|
|
2008
|
+
total_assets = len(guids)
|
|
2009
|
+
assets_per_job = math.ceil(total_assets / max_parallel)
|
|
2010
|
+
api_calls_per_job = math.ceil(assets_per_job / bulk_size)
|
|
2011
|
+
total_api_calls = api_calls_per_job * max_parallel
|
|
2012
|
+
|
|
2013
|
+
console.print(f"[blue][*] Mathematical Optimization Analysis:[/blue]")
|
|
2014
|
+
console.print(f" [INFO] Total Assets: {total_assets}")
|
|
2015
|
+
console.print(f" 🔄 Parallel Jobs: {max_parallel}")
|
|
2016
|
+
console.print(f" 📦 Assets per Job: {assets_per_job}")
|
|
2017
|
+
console.print(f" 🚀 Bulk Size: {bulk_size}")
|
|
2018
|
+
console.print(f" 📞 API Calls per Job: {api_calls_per_job}")
|
|
2019
|
+
console.print(f" 📈 Total API Calls: {total_api_calls}")
|
|
2020
|
+
|
|
2021
|
+
# Check for perfect division (like PowerShell mathematical optimization)
|
|
2022
|
+
if total_assets % (max_parallel * bulk_size) == 0:
|
|
2023
|
+
console.print(f"[green]✨ Perfect mathematical division achieved! Zero waste.[/green]")
|
|
2024
|
+
else:
|
|
2025
|
+
waste_assets = (total_api_calls * bulk_size) - total_assets
|
|
2026
|
+
console.print(f"[yellow][!] Mathematical waste: {waste_assets} empty slots in final requests[/yellow]")
|
|
2027
|
+
|
|
2028
|
+
if continuous and collection_name:
|
|
2029
|
+
deleted_count = _continuous_collection_deletion(
|
|
2030
|
+
ctx, collection_name, bulk_size, max_parallel,
|
|
2031
|
+
throttle_ms, batch_throttle_ms, dry_run
|
|
2032
|
+
)
|
|
2033
|
+
else:
|
|
2034
|
+
deleted_count = _execute_optimized_bulk_delete(
|
|
2035
|
+
ctx, list(guids), bulk_size, max_parallel,
|
|
2036
|
+
throttle_ms, batch_throttle_ms, dry_run
|
|
2037
|
+
)
|
|
2038
|
+
|
|
2039
|
+
console.print(f"[green][OK] {'Would delete' if dry_run else 'Successfully deleted'} {deleted_count} assets[/green]")
|
|
2040
|
+
|
|
2041
|
+
except Exception as e:
|
|
2042
|
+
from rich.console import Console
|
|
2043
|
+
console = Console()
|
|
2044
|
+
console.print(f"[red][X] Error in bulk-delete-optimized: {str(e)}[/red]")
|
|
2045
|
+
|
|
2046
|
+
|
|
2047
|
+
@entity.command("bulk-delete-from-collection")
|
|
2048
|
+
@click.argument("collection-name")
|
|
2049
|
+
@click.option("--bulk-size", type=int, default=50,
|
|
2050
|
+
help="Assets per bulk delete request (Microsoft recommended: 50)")
|
|
2051
|
+
@click.option("--max-parallel", type=int, default=10,
|
|
2052
|
+
help="Maximum parallel deletion jobs")
|
|
2053
|
+
@click.option("--batch-size", type=int, default=1000,
|
|
2054
|
+
help="Assets to process per batch cycle")
|
|
2055
|
+
@click.option("--throttle-ms", type=int, default=200,
|
|
2056
|
+
help="Throttle delay between API calls (milliseconds)")
|
|
2057
|
+
@click.option("--dry-run", is_flag=True,
|
|
2058
|
+
help="Show what would be deleted without actually deleting")
|
|
2059
|
+
@click.confirmation_option(prompt="Are you sure you want to delete all assets in this collection?")
|
|
2060
|
+
@click.pass_context
|
|
2061
|
+
def bulk_delete_from_collection(ctx, collection_name, bulk_size, max_parallel,
|
|
2062
|
+
batch_size, throttle_ms, dry_run):
|
|
2063
|
+
"""
|
|
2064
|
+
Delete all assets from a collection using continuous deletion strategy
|
|
2065
|
+
Features:
|
|
2066
|
+
- Continuous deletion until collection is empty
|
|
2067
|
+
- Mathematical optimization for each batch
|
|
2068
|
+
- Progress tracking and estimation
|
|
2069
|
+
- Handles 500K+ assets efficiently
|
|
2070
|
+
"""
|
|
2071
|
+
try:
|
|
2072
|
+
from rich.console import Console
|
|
2073
|
+
|
|
2074
|
+
console = Console()
|
|
2075
|
+
console.print(f"[blue]🎯 Starting continuous deletion for collection: {collection_name}[/blue]")
|
|
2076
|
+
|
|
2077
|
+
deleted_count = _continuous_collection_deletion(
|
|
2078
|
+
ctx, collection_name, bulk_size, max_parallel,
|
|
2079
|
+
throttle_ms, 800, dry_run, batch_size
|
|
2080
|
+
)
|
|
2081
|
+
|
|
2082
|
+
console.print(f"[green][OK] Collection cleanup complete: {'Would delete' if dry_run else 'Deleted'} {deleted_count} total assets[/green]")
|
|
2083
|
+
|
|
2084
|
+
except Exception as e:
|
|
2085
|
+
from rich.console import Console
|
|
2086
|
+
console = Console()
|
|
2087
|
+
console.print(f"[red][X] Error in bulk-delete-from-collection: {str(e)}[/red]")
|
|
2088
|
+
|
|
2089
|
+
|
|
2090
|
+
@entity.command("count-assets")
|
|
2091
|
+
@click.argument("collection-name")
|
|
2092
|
+
@click.option("--by-type", is_flag=True, help="Group count by asset type")
|
|
2093
|
+
@click.option("--include-relationships", is_flag=True, help="Include relationship counts")
|
|
2094
|
+
@click.pass_context
|
|
2095
|
+
def count_assets(ctx, collection_name, by_type, include_relationships):
|
|
2096
|
+
"""
|
|
2097
|
+
Count assets in a collection with detailed breakdown
|
|
2098
|
+
|
|
2099
|
+
"""
|
|
2100
|
+
try:
|
|
2101
|
+
from rich.console import Console
|
|
2102
|
+
from rich.table import Table
|
|
2103
|
+
|
|
2104
|
+
console = Console()
|
|
2105
|
+
console.print(f"[blue][INFO] Counting assets in collection: {collection_name}[/blue]")
|
|
2106
|
+
|
|
2107
|
+
# Get asset count using search API
|
|
2108
|
+
total_count = _get_collection_asset_count(collection_name)
|
|
2109
|
+
|
|
2110
|
+
console.print(f"[green][OK] Total assets: {total_count}[/green]")
|
|
2111
|
+
|
|
2112
|
+
if by_type:
|
|
2113
|
+
type_counts = _get_asset_type_breakdown(collection_name)
|
|
2114
|
+
_display_type_breakdown(type_counts)
|
|
2115
|
+
|
|
2116
|
+
if include_relationships:
|
|
2117
|
+
rel_count = _get_relationship_count(collection_name)
|
|
2118
|
+
console.print(f"[blue]🔗 Total relationships: {rel_count}[/blue]")
|
|
2119
|
+
|
|
2120
|
+
except Exception as e:
|
|
2121
|
+
from rich.console import Console
|
|
2122
|
+
console = Console()
|
|
2123
|
+
console.print(f"[red][X] Error in count-assets: {str(e)}[/red]")
|
|
2124
|
+
|
|
2125
|
+
|
|
2126
|
+
@entity.command("analyze-performance")
|
|
2127
|
+
@click.option("--bulk-size", type=int, default=50, help="Bulk size to analyze")
|
|
2128
|
+
@click.option("--max-parallel", type=int, default=10, help="Parallel jobs to analyze")
|
|
2129
|
+
@click.option("--asset-count", type=int, default=1000, help="Total assets for analysis")
|
|
2130
|
+
@click.pass_context
|
|
2131
|
+
def analyze_performance(ctx, bulk_size, max_parallel, asset_count):
|
|
2132
|
+
"""
|
|
2133
|
+
Analyze bulk deletion performance with mathematical optimization
|
|
2134
|
+
"""
|
|
2135
|
+
try:
|
|
2136
|
+
from rich.console import Console
|
|
2137
|
+
from rich.table import Table
|
|
2138
|
+
import math
|
|
2139
|
+
|
|
2140
|
+
console = Console()
|
|
2141
|
+
console.print("[blue]📈 Performance Analysis[/blue]")
|
|
2142
|
+
|
|
2143
|
+
# Mathematical calculations (from PowerShell scripts)
|
|
2144
|
+
assets_per_job = math.ceil(asset_count / max_parallel)
|
|
2145
|
+
api_calls_per_job = math.ceil(assets_per_job / bulk_size)
|
|
2146
|
+
total_api_calls = api_calls_per_job * max_parallel
|
|
2147
|
+
|
|
2148
|
+
# Time estimations (based on PowerShell measurements)
|
|
2149
|
+
avg_api_time_ms = 1500 # Average API call time
|
|
2150
|
+
throttle_time_ms = 200 # Throttle between calls
|
|
2151
|
+
total_time_per_call = avg_api_time_ms + throttle_time_ms
|
|
2152
|
+
|
|
2153
|
+
estimated_time_seconds = (total_api_calls * total_time_per_call) / 1000
|
|
2154
|
+
estimated_time_minutes = estimated_time_seconds / 60
|
|
2155
|
+
estimated_time_hours = estimated_time_minutes / 60
|
|
2156
|
+
|
|
2157
|
+
# Create performance table
|
|
2158
|
+
table = Table(title="Performance Analysis")
|
|
2159
|
+
table.add_column("Metric", style="cyan")
|
|
2160
|
+
table.add_column("Value", style="green")
|
|
2161
|
+
table.add_column("Details", style="yellow")
|
|
2162
|
+
|
|
2163
|
+
table.add_row("Total Assets", f"{asset_count:,}", "Assets to process")
|
|
2164
|
+
table.add_row("Parallel Jobs", f"{max_parallel}", "Concurrent deletion jobs")
|
|
2165
|
+
table.add_row("Bulk Size", f"{bulk_size}", "Assets per API call")
|
|
2166
|
+
table.add_row("Assets per Job", f"{assets_per_job}", f"{asset_count} ÷ {max_parallel}")
|
|
2167
|
+
table.add_row("API Calls per Job", f"{api_calls_per_job}", f"{assets_per_job} ÷ {bulk_size}")
|
|
2168
|
+
table.add_row("Total API Calls", f"{total_api_calls}", f"{api_calls_per_job} × {max_parallel}")
|
|
2169
|
+
table.add_row("Estimated Time", f"{estimated_time_hours:.1f} hours", f"{estimated_time_minutes:.1f} minutes")
|
|
2170
|
+
|
|
2171
|
+
# Efficiency calculation
|
|
2172
|
+
theoretical_minimum_calls = math.ceil(asset_count / bulk_size)
|
|
2173
|
+
efficiency = (theoretical_minimum_calls / total_api_calls) * 100
|
|
2174
|
+
table.add_row("Efficiency", f"{efficiency:.1f}%", f"{theoretical_minimum_calls} minimum calls")
|
|
2175
|
+
|
|
2176
|
+
console.print(table)
|
|
2177
|
+
|
|
2178
|
+
# Recommendations (from PowerShell optimization experience)
|
|
2179
|
+
console.print("\n[blue]💡 Optimization Recommendations:[/blue]")
|
|
2180
|
+
|
|
2181
|
+
if asset_count % (max_parallel * bulk_size) == 0:
|
|
2182
|
+
console.print("[green][OK] Perfect mathematical division - optimal configuration![/green]")
|
|
2183
|
+
else:
|
|
2184
|
+
# Calculate optimal configurations
|
|
2185
|
+
optimal_configs = _calculate_optimal_configs(asset_count, bulk_size)
|
|
2186
|
+
console.print("[yellow]💡 Consider these optimal configurations:[/yellow]")
|
|
2187
|
+
for config in optimal_configs[:3]:
|
|
2188
|
+
console.print(f" • {config['parallel']} parallel jobs: {config['efficiency']:.1f}% efficiency")
|
|
2189
|
+
|
|
2190
|
+
except Exception as e:
|
|
2191
|
+
from rich.console import Console
|
|
2192
|
+
console = Console()
|
|
2193
|
+
console.print(f"[red][X] Error in analyze-performance: {str(e)}[/red]")
|
|
2194
|
+
|
|
2195
|
+
|
|
2196
|
+
# === ENHANCED BULK OPERATION FUNCTIONS ===
|
|
2197
|
+
|
|
2198
|
+
def _execute_optimized_bulk_delete(ctx, guids, bulk_size, max_parallel, throttle_ms, batch_throttle_ms, dry_run):
|
|
2199
|
+
"""
|
|
2200
|
+
Execute optimized bulk delete with parallel processing
|
|
2201
|
+
(Core logic from PowerShell Remove-PurviewAsset-Batch.ps1)
|
|
2202
|
+
"""
|
|
2203
|
+
from rich.console import Console
|
|
2204
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
|
|
2205
|
+
import concurrent.futures
|
|
2206
|
+
import math
|
|
2207
|
+
import time
|
|
2208
|
+
|
|
2209
|
+
console = Console()
|
|
2210
|
+
|
|
2211
|
+
if not guids:
|
|
2212
|
+
return 0
|
|
2213
|
+
|
|
2214
|
+
total_assets = len(guids)
|
|
2215
|
+
deleted_count = 0
|
|
2216
|
+
|
|
2217
|
+
if dry_run:
|
|
2218
|
+
console.print(f"[yellow][*] DRY RUN: Would delete {total_assets} assets[/yellow]")
|
|
2219
|
+
return total_assets
|
|
2220
|
+
|
|
2221
|
+
from purviewcli.client._entity import Entity
|
|
2222
|
+
entity_client = Entity()
|
|
2223
|
+
|
|
2224
|
+
# Split GUIDs into job batches
|
|
2225
|
+
assets_per_job = math.ceil(total_assets / max_parallel)
|
|
2226
|
+
job_batches = []
|
|
2227
|
+
|
|
2228
|
+
for i in range(max_parallel):
|
|
2229
|
+
start_idx = i * assets_per_job
|
|
2230
|
+
end_idx = min(start_idx + assets_per_job, total_assets)
|
|
2231
|
+
if start_idx < total_assets:
|
|
2232
|
+
job_batches.append(guids[start_idx:end_idx])
|
|
2233
|
+
|
|
2234
|
+
console.print(f"[blue]🚀 Starting {len(job_batches)} parallel deletion jobs...[/blue]")
|
|
2235
|
+
|
|
2236
|
+
with Progress(
|
|
2237
|
+
SpinnerColumn(),
|
|
2238
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2239
|
+
BarColumn(),
|
|
2240
|
+
TaskProgressColumn(),
|
|
2241
|
+
console=console
|
|
2242
|
+
) as progress:
|
|
2243
|
+
|
|
2244
|
+
task = progress.add_task("[red]Deleting assets...", total=total_assets)
|
|
2245
|
+
|
|
2246
|
+
# Execute parallel deletions
|
|
2247
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_parallel) as executor:
|
|
2248
|
+
future_to_batch = {
|
|
2249
|
+
executor.submit(_delete_batch_job, entity_client, batch, bulk_size, throttle_ms, i): batch
|
|
2250
|
+
for i, batch in enumerate(job_batches)
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
for future in concurrent.futures.as_completed(future_to_batch):
|
|
2254
|
+
batch = future_to_batch[future]
|
|
2255
|
+
try:
|
|
2256
|
+
batch_deleted = future.result()
|
|
2257
|
+
deleted_count += batch_deleted
|
|
2258
|
+
progress.update(task, advance=batch_deleted)
|
|
2259
|
+
|
|
2260
|
+
# Batch throttle
|
|
2261
|
+
if batch_throttle_ms > 0:
|
|
2262
|
+
time.sleep(batch_throttle_ms / 1000)
|
|
2263
|
+
|
|
2264
|
+
except Exception as e:
|
|
2265
|
+
console.print(f"[red][X] Batch deletion failed: {str(e)}[/red]")
|
|
2266
|
+
|
|
2267
|
+
return deleted_count
|
|
2268
|
+
|
|
2269
|
+
|
|
2270
|
+
def _delete_batch_job(entity_client, guid_batch, bulk_size, throttle_ms, job_id):
|
|
2271
|
+
"""
|
|
2272
|
+
Execute a single batch job (parallel worker function)
|
|
2273
|
+
"""
|
|
2274
|
+
import time
|
|
2275
|
+
|
|
2276
|
+
deleted_in_job = 0
|
|
2277
|
+
|
|
2278
|
+
# Split batch into bulk delete chunks
|
|
2279
|
+
for i in range(0, len(guid_batch), bulk_size):
|
|
2280
|
+
bulk_guids = guid_batch[i:i + bulk_size]
|
|
2281
|
+
|
|
2282
|
+
try:
|
|
2283
|
+
# Execute bulk delete API call
|
|
2284
|
+
args = {"--guid": bulk_guids}
|
|
2285
|
+
result = entity_client.entityDeleteBulk(args)
|
|
2286
|
+
|
|
2287
|
+
if result:
|
|
2288
|
+
deleted_in_job += len(bulk_guids)
|
|
2289
|
+
|
|
2290
|
+
# Throttle between API calls
|
|
2291
|
+
if throttle_ms > 0 and i + bulk_size < len(guid_batch):
|
|
2292
|
+
time.sleep(throttle_ms / 1000)
|
|
2293
|
+
|
|
2294
|
+
except Exception as e:
|
|
2295
|
+
from rich.console import Console
|
|
2296
|
+
console = Console()
|
|
2297
|
+
console.print(f"[red][X] Job {job_id} bulk delete failed: {str(e)}[/red]")
|
|
2298
|
+
|
|
2299
|
+
return deleted_in_job
|
|
2300
|
+
|
|
2301
|
+
|
|
2302
|
+
def _continuous_collection_deletion(ctx, collection_name, bulk_size, max_parallel, throttle_ms, batch_throttle_ms, dry_run, batch_size=1000):
|
|
2303
|
+
"""
|
|
2304
|
+
Continuous deletion strategy for large collections
|
|
2305
|
+
"""
|
|
2306
|
+
from rich.console import Console
|
|
2307
|
+
|
|
2308
|
+
console = Console()
|
|
2309
|
+
total_deleted = 0
|
|
2310
|
+
iteration = 1
|
|
2311
|
+
|
|
2312
|
+
console.print(f"[blue]🔄 Starting continuous deletion for collection: {collection_name}[/blue]")
|
|
2313
|
+
|
|
2314
|
+
while True:
|
|
2315
|
+
console.print(f"\n[blue]📅 Iteration {iteration}: Finding assets to delete...[/blue]")
|
|
2316
|
+
|
|
2317
|
+
# Get next batch of assets from collection
|
|
2318
|
+
asset_guids = _get_collection_assets_batch(collection_name, batch_size)
|
|
2319
|
+
|
|
2320
|
+
if not asset_guids:
|
|
2321
|
+
console.print("[green][OK] No more assets found - collection is clean![/green]")
|
|
2322
|
+
break
|
|
2323
|
+
|
|
2324
|
+
found_count = len(asset_guids)
|
|
2325
|
+
console.print(f"[blue][INFO] Found {found_count} assets in iteration {iteration}[/blue]")
|
|
2326
|
+
|
|
2327
|
+
if dry_run:
|
|
2328
|
+
console.print(f"[yellow][*] DRY RUN: Would delete {found_count} assets[/yellow]")
|
|
2329
|
+
total_deleted += found_count
|
|
2330
|
+
else:
|
|
2331
|
+
# Execute optimized deletion for this batch
|
|
2332
|
+
deleted_in_iteration = _execute_optimized_bulk_delete(
|
|
2333
|
+
ctx, asset_guids, bulk_size, max_parallel,
|
|
2334
|
+
throttle_ms, batch_throttle_ms, False
|
|
2335
|
+
)
|
|
2336
|
+
|
|
2337
|
+
total_deleted += deleted_in_iteration
|
|
2338
|
+
console.print(f"[green][OK] Iteration {iteration}: Deleted {deleted_in_iteration}/{found_count} assets[/green]")
|
|
2339
|
+
console.print(f"[blue]📈 Running total: {total_deleted} assets deleted[/blue]")
|
|
2340
|
+
|
|
2341
|
+
iteration += 1
|
|
2342
|
+
|
|
2343
|
+
# Break after reasonable number of iterations in dry-run
|
|
2344
|
+
if dry_run and iteration > 5:
|
|
2345
|
+
console.print("[yellow][*] DRY RUN: Simulated 5 iterations[/yellow]")
|
|
2346
|
+
break
|
|
2347
|
+
|
|
2348
|
+
return total_deleted
|
|
2349
|
+
|
|
2350
|
+
|
|
2351
|
+
def _get_collection_assets_batch(collection_name, batch_size):
|
|
2352
|
+
"""
|
|
2353
|
+
Get a batch of asset GUIDs from a collection
|
|
2354
|
+
(Would integrate with search API)
|
|
2355
|
+
"""
|
|
2356
|
+
# Placeholder - would use search API to get actual asset GUIDs
|
|
2357
|
+
# For testing, return mock data that decreases over iterations
|
|
2358
|
+
import random
|
|
2359
|
+
mock_count = random.randint(0, min(batch_size, 100))
|
|
2360
|
+
return [f"mock-guid-{i}" for i in range(mock_count)]
|
|
2361
|
+
|
|
2362
|
+
|
|
2363
|
+
def _get_collection_asset_count(collection_name):
|
|
2364
|
+
"""Get total asset count for a collection"""
|
|
2365
|
+
# Placeholder - would use search API
|
|
2366
|
+
return 1500 # Mock count
|
|
2367
|
+
|
|
2368
|
+
|
|
2369
|
+
def _get_asset_type_breakdown(collection_name):
|
|
2370
|
+
"""Get asset count breakdown by type"""
|
|
2371
|
+
# Placeholder - would use search API with type filters
|
|
2372
|
+
return {
|
|
2373
|
+
"DataSet": 450,
|
|
2374
|
+
"Table": 320,
|
|
2375
|
+
"Column": 580,
|
|
2376
|
+
"Process": 150
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
|
|
2380
|
+
def _get_relationship_count(collection_name):
|
|
2381
|
+
"""Get relationship count for collection"""
|
|
2382
|
+
# Placeholder - would use relationship API
|
|
2383
|
+
return 2340
|
|
2384
|
+
|
|
2385
|
+
|
|
2386
|
+
def _display_type_breakdown(type_counts):
|
|
2387
|
+
"""Display asset type breakdown in a table"""
|
|
2388
|
+
from rich.table import Table
|
|
2389
|
+
from rich.console import Console
|
|
2390
|
+
|
|
2391
|
+
console = Console()
|
|
2392
|
+
table = Table(title="Asset Type Breakdown")
|
|
2393
|
+
table.add_column("Asset Type", style="cyan")
|
|
2394
|
+
table.add_column("Count", style="green")
|
|
2395
|
+
table.add_column("Percentage", style="yellow")
|
|
2396
|
+
|
|
2397
|
+
total = sum(type_counts.values())
|
|
2398
|
+
|
|
2399
|
+
for asset_type, count in sorted(type_counts.items(), key=lambda x: x[1], reverse=True):
|
|
2400
|
+
percentage = (count / total) * 100 if total > 0 else 0
|
|
2401
|
+
table.add_row(asset_type, f"{count:,}", f"{percentage:.1f}%")
|
|
2402
|
+
|
|
2403
|
+
table.add_row("[bold]Total[/bold]", f"[bold]{total:,}[/bold]", "[bold]100.0%[/bold]")
|
|
2404
|
+
console.print(table)
|
|
2405
|
+
|
|
2406
|
+
|
|
2407
|
+
def _calculate_optimal_configs(asset_count, bulk_size):
|
|
2408
|
+
"""
|
|
2409
|
+
Calculate optimal parallel job configurations
|
|
2410
|
+
(Mathematical optimization from PowerShell)
|
|
2411
|
+
"""
|
|
2412
|
+
import math
|
|
2413
|
+
|
|
2414
|
+
configs = []
|
|
2415
|
+
|
|
2416
|
+
for parallel_jobs in range(1, 21): # Test 1-20 parallel jobs
|
|
2417
|
+
assets_per_job = math.ceil(asset_count / parallel_jobs)
|
|
2418
|
+
api_calls_per_job = math.ceil(assets_per_job / bulk_size)
|
|
2419
|
+
total_api_calls = api_calls_per_job * parallel_jobs
|
|
2420
|
+
|
|
2421
|
+
theoretical_minimum = math.ceil(asset_count / bulk_size)
|
|
2422
|
+
efficiency = (theoretical_minimum / total_api_calls) * 100
|
|
2423
|
+
|
|
2424
|
+
configs.append({
|
|
2425
|
+
'parallel': parallel_jobs,
|
|
2426
|
+
'efficiency': efficiency,
|
|
2427
|
+
'total_calls': total_api_calls,
|
|
2428
|
+
'waste': total_api_calls - theoretical_minimum
|
|
2429
|
+
})
|
|
2430
|
+
|
|
2431
|
+
# Sort by efficiency (descending)
|
|
2432
|
+
return sorted(configs, key=lambda x: x['efficiency'], reverse=True)
|
|
2433
|
+
|
|
2434
|
+
|
|
2435
|
+
# Make the entity group available for import
|
|
2436
|
+
__all__ = ["entity"]
|