atlan-application-sdk 2.1.0__py3-none-any.whl → 2.1.1__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.
- application_sdk/activities/__init__.py +14 -0
- application_sdk/activities/metadata_extraction/sql.py +40 -2
- application_sdk/transformers/query/__init__.py +4 -3
- application_sdk/version.py +1 -1
- {atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/METADATA +2 -2
- {atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/RECORD +9 -9
- {atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/WHEEL +0 -0
- {atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -15,6 +15,7 @@ Example:
|
|
|
15
15
|
|
|
16
16
|
import os
|
|
17
17
|
from abc import ABC
|
|
18
|
+
from datetime import datetime, timedelta
|
|
18
19
|
from typing import Any, Dict, Generic, Optional, TypeVar
|
|
19
20
|
|
|
20
21
|
from pydantic import BaseModel
|
|
@@ -62,6 +63,7 @@ class ActivitiesState(BaseModel, Generic[HandlerType]):
|
|
|
62
63
|
model_config = {"arbitrary_types_allowed": True}
|
|
63
64
|
handler: Optional[HandlerType] = None
|
|
64
65
|
workflow_args: Optional[Dict[str, Any]] = None
|
|
66
|
+
last_updated_timestamp: Optional[datetime] = None
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
ActivitiesStateType = TypeVar("ActivitiesStateType", bound=ActivitiesState)
|
|
@@ -113,12 +115,15 @@ class ActivitiesInterface(ABC, Generic[ActivitiesStateType]):
|
|
|
113
115
|
Note:
|
|
114
116
|
The workflow ID is automatically retrieved from the current activity context.
|
|
115
117
|
If no state exists for the current workflow, a new one will be created.
|
|
118
|
+
This method also updates the last_updated_timestamp to enable time-based
|
|
119
|
+
state refresh functionality.
|
|
116
120
|
"""
|
|
117
121
|
workflow_id = get_workflow_id()
|
|
118
122
|
if not self._state.get(workflow_id):
|
|
119
123
|
self._state[workflow_id] = ActivitiesState()
|
|
120
124
|
|
|
121
125
|
self._state[workflow_id].workflow_args = workflow_args
|
|
126
|
+
self._state[workflow_id].last_updated_timestamp = datetime.now()
|
|
122
127
|
|
|
123
128
|
async def _get_state(self, workflow_args: Dict[str, Any]) -> ActivitiesStateType:
|
|
124
129
|
"""Retrieve the state for the current workflow.
|
|
@@ -142,6 +147,15 @@ class ActivitiesInterface(ABC, Generic[ActivitiesStateType]):
|
|
|
142
147
|
workflow_id = get_workflow_id()
|
|
143
148
|
if workflow_id not in self._state:
|
|
144
149
|
await self._set_state(workflow_args)
|
|
150
|
+
|
|
151
|
+
else:
|
|
152
|
+
current_timestamp = datetime.now()
|
|
153
|
+
# if difference of current_timestamp and last_updated_timestamp is greater than 15 minutes, then again _set_state
|
|
154
|
+
last_updated = self._state[workflow_id].last_updated_timestamp
|
|
155
|
+
if last_updated and current_timestamp - last_updated > timedelta(
|
|
156
|
+
minutes=15
|
|
157
|
+
):
|
|
158
|
+
await self._set_state(workflow_args)
|
|
145
159
|
return self._state[workflow_id]
|
|
146
160
|
except OrchestratorError as e:
|
|
147
161
|
logger.error(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from typing import (
|
|
3
4
|
TYPE_CHECKING,
|
|
4
5
|
Any,
|
|
@@ -60,6 +61,7 @@ class BaseSQLMetadataExtractionActivitiesState(ActivitiesState):
|
|
|
60
61
|
sql_client: Optional[BaseSQLClient] = None
|
|
61
62
|
handler: Optional[BaseSQLHandler] = None
|
|
62
63
|
transformer: Optional[TransformerInterface] = None
|
|
64
|
+
last_updated_timestamp: Optional[datetime] = None
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
class BaseSQLMetadataExtractionActivities(ActivitiesInterface):
|
|
@@ -149,13 +151,30 @@ class BaseSQLMetadataExtractionActivities(ActivitiesInterface):
|
|
|
149
151
|
|
|
150
152
|
Args:
|
|
151
153
|
workflow_args (Dict[str, Any]): Arguments passed to the workflow.
|
|
154
|
+
|
|
155
|
+
Note:
|
|
156
|
+
This method creates and configures the new SQL client before closing
|
|
157
|
+
the old one to ensure state is never left with a closed client if
|
|
158
|
+
initialization fails. The timestamp is only updated after the new
|
|
159
|
+
client is successfully created and assigned.
|
|
152
160
|
"""
|
|
153
161
|
workflow_id = get_workflow_id()
|
|
154
162
|
if not self._state.get(workflow_id):
|
|
155
163
|
self._state[workflow_id] = BaseSQLMetadataExtractionActivitiesState()
|
|
156
164
|
|
|
157
|
-
|
|
165
|
+
existing_state = self._state[workflow_id]
|
|
166
|
+
|
|
167
|
+
# Update workflow_args early, but preserve old timestamp until new client is ready
|
|
168
|
+
# This ensures that if initialization fails, the state can still be refreshed
|
|
169
|
+
existing_state.workflow_args = workflow_args
|
|
170
|
+
|
|
171
|
+
# Store reference to old client for cleanup after new client is ready
|
|
172
|
+
old_sql_client = None
|
|
173
|
+
if existing_state and existing_state.sql_client is not None:
|
|
174
|
+
old_sql_client = existing_state.sql_client
|
|
158
175
|
|
|
176
|
+
# Create and configure new client BEFORE closing old one
|
|
177
|
+
# This ensures state is never left with a closed client if initialization fails
|
|
159
178
|
sql_client = self.sql_client_class()
|
|
160
179
|
|
|
161
180
|
# Load credentials BEFORE creating handler to avoid race condition
|
|
@@ -165,10 +184,29 @@ class BaseSQLMetadataExtractionActivities(ActivitiesInterface):
|
|
|
165
184
|
)
|
|
166
185
|
await sql_client.load(credentials)
|
|
167
186
|
|
|
168
|
-
#
|
|
187
|
+
# Only after new client is successfully created and configured,
|
|
188
|
+
# close old client and assign new one to state
|
|
189
|
+
if old_sql_client is not None:
|
|
190
|
+
try:
|
|
191
|
+
await old_sql_client.close()
|
|
192
|
+
logger.debug(
|
|
193
|
+
f"Closed existing SQL client for workflow {workflow_id} during state refresh"
|
|
194
|
+
)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.warning(
|
|
197
|
+
f"Failed to close existing SQL client for workflow {workflow_id}: {e}",
|
|
198
|
+
exc_info=True,
|
|
199
|
+
)
|
|
200
|
+
# Continue even if close fails - new client is already ready
|
|
201
|
+
|
|
202
|
+
# Assign sql_client and handler to state AFTER new client is ready
|
|
169
203
|
self._state[workflow_id].sql_client = sql_client
|
|
170
204
|
handler = self.handler_class(sql_client)
|
|
171
205
|
self._state[workflow_id].handler = handler
|
|
206
|
+
# Update timestamp only after successful client creation and assignment
|
|
207
|
+
# This ensures that if initialization fails, the old timestamp remains
|
|
208
|
+
# and the state can be refreshed again immediately
|
|
209
|
+
self._state[workflow_id].last_updated_timestamp = datetime.now()
|
|
172
210
|
|
|
173
211
|
# Create transformer with required parameters from ApplicationConstants
|
|
174
212
|
transformer_params = {
|
|
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple, Type
|
|
|
4
4
|
|
|
5
5
|
import daft
|
|
6
6
|
import yaml
|
|
7
|
+
from daft.functions import to_struct, when
|
|
7
8
|
from pyatlan.model.enums import AtlanConnectorType
|
|
8
9
|
|
|
9
10
|
from application_sdk.observability.logger_adaptor import get_logger
|
|
@@ -227,7 +228,7 @@ class QueryBasedTransformer(TransformerInterface):
|
|
|
227
228
|
# Only create a struct if we have fields
|
|
228
229
|
if struct_fields:
|
|
229
230
|
# Create the struct first
|
|
230
|
-
struct =
|
|
231
|
+
struct = to_struct(*struct_fields)
|
|
231
232
|
|
|
232
233
|
# If we have non-null checks, apply them
|
|
233
234
|
if non_null_fields:
|
|
@@ -236,8 +237,8 @@ class QueryBasedTransformer(TransformerInterface):
|
|
|
236
237
|
for check in non_null_fields[1:]:
|
|
237
238
|
any_non_null = any_non_null | check
|
|
238
239
|
|
|
239
|
-
# Use if_else
|
|
240
|
-
return any_non_null.
|
|
240
|
+
# Use when().otherwise() for conditional expression (replaces if_else in daft 0.7+)
|
|
241
|
+
return when(any_non_null, struct).otherwise(None).alias(prefix)
|
|
241
242
|
|
|
242
243
|
return struct.alias(prefix)
|
|
243
244
|
|
application_sdk/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atlan-application-sdk
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Atlan Application SDK is a Python library for developing applications on the Atlan Platform
|
|
5
5
|
Project-URL: Repository, https://github.com/atlanhq/application-sdk
|
|
6
6
|
Project-URL: Documentation, https://github.com/atlanhq/application-sdk/README.md
|
|
@@ -31,7 +31,7 @@ Requires-Dist: pydantic<2.13.0,>=2.10.6
|
|
|
31
31
|
Requires-Dist: python-dotenv<1.3.0,>=1.1.0
|
|
32
32
|
Requires-Dist: uvloop<0.23.0,>=0.21.0; sys_platform != 'win32'
|
|
33
33
|
Provides-Extra: daft
|
|
34
|
-
Requires-Dist: daft<0.8.0,>=0.
|
|
34
|
+
Requires-Dist: daft<0.8.0,>=0.7.1; extra == 'daft'
|
|
35
35
|
Provides-Extra: distributed-lock
|
|
36
36
|
Requires-Dist: redis[hiredis]<7.2.0,>=5.2.0; extra == 'distributed-lock'
|
|
37
37
|
Provides-Extra: iam-auth
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
application_sdk/__init__.py,sha256=2e2mvmLJ5dxmJGPELtb33xwP-j6JMdoIuqKycEn7hjg,151
|
|
2
2
|
application_sdk/constants.py,sha256=TvdmKQShVWBNQZdVF2y-fxuE31FmeraTnqQ9jT_n5XY,11567
|
|
3
|
-
application_sdk/version.py,sha256=
|
|
3
|
+
application_sdk/version.py,sha256=sNbvXviG7NgxM58lOHKhbZfERat5qAJNr3UZy_toVQs,84
|
|
4
4
|
application_sdk/worker.py,sha256=DLMocpHvvwpdAopyXhxwM7ftaNlKvZMQfkgy1MFyiik,7561
|
|
5
|
-
application_sdk/activities/__init__.py,sha256=
|
|
5
|
+
application_sdk/activities/__init__.py,sha256=i7iY6aL1VFg185n2rLLvD_sI2BA9zJ33jL5rD_sY__U,12350
|
|
6
6
|
application_sdk/activities/lock_management.py,sha256=6Wdf3jMKitoarHQP91PIJOoGFz4aaOLS_40c7n1yAOA,3902
|
|
7
7
|
application_sdk/activities/.cursor/BUGBOT.md,sha256=FNykX5aMkdOhzgpiGqstOnSp9JN63iR2XP3onU4AGh8,15843
|
|
8
8
|
application_sdk/activities/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -12,7 +12,7 @@ application_sdk/activities/common/utils.py,sha256=ngyFmiZnMCAQtyu6vGeAlkzwNkM29M
|
|
|
12
12
|
application_sdk/activities/metadata_extraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
application_sdk/activities/metadata_extraction/base.py,sha256=ENFojpxqKdN_eVSL4iet3cGfylPOfcl1jnflfo4zhs8,3920
|
|
14
14
|
application_sdk/activities/metadata_extraction/rest.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
application_sdk/activities/metadata_extraction/sql.py,sha256=
|
|
15
|
+
application_sdk/activities/metadata_extraction/sql.py,sha256=CmE77EsgbOuDL5AKaRCnq1jApJnDWNVxx-RZ49cJwus,27415
|
|
16
16
|
application_sdk/activities/query_extraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
application_sdk/activities/query_extraction/sql.py,sha256=Gsa79R8CYY0uyt3rA2nLMfQs8-C4_zg1pJ_yYSF2cZw,21193
|
|
18
18
|
application_sdk/application/__init__.py,sha256=vcrQsqlfmGvKcCZuOtHHaNRqHSGdXlEDftkb8Tv_shI,9867
|
|
@@ -138,7 +138,7 @@ application_sdk/transformers/atlas/__init__.py,sha256=fw3D8bBtt61SseAfYut3JZddpX
|
|
|
138
138
|
application_sdk/transformers/atlas/sql.py,sha256=rkQXNZ7oebts5oF5E_Bw8NpcHHKScU0TmKciH_1l_k4,50419
|
|
139
139
|
application_sdk/transformers/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
140
140
|
application_sdk/transformers/common/utils.py,sha256=4ISMIQ0Gzghmi31p51FOFm5KLF7XF-fmH9PVT7i0DFE,4899
|
|
141
|
-
application_sdk/transformers/query/__init__.py,sha256=
|
|
141
|
+
application_sdk/transformers/query/__init__.py,sha256=4uVCU-NfDe08PlffjWQ5p4smQa7c518IL2rDgIk6694,17446
|
|
142
142
|
application_sdk/transformers/query/templates/column.yaml,sha256=EXLYwGXN7LKT-v51n2EZnY99o6vHucyFaVSpM-sUSXw,7679
|
|
143
143
|
application_sdk/transformers/query/templates/database.yaml,sha256=SD1hJg5LI7gsBHQL5mW341sa51EkhcsIDDFlIOi9zdk,1374
|
|
144
144
|
application_sdk/transformers/query/templates/extras-procedure.yaml,sha256=XhAfVY4zm99K8fcgkYA1XPLv4ks-SA6SzMO3SMtQ60s,2298
|
|
@@ -152,8 +152,8 @@ application_sdk/workflows/metadata_extraction/__init__.py,sha256=jHUe_ZBQ66jx8bg
|
|
|
152
152
|
application_sdk/workflows/metadata_extraction/sql.py,sha256=6ZaVt84n-8U2ZvR9GR7uIJKv5v8CuyQjhlnoRJvDszc,12435
|
|
153
153
|
application_sdk/workflows/query_extraction/__init__.py,sha256=n066_CX5RpJz6DIxGMkKS3eGSRg03ilaCtsqfJWQb7Q,117
|
|
154
154
|
application_sdk/workflows/query_extraction/sql.py,sha256=kT_JQkLCRZ44ZpaC4QvPL6DxnRIIVh8gYHLqRbMI-hA,4826
|
|
155
|
-
atlan_application_sdk-2.1.
|
|
156
|
-
atlan_application_sdk-2.1.
|
|
157
|
-
atlan_application_sdk-2.1.
|
|
158
|
-
atlan_application_sdk-2.1.
|
|
159
|
-
atlan_application_sdk-2.1.
|
|
155
|
+
atlan_application_sdk-2.1.1.dist-info/METADATA,sha256=Vc2uG2FMhuNXyZFXmGMmvc_LRpCBaNTcQEHpSV8NpOE,5805
|
|
156
|
+
atlan_application_sdk-2.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
157
|
+
atlan_application_sdk-2.1.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
158
|
+
atlan_application_sdk-2.1.1.dist-info/licenses/NOTICE,sha256=A-XVVGt3KOYuuMmvSMIFkg534F1vHiCggEBp4Ez3wGk,1041
|
|
159
|
+
atlan_application_sdk-2.1.1.dist-info/RECORD,,
|
|
File without changes
|
{atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{atlan_application_sdk-2.1.0.dist-info → atlan_application_sdk-2.1.1.dist-info}/licenses/NOTICE
RENAMED
|
File without changes
|