datasourcelib 0.1.7__py3-none-any.whl → 0.1.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.
- datasourcelib/datasources/dataverse_source.py +68 -7
- datasourcelib/datasources/sql_source.py +16 -2
- {datasourcelib-0.1.7.dist-info → datasourcelib-0.1.9.dist-info}/METADATA +1 -1
- {datasourcelib-0.1.7.dist-info → datasourcelib-0.1.9.dist-info}/RECORD +7 -7
- {datasourcelib-0.1.7.dist-info → datasourcelib-0.1.9.dist-info}/WHEEL +0 -0
- {datasourcelib-0.1.7.dist-info → datasourcelib-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {datasourcelib-0.1.7.dist-info → datasourcelib-0.1.9.dist-info}/top_level.txt +0 -0
|
@@ -3,6 +3,7 @@ from datasourcelib.datasources.datasource_base import DataSourceBase
|
|
|
3
3
|
from datasourcelib.utils.logger import get_logger
|
|
4
4
|
from datasourcelib.utils.validators import require_keys
|
|
5
5
|
from datasourcelib.utils.aggregation import generate_grouped_summaries
|
|
6
|
+
from azure.identity import DefaultAzureCredential
|
|
6
7
|
import pyodbc
|
|
7
8
|
import time
|
|
8
9
|
import pandas as pd
|
|
@@ -26,12 +27,26 @@ class DataverseSource(DataSourceBase):
|
|
|
26
27
|
self._max_retries = int(self.config.get("dv_max_retries", 3))
|
|
27
28
|
|
|
28
29
|
def validate_config(self) -> bool:
|
|
30
|
+
|
|
29
31
|
"""
|
|
30
32
|
Validate required keys depending on selected dv_mode.
|
|
31
33
|
- tds: requires either 'tds_connection_string' OR ('dataverse_server' and 'dataverse_database')
|
|
32
|
-
- webapi:
|
|
34
|
+
- webapi:
|
|
35
|
+
* client credentials: 'dv_webapi_url','dv_webapi_client_id','dv_webapi_client_secret','dv_webapi_tenant_id'
|
|
36
|
+
* managed identity: 'dv_webapi_url' + dv_webapi_managed_identity_auth=True
|
|
33
37
|
"""
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
if self._mode == "webapi":
|
|
41
|
+
use_mi = bool(self.config.get("dv_webapi_managed_identity_auth", False))
|
|
42
|
+
require_keys(self.config, ["dv_webapi_url"])
|
|
43
|
+
if not use_mi:
|
|
44
|
+
require_keys(
|
|
45
|
+
self.config,
|
|
46
|
+
["dv_webapi_client_id", "dv_webapi_client_secret", "dv_webapi_tenant_id"]
|
|
47
|
+
)
|
|
48
|
+
return True
|
|
49
|
+
|
|
35
50
|
if self._mode == "webapi":
|
|
36
51
|
require_keys(self.config, ["dv_webapi_url", "dv_webapi_client_id", "dv_webapi_client_secret", "dv_webapi_tenant_id"])
|
|
37
52
|
else:
|
|
@@ -123,11 +138,44 @@ class DataverseSource(DataSourceBase):
|
|
|
123
138
|
return conn_str
|
|
124
139
|
|
|
125
140
|
def _obtain_webapi_token(self) -> Tuple[str, Dict[str, str]]:
|
|
141
|
+
|
|
126
142
|
"""
|
|
127
|
-
Acquire OAuth2 token
|
|
143
|
+
Acquire OAuth2 token for Dataverse Web API.
|
|
128
144
|
Returns (access_token, headers)
|
|
129
|
-
|
|
145
|
+
|
|
146
|
+
Paths:
|
|
147
|
+
- Managed Identity (dv_webapi_managed_identity_auth=True): uses DefaultAzureCredential and scope '<webapi_url>/.default'
|
|
148
|
+
- Client Credentials (dv_webapi_managed_identity_auth=False): uses OAuth2 client credentials with tenant/client/secret
|
|
130
149
|
"""
|
|
150
|
+
|
|
151
|
+
webapi_url = self.config["dv_webapi_url"].rstrip("/")
|
|
152
|
+
# Scope for MSAL-style request
|
|
153
|
+
scope = f"{webapi_url}/.default"
|
|
154
|
+
|
|
155
|
+
use_mi = bool(self.config.get("dv_webapi_managed_identity_auth", False))
|
|
156
|
+
|
|
157
|
+
# --- Managed Identity / DefaultAzureCredential path ---
|
|
158
|
+
if use_mi:
|
|
159
|
+
if DefaultAzureCredential is None:
|
|
160
|
+
raise RuntimeError(
|
|
161
|
+
"azure-identity package required for managed identity auth (pip install azure-identity)"
|
|
162
|
+
)
|
|
163
|
+
# DefaultAzureCredential works with system/user-assigned MI in Azure,
|
|
164
|
+
# and falls back to developer credentials locally (Azure CLI/VS Code).
|
|
165
|
+
credential = DefaultAzureCredential()
|
|
166
|
+
# Obtain token for Dataverse scope
|
|
167
|
+
token_obj = credential.get_token(scope)
|
|
168
|
+
token = token_obj.token
|
|
169
|
+
if not token:
|
|
170
|
+
raise RuntimeError("Failed to obtain Managed Identity token for Dataverse Web API")
|
|
171
|
+
headers = {
|
|
172
|
+
"Authorization": f"Bearer {token}",
|
|
173
|
+
"Accept": "application/json",
|
|
174
|
+
"OData-MaxVersion": "4.0",
|
|
175
|
+
"OData-Version": "4.0"
|
|
176
|
+
}
|
|
177
|
+
return token, headers
|
|
178
|
+
|
|
131
179
|
if requests is None:
|
|
132
180
|
raise RuntimeError("requests package required for Dataverse Web API mode")
|
|
133
181
|
tenant = self.config["dv_webapi_tenant_id"]
|
|
@@ -166,7 +214,10 @@ class DataverseSource(DataSourceBase):
|
|
|
166
214
|
self._access_token = token
|
|
167
215
|
self._headers = headers
|
|
168
216
|
self._connected = True
|
|
169
|
-
|
|
217
|
+
|
|
218
|
+
auth_mode = "managed identity" if bool(self.config.get("dv_webapi_managed_identity_auth", False)) else "client credentials"
|
|
219
|
+
logger.info("DataverseSource connected (webapi, %s) to %s", auth_mode, self.config.get("dv_webapi_url"))
|
|
220
|
+
|
|
170
221
|
return True
|
|
171
222
|
# else TDS mode
|
|
172
223
|
conn_str = self._build_tds_conn_str()
|
|
@@ -251,9 +302,19 @@ class DataverseSource(DataSourceBase):
|
|
|
251
302
|
# exclude SharePoint metadata columns (start with '__' or prefixed with '@')
|
|
252
303
|
cols_to_keep = [c for c in df.columns if not str(c).startswith("__") and not str(c).startswith("@")]
|
|
253
304
|
df = df[cols_to_keep]
|
|
305
|
+
summaries = generate_grouped_summaries(
|
|
306
|
+
df=df,
|
|
307
|
+
aggregation_field=self.config.get("dv_webapi_aggregation_field"),
|
|
308
|
+
row_format=self.config.get("dv_webapi_row_format"),
|
|
309
|
+
constants={"title": ""},
|
|
310
|
+
header_format=self.config.get("dv_webapi_header_format"),
|
|
311
|
+
sort_by=self.config.get("dv_webapi_sort_by"), # or a column/list if you want ordering
|
|
312
|
+
validate=True # ensures all placeholders exist
|
|
313
|
+
)
|
|
254
314
|
|
|
255
|
-
|
|
256
|
-
|
|
315
|
+
return summaries
|
|
316
|
+
#results = df.to_dict("records")
|
|
317
|
+
#return results
|
|
257
318
|
# else TDS mode
|
|
258
319
|
sql = query or self.config.get("dv_tds_query") or self.config.get("dv_sql_query")
|
|
259
320
|
if not sql:
|
|
@@ -2,9 +2,10 @@ from typing import Any, Dict, List, Optional
|
|
|
2
2
|
from datasourcelib.datasources.datasource_base import DataSourceBase
|
|
3
3
|
from datasourcelib.utils.logger import get_logger
|
|
4
4
|
from datasourcelib.utils.validators import require_keys
|
|
5
|
+
from datasourcelib.utils.aggregation import generate_grouped_summaries
|
|
5
6
|
import os
|
|
6
7
|
import pyodbc
|
|
7
|
-
|
|
8
|
+
import pandas as pd
|
|
8
9
|
|
|
9
10
|
logger = get_logger(__name__)
|
|
10
11
|
|
|
@@ -121,7 +122,20 @@ class SQLDataSource(DataSourceBase):
|
|
|
121
122
|
results: List[Dict[str, Any]] = []
|
|
122
123
|
for r in rows:
|
|
123
124
|
results.append({cols[i]: r[i] for i in range(len(cols))})
|
|
124
|
-
|
|
125
|
+
|
|
126
|
+
df = pd.DataFrame(results)
|
|
127
|
+
summaries = generate_grouped_summaries(
|
|
128
|
+
df=df,
|
|
129
|
+
aggregation_field=self.config.get("sql_aggregation_field"),
|
|
130
|
+
row_format=self.config.get("sql_aggregation_row_format"),
|
|
131
|
+
constants={"title": ""},
|
|
132
|
+
header_format=self.config.get("sql_aggregation_header_format"),
|
|
133
|
+
sort_by=self.config.get("sql_aggregation_sort_by"), # or a column/list if you want ordering
|
|
134
|
+
validate=True # ensures all placeholders exist
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return summaries
|
|
138
|
+
|
|
125
139
|
finally:
|
|
126
140
|
try:
|
|
127
141
|
cur.close()
|
|
@@ -9,10 +9,10 @@ datasourcelib/datasources/azure_devops_source.py,sha256=3hyZIrUdgwZEQNjb2iZGDMJc
|
|
|
9
9
|
datasourcelib/datasources/blob_source.py,sha256=Qk61_ulqUSPYDaiMzqgvJAu43c4AjTlDRdfFg4VwgDU,3574
|
|
10
10
|
datasourcelib/datasources/datasource_base.py,sha256=N8fOGvTl8oWWAiydLI0Joz66luq73a5yovO0XA9Q3jk,1068
|
|
11
11
|
datasourcelib/datasources/datasource_types.py,sha256=jpm4f9n1l7X9aBD58Pbr9evXiCHHEhRCLojGwchUD7A,205
|
|
12
|
-
datasourcelib/datasources/dataverse_source.py,sha256=
|
|
12
|
+
datasourcelib/datasources/dataverse_source.py,sha256=FRsdFdkTTXkSC_sY8IujAFBGRKCqG6xcRfres48IhaM,17021
|
|
13
13
|
datasourcelib/datasources/sharepoint_source - Copy.py,sha256=7V1c-zyvTo4IuPN_YMrKwLZFgbtipbP-mtunmXjOLJQ,17664
|
|
14
14
|
datasourcelib/datasources/sharepoint_source.py,sha256=t3rly2mVEI2qEDuUVqstck5ktkZW0BnF16Bke_NjPLI,23126
|
|
15
|
-
datasourcelib/datasources/sql_source.py,sha256=
|
|
15
|
+
datasourcelib/datasources/sql_source.py,sha256=pXs5UDAxRyRYuvw-zMNieJAZSqDndh6LlJy9GS6GoiY,7159
|
|
16
16
|
datasourcelib/datasources/sql_source_bkup.py,sha256=ntZjiFXpa7V797x7mAATJV0LH-g878VHuRw-QTxEe28,6372
|
|
17
17
|
datasourcelib/indexes/__init__.py,sha256=S8dz-lyxy1BTuDuLGRJNLrZD_1ku_FIUnDEm6HhMyT0,94
|
|
18
18
|
datasourcelib/indexes/azure_search_index.py,sha256=kznAz06UXgyT1Clqj6gRhnBQ5HFw40ZQHJElRFIcbRo,22115
|
|
@@ -29,8 +29,8 @@ datasourcelib/utils/exceptions.py,sha256=mgcDaW1k3VndgpMOwSm7NqgyRTvvE2a5ehn3x4f
|
|
|
29
29
|
datasourcelib/utils/file_reader.py,sha256=Zr0rwNTRWE6KeVJEXgTOPS1_JI74LiUSiX5-6qojmN0,7301
|
|
30
30
|
datasourcelib/utils/logger.py,sha256=Sl6lNlvubxtK9ztzyq7vjGVyA8_-pZ_ixpk5jfVsh6U,424
|
|
31
31
|
datasourcelib/utils/validators.py,sha256=fLgmRAb5OZSdMVlHu_n0RKJUDl-G8dI8JsRSfxIquh8,205
|
|
32
|
-
datasourcelib-0.1.
|
|
33
|
-
datasourcelib-0.1.
|
|
34
|
-
datasourcelib-0.1.
|
|
35
|
-
datasourcelib-0.1.
|
|
36
|
-
datasourcelib-0.1.
|
|
32
|
+
datasourcelib-0.1.9.dist-info/licenses/LICENSE,sha256=9S0AcKETmp9XOcC73jEjN7WSkuSWGFGreiBat6ONClo,1087
|
|
33
|
+
datasourcelib-0.1.9.dist-info/METADATA,sha256=e5FeHitCJ3JZaYoBST4sE4awseM3Sl7-kBVTAwVEXfk,1199
|
|
34
|
+
datasourcelib-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
datasourcelib-0.1.9.dist-info/top_level.txt,sha256=wIwiwdIj8T9pAvE2TkGLUvT2oIi43C2vkkTKibUlv3U,14
|
|
36
|
+
datasourcelib-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|