pvw-cli 1.2.8__py3-none-any.whl → 1.3.4__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 +1 -1
- purviewcli/cli/unified_catalog.py +278 -1
- {pvw_cli-1.2.8.dist-info → pvw_cli-1.3.4.dist-info}/METADATA +20 -20
- {pvw_cli-1.2.8.dist-info → pvw_cli-1.3.4.dist-info}/RECORD +7 -7
- {pvw_cli-1.2.8.dist-info → pvw_cli-1.3.4.dist-info}/WHEEL +0 -0
- {pvw_cli-1.2.8.dist-info → pvw_cli-1.3.4.dist-info}/entry_points.txt +0 -0
- {pvw_cli-1.2.8.dist-info → pvw_cli-1.3.4.dist-info}/top_level.txt +0 -0
purviewcli/__init__.py
CHANGED
|
@@ -1069,7 +1069,7 @@ def verify_glossary_links():
|
|
|
1069
1069
|
console.print(f" • Unlinked domains: [yellow]{unlinked_count}[/yellow]")
|
|
1070
1070
|
|
|
1071
1071
|
if unlinked_count > 0:
|
|
1072
|
-
console.print(f"\n[dim]
|
|
1072
|
+
console.print(f"\n[dim][TIP] Tip: Run 'pvw uc glossary create-for-domains' to create glossaries for unlinked domains[/dim]")
|
|
1073
1073
|
|
|
1074
1074
|
except Exception as e:
|
|
1075
1075
|
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
@@ -2048,6 +2048,283 @@ def query_terms(ids, domain_ids, name_keyword, acronyms, owners, status, multi_s
|
|
|
2048
2048
|
console.print(f"[red]ERROR:[/red] {str(e)}")
|
|
2049
2049
|
|
|
2050
2050
|
|
|
2051
|
+
@term.command(name="sync-classic")
|
|
2052
|
+
@click.option("--domain-id", required=False, help="Governance domain ID to sync terms from (if not provided, syncs all domains)")
|
|
2053
|
+
@click.option("--glossary-guid", required=False, help="Target classic glossary GUID (if not provided, creates/uses glossary with domain name)")
|
|
2054
|
+
@click.option("--create-glossary", is_flag=True, help="Create classic glossary if it doesn't exist")
|
|
2055
|
+
@click.option("--dry-run", is_flag=True, help="Preview changes without applying them")
|
|
2056
|
+
@click.option("--update-existing", is_flag=True, help="Update existing classic terms if they already exist")
|
|
2057
|
+
def sync_classic(domain_id, glossary_guid, create_glossary, dry_run, update_existing):
|
|
2058
|
+
"""Synchronize Unified Catalog terms to classic glossary terms.
|
|
2059
|
+
|
|
2060
|
+
This command bridges the Unified Catalog (business metadata) with classic glossaries,
|
|
2061
|
+
enabling you to sync terms from governance domains to traditional glossary structures.
|
|
2062
|
+
|
|
2063
|
+
Examples:
|
|
2064
|
+
# Sync all terms from a specific domain to its corresponding glossary
|
|
2065
|
+
pvw uc term sync-classic --domain-id <domain-guid>
|
|
2066
|
+
|
|
2067
|
+
# Sync to a specific glossary
|
|
2068
|
+
pvw uc term sync-classic --domain-id <domain-guid> --glossary-guid <glossary-guid>
|
|
2069
|
+
|
|
2070
|
+
# Create glossary if needed and sync
|
|
2071
|
+
pvw uc term sync-classic --domain-id <domain-guid> --create-glossary
|
|
2072
|
+
|
|
2073
|
+
# Preview sync without making changes
|
|
2074
|
+
pvw uc term sync-classic --domain-id <domain-guid> --dry-run
|
|
2075
|
+
|
|
2076
|
+
# Update existing terms in classic glossary
|
|
2077
|
+
pvw uc term sync-classic --domain-id <domain-guid> --update-existing
|
|
2078
|
+
"""
|
|
2079
|
+
try:
|
|
2080
|
+
from purviewcli.client._glossary import Glossary
|
|
2081
|
+
import tempfile
|
|
2082
|
+
import traceback
|
|
2083
|
+
|
|
2084
|
+
uc_client = UnifiedCatalogClient()
|
|
2085
|
+
glossary_client = Glossary()
|
|
2086
|
+
|
|
2087
|
+
console.print("[cyan]" + "-" * 59 + "[/cyan]")
|
|
2088
|
+
console.print("[bold cyan] Unified Catalog → Classic Glossary Sync [/bold cyan]")
|
|
2089
|
+
console.print("[cyan]" + "-" * 59 + "[/cyan]\n")
|
|
2090
|
+
|
|
2091
|
+
if dry_run:
|
|
2092
|
+
console.print("[yellow][*] DRY RUN MODE - No changes will be made[/yellow]\n")
|
|
2093
|
+
|
|
2094
|
+
# Step 1: Get UC terms
|
|
2095
|
+
console.print("[bold]Step 1:[/bold] Fetching Unified Catalog terms...")
|
|
2096
|
+
uc_args = {}
|
|
2097
|
+
if domain_id:
|
|
2098
|
+
uc_args["--governance-domain-id"] = [domain_id]
|
|
2099
|
+
|
|
2100
|
+
uc_result = uc_client.get_terms(uc_args)
|
|
2101
|
+
|
|
2102
|
+
# Extract terms from response
|
|
2103
|
+
uc_terms = []
|
|
2104
|
+
if isinstance(uc_result, dict):
|
|
2105
|
+
uc_terms = uc_result.get("value", [])
|
|
2106
|
+
elif isinstance(uc_result, (list, tuple)):
|
|
2107
|
+
uc_terms = uc_result
|
|
2108
|
+
|
|
2109
|
+
if not uc_terms:
|
|
2110
|
+
console.print("[yellow][!] No Unified Catalog terms found.[/yellow]")
|
|
2111
|
+
return
|
|
2112
|
+
|
|
2113
|
+
console.print(f"[green][OK][/green] Found {len(uc_terms)} UC term(s)\n")
|
|
2114
|
+
|
|
2115
|
+
# Step 2: Determine or create target glossary
|
|
2116
|
+
console.print("[bold]Step 2:[/bold] Determining target glossary...")
|
|
2117
|
+
|
|
2118
|
+
target_glossary_guid = glossary_guid
|
|
2119
|
+
|
|
2120
|
+
if not target_glossary_guid:
|
|
2121
|
+
# Get domain info to use domain name as glossary name
|
|
2122
|
+
if domain_id:
|
|
2123
|
+
domain_info = uc_client.get_governance_domain_by_id({"--domain-id": [domain_id]})
|
|
2124
|
+
domain_name = domain_info.get("name", "Unknown Domain")
|
|
2125
|
+
console.print(f" Domain: [cyan]{domain_name}[/cyan]")
|
|
2126
|
+
|
|
2127
|
+
# Try to find existing glossary with matching name
|
|
2128
|
+
all_glossaries = glossary_client.glossaryRead({})
|
|
2129
|
+
if isinstance(all_glossaries, dict):
|
|
2130
|
+
all_glossaries = all_glossaries.get("value", [])
|
|
2131
|
+
|
|
2132
|
+
for g in all_glossaries:
|
|
2133
|
+
g_name = g.get("name", "")
|
|
2134
|
+
g_qualified = g.get("qualifiedName", "")
|
|
2135
|
+
|
|
2136
|
+
# Check multiple formats for compatibility:
|
|
2137
|
+
# 1. Exact name match
|
|
2138
|
+
# 2. Standard format: DomainName@Glossary
|
|
2139
|
+
# 3. Old format (for backward compatibility): DomainName@domain-id
|
|
2140
|
+
if (g_name == domain_name or
|
|
2141
|
+
g_qualified == f"{domain_name}@Glossary" or
|
|
2142
|
+
g_qualified == f"{domain_name}@{domain_id}"):
|
|
2143
|
+
target_glossary_guid = g.get("guid")
|
|
2144
|
+
console.print(f"[green][OK][/green] Found existing glossary: {g_name} ({target_glossary_guid})\n")
|
|
2145
|
+
break
|
|
2146
|
+
|
|
2147
|
+
if not target_glossary_guid and create_glossary:
|
|
2148
|
+
if dry_run:
|
|
2149
|
+
console.print(f"[yellow]Would create glossary:[/yellow] {domain_name}\n")
|
|
2150
|
+
else:
|
|
2151
|
+
# Create glossary with simple qualifiedName format
|
|
2152
|
+
qualified_name = domain_name
|
|
2153
|
+
|
|
2154
|
+
glossary_payload = {
|
|
2155
|
+
"name": domain_name,
|
|
2156
|
+
"qualifiedName": qualified_name,
|
|
2157
|
+
"shortDescription": f"Auto-synced from Unified Catalog domain: {domain_name}",
|
|
2158
|
+
"longDescription": f"This glossary is automatically synchronized with the Unified Catalog governance domain '{domain_name}' (ID: {domain_id})"
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
import tempfile
|
|
2162
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
2163
|
+
json.dump(glossary_payload, f)
|
|
2164
|
+
temp_file = f.name
|
|
2165
|
+
|
|
2166
|
+
try:
|
|
2167
|
+
new_glossary = glossary_client.glossaryCreate({"--payloadFile": temp_file})
|
|
2168
|
+
target_glossary_guid = new_glossary.get("guid")
|
|
2169
|
+
console.print(f"[green][OK][/green] Created glossary: {domain_name} ({target_glossary_guid})\n")
|
|
2170
|
+
finally:
|
|
2171
|
+
os.unlink(temp_file)
|
|
2172
|
+
elif not target_glossary_guid:
|
|
2173
|
+
console.print(f"[red]ERROR:[/red] No target glossary found. Use --glossary-guid or --create-glossary")
|
|
2174
|
+
return
|
|
2175
|
+
else:
|
|
2176
|
+
console.print("[red]ERROR:[/red] Either --domain-id or --glossary-guid must be provided")
|
|
2177
|
+
return
|
|
2178
|
+
else:
|
|
2179
|
+
console.print(f"[green][OK][/green] Using target glossary: {target_glossary_guid}\n")
|
|
2180
|
+
|
|
2181
|
+
# Step 3: Get existing classic glossary terms and glossary name
|
|
2182
|
+
console.print("[bold]Step 3:[/bold] Checking existing classic glossary terms...")
|
|
2183
|
+
|
|
2184
|
+
existing_terms = {}
|
|
2185
|
+
glossary_name = "Glossary" # Default fallback
|
|
2186
|
+
glossary_qualified_name = "Glossary" # Default fallback for qualified name
|
|
2187
|
+
try:
|
|
2188
|
+
glossary_details = glossary_client.glossaryReadDetailed({"--glossaryGuid": [target_glossary_guid]})
|
|
2189
|
+
existing_term_list = glossary_details.get("terms", [])
|
|
2190
|
+
|
|
2191
|
+
# Get glossary name and qualifiedName for term qualifiedName construction
|
|
2192
|
+
glossary_name = glossary_details.get("name", "Glossary")
|
|
2193
|
+
glossary_qualified_name = glossary_details.get("qualifiedName", f"{glossary_name}@Glossary")
|
|
2194
|
+
|
|
2195
|
+
for term in existing_term_list:
|
|
2196
|
+
term_name = term.get("displayText") or term.get("name")
|
|
2197
|
+
term_guid = term.get("termGuid") or term.get("guid")
|
|
2198
|
+
if term_name:
|
|
2199
|
+
existing_terms[term_name.lower()] = term_guid
|
|
2200
|
+
|
|
2201
|
+
console.print(f"[green][OK][/green] Found {len(existing_terms)} existing term(s) in classic glossary\n")
|
|
2202
|
+
except Exception as e:
|
|
2203
|
+
console.print(f"[yellow][!][/yellow] Could not fetch existing terms: {e}\n")
|
|
2204
|
+
|
|
2205
|
+
# Step 4: Sync terms
|
|
2206
|
+
console.print("[bold]Step 4:[/bold] Synchronizing terms...")
|
|
2207
|
+
|
|
2208
|
+
created_count = 0
|
|
2209
|
+
updated_count = 0
|
|
2210
|
+
skipped_count = 0
|
|
2211
|
+
failed_count = 0
|
|
2212
|
+
|
|
2213
|
+
for uc_term in uc_terms:
|
|
2214
|
+
term_name = uc_term.get("name", "")
|
|
2215
|
+
term_description = uc_term.get("description", "")
|
|
2216
|
+
term_status = uc_term.get("status", "Draft")
|
|
2217
|
+
|
|
2218
|
+
# Check if term already exists
|
|
2219
|
+
existing_guid = existing_terms.get(term_name.lower())
|
|
2220
|
+
|
|
2221
|
+
if existing_guid and not update_existing:
|
|
2222
|
+
console.print(f" [dim][-] Skipping:[/dim] {term_name} (already exists)")
|
|
2223
|
+
skipped_count += 1
|
|
2224
|
+
continue
|
|
2225
|
+
|
|
2226
|
+
try:
|
|
2227
|
+
if existing_guid and update_existing:
|
|
2228
|
+
# Update existing term
|
|
2229
|
+
if dry_run:
|
|
2230
|
+
console.print(f" [yellow]Would update:[/yellow] {term_name}")
|
|
2231
|
+
updated_count += 1
|
|
2232
|
+
else:
|
|
2233
|
+
# Construct qualifiedName: TermName@GlossaryQualifiedName
|
|
2234
|
+
# Use the stored glossary_qualified_name from Step 3
|
|
2235
|
+
term_qualified_name = f"{term_name}@{glossary_qualified_name}"
|
|
2236
|
+
|
|
2237
|
+
update_payload = {
|
|
2238
|
+
"guid": existing_guid,
|
|
2239
|
+
"name": term_name,
|
|
2240
|
+
"qualifiedName": term_qualified_name,
|
|
2241
|
+
"longDescription": term_description,
|
|
2242
|
+
"status": term_status,
|
|
2243
|
+
"anchor": {"glossaryGuid": target_glossary_guid},
|
|
2244
|
+
"termTemplate": {
|
|
2245
|
+
"termTemplateName": "System default"
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
2250
|
+
json.dump(update_payload, f)
|
|
2251
|
+
temp_file = f.name
|
|
2252
|
+
|
|
2253
|
+
try:
|
|
2254
|
+
glossary_client.glossaryUpdateTerm({"--payloadFile": temp_file})
|
|
2255
|
+
console.print(f" [green][OK] Updated:[/green] {term_name}")
|
|
2256
|
+
updated_count += 1
|
|
2257
|
+
finally:
|
|
2258
|
+
os.unlink(temp_file)
|
|
2259
|
+
else:
|
|
2260
|
+
# Create new term
|
|
2261
|
+
if dry_run:
|
|
2262
|
+
console.print(f" [yellow]Would create:[/yellow] {term_name}")
|
|
2263
|
+
created_count += 1
|
|
2264
|
+
else:
|
|
2265
|
+
# Construct qualifiedName: TermName@GlossaryQualifiedName
|
|
2266
|
+
# Use the stored glossary_qualified_name from Step 3
|
|
2267
|
+
term_qualified_name = f"{term_name}@{glossary_qualified_name}"
|
|
2268
|
+
|
|
2269
|
+
create_payload = {
|
|
2270
|
+
"name": term_name,
|
|
2271
|
+
"qualifiedName": term_qualified_name,
|
|
2272
|
+
"longDescription": term_description,
|
|
2273
|
+
"status": term_status,
|
|
2274
|
+
"anchor": {"glossaryGuid": target_glossary_guid},
|
|
2275
|
+
"termTemplate": {
|
|
2276
|
+
"termTemplateName": "System default"
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
# Add optional fields
|
|
2281
|
+
if uc_term.get("acronyms"):
|
|
2282
|
+
create_payload["abbreviation"] = ", ".join(uc_term["acronyms"])
|
|
2283
|
+
|
|
2284
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
2285
|
+
json.dump(create_payload, f)
|
|
2286
|
+
temp_file = f.name
|
|
2287
|
+
|
|
2288
|
+
try:
|
|
2289
|
+
glossary_client.glossaryCreateTerm({"--payloadFile": temp_file})
|
|
2290
|
+
console.print(f" [green][OK] Created:[/green] {term_name}")
|
|
2291
|
+
created_count += 1
|
|
2292
|
+
finally:
|
|
2293
|
+
os.unlink(temp_file)
|
|
2294
|
+
|
|
2295
|
+
except Exception as e:
|
|
2296
|
+
console.print(f" [red][X] Failed:[/red] {term_name} - {str(e)}")
|
|
2297
|
+
failed_count += 1
|
|
2298
|
+
|
|
2299
|
+
# Summary
|
|
2300
|
+
console.print("\n[cyan]" + "-" * 59 + "[/cyan]")
|
|
2301
|
+
console.print("[bold cyan] Synchronization Summary [/bold cyan]")
|
|
2302
|
+
console.print("[cyan]" + "-" * 59 + "[/cyan]")
|
|
2303
|
+
|
|
2304
|
+
summary_table = Table(show_header=False, box=None)
|
|
2305
|
+
summary_table.add_column("Metric", style="bold")
|
|
2306
|
+
summary_table.add_column("Count", style="cyan")
|
|
2307
|
+
|
|
2308
|
+
summary_table.add_row("Total UC Terms", str(len(uc_terms)))
|
|
2309
|
+
summary_table.add_row("Created", f"[green]{created_count}[/green]")
|
|
2310
|
+
summary_table.add_row("Updated", f"[yellow]{updated_count}[/yellow]")
|
|
2311
|
+
summary_table.add_row("Skipped", f"[dim]{skipped_count}[/dim]")
|
|
2312
|
+
summary_table.add_row("Failed", f"[red]{failed_count}[/red]")
|
|
2313
|
+
|
|
2314
|
+
console.print(summary_table)
|
|
2315
|
+
|
|
2316
|
+
if dry_run:
|
|
2317
|
+
console.print("\n[yellow][TIP] This was a dry run. Use without --dry-run to apply changes.[/yellow]")
|
|
2318
|
+
elif failed_count == 0 and (created_count > 0 or updated_count > 0):
|
|
2319
|
+
console.print("\n[green][OK] Synchronization completed successfully![/green]")
|
|
2320
|
+
|
|
2321
|
+
except Exception as e:
|
|
2322
|
+
console.print(f"\n[red]ERROR:[/red] {str(e)}")
|
|
2323
|
+
import traceback
|
|
2324
|
+
if os.getenv("PURVIEWCLI_DEBUG"):
|
|
2325
|
+
console.print(traceback.format_exc())
|
|
2326
|
+
|
|
2327
|
+
|
|
2051
2328
|
# ========================================
|
|
2052
2329
|
# OBJECTIVES AND KEY RESULTS (OKRs)
|
|
2053
2330
|
# ========================================
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pvw-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.4
|
|
4
4
|
Summary: Microsoft Purview CLI with comprehensive automation capabilities
|
|
5
5
|
Author-email: AYOUB KEBAILI <keayoub@msn.com>
|
|
6
6
|
Maintainer-email: AYOUB KEBAILI <keayoub@msn.com>
|
|
7
7
|
License-Expression: MIT
|
|
8
|
-
Project-URL: Homepage, https://github.com/Keayoub/
|
|
9
|
-
Project-URL: Documentation, https://github.com/Keayoub/
|
|
10
|
-
Project-URL: Repository, https://github.com/Keayoub/
|
|
11
|
-
Project-URL: Bug Tracker, https://github.com/Keayoub/
|
|
12
|
-
Project-URL: Source, https://github.com/Keayoub/
|
|
8
|
+
Project-URL: Homepage, https://github.com/Keayoub/pvw-cli
|
|
9
|
+
Project-URL: Documentation, https://github.com/Keayoub/pvw-cli/wiki
|
|
10
|
+
Project-URL: Repository, https://github.com/Keayoub/pvw-cli.git
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/Keayoub/pvw-cli/issues
|
|
12
|
+
Project-URL: Source, https://github.com/Keayoub/pvw-cli
|
|
13
13
|
Keywords: azure,purview,cli,data,catalog,governance,automation,pvw
|
|
14
14
|
Classifier: Development Status :: 4 - Beta
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -56,14 +56,14 @@ Requires-Dist: pytest-asyncio>=0.20.0; extra == "test"
|
|
|
56
56
|
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
57
57
|
Requires-Dist: requests-mock>=1.9.0; extra == "test"
|
|
58
58
|
|
|
59
|
-
# PURVIEW CLI v1.
|
|
59
|
+
# PURVIEW CLI v1.3.4 - Microsoft Purview Automation & Data Governance
|
|
60
60
|
|
|
61
|
-
[](https://github.com/Keayoub/pvw-cli/releases/tag/v1.3.4)
|
|
62
62
|
[](https://github.com/Keayoub/pvw-cli)
|
|
63
63
|
[](https://github.com/Keayoub/pvw-cli)
|
|
64
64
|
[](https://github.com/Keayoub/pvw-cli)
|
|
65
65
|
|
|
66
|
-
> **LATEST UPDATE v1.
|
|
66
|
+
> **LATEST UPDATE v1.3.4 (November 3, 2025):**
|
|
67
67
|
>
|
|
68
68
|
> **🔗 Advanced Lineage Features & Column-Level Mapping**
|
|
69
69
|
>
|
|
@@ -93,13 +93,13 @@ Requires-Dist: requests-mock>=1.9.0; extra == "test"
|
|
|
93
93
|
> - **v1.2.6** - Initial lineage improvements
|
|
94
94
|
> - **v1.2.5** - 86% UC API Coverage (45/52 operations) with Relationships, Query, Policies APIs
|
|
95
95
|
>
|
|
96
|
-
> **[Full Release Notes v1.
|
|
96
|
+
> **[Full Release Notes v1.3.4](releases/v1.3.4.md)** | **[v1.2.5 Release Notes](releases/v1.2.5.md)** | **[Migration Guide](releases/v1.3.4.md#migration-guide)**
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
|
100
100
|
## What is PVW CLI?
|
|
101
101
|
|
|
102
|
-
**PVW CLI v1.
|
|
102
|
+
**PVW CLI v1.3.4** is a modern, full-featured command-line interface and Python library for Microsoft Purview. It enables automation and management of *all major Purview APIs* with **86% Unified Catalog API coverage** (45 of 52 operations).
|
|
103
103
|
|
|
104
104
|
### Key Capabilities
|
|
105
105
|
|
|
@@ -142,7 +142,7 @@ The CLI is designed for data engineers, stewards, architects, and platform teams
|
|
|
142
142
|
|
|
143
143
|
## What's New in Recent Releases
|
|
144
144
|
|
|
145
|
-
### v1.
|
|
145
|
+
### v1.3.4 (November 3, 2025) - Advanced Lineage Features
|
|
146
146
|
|
|
147
147
|
**Column-Level Lineage & Direct Relationships:**
|
|
148
148
|
- Column-level lineage with multi-target support (1→N)
|
|
@@ -166,7 +166,7 @@ source_entity_guid,target_entity_guid,relationship_type,column_mapping
|
|
|
166
166
|
guid1,guid2,direct_lineage_dataset_dataset,"[{""Source"":""ID"",""Sink"":""ID""}]"
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
**[Full v1.
|
|
169
|
+
**[Full v1.3.4 Release Notes](releases/v1.3.4.md)**
|
|
170
170
|
|
|
171
171
|
---
|
|
172
172
|
|
|
@@ -244,7 +244,7 @@ Version 1.2.5 achieves **86% coverage** of the Microsoft Purview Unified Catalog
|
|
|
244
244
|
- Complete API coverage gap analysis
|
|
245
245
|
- Roadmap to 100% with implementation plans
|
|
246
246
|
|
|
247
|
-
**[View Full Release Notes](releases/v1.
|
|
247
|
+
**[View Full Release Notes](releases/v1.3.4.md)**
|
|
248
248
|
|
|
249
249
|
---
|
|
250
250
|
|
|
@@ -323,7 +323,7 @@ For more advanced usage, see the documentation in `doc/` or the project docs: <h
|
|
|
323
323
|
|
|
324
324
|
## Quick Start Examples
|
|
325
325
|
|
|
326
|
-
### v1.
|
|
326
|
+
### v1.3.4 - Column-Level Lineage
|
|
327
327
|
|
|
328
328
|
```bash
|
|
329
329
|
# Create column-level lineage (Process-based)
|
|
@@ -414,7 +414,7 @@ pvw uc custom-attribute create --name "Department" --type String
|
|
|
414
414
|
|
|
415
415
|
## Overview
|
|
416
416
|
|
|
417
|
-
**PVW CLI v1.
|
|
417
|
+
**PVW CLI v1.3.4** is a modern command-line interface and Python library for Microsoft Purview, enabling:
|
|
418
418
|
|
|
419
419
|
- **MCP Server** - Natural language interface for AI assistants (Claude, Cline)
|
|
420
420
|
- Advanced data catalog search and discovery
|
|
@@ -779,7 +779,7 @@ The PVW CLI provides advanced search using the latest Microsoft Purview Discover
|
|
|
779
779
|
- Use autocomplete and suggestion endpoints
|
|
780
780
|
- Perform faceted, time-based, and entity-type-specific queries
|
|
781
781
|
|
|
782
|
-
**v1.
|
|
782
|
+
**v1.3.4 Improvements:**
|
|
783
783
|
|
|
784
784
|
- Fixed `suggest` and `autocomplete` API payload format (removed empty filter causing HTTP 400 errors)
|
|
785
785
|
- Enhanced collection display with robust type checking and fallback logic
|
|
@@ -1543,7 +1543,7 @@ PVW CLI includes comprehensive sample files and scripts for bulk operations:
|
|
|
1543
1543
|
- Success/failure tracking per term
|
|
1544
1544
|
- Rate limiting (200ms delay)
|
|
1545
1545
|
|
|
1546
|
-
### Critical Fixes (v1.
|
|
1546
|
+
### Critical Fixes (v1.3.4)
|
|
1547
1547
|
|
|
1548
1548
|
- **Search API Suggest/Autocomplete:** Fixed HTTP 400 errors by removing empty filter objects from payload
|
|
1549
1549
|
- **Collection Display:** Enhanced collection name detection with proper fallback logic (isinstance checks)
|
|
@@ -1606,9 +1606,9 @@ See [LICENSE](LICENSE) file for details.
|
|
|
1606
1606
|
|
|
1607
1607
|
---
|
|
1608
1608
|
|
|
1609
|
-
**PVW CLI v1.
|
|
1609
|
+
**PVW CLI v1.3.4 empowers data engineers, stewards, and architects to automate, scale, and enhance their Microsoft Purview experience with powerful command-line and programmatic capabilities.**
|
|
1610
1610
|
|
|
1611
|
-
**Latest in v1.
|
|
1611
|
+
**Latest in v1.3.4:**
|
|
1612
1612
|
|
|
1613
1613
|
- Fixed Search API suggest/autocomplete (HTTP 400 errors resolved)
|
|
1614
1614
|
- Enhanced collection display with robust fallback logic
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
purviewcli/__init__.py,sha256=
|
|
1
|
+
purviewcli/__init__.py,sha256=vX5E5CIbxmJO1rp23NSD8HG9cI0bcJGi_9NWHKZBcnY,413
|
|
2
2
|
purviewcli/__main__.py,sha256=n_PFo1PjW8L1OKCNLsW0vlVSo8tzac_saEYYLTu93iQ,372
|
|
3
3
|
purviewcli/cli/__init__.py,sha256=UGMctZaXXsV2l2ycnmhTgyksH81_JBQjAPq3oRF2Dqk,56
|
|
4
4
|
purviewcli/cli/account.py,sha256=Z_bwhKriMQpoBicOORM64wpQ1MJ94QG7jGKiaG-D_r8,7092
|
|
@@ -17,7 +17,7 @@ purviewcli/cli/scan.py,sha256=91iKDH8iVNJKndJAisrKx3J4HRoPH2qfmxguLZH3xHY,13807
|
|
|
17
17
|
purviewcli/cli/search.py,sha256=QNAKiFSi8oSEwzh-ksR4vhNAZ1z0dje1icdg36xRMvk,22667
|
|
18
18
|
purviewcli/cli/share.py,sha256=QRZhHM59RxdYqXOjSYLfVRZmjwMg4Y-bWxMSQVTQiIE,20197
|
|
19
19
|
purviewcli/cli/types.py,sha256=G9QPMPrizEqF36unTvKa2oWkycUV30FTKJu557l01cQ,39101
|
|
20
|
-
purviewcli/cli/unified_catalog.py,sha256=
|
|
20
|
+
purviewcli/cli/unified_catalog.py,sha256=JJiq4xjFju5XfEZ2cgnxivQ629Kny1TSowH1QmZZ3FU,156842
|
|
21
21
|
purviewcli/cli/workflow.py,sha256=c1Gmlffbs7DV_rBw7LIunyc2PukcRiV1Sv5ifnQBZD4,14735
|
|
22
22
|
purviewcli/client/__init__.py,sha256=ooz1vLcKZlw0ZKEvCADe0RJ84Z5sNQKbs3ZSev6w_-o,485
|
|
23
23
|
purviewcli/client/_account.py,sha256=mIZTxwmaQxN-8ePanlVZ2mEjUP1KVX-aJDfIl6PnAzk,64923
|
|
@@ -53,8 +53,8 @@ purviewcli/client/settings.py,sha256=nYdnYurTZsgv9vcgljnzVxLPtYVl9q6IplqOzi1aRvI
|
|
|
53
53
|
purviewcli/client/sync_client.py,sha256=Y23bUCN_x6ErPFSRGa62-PhSV01Gz2jQFpE9d-CdSt4,11338
|
|
54
54
|
purviewcli/plugins/__init__.py,sha256=rpt3OhFt_wSE_o8Ga8AXvw1pqkdBxLmjrhYtE_-LuJo,29
|
|
55
55
|
purviewcli/plugins/plugin_system.py,sha256=C-_dL4FUj90o1JS7Saxkpov6fz0GIF5PFhZTYwqBkWE,26774
|
|
56
|
-
pvw_cli-1.
|
|
57
|
-
pvw_cli-1.
|
|
58
|
-
pvw_cli-1.
|
|
59
|
-
pvw_cli-1.
|
|
60
|
-
pvw_cli-1.
|
|
56
|
+
pvw_cli-1.3.4.dist-info/METADATA,sha256=ipLJWUIxqXrqp-qlc-t6aYyyjuhQ0ACjMmlfq-Nj_iw,51566
|
|
57
|
+
pvw_cli-1.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
58
|
+
pvw_cli-1.3.4.dist-info/entry_points.txt,sha256=VI6AAbc6sWahOCX7sn_lhJIr9OiJM0pHF7rmw1YVGlE,82
|
|
59
|
+
pvw_cli-1.3.4.dist-info/top_level.txt,sha256=LrADzPoKwF1xY0pGKpWauyOVruHCIWKCkT7cwIl6IuI,11
|
|
60
|
+
pvw_cli-1.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|