pvw-cli 1.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pvw-cli might be problematic. Click here for more details.
- purviewcli/__init__.py +27 -0
- purviewcli/__main__.py +15 -0
- purviewcli/cli/__init__.py +5 -0
- purviewcli/cli/account.py +199 -0
- purviewcli/cli/cli.py +170 -0
- purviewcli/cli/collections.py +502 -0
- purviewcli/cli/domain.py +361 -0
- purviewcli/cli/entity.py +2436 -0
- purviewcli/cli/glossary.py +533 -0
- purviewcli/cli/health.py +250 -0
- purviewcli/cli/insight.py +113 -0
- purviewcli/cli/lineage.py +1103 -0
- purviewcli/cli/management.py +141 -0
- purviewcli/cli/policystore.py +103 -0
- purviewcli/cli/relationship.py +75 -0
- purviewcli/cli/scan.py +357 -0
- purviewcli/cli/search.py +527 -0
- purviewcli/cli/share.py +478 -0
- purviewcli/cli/types.py +831 -0
- purviewcli/cli/unified_catalog.py +3540 -0
- purviewcli/cli/workflow.py +402 -0
- purviewcli/client/__init__.py +21 -0
- purviewcli/client/_account.py +1877 -0
- purviewcli/client/_collections.py +1761 -0
- purviewcli/client/_domain.py +414 -0
- purviewcli/client/_entity.py +3545 -0
- purviewcli/client/_glossary.py +3233 -0
- purviewcli/client/_health.py +501 -0
- purviewcli/client/_insight.py +2873 -0
- purviewcli/client/_lineage.py +2138 -0
- purviewcli/client/_management.py +2202 -0
- purviewcli/client/_policystore.py +2915 -0
- purviewcli/client/_relationship.py +1351 -0
- purviewcli/client/_scan.py +2607 -0
- purviewcli/client/_search.py +1472 -0
- purviewcli/client/_share.py +272 -0
- purviewcli/client/_types.py +2708 -0
- purviewcli/client/_unified_catalog.py +5112 -0
- purviewcli/client/_workflow.py +2734 -0
- purviewcli/client/api_client.py +1295 -0
- purviewcli/client/business_rules.py +675 -0
- purviewcli/client/config.py +231 -0
- purviewcli/client/data_quality.py +433 -0
- purviewcli/client/endpoint.py +123 -0
- purviewcli/client/endpoints.py +554 -0
- purviewcli/client/exceptions.py +38 -0
- purviewcli/client/lineage_visualization.py +797 -0
- purviewcli/client/monitoring_dashboard.py +712 -0
- purviewcli/client/rate_limiter.py +30 -0
- purviewcli/client/retry_handler.py +125 -0
- purviewcli/client/scanning_operations.py +523 -0
- purviewcli/client/settings.py +1 -0
- purviewcli/client/sync_client.py +250 -0
- purviewcli/plugins/__init__.py +1 -0
- purviewcli/plugins/plugin_system.py +709 -0
- pvw_cli-1.2.8.dist-info/METADATA +1618 -0
- pvw_cli-1.2.8.dist-info/RECORD +60 -0
- pvw_cli-1.2.8.dist-info/WHEEL +5 -0
- pvw_cli-1.2.8.dist-info/entry_points.txt +3 -0
- pvw_cli-1.2.8.dist-info/top_level.txt +1 -0
purviewcli/cli/types.py
ADDED
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
"""
|
|
2
|
+
usage:
|
|
3
|
+
pvw types createTypeDefs --payloadFile=<val>
|
|
4
|
+
pvw types deleteTypeDef --name=<val>
|
|
5
|
+
pvw types deleteTypeDefs --payloadFile=<val>
|
|
6
|
+
pvw types putTypeDefs --payloadFile=<val>
|
|
7
|
+
pvw types readClassificationDef (--guid=<val> | --name=<val>)
|
|
8
|
+
pvw types readEntityDef (--guid=<val> | --name=<val>)
|
|
9
|
+
pvw types readEnumDef (--guid=<val> | --name=<val>)
|
|
10
|
+
pvw types readRelationshipDef (--guid=<val> | --name=<val>)
|
|
11
|
+
pvw types readStatistics
|
|
12
|
+
pvw types readStructDef (--guid=<val> | --name=<val>)
|
|
13
|
+
pvw types readBusinessMetadataDef (--guid=<val> | --name=<val>)
|
|
14
|
+
pvw types readTermTemplateDef (--guid=<val> | --name=<val>)
|
|
15
|
+
pvw types readTypeDef (--guid=<val> | --name=<val>)
|
|
16
|
+
pvw types readTypeDefs [--includeTermTemplate --type=<val>]
|
|
17
|
+
pvw types readTypeDefsHeaders [--includeTermTemplate --type=<val>]
|
|
18
|
+
|
|
19
|
+
options:
|
|
20
|
+
--purviewName=<val> [string] Microsoft Purview account name.
|
|
21
|
+
--guid=<val> [string] The globally unique identifier.
|
|
22
|
+
--includeTermTemplate [boolean] Whether to include termtemplatedef [default: false].
|
|
23
|
+
--name=<val> [string] The name of the definition.
|
|
24
|
+
--payloadFile=<val> [string] File path to a valid JSON document.
|
|
25
|
+
--type=<val> [string] Typedef name as search filter (classification | entity | enum | relationship | struct).
|
|
26
|
+
|
|
27
|
+
Advanced Workflows & API Mapping:
|
|
28
|
+
---------------------------------
|
|
29
|
+
- Bulk Operations: Use `create_typedefs`, `put_typedefs`, and `delete_typedefs` to manage multiple type definitions at once via JSON files. These map to Atlas v2 Data Map API bulk endpoints (typesCreateTypeDefs, typesPutTypeDefs, typesDeleteTypeDefs).
|
|
30
|
+
- Per-Type Reads: Use `read_classification_def`, `read_entity_def`, `read_enum_def`, `read_relationship_def`, `read_struct_def`, `read_business_metadata_def`, `read_term_template_def` for fine-grained inspection of type definitions. These map to Atlas v2 endpoints for each type.
|
|
31
|
+
- Filtering: Use `read_typedefs` and `read_typedefs_headers` with `--type` and `--include-term-template` to filter results, mapping to Atlas v2's flexible type listing APIs.
|
|
32
|
+
- Statistics: Use `read_statistics` to get a summary of type system state (maps to typesReadStatistics).
|
|
33
|
+
- Error Handling: For bulk operations, errors are reported in the CLI output. For advanced error reporting (e.g., failed items to file), see future roadmap.
|
|
34
|
+
- API Coverage: This CLI covers all read operations and bulk create/update/delete. For per-type create/update/delete, use JSON payloads with the bulk endpoints. For advanced features (versioning, validation, dry-run), monitor API updates and CLI roadmap.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
---------
|
|
38
|
+
# Bulk create/update type definitions from a JSON file
|
|
39
|
+
pvw types createTypeDefs --payloadFile=types.json
|
|
40
|
+
|
|
41
|
+
# Delete a single type definition by name
|
|
42
|
+
pvw types deleteTypeDef --name=MyEntityType
|
|
43
|
+
|
|
44
|
+
# Read all entity type definitions
|
|
45
|
+
pvw types readTypeDefs --type=entity
|
|
46
|
+
|
|
47
|
+
# Read a classification definition by GUID
|
|
48
|
+
pvw types readClassificationDef --guid=1234-5678
|
|
49
|
+
|
|
50
|
+
# Read type system statistics
|
|
51
|
+
pvw types readStatistics
|
|
52
|
+
|
|
53
|
+
For more advanced examples and templates, see the documentation in `doc/commands/types/` and sample JSON in `samples/json/`.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
import json
|
|
57
|
+
import click
|
|
58
|
+
from purviewcli.client._types import Types
|
|
59
|
+
|
|
60
|
+
@click.group()
|
|
61
|
+
def types():
|
|
62
|
+
"""Manage types (schemas, entity types, relationship types, etc.)"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@types.command()
|
|
66
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
67
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
68
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
69
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
70
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
71
|
+
def create_typedefs(payload_file, dry_run, validate, output_file, error_file):
|
|
72
|
+
"""Create type definitions from a JSON file"""
|
|
73
|
+
try:
|
|
74
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
75
|
+
payload = json.load(f)
|
|
76
|
+
if validate:
|
|
77
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
78
|
+
if dry_run:
|
|
79
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
80
|
+
click.echo(json.dumps(payload, indent=2))
|
|
81
|
+
return
|
|
82
|
+
args = {'--payloadFile': payload_file}
|
|
83
|
+
client = Types()
|
|
84
|
+
result = client.typesCreateTypeDefs(args)
|
|
85
|
+
if output_file:
|
|
86
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
87
|
+
outf.write(json.dumps(result, indent=2))
|
|
88
|
+
else:
|
|
89
|
+
click.echo(json.dumps(result, indent=2))
|
|
90
|
+
except Exception as e:
|
|
91
|
+
if error_file:
|
|
92
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
93
|
+
errf.write(str(e))
|
|
94
|
+
else:
|
|
95
|
+
click.echo(f"Error: {e}")
|
|
96
|
+
|
|
97
|
+
@types.command()
|
|
98
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
99
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
100
|
+
@click.option('--name', required=True, help='Name of the type definition to delete')
|
|
101
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
102
|
+
def delete_typedef(name, dry_run, output_file, error_file):
|
|
103
|
+
"""Delete a type definition by name"""
|
|
104
|
+
try:
|
|
105
|
+
if dry_run:
|
|
106
|
+
click.echo(f'[DRY-RUN] Would delete type definition with name: {name}')
|
|
107
|
+
return
|
|
108
|
+
args = {'--name': name}
|
|
109
|
+
client = Types()
|
|
110
|
+
result = client.typesDeleteTypeDef(args)
|
|
111
|
+
if output_file:
|
|
112
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
113
|
+
outf.write(json.dumps(result, indent=2))
|
|
114
|
+
else:
|
|
115
|
+
click.echo(json.dumps(result, indent=2))
|
|
116
|
+
except Exception as e:
|
|
117
|
+
if error_file:
|
|
118
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
119
|
+
errf.write(str(e))
|
|
120
|
+
else:
|
|
121
|
+
click.echo(f"Error: {e}")
|
|
122
|
+
|
|
123
|
+
@types.command()
|
|
124
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
125
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
126
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
127
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
128
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
129
|
+
def put_typedefs(payload_file, dry_run, validate, output_file, error_file):
|
|
130
|
+
"""Update or create type definitions from a JSON file"""
|
|
131
|
+
try:
|
|
132
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
133
|
+
payload = json.load(f)
|
|
134
|
+
if validate:
|
|
135
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
136
|
+
if dry_run:
|
|
137
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
138
|
+
click.echo(json.dumps(payload, indent=2))
|
|
139
|
+
return
|
|
140
|
+
args = {'--payloadFile': payload_file}
|
|
141
|
+
client = Types()
|
|
142
|
+
result = client.typesPutTypeDefs(args)
|
|
143
|
+
if output_file:
|
|
144
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
145
|
+
outf.write(json.dumps(result, indent=2))
|
|
146
|
+
else:
|
|
147
|
+
click.echo(json.dumps(result, indent=2))
|
|
148
|
+
except Exception as e:
|
|
149
|
+
if error_file:
|
|
150
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
151
|
+
errf.write(str(e))
|
|
152
|
+
else:
|
|
153
|
+
click.echo(f"Error: {e}")
|
|
154
|
+
|
|
155
|
+
@types.command()
|
|
156
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
157
|
+
@click.option('--name', required=False, help='The name of the classification definition')
|
|
158
|
+
def read_classification_def(guid, name):
|
|
159
|
+
"""Read a classification definition by GUID or name"""
|
|
160
|
+
try:
|
|
161
|
+
args = {'--guid': guid, '--name': name}
|
|
162
|
+
client = Types()
|
|
163
|
+
result = client.typesReadClassificationDef(args)
|
|
164
|
+
click.echo(json.dumps(result, indent=2))
|
|
165
|
+
except Exception as e:
|
|
166
|
+
click.echo(f"Error: {e}")
|
|
167
|
+
|
|
168
|
+
@types.command()
|
|
169
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
170
|
+
@click.option('--name', required=False, help='The name of the entity definition')
|
|
171
|
+
def read_entity_def(guid, name):
|
|
172
|
+
"""Read an entity definition by GUID or name"""
|
|
173
|
+
try:
|
|
174
|
+
args = {'--guid': guid, '--name': name}
|
|
175
|
+
client = Types()
|
|
176
|
+
result = client.typesReadEntityDef(args)
|
|
177
|
+
click.echo(json.dumps(result, indent=2))
|
|
178
|
+
except Exception as e:
|
|
179
|
+
click.echo(f"Error: {e}")
|
|
180
|
+
|
|
181
|
+
@types.command()
|
|
182
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
183
|
+
@click.option('--name', required=False, help='The name of the enum definition')
|
|
184
|
+
def read_enum_def(guid, name):
|
|
185
|
+
"""Read an enum definition by GUID or name"""
|
|
186
|
+
try:
|
|
187
|
+
args = {'--guid': guid, '--name': name}
|
|
188
|
+
client = Types()
|
|
189
|
+
result = client.typesReadEnumDef(args)
|
|
190
|
+
click.echo(json.dumps(result, indent=2))
|
|
191
|
+
except Exception as e:
|
|
192
|
+
click.echo(f"Error: {e}")
|
|
193
|
+
|
|
194
|
+
@types.command()
|
|
195
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
196
|
+
@click.option('--name', required=False, help='The name of the relationship definition')
|
|
197
|
+
def read_relationship_def(guid, name):
|
|
198
|
+
"""Read a relationship definition by GUID or name"""
|
|
199
|
+
try:
|
|
200
|
+
args = {'--guid': guid, '--name': name}
|
|
201
|
+
client = Types()
|
|
202
|
+
result = client.typesReadRelationshipDef(args)
|
|
203
|
+
click.echo(json.dumps(result, indent=2))
|
|
204
|
+
except Exception as e:
|
|
205
|
+
click.echo(f"Error: {e}")
|
|
206
|
+
|
|
207
|
+
@types.command()
|
|
208
|
+
def read_statistics():
|
|
209
|
+
"""Read type statistics"""
|
|
210
|
+
try:
|
|
211
|
+
args = {}
|
|
212
|
+
client = Types()
|
|
213
|
+
result = client.typesReadStatistics(args)
|
|
214
|
+
click.echo(json.dumps(result, indent=2))
|
|
215
|
+
except Exception as e:
|
|
216
|
+
click.echo(f"Error: {e}")
|
|
217
|
+
|
|
218
|
+
@types.command()
|
|
219
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
220
|
+
@click.option('--name', required=False, help='The name of the struct definition')
|
|
221
|
+
def read_struct_def(guid, name):
|
|
222
|
+
"""Read a struct definition by GUID or name"""
|
|
223
|
+
try:
|
|
224
|
+
args = {'--guid': guid, '--name': name}
|
|
225
|
+
client = Types()
|
|
226
|
+
result = client.typesReadStructDef(args)
|
|
227
|
+
click.echo(json.dumps(result, indent=2))
|
|
228
|
+
except Exception as e:
|
|
229
|
+
click.echo(f"Error: {e}")
|
|
230
|
+
|
|
231
|
+
@types.command()
|
|
232
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
233
|
+
@click.option('--name', required=False, help='The name of the business metadata definition')
|
|
234
|
+
def read_business_metadata_def(guid, name):
|
|
235
|
+
"""Read a business metadata definition by GUID or name"""
|
|
236
|
+
try:
|
|
237
|
+
args = {'--guid': guid, '--name': name}
|
|
238
|
+
client = Types()
|
|
239
|
+
result = client.typesReadBusinessMetadataDef(args)
|
|
240
|
+
click.echo(json.dumps(result, indent=2))
|
|
241
|
+
except Exception as e:
|
|
242
|
+
click.echo(f"Error: {e}")
|
|
243
|
+
|
|
244
|
+
@types.command()
|
|
245
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
246
|
+
@click.option('--name', required=False, help='The name of the term template definition')
|
|
247
|
+
def read_term_template_def(guid, name):
|
|
248
|
+
"""Read a term template definition by GUID or name"""
|
|
249
|
+
try:
|
|
250
|
+
args = {'--guid': guid, '--name': name}
|
|
251
|
+
client = Types()
|
|
252
|
+
result = client.typesReadTermTemplateDef(args)
|
|
253
|
+
click.echo(json.dumps(result, indent=2))
|
|
254
|
+
except Exception as e:
|
|
255
|
+
click.echo(f"Error: {e}")
|
|
256
|
+
|
|
257
|
+
@types.command()
|
|
258
|
+
@click.option('--guid', required=False, help='The globally unique identifier')
|
|
259
|
+
@click.option('--name', required=False, help='The name of the type definition')
|
|
260
|
+
def read_typedef(guid, name):
|
|
261
|
+
"""Read a type definition by GUID or name"""
|
|
262
|
+
try:
|
|
263
|
+
args = {'--guid': guid, '--name': name}
|
|
264
|
+
client = Types()
|
|
265
|
+
result = client.typesReadTypeDef(args)
|
|
266
|
+
click.echo(json.dumps(result, indent=2))
|
|
267
|
+
except Exception as e:
|
|
268
|
+
click.echo(f"Error: {e}")
|
|
269
|
+
|
|
270
|
+
@types.command()
|
|
271
|
+
@click.option('--include-term-template', is_flag=True, default=False, help='Include term template definitions')
|
|
272
|
+
@click.option('--type', 'type_', required=False, help='Typedef name as search filter (classification | entity | enum | relationship | struct)')
|
|
273
|
+
def read_typedefs(include_term_template, type_):
|
|
274
|
+
"""Read all type definitions, optionally filtered by type or including term templates"""
|
|
275
|
+
try:
|
|
276
|
+
args = {'--includeTermTemplate': include_term_template, '--type': type_}
|
|
277
|
+
client = Types()
|
|
278
|
+
result = client.typesRead(args)
|
|
279
|
+
click.echo(json.dumps(result, indent=2))
|
|
280
|
+
except Exception as e:
|
|
281
|
+
click.echo(f"Error: {e}")
|
|
282
|
+
|
|
283
|
+
@types.command()
|
|
284
|
+
@click.option('--include-term-template', is_flag=True, default=False, help='Include term template definitions')
|
|
285
|
+
@click.option('--type', 'type_', required=False, help='Typedef name as search filter (classification | entity | enum | relationship | struct)')
|
|
286
|
+
def read_typedefs_headers(include_term_template, type_):
|
|
287
|
+
"""Read type definition headers, optionally filtered by type or including term templates"""
|
|
288
|
+
try:
|
|
289
|
+
args = {'--includeTermTemplate': include_term_template, '--type': type_}
|
|
290
|
+
client = Types()
|
|
291
|
+
result = client.typesReadHeaders(args)
|
|
292
|
+
click.echo(json.dumps(result, indent=2))
|
|
293
|
+
except Exception as e:
|
|
294
|
+
click.echo(f"Error: {e}")
|
|
295
|
+
|
|
296
|
+
@types.command()
|
|
297
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
298
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
299
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
300
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
301
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
302
|
+
def create_business_metadata_def(payload_file, dry_run, validate, output_file, error_file):
|
|
303
|
+
"""Create business metadata definition from a JSON file"""
|
|
304
|
+
try:
|
|
305
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
306
|
+
payload = json.load(f)
|
|
307
|
+
if validate:
|
|
308
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
309
|
+
# Optionally, add more schema validation here
|
|
310
|
+
if dry_run:
|
|
311
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
312
|
+
click.echo(json.dumps(payload, indent=2))
|
|
313
|
+
return
|
|
314
|
+
args = {'--payloadFile': payload_file}
|
|
315
|
+
client = Types()
|
|
316
|
+
result = client.createBusinessMetadataDef(args)
|
|
317
|
+
if output_file:
|
|
318
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
319
|
+
outf.write(json.dumps(result, indent=2))
|
|
320
|
+
else:
|
|
321
|
+
click.echo(json.dumps(result, indent=2))
|
|
322
|
+
except Exception as e:
|
|
323
|
+
if error_file:
|
|
324
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
325
|
+
errf.write(str(e))
|
|
326
|
+
else:
|
|
327
|
+
click.echo(f"Error: {e}")
|
|
328
|
+
|
|
329
|
+
@types.command()
|
|
330
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
331
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
332
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
333
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
334
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
335
|
+
def update_business_metadata_def(payload_file, dry_run, validate, output_file, error_file):
|
|
336
|
+
"""Update business metadata definition from a JSON file"""
|
|
337
|
+
try:
|
|
338
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
339
|
+
payload = json.load(f)
|
|
340
|
+
if validate:
|
|
341
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
342
|
+
# Optionally, add more schema validation here
|
|
343
|
+
if dry_run:
|
|
344
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
345
|
+
click.echo(json.dumps(payload, indent=2))
|
|
346
|
+
return
|
|
347
|
+
args = {'--payloadFile': payload_file}
|
|
348
|
+
client = Types()
|
|
349
|
+
result = client.updateBusinessMetadataDef(args)
|
|
350
|
+
if output_file:
|
|
351
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
352
|
+
outf.write(json.dumps(result, indent=2))
|
|
353
|
+
else:
|
|
354
|
+
click.echo(json.dumps(result, indent=2))
|
|
355
|
+
except Exception as e:
|
|
356
|
+
if error_file:
|
|
357
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
358
|
+
errf.write(str(e))
|
|
359
|
+
else:
|
|
360
|
+
click.echo(f"Error: {e}")
|
|
361
|
+
|
|
362
|
+
@types.command()
|
|
363
|
+
@click.option('--name', required=True, help='Name of the business metadata definition to delete')
|
|
364
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
365
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
366
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
367
|
+
def delete_business_metadata_def(name, dry_run, output_file, error_file):
|
|
368
|
+
"""Delete a business metadata definition by name"""
|
|
369
|
+
try:
|
|
370
|
+
if dry_run:
|
|
371
|
+
click.echo(f'[DRY-RUN] Would delete business metadata definition with name: {name}')
|
|
372
|
+
return
|
|
373
|
+
args = {'--name': name}
|
|
374
|
+
client = Types()
|
|
375
|
+
result = client.deleteBusinessMetadataDef(args)
|
|
376
|
+
if output_file:
|
|
377
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
378
|
+
outf.write(json.dumps(result, indent=2))
|
|
379
|
+
else:
|
|
380
|
+
click.echo(json.dumps(result, indent=2))
|
|
381
|
+
except Exception as e:
|
|
382
|
+
if error_file:
|
|
383
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
384
|
+
errf.write(str(e))
|
|
385
|
+
else:
|
|
386
|
+
click.echo(f"Error: {e}")
|
|
387
|
+
|
|
388
|
+
@types.command()
|
|
389
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
390
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
391
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
392
|
+
def create_term_template_def(payload_file, dry_run, validate, output_file, error_file):
|
|
393
|
+
"""Create term template definition from a JSON file"""
|
|
394
|
+
try:
|
|
395
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
396
|
+
payload = json.load(f)
|
|
397
|
+
if validate:
|
|
398
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
399
|
+
# Optionally, add more schema validation here
|
|
400
|
+
if dry_run:
|
|
401
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
402
|
+
click.echo(json.dumps(payload, indent=2))
|
|
403
|
+
return
|
|
404
|
+
args = {'--payloadFile': payload_file}
|
|
405
|
+
client = Types()
|
|
406
|
+
result = client.createTermTemplateDef(args)
|
|
407
|
+
if output_file:
|
|
408
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
409
|
+
outf.write(json.dumps(result, indent=2))
|
|
410
|
+
else:
|
|
411
|
+
click.echo(json.dumps(result, indent=2))
|
|
412
|
+
except Exception as e:
|
|
413
|
+
if error_file:
|
|
414
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
415
|
+
errf.write(str(e))
|
|
416
|
+
else:
|
|
417
|
+
click.echo(f"Error: {e}")
|
|
418
|
+
|
|
419
|
+
@types.command()
|
|
420
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
421
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
422
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
423
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
424
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
425
|
+
def update_term_template_def(payload_file, dry_run, validate, output_file, error_file):
|
|
426
|
+
"""Update term template definition from a JSON file"""
|
|
427
|
+
try:
|
|
428
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
429
|
+
payload = json.load(f)
|
|
430
|
+
if validate:
|
|
431
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
432
|
+
# Optionally, add more schema validation here
|
|
433
|
+
if dry_run:
|
|
434
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
435
|
+
click.echo(json.dumps(payload, indent=2))
|
|
436
|
+
return
|
|
437
|
+
args = {'--payloadFile': payload_file}
|
|
438
|
+
client = Types()
|
|
439
|
+
result = client.updateTermTemplateDef(args)
|
|
440
|
+
if output_file:
|
|
441
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
442
|
+
outf.write(json.dumps(result, indent=2))
|
|
443
|
+
else:
|
|
444
|
+
click.echo(json.dumps(result, indent=2))
|
|
445
|
+
except Exception as e:
|
|
446
|
+
if error_file:
|
|
447
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
448
|
+
errf.write(str(e))
|
|
449
|
+
else:
|
|
450
|
+
click.echo(f"Error: {e}")
|
|
451
|
+
|
|
452
|
+
@types.command()
|
|
453
|
+
@click.option('--output-file', type=click.Path(), required=False, help='Write result to this file instead of stdout')
|
|
454
|
+
@click.option('--error-file', type=click.Path(), required=False, help='Write errors to this file instead of stdout')
|
|
455
|
+
@click.option('--name', required=True, help='Name of the term template definition to delete')
|
|
456
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
457
|
+
def delete_term_template_def(name, dry_run, output_file, error_file):
|
|
458
|
+
"""Delete a term template definition by name"""
|
|
459
|
+
try:
|
|
460
|
+
if dry_run:
|
|
461
|
+
click.echo(f'[DRY-RUN] Would delete term template definition with name: {name}')
|
|
462
|
+
return
|
|
463
|
+
args = {'--name': name}
|
|
464
|
+
client = Types()
|
|
465
|
+
result = client.deleteTermTemplateDef(args)
|
|
466
|
+
if output_file:
|
|
467
|
+
with open(output_file, 'w', encoding='utf-8') as outf:
|
|
468
|
+
outf.write(json.dumps(result, indent=2))
|
|
469
|
+
else:
|
|
470
|
+
click.echo(json.dumps(result, indent=2))
|
|
471
|
+
except Exception as e:
|
|
472
|
+
if error_file:
|
|
473
|
+
with open(error_file, 'w', encoding='utf-8') as errf:
|
|
474
|
+
errf.write(str(e))
|
|
475
|
+
else:
|
|
476
|
+
click.echo(f"Error: {e}")
|
|
477
|
+
|
|
478
|
+
@types.command()
|
|
479
|
+
@click.option('--payload-file', type=click.Path(exists=True), required=True, help='File path to a valid JSON document')
|
|
480
|
+
@click.option('--dry-run/--no-dry-run', default=False, help='Simulate the operation without making changes')
|
|
481
|
+
@click.option('--validate/--no-validate', default=False, help='Validate the payload without making changes')
|
|
482
|
+
def update_enum_def(payload_file, dry_run, validate):
|
|
483
|
+
"""Update enum definition from a JSON file (example for extensibility)"""
|
|
484
|
+
try:
|
|
485
|
+
with open(payload_file, 'r', encoding='utf-8') as f:
|
|
486
|
+
payload = json.load(f)
|
|
487
|
+
if validate:
|
|
488
|
+
click.echo('[VALIDATION] Payload is valid JSON.')
|
|
489
|
+
if dry_run:
|
|
490
|
+
click.echo('[DRY-RUN] Would send the following payload:')
|
|
491
|
+
click.echo(json.dumps(payload, indent=2))
|
|
492
|
+
return
|
|
493
|
+
# args and client logic would go here
|
|
494
|
+
click.echo('[NOT IMPLEMENTED] This is a placeholder for extensibility.')
|
|
495
|
+
except Exception as e:
|
|
496
|
+
click.echo(f"Error: {e}")
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@types.command(name="list-business-attributes")
|
|
500
|
+
@click.option('--output', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
|
501
|
+
@click.option('--show-empty-groups/--hide-empty-groups', default=True, help='Show groups with no attributes')
|
|
502
|
+
def list_business_attributes(output, show_empty_groups):
|
|
503
|
+
"""List all business metadata attributes (Custom metadata in Purview UI)
|
|
504
|
+
|
|
505
|
+
This command displays individual attributes organized by their parent groups,
|
|
506
|
+
matching what you see in the Purview "Custom metadata (preview)" interface
|
|
507
|
+
under "Business concept attributes" tab.
|
|
508
|
+
|
|
509
|
+
Examples:
|
|
510
|
+
pvw types list-business-attributes
|
|
511
|
+
pvw types list-business-attributes --output json
|
|
512
|
+
pvw types list-business-attributes --hide-empty-groups
|
|
513
|
+
"""
|
|
514
|
+
from rich.console import Console
|
|
515
|
+
from rich.table import Table
|
|
516
|
+
|
|
517
|
+
console = Console()
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
client = Types()
|
|
521
|
+
result = client.typesRead({})
|
|
522
|
+
|
|
523
|
+
if not result:
|
|
524
|
+
console.print("[red]ERROR:[/red] Failed to retrieve type definitions")
|
|
525
|
+
return
|
|
526
|
+
|
|
527
|
+
biz = result.get('businessMetadataDefs', [])
|
|
528
|
+
|
|
529
|
+
if not biz:
|
|
530
|
+
console.print("[yellow]No business metadata found[/yellow]")
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
if output == 'json':
|
|
534
|
+
# JSON output: list of attributes with their group info
|
|
535
|
+
attributes_list = []
|
|
536
|
+
for group in biz:
|
|
537
|
+
group_name = group.get('name', 'N/A')
|
|
538
|
+
group_guid = group.get('guid', 'N/A')
|
|
539
|
+
|
|
540
|
+
attributes = group.get('attributeDefs', [])
|
|
541
|
+
if not attributes and not show_empty_groups:
|
|
542
|
+
continue
|
|
543
|
+
|
|
544
|
+
for attr in attributes:
|
|
545
|
+
attributes_list.append({
|
|
546
|
+
'attributeName': attr.get('name'),
|
|
547
|
+
'group': group_name,
|
|
548
|
+
'groupGuid': group_guid,
|
|
549
|
+
'type': attr.get('typeName'),
|
|
550
|
+
'description': attr.get('description', ''),
|
|
551
|
+
'isOptional': attr.get('isOptional', True),
|
|
552
|
+
'isIndexable': attr.get('isIndexable', False)
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
click.echo(json.dumps(attributes_list, indent=2))
|
|
556
|
+
return
|
|
557
|
+
|
|
558
|
+
# Table output
|
|
559
|
+
table = Table(title="[bold cyan]Business Concept Attributes (Custom Metadata)[/bold cyan]")
|
|
560
|
+
table.add_column("Attribute Name", style="green", no_wrap=True)
|
|
561
|
+
table.add_column("Group", style="cyan")
|
|
562
|
+
table.add_column("Type", style="yellow")
|
|
563
|
+
table.add_column("Description", style="white", max_width=40)
|
|
564
|
+
table.add_column("Scope", style="magenta")
|
|
565
|
+
|
|
566
|
+
total_attributes = 0
|
|
567
|
+
|
|
568
|
+
for group in biz:
|
|
569
|
+
group_name = group.get('name', 'N/A')
|
|
570
|
+
|
|
571
|
+
# Parse data governance options for scope
|
|
572
|
+
scope_info = "N/A"
|
|
573
|
+
options = group.get('options', {})
|
|
574
|
+
if 'dataGovernanceOptions' in options:
|
|
575
|
+
try:
|
|
576
|
+
dg_opts_str = options.get('dataGovernanceOptions', '{}')
|
|
577
|
+
dg_opts = json.loads(dg_opts_str) if isinstance(dg_opts_str, str) else dg_opts_str
|
|
578
|
+
applicable = dg_opts.get('applicableConstructs', [])
|
|
579
|
+
if applicable:
|
|
580
|
+
# Extract construct types (domain, businessConcept, etc.)
|
|
581
|
+
scope_parts = []
|
|
582
|
+
for construct in applicable[:2]: # Show first 2
|
|
583
|
+
if ':' in construct:
|
|
584
|
+
scope_parts.append(construct.split(':')[0])
|
|
585
|
+
else:
|
|
586
|
+
scope_parts.append(construct)
|
|
587
|
+
scope_info = ', '.join(scope_parts)
|
|
588
|
+
if len(applicable) > 2:
|
|
589
|
+
scope_info += f", +{len(applicable)-2} more"
|
|
590
|
+
except:
|
|
591
|
+
pass
|
|
592
|
+
|
|
593
|
+
# List all attributes in this group
|
|
594
|
+
attributes = group.get('attributeDefs', [])
|
|
595
|
+
|
|
596
|
+
if attributes:
|
|
597
|
+
for attr in attributes:
|
|
598
|
+
total_attributes += 1
|
|
599
|
+
attr_name = attr.get('name', 'N/A')
|
|
600
|
+
attr_type = attr.get('typeName', 'N/A')
|
|
601
|
+
|
|
602
|
+
# Simplify enum types for display
|
|
603
|
+
if 'ATTRIBUTE_ENUM_' in attr_type:
|
|
604
|
+
attr_type = 'Enum (Single choice)'
|
|
605
|
+
|
|
606
|
+
attr_desc = attr.get('description', '')
|
|
607
|
+
|
|
608
|
+
# Get scope from attribute if it overrides group
|
|
609
|
+
attr_scope = scope_info
|
|
610
|
+
attr_opts = attr.get('options', {})
|
|
611
|
+
if 'dataGovernanceOptions' in attr_opts:
|
|
612
|
+
try:
|
|
613
|
+
attr_dg_str = attr_opts.get('dataGovernanceOptions', '{}')
|
|
614
|
+
attr_dg = json.loads(attr_dg_str) if isinstance(attr_dg_str, str) else attr_dg_str
|
|
615
|
+
inherit = attr_dg.get('inheritApplicableConstructsFromGroup', True)
|
|
616
|
+
if not inherit:
|
|
617
|
+
attr_applicable = attr_dg.get('applicableConstructs', [])
|
|
618
|
+
if attr_applicable:
|
|
619
|
+
attr_scope = f"{len(attr_applicable)} custom scope(s)"
|
|
620
|
+
except:
|
|
621
|
+
pass
|
|
622
|
+
|
|
623
|
+
table.add_row(
|
|
624
|
+
attr_name,
|
|
625
|
+
group_name,
|
|
626
|
+
attr_type,
|
|
627
|
+
attr_desc[:40] + "..." if len(attr_desc) > 40 else attr_desc,
|
|
628
|
+
attr_scope
|
|
629
|
+
)
|
|
630
|
+
elif show_empty_groups:
|
|
631
|
+
# Show group with no attributes
|
|
632
|
+
table.add_row(
|
|
633
|
+
f"[dim](no attributes)[/dim]",
|
|
634
|
+
group_name,
|
|
635
|
+
"-",
|
|
636
|
+
f"[dim]Empty group[/dim]",
|
|
637
|
+
scope_info
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
console.print(table)
|
|
641
|
+
console.print(f"\n[cyan]Total:[/cyan] {total_attributes} attribute(s) in {len(biz)} group(s)")
|
|
642
|
+
|
|
643
|
+
if total_attributes > 0:
|
|
644
|
+
console.print("\n[dim]Tip: Use 'pvw types read-business-metadata-def --name <GroupName>' for details[/dim]")
|
|
645
|
+
console.print("[dim]Tip: Use 'pvw types list-business-metadata-groups' to see group-level summary[/dim]")
|
|
646
|
+
|
|
647
|
+
except Exception as e:
|
|
648
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@types.command(name="list-business-metadata-groups")
|
|
652
|
+
@click.option('--output', type=click.Choice(['table', 'json']), default='table', help='Output format')
|
|
653
|
+
def list_business_metadata_groups(output):
|
|
654
|
+
"""List business metadata groups with their scope (Business Concept vs Data Asset).
|
|
655
|
+
|
|
656
|
+
Shows a summary view of metadata groups to distinguish which apply to:
|
|
657
|
+
- Business Concepts (Terms, Domains, Business Rules)
|
|
658
|
+
- Data Assets (Tables, Files, Databases)
|
|
659
|
+
- Universal (Both)
|
|
660
|
+
|
|
661
|
+
Examples:
|
|
662
|
+
pvw types list-business-metadata-groups
|
|
663
|
+
pvw types list-business-metadata-groups --output json
|
|
664
|
+
"""
|
|
665
|
+
from rich.console import Console
|
|
666
|
+
from rich.table import Table
|
|
667
|
+
import json
|
|
668
|
+
|
|
669
|
+
console = Console()
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
client = Types()
|
|
673
|
+
result = client.typesRead({})
|
|
674
|
+
|
|
675
|
+
if not result:
|
|
676
|
+
console.print("[red]ERROR:[/red] Failed to retrieve type definitions")
|
|
677
|
+
return
|
|
678
|
+
|
|
679
|
+
biz = result.get('businessMetadataDefs', [])
|
|
680
|
+
|
|
681
|
+
if not biz:
|
|
682
|
+
console.print("[yellow]No business metadata groups found[/yellow]")
|
|
683
|
+
return
|
|
684
|
+
|
|
685
|
+
if output == 'json':
|
|
686
|
+
# JSON output
|
|
687
|
+
groups_list = []
|
|
688
|
+
for group in biz:
|
|
689
|
+
group_name = group.get('name', 'N/A')
|
|
690
|
+
group_guid = group.get('guid', 'N/A')
|
|
691
|
+
attr_count = len(group.get('attributeDefs', []))
|
|
692
|
+
|
|
693
|
+
# Determine scope
|
|
694
|
+
scope = "N/A"
|
|
695
|
+
scope_type = "unknown"
|
|
696
|
+
options = group.get('options', {})
|
|
697
|
+
|
|
698
|
+
if 'dataGovernanceOptions' in options:
|
|
699
|
+
try:
|
|
700
|
+
dg_opts_str = options.get('dataGovernanceOptions', '{}')
|
|
701
|
+
dg_opts = json.loads(dg_opts_str) if isinstance(dg_opts_str, str) else dg_opts_str
|
|
702
|
+
applicable = dg_opts.get('applicableConstructs', [])
|
|
703
|
+
|
|
704
|
+
if applicable:
|
|
705
|
+
has_business_concept = any('businessConcept' in c or 'domain' in c for c in applicable)
|
|
706
|
+
has_dataset = any('dataset' in c.lower() for c in applicable)
|
|
707
|
+
|
|
708
|
+
if has_business_concept and has_dataset:
|
|
709
|
+
scope = "Universal (Concept + Dataset)"
|
|
710
|
+
scope_type = "universal"
|
|
711
|
+
elif has_business_concept:
|
|
712
|
+
scope = "Business Concept"
|
|
713
|
+
scope_type = "business_concept"
|
|
714
|
+
elif has_dataset:
|
|
715
|
+
scope = "Data Asset"
|
|
716
|
+
scope_type = "data_asset"
|
|
717
|
+
else:
|
|
718
|
+
scope = ', '.join([c.split(':')[0] if ':' in c else c for c in applicable[:3]])
|
|
719
|
+
scope_type = "custom"
|
|
720
|
+
except:
|
|
721
|
+
pass
|
|
722
|
+
|
|
723
|
+
# Check for legacy applicableEntityTypes in attributes
|
|
724
|
+
if scope == "N/A":
|
|
725
|
+
for attr in group.get('attributeDefs', []):
|
|
726
|
+
attr_opts = attr.get('options', {})
|
|
727
|
+
if 'applicableEntityTypes' in attr_opts:
|
|
728
|
+
try:
|
|
729
|
+
entity_types_str = attr_opts.get('applicableEntityTypes', '[]')
|
|
730
|
+
entity_types = json.loads(entity_types_str) if isinstance(entity_types_str, str) else entity_types_str
|
|
731
|
+
if entity_types and isinstance(entity_types, list):
|
|
732
|
+
if any('table' in et.lower() or 'database' in et.lower() for et in entity_types):
|
|
733
|
+
scope = "Data Asset (Legacy)"
|
|
734
|
+
scope_type = "data_asset_legacy"
|
|
735
|
+
break
|
|
736
|
+
except:
|
|
737
|
+
pass
|
|
738
|
+
|
|
739
|
+
groups_list.append({
|
|
740
|
+
'groupName': group_name,
|
|
741
|
+
'groupGuid': group_guid,
|
|
742
|
+
'scope': scope,
|
|
743
|
+
'scopeType': scope_type,
|
|
744
|
+
'attributeCount': attr_count,
|
|
745
|
+
'description': group.get('description', '')
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
click.echo(json.dumps(groups_list, indent=2))
|
|
749
|
+
return
|
|
750
|
+
|
|
751
|
+
# Table output
|
|
752
|
+
table = Table(title="[bold cyan]Business Metadata Groups[/bold cyan]", show_header=True)
|
|
753
|
+
table.add_column("Group Name", style="cyan", no_wrap=True)
|
|
754
|
+
table.add_column("Scope", style="magenta", max_width=30)
|
|
755
|
+
table.add_column("Attributes", style="yellow", justify="center")
|
|
756
|
+
table.add_column("Description", style="white", max_width=40)
|
|
757
|
+
|
|
758
|
+
for group in biz:
|
|
759
|
+
group_name = group.get('name', 'N/A')
|
|
760
|
+
attr_count = len(group.get('attributeDefs', []))
|
|
761
|
+
group_desc = group.get('description', '')
|
|
762
|
+
|
|
763
|
+
# Determine scope
|
|
764
|
+
scope = "N/A"
|
|
765
|
+
scope_style = "white"
|
|
766
|
+
options = group.get('options', {})
|
|
767
|
+
|
|
768
|
+
if 'dataGovernanceOptions' in options:
|
|
769
|
+
try:
|
|
770
|
+
dg_opts_str = options.get('dataGovernanceOptions', '{}')
|
|
771
|
+
dg_opts = json.loads(dg_opts_str) if isinstance(dg_opts_str, str) else dg_opts_str
|
|
772
|
+
applicable = dg_opts.get('applicableConstructs', [])
|
|
773
|
+
|
|
774
|
+
if applicable:
|
|
775
|
+
has_business_concept = any('businessConcept' in c or 'domain' in c for c in applicable)
|
|
776
|
+
has_dataset = any('dataset' in c.lower() for c in applicable)
|
|
777
|
+
|
|
778
|
+
if has_business_concept and has_dataset:
|
|
779
|
+
scope = "Universal"
|
|
780
|
+
scope_style = "magenta bold"
|
|
781
|
+
elif has_business_concept:
|
|
782
|
+
scope = "Business Concept"
|
|
783
|
+
scope_style = "green"
|
|
784
|
+
elif has_dataset:
|
|
785
|
+
scope = "Data Asset"
|
|
786
|
+
scope_style = "blue"
|
|
787
|
+
else:
|
|
788
|
+
scope = ', '.join([c.split(':')[0] if ':' in c else c for c in applicable[:2]])
|
|
789
|
+
scope_style = "yellow"
|
|
790
|
+
except:
|
|
791
|
+
pass
|
|
792
|
+
|
|
793
|
+
# Check for legacy applicableEntityTypes
|
|
794
|
+
if scope == "N/A":
|
|
795
|
+
for attr in group.get('attributeDefs', []):
|
|
796
|
+
attr_opts = attr.get('options', {})
|
|
797
|
+
if 'applicableEntityTypes' in attr_opts:
|
|
798
|
+
try:
|
|
799
|
+
entity_types_str = attr_opts.get('applicableEntityTypes', '[]')
|
|
800
|
+
entity_types = json.loads(entity_types_str) if isinstance(entity_types_str, str) else entity_types_str
|
|
801
|
+
if entity_types and isinstance(entity_types, list):
|
|
802
|
+
if any('table' in et.lower() or 'database' in et.lower() for et in entity_types):
|
|
803
|
+
scope = "Data Asset (Legacy)"
|
|
804
|
+
scope_style = "blue dim"
|
|
805
|
+
break
|
|
806
|
+
except:
|
|
807
|
+
pass
|
|
808
|
+
|
|
809
|
+
table.add_row(
|
|
810
|
+
group_name,
|
|
811
|
+
f"[{scope_style}]{scope}[/{scope_style}]",
|
|
812
|
+
str(attr_count),
|
|
813
|
+
group_desc[:40] + "..." if len(group_desc) > 40 else group_desc
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
console.print(table)
|
|
817
|
+
console.print(f"\n[cyan]Total:[/cyan] {len(biz)} group(s)")
|
|
818
|
+
|
|
819
|
+
console.print("\n[bold]Legend:[/bold]")
|
|
820
|
+
console.print(" [green]Business Concept[/green] = Applies to Glossary Terms, Domains, Business Rules")
|
|
821
|
+
console.print(" [blue]Data Asset[/blue] = Applies to Tables, Files, Databases, etc.")
|
|
822
|
+
console.print(" [magenta bold]Universal[/magenta bold] = Applies to both Concepts and Assets")
|
|
823
|
+
|
|
824
|
+
console.print("\n[dim]Tip: Use 'pvw types list-business-attributes' to see individual attributes[/dim]")
|
|
825
|
+
console.print("[dim]Tip: Use 'pvw types read-business-metadata-def --name <GroupName>' for full details[/dim]")
|
|
826
|
+
|
|
827
|
+
except Exception as e:
|
|
828
|
+
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
__all__ = ['types']
|