pvw-cli 1.0.6__py3-none-any.whl → 1.0.9__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.

@@ -22,10 +22,15 @@ def get_data(http_dict):
22
22
  account_name = os.getenv(
23
23
  "PURVIEW_ACCOUNT_NAME", http_dict.get("account_name", "test-purview-account")
24
24
  )
25
+
26
+ # Get account ID from environment (optional)
27
+ account_id = os.getenv("PURVIEW_ACCOUNT_ID")
25
28
 
26
29
  # Create config
27
30
  config = SyncPurviewConfig(
28
- account_name=account_name, azure_region=os.getenv("AZURE_REGION", "public")
31
+ account_name=account_name,
32
+ azure_region=os.getenv("AZURE_REGION", "public"),
33
+ account_id=account_id
29
34
  )
30
35
 
31
36
  # Create synchronous client
@@ -39,6 +44,13 @@ def get_data(http_dict):
39
44
  json=http_dict.get("payload"),
40
45
  )
41
46
 
47
+ # The synchronous client returns a wrapper dict like
48
+ # {"status": "success", "data": <json>, "status_code": 200}
49
+ # Normalize to return the raw JSON payload when available so
50
+ # calling code (which expects the API JSON) works consistently
51
+ if isinstance(result, dict) and result.get("status") == "success" and "data" in result:
52
+ return result.get("data")
53
+
42
54
  return result
43
55
 
44
56
  except Exception as e:
@@ -13,9 +13,10 @@ from azure.core.exceptions import ClientAuthenticationError
13
13
  class SyncPurviewConfig:
14
14
  """Simple synchronous config"""
15
15
 
16
- def __init__(self, account_name: str, azure_region: str = "public"):
16
+ def __init__(self, account_name: str, azure_region: str = "public", account_id: Optional[str] = None):
17
17
  self.account_name = account_name
18
18
  self.azure_region = azure_region
19
+ self.account_id = account_id # Optional Purview account ID for UC endpoints
19
20
 
20
21
 
21
22
  class SyncPurviewClient:
@@ -24,7 +25,7 @@ class SyncPurviewClient:
24
25
  def __init__(self, config: SyncPurviewConfig):
25
26
  self.config = config
26
27
 
27
- # Set up endpoints based on Azure region
28
+ # Set up regular Purview API endpoints based on Azure region, using account name in the URL
28
29
  if config.azure_region and config.azure_region.lower() == "china":
29
30
  self.base_url = f"https://{config.account_name}.purview.azure.cn"
30
31
  self.auth_scope = "https://purview.azure.cn/.default"
@@ -35,11 +36,49 @@ class SyncPurviewClient:
35
36
  self.base_url = f"https://{config.account_name}.purview.azure.com"
36
37
  self.auth_scope = "https://purview.azure.net/.default"
37
38
 
39
+ # Set up Unified Catalog endpoint using Purview account ID format
40
+ self.account_id = config.account_id or self._get_purview_account_id()
41
+ self.uc_base_url = f"https://{self.account_id}-api.purview-service.microsoft.com"
42
+ self.uc_auth_scope = "73c2949e-da2d-457a-9607-fcc665198967/.default"
43
+
38
44
  self._token = None
45
+ self._uc_token = None
39
46
  self._credential = None
40
47
 
