datasourcelib 0.1.8__py3-none-any.whl → 0.1.10__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/azure_devops_source.py +1 -2
- datasourcelib/datasources/dataverse_source.py +68 -7
- {datasourcelib-0.1.8.dist-info → datasourcelib-0.1.10.dist-info}/METADATA +1 -1
- {datasourcelib-0.1.8.dist-info → datasourcelib-0.1.10.dist-info}/RECORD +7 -7
- {datasourcelib-0.1.8.dist-info → datasourcelib-0.1.10.dist-info}/WHEEL +0 -0
- {datasourcelib-0.1.8.dist-info → datasourcelib-0.1.10.dist-info}/licenses/LICENSE +0 -0
- {datasourcelib-0.1.8.dist-info → datasourcelib-0.1.10.dist-info}/top_level.txt +0 -0
|
@@ -176,8 +176,7 @@ class AzureDevOpsSource(DataSourceBase):
|
|
|
176
176
|
"release_type": rtype,
|
|
177
177
|
"target_date": target_date,
|
|
178
178
|
"description": c_desc,
|
|
179
|
-
"full": fullfeature
|
|
180
|
-
"fields": norm_fields # full field set for this work item
|
|
179
|
+
"full": fullfeature
|
|
181
180
|
}
|
|
182
181
|
work_item_details.append(entry)
|
|
183
182
|
|
|
@@ -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:
|
|
@@ -5,11 +5,11 @@ datasourcelib/core/sync_manager.py,sha256=pfnvWv4AwmlJJUIsfxNNxYDBOsa7juTIxgFJIE
|
|
|
5
5
|
datasourcelib/core/sync_types.py,sha256=KVZB7PkfkFTzghoe--U8jLeAU8XAfba9qMRIVcUjuMc,297
|
|
6
6
|
datasourcelib/datasources/__init__.py,sha256=lZtgs0vT-2gub5UZo8BUnREZl3K_-_xYqUP8mjf8vhM,436
|
|
7
7
|
datasourcelib/datasources/azure_devops_source copy.py,sha256=g-IOCq5vGwwteU21jZPWW_GggMu1_myVJkP0_BmSdGY,7282
|
|
8
|
-
datasourcelib/datasources/azure_devops_source.py,sha256=
|
|
8
|
+
datasourcelib/datasources/azure_devops_source.py,sha256=J48E78AEfqkS-eBq7sesA48zmSiZ9oSfJkQjL7RAbyA,7928
|
|
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
15
|
datasourcelib/datasources/sql_source.py,sha256=pXs5UDAxRyRYuvw-zMNieJAZSqDndh6LlJy9GS6GoiY,7159
|
|
@@ -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.10.dist-info/licenses/LICENSE,sha256=9S0AcKETmp9XOcC73jEjN7WSkuSWGFGreiBat6ONClo,1087
|
|
33
|
+
datasourcelib-0.1.10.dist-info/METADATA,sha256=d3oHE59WsVQ52-wWztDEKwcwg51lUIVHTIGrss9HP7E,1200
|
|
34
|
+
datasourcelib-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
datasourcelib-0.1.10.dist-info/top_level.txt,sha256=wIwiwdIj8T9pAvE2TkGLUvT2oIi43C2vkkTKibUlv3U,14
|
|
36
|
+
datasourcelib-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|