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
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Synchronous Purview Client for CLI compatibility
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
from azure.identity import DefaultAzureCredential, ClientSecretCredential
|
|
10
|
+
from azure.core.exceptions import ClientAuthenticationError
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util.retry import Retry
|
|
13
|
+
import ssl
|
|
14
|
+
import urllib3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SyncPurviewConfig:
|
|
18
|
+
"""Simple synchronous config"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, account_name: str, azure_region: str = "public", account_id: Optional[str] = None):
|
|
21
|
+
self.account_name = account_name
|
|
22
|
+
self.azure_region = azure_region
|
|
23
|
+
self.account_id = account_id # Optional Purview account ID for UC endpoints
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SyncPurviewClient:
|
|
27
|
+
"""Synchronous client for CLI operations with real Azure authentication"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: SyncPurviewConfig):
|
|
30
|
+
self.config = config
|
|
31
|
+
|
|
32
|
+
# Set up regular Purview API endpoints based on Azure region, using account name in the URL
|
|
33
|
+
if config.azure_region and config.azure_region.lower() == "china":
|
|
34
|
+
self.base_url = f"https://{config.account_name}.purview.azure.cn"
|
|
35
|
+
self.auth_scope = "https://purview.azure.cn/.default"
|
|
36
|
+
elif config.azure_region and config.azure_region.lower() == "usgov":
|
|
37
|
+
self.base_url = f"https://{config.account_name}.purview.azure.us"
|
|
38
|
+
self.auth_scope = "https://purview.azure.us/.default"
|
|
39
|
+
else:
|
|
40
|
+
self.base_url = f"https://{config.account_name}.purview.azure.com"
|
|
41
|
+
self.auth_scope = "https://purview.azure.net/.default"
|
|
42
|
+
|
|
43
|
+
# Set up Unified Catalog endpoint using Purview account ID format
|
|
44
|
+
self.account_id = config.account_id or self._get_purview_account_id()
|
|
45
|
+
self.uc_base_url = f"https://{self.account_id}-api.purview-service.microsoft.com"
|
|
46
|
+
self.uc_auth_scope = "73c2949e-da2d-457a-9607-fcc665198967/.default"
|
|
47
|
+
|
|
48
|
+
self._token = None
|
|
49
|
+
self._uc_token = None
|
|
50
|
+
self._credential = None
|
|
51
|
+
|
|
52
|
+
# Configure session with retry strategy for Azure Front Door SSL issues
|
|
53
|
+
self._session = self._create_session_with_retries()
|
|
54
|
+
|
|
55
|
+
def _create_session_with_retries(self):
|
|
56
|
+
"""Create a requests session with retry strategy and SSL workarounds for Azure Front Door"""
|
|
57
|
+
session = requests.Session()
|
|
58
|
+
|
|
59
|
+
# Retry strategy for transient errors and SSL issues
|
|
60
|
+
retry_strategy = Retry(
|
|
61
|
+
total=5, # Total number of retries
|
|
62
|
+
backoff_factor=1, # Wait 1, 2, 4, 8, 16 seconds between retries
|
|
63
|
+
status_forcelist=[429, 500, 502, 503, 504], # Retry on these HTTP status codes
|
|
64
|
+
allowed_methods=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"] # Retry on all methods
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
68
|
+
session.mount("https://", adapter)
|
|
69
|
+
session.mount("http://", adapter)
|
|
70
|
+
|
|
71
|
+
# Workaround for Azure Front Door SSL issues (TLS inspection, protocol mismatch)
|
|
72
|
+
# Disable SSL verification warnings (only if needed in corporate environments)
|
|
73
|
+
if os.getenv("PURVIEW_DISABLE_SSL_VERIFY", "false").lower() == "true":
|
|
74
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
75
|
+
session.verify = False
|
|
76
|
+
|
|
77
|
+
return session
|
|
78
|
+
|
|
79
|
+
def _get_purview_account_id(self):
|
|
80
|
+
"""Get Purview account ID from Atlas endpoint URL"""
|
|
81
|
+
account_id = os.getenv("PURVIEW_ACCOUNT_ID")
|
|
82
|
+
if not account_id:
|
|
83
|
+
import subprocess
|
|
84
|
+
try:
|
|
85
|
+
# Get the Atlas catalog endpoint and extract account ID from it
|
|
86
|
+
result = subprocess.run([
|
|
87
|
+
"az", "purview", "account", "show",
|
|
88
|
+
"--name", self.config.account_name,
|
|
89
|
+
"--resource-group", os.getenv("PURVIEW_RESOURCE_GROUP", "fabric-artifacts"),
|
|
90
|
+
"--query", "endpoints.catalog",
|
|
91
|
+
"-o", "tsv"
|
|
92
|
+
], capture_output=True, text=True, check=True)
|
|
93
|
+
atlas_url = result.stdout.strip()
|
|
94
|
+
|
|
95
|
+
if atlas_url and "-api.purview-service.microsoft.com" in atlas_url:
|
|
96
|
+
account_id = atlas_url.split("://")[1].split("-api.purview-service.microsoft.com")[0]
|
|
97
|
+
else:
|
|
98
|
+
raise Exception(f"Could not extract account ID from Atlas URL: {atlas_url}")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
# For Unified Catalog, the account ID is typically the Azure Tenant ID
|
|
101
|
+
try:
|
|
102
|
+
tenant_result = subprocess.run([
|
|
103
|
+
"az", "account", "show", "--query", "tenantId", "-o", "tsv"
|
|
104
|
+
], capture_output=True, text=True, check=True)
|
|
105
|
+
account_id = tenant_result.stdout.strip()
|
|
106
|
+
print(f"Info: Using Tenant ID as Purview Account ID for Unified Catalog: {account_id}")
|
|
107
|
+
except Exception:
|
|
108
|
+
raise Exception(f"Could not determine Purview account ID. For Unified Catalog, this is typically your Azure Tenant ID. Please set PURVIEW_ACCOUNT_ID environment variable. Error: {e}")
|
|
109
|
+
return account_id
|
|
110
|
+
|
|
111
|
+
def _get_authentication_token(self, for_unified_catalog=False):
|
|
112
|
+
"""Get Azure authentication token for regular Purview or Unified Catalog APIs"""
|
|
113
|
+
try:
|
|
114
|
+
# Try different authentication methods in order of preference
|
|
115
|
+
|
|
116
|
+
# 1. Try client credentials if available
|
|
117
|
+
client_id = os.getenv("AZURE_CLIENT_ID")
|
|
118
|
+
client_secret = os.getenv("AZURE_CLIENT_SECRET")
|
|
119
|
+
tenant_id = os.getenv("AZURE_TENANT_ID")
|
|
120
|
+
|
|
121
|
+
if client_id and client_secret and tenant_id:
|
|
122
|
+
self._credential = ClientSecretCredential(
|
|
123
|
+
tenant_id=tenant_id, client_id=client_id, client_secret=client_secret
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
# 2. Use default credential (managed identity, VS Code, CLI, etc.)
|
|
127
|
+
self._credential = DefaultAzureCredential()
|
|
128
|
+
|
|
129
|
+
# Get the appropriate token based on the API type
|
|
130
|
+
if for_unified_catalog:
|
|
131
|
+
token = self._credential.get_token(self.uc_auth_scope)
|
|
132
|
+
self._uc_token = token.token
|
|
133
|
+
return self._uc_token
|
|
134
|
+
else:
|
|
135
|
+
token = self._credential.get_token(self.auth_scope)
|
|
136
|
+
self._token = token.token
|
|
137
|
+
return self._token
|
|
138
|
+
|
|
139
|
+
except ClientAuthenticationError as e:
|
|
140
|
+
raise Exception(f"Azure authentication failed: {str(e)}")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
raise Exception(f"Failed to get authentication token: {str(e)}")
|
|
143
|
+
|
|
144
|
+
def make_request(self, method: str, endpoint: str, **kwargs) -> Dict:
|
|
145
|
+
"""Make actual HTTP request to Microsoft Purview"""
|
|
146
|
+
try:
|
|
147
|
+
# Determine if this is a Unified Catalog / Data Map (Atlas) request
|
|
148
|
+
# Several endpoints use '/catalog' or '/datamap' prefixes (Atlas/DataMap APIs)
|
|
149
|
+
is_unified_catalog = (
|
|
150
|
+
endpoint.startswith('/datagovernance/catalog')
|
|
151
|
+
or endpoint.startswith('/catalog')
|
|
152
|
+
or endpoint.startswith('/datamap')
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Get the appropriate authentication token and base URL
|
|
156
|
+
if is_unified_catalog:
|
|
157
|
+
if not self._uc_token:
|
|
158
|
+
self._get_authentication_token(for_unified_catalog=True)
|
|
159
|
+
token = self._uc_token
|
|
160
|
+
base_url = self.uc_base_url
|
|
161
|
+
else:
|
|
162
|
+
if not self._token:
|
|
163
|
+
self._get_authentication_token(for_unified_catalog=False)
|
|
164
|
+
token = self._token
|
|
165
|
+
base_url = self.base_url
|
|
166
|
+
|
|
167
|
+
# Prepare the request
|
|
168
|
+
url = f"{base_url}{endpoint}"
|
|
169
|
+
headers = {
|
|
170
|
+
"Authorization": f"Bearer {token}",
|
|
171
|
+
"Content-Type": "application/json",
|
|
172
|
+
"User-Agent": "purviewcli/2.0",
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Make the actual HTTP request using session with retries
|
|
176
|
+
response = self._session.request(
|
|
177
|
+
method=method.upper(),
|
|
178
|
+
url=url,
|
|
179
|
+
headers=headers,
|
|
180
|
+
params=kwargs.get("params"),
|
|
181
|
+
json=kwargs.get("json"),
|
|
182
|
+
timeout=60, # Increased timeout for Azure Front Door
|
|
183
|
+
)
|
|
184
|
+
# Handle the response
|
|
185
|
+
if response.status_code in [200, 201]:
|
|
186
|
+
try:
|
|
187
|
+
data = response.json()
|
|
188
|
+
return {"status": "success", "data": data, "status_code": response.status_code}
|
|
189
|
+
except json.JSONDecodeError:
|
|
190
|
+
return {
|
|
191
|
+
"status": "success",
|
|
192
|
+
"data": response.text,
|
|
193
|
+
"status_code": response.status_code,
|
|
194
|
+
}
|
|
195
|
+
elif response.status_code == 401:
|
|
196
|
+
# Token might be expired, try to refresh
|
|
197
|
+
if is_unified_catalog:
|
|
198
|
+
self._uc_token = None
|
|
199
|
+
self._get_authentication_token(for_unified_catalog=True)
|
|
200
|
+
token = self._uc_token
|
|
201
|
+
else:
|
|
202
|
+
self._token = None
|
|
203
|
+
self._get_authentication_token(for_unified_catalog=False)
|
|
204
|
+
token = self._token
|
|
205
|
+
|
|
206
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
207
|
+
|
|
208
|
+
# Retry the request with session
|
|
209
|
+
response = self._session.request(
|
|
210
|
+
method=method.upper(),
|
|
211
|
+
url=url,
|
|
212
|
+
headers=headers,
|
|
213
|
+
params=kwargs.get("params"),
|
|
214
|
+
json=kwargs.get("json"),
|
|
215
|
+
timeout=60,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if response.status_code in [200, 201]:
|
|
219
|
+
try:
|
|
220
|
+
data = response.json()
|
|
221
|
+
return {
|
|
222
|
+
"status": "success",
|
|
223
|
+
"data": data,
|
|
224
|
+
"status_code": response.status_code,
|
|
225
|
+
}
|
|
226
|
+
except json.JSONDecodeError:
|
|
227
|
+
return {
|
|
228
|
+
"status": "success",
|
|
229
|
+
"data": response.text,
|
|
230
|
+
"status_code": response.status_code,
|
|
231
|
+
}
|
|
232
|
+
else:
|
|
233
|
+
return {
|
|
234
|
+
"status": "error",
|
|
235
|
+
"message": f"HTTP {response.status_code}: {response.text}",
|
|
236
|
+
"status_code": response.status_code,
|
|
237
|
+
}
|
|
238
|
+
else:
|
|
239
|
+
return {
|
|
240
|
+
"status": "error",
|
|
241
|
+
"message": f"HTTP {response.status_code}: {response.text}",
|
|
242
|
+
"status_code": response.status_code,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
except requests.exceptions.Timeout:
|
|
246
|
+
return {"status": "error", "message": "Request timed out after 30 seconds"}
|
|
247
|
+
except requests.exceptions.ConnectionError:
|
|
248
|
+
return {"status": "error", "message": f"Failed to connect to {self.base_url}"}
|
|
249
|
+
except Exception as e:
|
|
250
|
+
return {"status": "error", "message": f"Request failed: {str(e)}"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Plugins module for pvw-cli
|