41
- def _get_authentication_token(self):
42
- """Get Azure authentication token"""
48
+ def _get_purview_account_id(self):
49
+ """Get Purview account ID from Atlas endpoint URL"""
50
+ account_id = os.getenv("PURVIEW_ACCOUNT_ID")
51
+ if not account_id:
52
+ import subprocess
53
+ try:
54
+ # Get the Atlas catalog endpoint and extract account ID from it
55
+ result = subprocess.run([
56
+ "az", "purview", "account", "show",
57
+ "--name", self.config.account_name,
58
+ "--resource-group", os.getenv("PURVIEW_RESOURCE_GROUP", "fabric-artifacts"),
59
+ "--query", "endpoints.catalog",
60
+ "-o", "tsv"
61
+ ], capture_output=True, text=True, check=True)
62
+ atlas_url = result.stdout.strip()
63
+
64
+ if atlas_url and "-api.purview-service.microsoft.com" in atlas_url:
65
+ account_id = atlas_url.split("://")[1].split("-api.purview-service.microsoft.com")[0]
66
+ else:
67
+ raise Exception(f"Could not extract account ID from Atlas URL: {atlas_url}")
68
+ except Exception as e:
69
+ # For Unified Catalog, the account ID is typically the Azure Tenant ID
70
+ try:
71
+ tenant_result = subprocess.run([
72
+ "az", "account", "show", "--query", "tenantId", "-o", "tsv"
73
+ ], capture_output=True, text=True, check=True)
74
+ account_id = tenant_result.stdout.strip()
75
+ print(f"Info: Using Tenant ID as Purview Account ID for Unified Catalog: {account_id}")
76
+ except Exception:
77
+ 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}")
78
+ return account_id
79
+
80
+ def _get_authentication_token(self, for_unified_catalog=False):
81
+ """Get Azure authentication token for regular Purview or Unified Catalog APIs"""
43
82
  try:
44
83
  # Try different authentication methods in order of preference
45
84
 
@@ -56,10 +95,15 @@ class SyncPurviewClient:
56
95
  # 2. Use default credential (managed identity, VS Code, CLI, etc.)
57
96
  self._credential = DefaultAzureCredential()
58
97
 
59
- # Get the token
60
- token = self._credential.get_token(self.auth_scope)
61
- self._token = token.token
62
- return self._token
98
+ # Get the appropriate token based on the API type
99
+ if for_unified_catalog:
100
+ token = self._credential.get_token(self.uc_auth_scope)
101
+ self._uc_token = token.token
102
+ return self._uc_token
103
+ else:
104
+ token = self._credential.get_token(self.auth_scope)
105
+ self._token = token.token
106
+ return self._token
63
107
 
64
108
  except ClientAuthenticationError as e:
65
109
  raise Exception(f"Azure authentication failed: {str(e)}")
@@ -69,12 +113,25 @@ class SyncPurviewClient:
69
113
  def make_request(self, method: str, endpoint: str, **kwargs) -> Dict:
70
114
  """Make actual HTTP request to Microsoft Purview"""
71
115
  try:
72
- # Get authentication token
73
- if not self._token:
74
- self._get_authentication_token() # Prepare the request
75
- url = f"{self.base_url}{endpoint}"
116
+ # Determine if this is a Unified Catalog request
117
+ is_unified_catalog = endpoint.startswith('/datagovernance/catalog')
118
+
119
+ # Get the appropriate authentication token and base URL
120
+ if is_unified_catalog:
121
+ if not self._uc_token:
122
+ self._get_authentication_token(for_unified_catalog=True)
123
+ token = self._uc_token
124
+ base_url = self.uc_base_url
125
+ else:
126
+ if not self._token:
127
+ self._get_authentication_token(for_unified_catalog=False)
128
+ token = self._token
129
+ base_url = self.base_url
130
+
131
+ # Prepare the request
132
+ url = f"{base_url}{endpoint}"
76
133
  headers = {
77
- "Authorization": f"Bearer {self._token}",
134
+ "Authorization": f"Bearer {token}",
78
135
  "Content-Type": "application/json",
79
136
  "User-Agent": "purviewcli/2.0",
80
137
  }
@@ -89,7 +146,7 @@ class SyncPurviewClient:
89
146
  timeout=30,
90
147
  )
91
148
  # Handle the response
92
- if response.status_code == 200:
149
+ if response.status_code in [200, 201]:
93
150
  try:
94
151
  data = response.json()
95
152
  return {"status": "success", "data": data, "status_code": response.status_code}
@@ -115,7 +172,7 @@ class SyncPurviewClient:
115
172
  timeout=30,
116
173
  )
117
174
 
118
- if response.status_code == 200:
175
+ if response.status_code in [200, 201]:
119
176
  try:
120
177
  data = response.json()
121
178
  return {