msfabricpysdkcore 0.0.13__py3-none-any.whl → 0.1.2__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.
- msfabricpysdkcore/__init__.py +2 -1
- msfabricpysdkcore/admin_item.py +19 -45
- msfabricpysdkcore/admin_workspace.py +13 -60
- msfabricpysdkcore/adminapi.py +401 -476
- msfabricpysdkcore/auth.py +10 -6
- msfabricpysdkcore/client.py +124 -7
- msfabricpysdkcore/coreapi.py +2570 -822
- msfabricpysdkcore/deployment_pipeline.py +34 -146
- msfabricpysdkcore/domain.py +20 -219
- msfabricpysdkcore/environment.py +13 -172
- msfabricpysdkcore/fabric_azure_capacity.py +77 -0
- msfabricpysdkcore/fabric_azure_client.py +228 -0
- msfabricpysdkcore/item.py +55 -331
- msfabricpysdkcore/job_instance.py +8 -22
- msfabricpysdkcore/lakehouse.py +9 -118
- msfabricpysdkcore/long_running_operation.py +7 -37
- msfabricpysdkcore/onelakeshortcut.py +7 -21
- msfabricpysdkcore/otheritems.py +66 -91
- msfabricpysdkcore/spark_custom_pool.py +7 -47
- msfabricpysdkcore/tests/test_admin_apis.py +9 -10
- msfabricpysdkcore/tests/test_datapipelines.py +15 -18
- msfabricpysdkcore/tests/test_deployment_pipeline.py +3 -3
- msfabricpysdkcore/tests/test_domains.py +6 -5
- msfabricpysdkcore/tests/test_environments.py +54 -5
- msfabricpysdkcore/tests/test_evenhouses.py +47 -0
- msfabricpysdkcore/tests/test_evenstreams.py +20 -20
- msfabricpysdkcore/tests/test_external_data_shares.py +3 -3
- msfabricpysdkcore/tests/test_fabric_azure_client.py +78 -0
- msfabricpysdkcore/tests/test_git.py +8 -9
- msfabricpysdkcore/tests/test_items.py +81 -0
- msfabricpysdkcore/tests/test_jobs.py +2 -2
- msfabricpysdkcore/tests/test_kql_queryset.py +49 -0
- msfabricpysdkcore/tests/test_kqldatabases.py +3 -3
- msfabricpysdkcore/tests/test_lakehouse.py +84 -0
- msfabricpysdkcore/tests/test_ml_experiments.py +47 -0
- msfabricpysdkcore/tests/test_ml_models.py +47 -0
- msfabricpysdkcore/tests/test_notebooks.py +57 -0
- msfabricpysdkcore/tests/test_one_lake_data_access_security.py +2 -4
- msfabricpysdkcore/tests/test_other_items.py +45 -0
- msfabricpysdkcore/tests/test_reports.py +52 -0
- msfabricpysdkcore/tests/test_semantic_model.py +50 -0
- msfabricpysdkcore/tests/test_shortcuts.py +4 -4
- msfabricpysdkcore/tests/test_spark.py +9 -9
- msfabricpysdkcore/tests/test_sparkjobdefinition.py +2 -2
- msfabricpysdkcore/tests/test_warehouses.py +50 -0
- msfabricpysdkcore/tests/test_workspaces_capacities.py +16 -13
- msfabricpysdkcore/workspace.py +397 -1163
- {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/METADATA +72 -10
- msfabricpysdkcore-0.1.2.dist-info/RECORD +55 -0
- {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/WHEEL +1 -1
- msfabricpysdkcore-0.0.13.dist-info/RECORD +0 -41
- {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/LICENSE +0 -0
- {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/top_level.txt +0 -0
msfabricpysdkcore/auth.py
CHANGED
@@ -8,6 +8,9 @@ except ImportError:
|
|
8
8
|
class FabricAuth():
|
9
9
|
"""FabricAuth class to interact with Entra ID"""
|
10
10
|
|
11
|
+
def __init__(self, scope):
|
12
|
+
self.scope = scope
|
13
|
+
|
11
14
|
@abstractmethod
|
12
15
|
def get_token(self):
|
13
16
|
"""Get token from Azure AD"""
|
@@ -26,20 +29,23 @@ class FabricAuth():
|
|
26
29
|
class FabricAuthClient(FabricAuth):
|
27
30
|
"""FabricAuthClient class to interact with Entra ID"""
|
28
31
|
|
29
|
-
def __init__(self, silent = False):
|
32
|
+
def __init__(self, scope, silent = False):
|
33
|
+
super().__init__(scope)
|
30
34
|
if not silent:
|
31
35
|
print("Using Azure CLI for authentication")
|
32
36
|
self.auth = AzureCliCredential()
|
33
37
|
|
34
38
|
def get_token(self):
|
35
39
|
"""Get token from Azure AD"""
|
36
|
-
token = self.auth.get_token(
|
40
|
+
token = self.auth.get_token(self.scope)
|
37
41
|
return token.token
|
38
42
|
|
39
43
|
class FabricServicePrincipal(FabricAuth):
|
40
44
|
"""FabricServicePrincipal class to interact with Entra ID"""
|
41
45
|
|
42
|
-
def __init__(self, tenant_id, client_id, client_secret, silent = False):
|
46
|
+
def __init__(self, tenant_id, client_id, client_secret, scope, silent = False):
|
47
|
+
super().__init__(scope)
|
48
|
+
|
43
49
|
if not silent:
|
44
50
|
print("Using Service Principal for authentication")
|
45
51
|
|
@@ -47,8 +53,6 @@ class FabricServicePrincipal(FabricAuth):
|
|
47
53
|
self.client_id = client_id
|
48
54
|
self.client_secret = client_secret
|
49
55
|
|
50
|
-
self.scope = "https://api.fabric.microsoft.com/.default"
|
51
|
-
|
52
56
|
|
53
57
|
def get_token(self):
|
54
58
|
"""Get token from Azure AD"""
|
@@ -67,7 +71,7 @@ class FabricServicePrincipal(FabricAuth):
|
|
67
71
|
class FabricSparkUtilsAuthentication(FabricAuth):
|
68
72
|
"""FabricSparkUtilsAuthentication class to interact with Entra ID"""
|
69
73
|
|
70
|
-
def __init__(self, silent = False):
|
74
|
+
def __init__(self, scope, silent = False):
|
71
75
|
mssparkutils.credentials.getToken("pbi")
|
72
76
|
if not silent:
|
73
77
|
print("Using Synapse Spark Utils for authentication")
|
msfabricpysdkcore/client.py
CHANGED
@@ -1,26 +1,143 @@
|
|
1
|
+
from abc import abstractmethod
|
1
2
|
import os
|
2
3
|
from time import sleep
|
4
|
+
import requests
|
5
|
+
import json
|
3
6
|
|
4
7
|
from msfabricpysdkcore.auth import FabricAuthClient, FabricServicePrincipal, FabricSparkUtilsAuthentication
|
5
8
|
|
6
9
|
class FabricClient():
|
7
10
|
"""FabricClient class to interact with Fabric API"""
|
8
11
|
|
9
|
-
def __init__(self, tenant_id = None, client_id = None, client_secret = None, silent=False) -> None:
|
12
|
+
def __init__(self, scope, tenant_id = None, client_id = None, client_secret = None, silent=False) -> None:
|
10
13
|
"""Initialize FabricClient object"""
|
11
14
|
self.tenant_id = tenant_id if tenant_id else os.getenv("FABRIC_TENANT_ID")
|
12
15
|
self.client_id = client_id if client_id else os.getenv("FABRIC_CLIENT_ID")
|
13
16
|
self.client_secret = client_secret if client_secret else os.getenv("FABRIC_CLIENT_SECRET")
|
14
|
-
|
15
|
-
self.scope = "https://api.fabric.microsoft.com/.default"
|
17
|
+
self.scope = scope
|
18
|
+
#self.scope = "https://api.fabric.microsoft.com/.default"
|
16
19
|
|
17
20
|
if self.client_id is None or self.client_secret is None or self.tenant_id is None:
|
18
21
|
try:
|
19
|
-
self.auth = FabricSparkUtilsAuthentication(silent=silent)
|
22
|
+
self.auth = FabricSparkUtilsAuthentication(self.scope, silent=silent)
|
20
23
|
except:
|
21
|
-
self.auth = FabricAuthClient(silent=silent)
|
24
|
+
self.auth = FabricAuthClient(self.scope, silent=silent)
|
22
25
|
else:
|
23
|
-
self.auth = FabricServicePrincipal(
|
26
|
+
self.auth = FabricServicePrincipal(scope= self.scope,
|
27
|
+
tenant_id = self.tenant_id,
|
24
28
|
client_id = self.client_id,
|
25
29
|
client_secret = self.client_secret,
|
26
|
-
silent=silent)
|
30
|
+
silent=silent)
|
31
|
+
|
32
|
+
def get_token(self):
|
33
|
+
"""Get token from Entra"""
|
34
|
+
return self.auth.get_token()
|
35
|
+
|
36
|
+
def calling_routine(self, url, operation, body = None, headers=None, file_path = None, response_codes = [200], error_message = "Error",
|
37
|
+
continue_on_error_code = False, return_format = "value_json", paging = False,
|
38
|
+
wait_for_completion = True, continuation_token = None):
|
39
|
+
"""Routine to make API calls
|
40
|
+
Args:
|
41
|
+
url (str): The URL of the API
|
42
|
+
operation (str): The operation to perform
|
43
|
+
body (dict): The body of the request
|
44
|
+
response_codes (list): The response codes to expect
|
45
|
+
error_message (str): The error message
|
46
|
+
continue_on_error_code (bool): Whether to continue on error code
|
47
|
+
return_format (str): The format of the return
|
48
|
+
paging (bool): Whether to paginate
|
49
|
+
wait_for_completion (bool): Whether to wait for the operation to complete
|
50
|
+
Returns:
|
51
|
+
dict: The response
|
52
|
+
"""
|
53
|
+
original_url = url
|
54
|
+
|
55
|
+
if continuation_token:
|
56
|
+
last_part_url = url.split("/")[-1]
|
57
|
+
if "?" not in last_part_url:
|
58
|
+
continuation_token = f"?continuationToken={continuation_token}"
|
59
|
+
else:
|
60
|
+
continuation_token = f"&continuationToken={continuation_token}"
|
61
|
+
url = f"{url}{continuation_token}"
|
62
|
+
|
63
|
+
response_codes.append(429)
|
64
|
+
if headers is None:
|
65
|
+
headers = self.auth.get_headers()
|
66
|
+
for _ in range(10):
|
67
|
+
if operation == "GET":
|
68
|
+
response = requests.get(url=url, headers=headers)
|
69
|
+
elif operation == "PATCH":
|
70
|
+
if body is None:
|
71
|
+
response = requests.patch(url=url, headers=headers)
|
72
|
+
else:
|
73
|
+
response = requests.patch(url=url, headers=headers, json=body)
|
74
|
+
elif operation == "POST":
|
75
|
+
if body is not None:
|
76
|
+
response = requests.post(url=url, headers=headers, json=body)
|
77
|
+
elif file_path is not None:
|
78
|
+
headers.pop('Content-Type')
|
79
|
+
with open(file_path, 'rb') as f:
|
80
|
+
files = {"file": f}
|
81
|
+
response = requests.post(url=url, files=files, headers=headers)
|
82
|
+
else:
|
83
|
+
response = requests.post(url=url, headers=headers)
|
84
|
+
elif operation == "PUT":
|
85
|
+
if body is None:
|
86
|
+
response = requests.put(url=url, headers=headers)
|
87
|
+
else:
|
88
|
+
response = requests.put(url=url, headers=headers, json=body)
|
89
|
+
elif operation == "DELETE":
|
90
|
+
response = requests.delete(url=url, headers=headers)
|
91
|
+
else:
|
92
|
+
raise ValueError("Invalid operation")
|
93
|
+
if response.status_code == 429:
|
94
|
+
print("Too many requests, waiting 10 seconds")
|
95
|
+
sleep(10)
|
96
|
+
continue
|
97
|
+
elif response.status_code == 202:
|
98
|
+
if wait_for_completion:
|
99
|
+
operation_result = self.long_running_operation(response.headers)
|
100
|
+
if "operation_result" in return_format:
|
101
|
+
return operation_result
|
102
|
+
return response
|
103
|
+
elif response.status_code not in response_codes:
|
104
|
+
if continue_on_error_code:
|
105
|
+
return response
|
106
|
+
raise Exception(f"{error_message}: {response.status_code} {response.text}")
|
107
|
+
break
|
108
|
+
|
109
|
+
if paging:
|
110
|
+
resp_dict = json.loads(response.text)
|
111
|
+
|
112
|
+
if return_format in ["data", "itemEntities", "Overrides", "accessEntities", "workspaces"]:
|
113
|
+
items = resp_dict[return_format]
|
114
|
+
else:
|
115
|
+
items = resp_dict["value"]
|
116
|
+
|
117
|
+
|
118
|
+
if "continuationToken" in resp_dict and resp_dict["continuationToken"]:
|
119
|
+
continuation_token = resp_dict["continuationToken"]
|
120
|
+
items_next = self.calling_routine(url=original_url, operation=operation, body=body, headers=headers,
|
121
|
+
response_codes=response_codes,
|
122
|
+
error_message=error_message, continuation_token=continuation_token,
|
123
|
+
return_format=return_format, paging=True, wait_for_completion=wait_for_completion)
|
124
|
+
items.extend(items_next)
|
125
|
+
if "etag" in return_format:
|
126
|
+
return items, response.headers.get('ETag')
|
127
|
+
return items
|
128
|
+
|
129
|
+
if "value_json" in return_format:
|
130
|
+
resp_dict = json.loads(response.text)
|
131
|
+
if "etag" in return_format:
|
132
|
+
return resp_dict["value"], response.headers.get('ETag')
|
133
|
+
return resp_dict["value"]
|
134
|
+
|
135
|
+
if "json" in return_format:
|
136
|
+
return json.loads(response.text)
|
137
|
+
|
138
|
+
return response
|
139
|
+
|
140
|
+
@abstractmethod
|
141
|
+
def long_running_operation(self, headers):
|
142
|
+
"""Long running operation"""
|
143
|
+
pass
|