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.
Files changed (53) hide show
  1. msfabricpysdkcore/__init__.py +2 -1
  2. msfabricpysdkcore/admin_item.py +19 -45
  3. msfabricpysdkcore/admin_workspace.py +13 -60
  4. msfabricpysdkcore/adminapi.py +401 -476
  5. msfabricpysdkcore/auth.py +10 -6
  6. msfabricpysdkcore/client.py +124 -7
  7. msfabricpysdkcore/coreapi.py +2570 -822
  8. msfabricpysdkcore/deployment_pipeline.py +34 -146
  9. msfabricpysdkcore/domain.py +20 -219
  10. msfabricpysdkcore/environment.py +13 -172
  11. msfabricpysdkcore/fabric_azure_capacity.py +77 -0
  12. msfabricpysdkcore/fabric_azure_client.py +228 -0
  13. msfabricpysdkcore/item.py +55 -331
  14. msfabricpysdkcore/job_instance.py +8 -22
  15. msfabricpysdkcore/lakehouse.py +9 -118
  16. msfabricpysdkcore/long_running_operation.py +7 -37
  17. msfabricpysdkcore/onelakeshortcut.py +7 -21
  18. msfabricpysdkcore/otheritems.py +66 -91
  19. msfabricpysdkcore/spark_custom_pool.py +7 -47
  20. msfabricpysdkcore/tests/test_admin_apis.py +9 -10
  21. msfabricpysdkcore/tests/test_datapipelines.py +15 -18
  22. msfabricpysdkcore/tests/test_deployment_pipeline.py +3 -3
  23. msfabricpysdkcore/tests/test_domains.py +6 -5
  24. msfabricpysdkcore/tests/test_environments.py +54 -5
  25. msfabricpysdkcore/tests/test_evenhouses.py +47 -0
  26. msfabricpysdkcore/tests/test_evenstreams.py +20 -20
  27. msfabricpysdkcore/tests/test_external_data_shares.py +3 -3
  28. msfabricpysdkcore/tests/test_fabric_azure_client.py +78 -0
  29. msfabricpysdkcore/tests/test_git.py +8 -9
  30. msfabricpysdkcore/tests/test_items.py +81 -0
  31. msfabricpysdkcore/tests/test_jobs.py +2 -2
  32. msfabricpysdkcore/tests/test_kql_queryset.py +49 -0
  33. msfabricpysdkcore/tests/test_kqldatabases.py +3 -3
  34. msfabricpysdkcore/tests/test_lakehouse.py +84 -0
  35. msfabricpysdkcore/tests/test_ml_experiments.py +47 -0
  36. msfabricpysdkcore/tests/test_ml_models.py +47 -0
  37. msfabricpysdkcore/tests/test_notebooks.py +57 -0
  38. msfabricpysdkcore/tests/test_one_lake_data_access_security.py +2 -4
  39. msfabricpysdkcore/tests/test_other_items.py +45 -0
  40. msfabricpysdkcore/tests/test_reports.py +52 -0
  41. msfabricpysdkcore/tests/test_semantic_model.py +50 -0
  42. msfabricpysdkcore/tests/test_shortcuts.py +4 -4
  43. msfabricpysdkcore/tests/test_spark.py +9 -9
  44. msfabricpysdkcore/tests/test_sparkjobdefinition.py +2 -2
  45. msfabricpysdkcore/tests/test_warehouses.py +50 -0
  46. msfabricpysdkcore/tests/test_workspaces_capacities.py +16 -13
  47. msfabricpysdkcore/workspace.py +397 -1163
  48. {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/METADATA +72 -10
  49. msfabricpysdkcore-0.1.2.dist-info/RECORD +55 -0
  50. {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/WHEEL +1 -1
  51. msfabricpysdkcore-0.0.13.dist-info/RECORD +0 -41
  52. {msfabricpysdkcore-0.0.13.dist-info → msfabricpysdkcore-0.1.2.dist-info}/LICENSE +0 -0
  53. {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("https://api.fabric.microsoft.com/.default")
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")
@@ -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(tenant_id = self.tenant_id,
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