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.
Files changed (60) hide show
  1. purviewcli/__init__.py +27 -0
  2. purviewcli/__main__.py +15 -0
  3. purviewcli/cli/__init__.py +5 -0
  4. purviewcli/cli/account.py +199 -0
  5. purviewcli/cli/cli.py +170 -0
  6. purviewcli/cli/collections.py +502 -0
  7. purviewcli/cli/domain.py +361 -0
  8. purviewcli/cli/entity.py +2436 -0
  9. purviewcli/cli/glossary.py +533 -0
  10. purviewcli/cli/health.py +250 -0
  11. purviewcli/cli/insight.py +113 -0
  12. purviewcli/cli/lineage.py +1103 -0
  13. purviewcli/cli/management.py +141 -0
  14. purviewcli/cli/policystore.py +103 -0
  15. purviewcli/cli/relationship.py +75 -0
  16. purviewcli/cli/scan.py +357 -0
  17. purviewcli/cli/search.py +527 -0
  18. purviewcli/cli/share.py +478 -0
  19. purviewcli/cli/types.py +831 -0
  20. purviewcli/cli/unified_catalog.py +3796 -0
  21. purviewcli/cli/workflow.py +402 -0
  22. purviewcli/client/__init__.py +21 -0
  23. purviewcli/client/_account.py +1877 -0
  24. purviewcli/client/_collections.py +1761 -0
  25. purviewcli/client/_domain.py +414 -0
  26. purviewcli/client/_entity.py +3545 -0
  27. purviewcli/client/_glossary.py +3233 -0
  28. purviewcli/client/_health.py +501 -0
  29. purviewcli/client/_insight.py +2873 -0
  30. purviewcli/client/_lineage.py +2138 -0
  31. purviewcli/client/_management.py +2202 -0
  32. purviewcli/client/_policystore.py +2915 -0
  33. purviewcli/client/_relationship.py +1351 -0
  34. purviewcli/client/_scan.py +2607 -0
  35. purviewcli/client/_search.py +1472 -0
  36. purviewcli/client/_share.py +272 -0
  37. purviewcli/client/_types.py +2708 -0
  38. purviewcli/client/_unified_catalog.py +5112 -0
  39. purviewcli/client/_workflow.py +2734 -0
  40. purviewcli/client/api_client.py +1295 -0
  41. purviewcli/client/business_rules.py +675 -0
  42. purviewcli/client/config.py +231 -0
  43. purviewcli/client/data_quality.py +433 -0
  44. purviewcli/client/endpoint.py +123 -0
  45. purviewcli/client/endpoints.py +554 -0
  46. purviewcli/client/exceptions.py +38 -0
  47. purviewcli/client/lineage_visualization.py +797 -0
  48. purviewcli/client/monitoring_dashboard.py +712 -0
  49. purviewcli/client/rate_limiter.py +30 -0
  50. purviewcli/client/retry_handler.py +125 -0
  51. purviewcli/client/scanning_operations.py +523 -0
  52. purviewcli/client/settings.py +1 -0
  53. purviewcli/client/sync_client.py +250 -0
  54. purviewcli/plugins/__init__.py +1 -0
  55. purviewcli/plugins/plugin_system.py +709 -0
  56. pvw_cli-1.3.3.dist-info/METADATA +1618 -0
  57. pvw_cli-1.3.3.dist-info/RECORD +60 -0
  58. pvw_cli-1.3.3.dist-info/WHEEL +5 -0
  59. pvw_cli-1.3.3.dist-info/entry_points.txt +3 -0
  60. pvw_cli-1.3.3.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