atlan-application-sdk 2.2.0__py3-none-any.whl → 2.3.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/application/__init__.py +68 -1
- application_sdk/application/metadata_extraction/sql.py +44 -0
- application_sdk/clients/temporal.py +6 -1
- application_sdk/constants.py +25 -0
- application_sdk/version.py +1 -1
- application_sdk/worker.py +104 -6
- {atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/METADATA +3 -3
- {atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/RECORD +11 -11
- {atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/WHEEL +0 -0
- {atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
from concurrent.futures import ThreadPoolExecutor
|
|
2
2
|
from typing import Any, Dict, List, Optional, Tuple, Type
|
|
3
3
|
|
|
4
|
+
from typing_extensions import deprecated
|
|
5
|
+
|
|
4
6
|
from application_sdk.activities import ActivitiesInterface
|
|
5
7
|
from application_sdk.clients.base import BaseClient
|
|
6
8
|
from application_sdk.clients.utils import get_workflow_client
|
|
7
|
-
from application_sdk.constants import ENABLE_MCP
|
|
9
|
+
from application_sdk.constants import APPLICATION_MODE, ENABLE_MCP, ApplicationMode
|
|
8
10
|
from application_sdk.handlers.base import BaseHandler
|
|
9
11
|
from application_sdk.interceptors.models import EventRegistration
|
|
12
|
+
from application_sdk.observability.decorators.observability_decorator import (
|
|
13
|
+
observability,
|
|
14
|
+
)
|
|
10
15
|
from application_sdk.observability.logger_adaptor import get_logger
|
|
16
|
+
from application_sdk.observability.metrics_adaptor import get_metrics
|
|
17
|
+
from application_sdk.observability.traces_adaptor import get_traces
|
|
11
18
|
from application_sdk.server import ServerInterface
|
|
12
19
|
from application_sdk.server.fastapi import APIServer, HttpWorkflowTrigger
|
|
13
20
|
from application_sdk.server.fastapi.models import EventWorkflowTrigger
|
|
@@ -15,6 +22,8 @@ from application_sdk.worker import Worker
|
|
|
15
22
|
from application_sdk.workflows import WorkflowInterface
|
|
16
23
|
|
|
17
24
|
logger = get_logger(__name__)
|
|
25
|
+
metrics = get_metrics()
|
|
26
|
+
traces = get_traces()
|
|
18
27
|
|
|
19
28
|
|
|
20
29
|
class BaseApplication:
|
|
@@ -110,6 +119,40 @@ class BaseApplication:
|
|
|
110
119
|
|
|
111
120
|
self.event_subscriptions[event_id].workflow_class = workflow_class
|
|
112
121
|
|
|
122
|
+
async def start(
|
|
123
|
+
self,
|
|
124
|
+
workflow_class: Type[WorkflowInterface],
|
|
125
|
+
ui_enabled: bool = True,
|
|
126
|
+
has_configmap: bool = False,
|
|
127
|
+
):
|
|
128
|
+
"""Start the application based on the configured APPLICATION_MODE.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
workflow_class: The workflow class to register with the server.
|
|
132
|
+
ui_enabled: Whether to enable the UI. Defaults to True.
|
|
133
|
+
has_configmap: Whether the application has a configmap. Defaults to False.
|
|
134
|
+
|
|
135
|
+
Behavior based on APPLICATION_MODE:
|
|
136
|
+
- LOCAL: Starts worker in daemon mode and server (for local development)
|
|
137
|
+
- WORKER: Starts only the worker in non-daemon mode (for production worker pods)
|
|
138
|
+
- SERVER: Starts only the server (for production API server pods)
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If APPLICATION_MODE is not a valid ApplicationMode value.
|
|
142
|
+
"""
|
|
143
|
+
if APPLICATION_MODE in (ApplicationMode.LOCAL, ApplicationMode.WORKER):
|
|
144
|
+
await self._start_worker(
|
|
145
|
+
daemon=APPLICATION_MODE == ApplicationMode.LOCAL,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if APPLICATION_MODE in (ApplicationMode.LOCAL, ApplicationMode.SERVER):
|
|
149
|
+
await self._setup_server(
|
|
150
|
+
workflow_class=workflow_class,
|
|
151
|
+
ui_enabled=ui_enabled,
|
|
152
|
+
has_configmap=has_configmap,
|
|
153
|
+
)
|
|
154
|
+
await self._start_server()
|
|
155
|
+
|
|
113
156
|
async def setup_workflow(
|
|
114
157
|
self,
|
|
115
158
|
workflow_and_activities_classes: List[
|
|
@@ -167,7 +210,12 @@ class BaseApplication:
|
|
|
167
210
|
raise ValueError("Workflow client not initialized")
|
|
168
211
|
return await self.workflow_client.start_workflow(workflow_args, workflow_class) # type: ignore
|
|
169
212
|
|
|
213
|
+
@deprecated("Use application.start(). Deprecated since v2.3.0.")
|
|
214
|
+
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
170
215
|
async def start_worker(self, daemon: bool = True):
|
|
216
|
+
return await self._start_worker(daemon=daemon)
|
|
217
|
+
|
|
218
|
+
async def _start_worker(self, daemon: bool = True):
|
|
171
219
|
"""
|
|
172
220
|
Start the worker for the application.
|
|
173
221
|
|
|
@@ -178,11 +226,25 @@ class BaseApplication:
|
|
|
178
226
|
raise ValueError("Worker not initialized")
|
|
179
227
|
await self.worker.start(daemon=daemon)
|
|
180
228
|
|
|
229
|
+
@deprecated("Use application.start(). Deprecated since v2.3.0.")
|
|
230
|
+
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
181
231
|
async def setup_server(
|
|
182
232
|
self,
|
|
183
233
|
workflow_class: Type[WorkflowInterface],
|
|
184
234
|
ui_enabled: bool = True,
|
|
185
235
|
has_configmap: bool = False,
|
|
236
|
+
):
|
|
237
|
+
return await self._setup_server(
|
|
238
|
+
workflow_class=workflow_class,
|
|
239
|
+
ui_enabled=ui_enabled,
|
|
240
|
+
has_configmap=has_configmap,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
async def _setup_server(
|
|
244
|
+
self,
|
|
245
|
+
workflow_class: Type[WorkflowInterface],
|
|
246
|
+
ui_enabled: bool = True,
|
|
247
|
+
has_configmap: bool = False,
|
|
186
248
|
):
|
|
187
249
|
"""
|
|
188
250
|
Set up FastAPI server and automatically mount MCP if enabled.
|
|
@@ -239,7 +301,12 @@ class BaseApplication:
|
|
|
239
301
|
triggers=[HttpWorkflowTrigger()],
|
|
240
302
|
)
|
|
241
303
|
|
|
304
|
+
@deprecated("Use application.start(). Deprecated since v2.3.0.")
|
|
305
|
+
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
242
306
|
async def start_server(self):
|
|
307
|
+
return await self._start_server()
|
|
308
|
+
|
|
309
|
+
async def _start_server(self):
|
|
243
310
|
"""
|
|
244
311
|
Start the FastAPI server for the application.
|
|
245
312
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from concurrent.futures import ThreadPoolExecutor
|
|
2
2
|
from typing import Any, Dict, List, Optional, Tuple, Type
|
|
3
3
|
|
|
4
|
+
from typing_extensions import deprecated
|
|
5
|
+
|
|
4
6
|
from application_sdk.application import BaseApplication
|
|
5
7
|
from application_sdk.clients.sql import BaseSQLClient
|
|
6
8
|
from application_sdk.clients.utils import get_workflow_client
|
|
@@ -146,8 +148,33 @@ class BaseSQLMetadataExtractionApplication(BaseApplication):
|
|
|
146
148
|
)
|
|
147
149
|
return workflow_response
|
|
148
150
|
|
|
151
|
+
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
152
|
+
async def start(
|
|
153
|
+
self,
|
|
154
|
+
workflow_class: Type = BaseSQLMetadataExtractionWorkflow,
|
|
155
|
+
ui_enabled: bool = True,
|
|
156
|
+
has_configmap: bool = False,
|
|
157
|
+
):
|
|
158
|
+
"""Start the SQL metadata extraction application.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
workflow_class: The workflow class to register. Defaults to BaseSQLMetadataExtractionWorkflow.
|
|
162
|
+
ui_enabled: Whether to enable the UI. Defaults to True.
|
|
163
|
+
has_configmap: Whether the application has a configmap. Defaults to False.
|
|
164
|
+
"""
|
|
165
|
+
await super().start(
|
|
166
|
+
workflow_class=workflow_class,
|
|
167
|
+
ui_enabled=ui_enabled,
|
|
168
|
+
has_configmap=has_configmap,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@deprecated("Use application.start(). Deprecated since v2.3.0.")
|
|
149
172
|
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
150
173
|
async def start_worker(self, daemon: bool = True):
|
|
174
|
+
return await self._start_worker(daemon=daemon)
|
|
175
|
+
|
|
176
|
+
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
177
|
+
async def _start_worker(self, daemon: bool = True):
|
|
151
178
|
"""
|
|
152
179
|
Start the worker for the SQL metadata extraction application.
|
|
153
180
|
"""
|
|
@@ -155,12 +182,27 @@ class BaseSQLMetadataExtractionApplication(BaseApplication):
|
|
|
155
182
|
raise ValueError("Worker not initialized")
|
|
156
183
|
await self.worker.start(daemon=daemon)
|
|
157
184
|
|
|
185
|
+
@deprecated("Use application.start(). Deprecated since v2.3.0.")
|
|
158
186
|
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
159
187
|
async def setup_server(
|
|
188
|
+
self,
|
|
189
|
+
workflow_class: Type = BaseSQLMetadataExtractionWorkflow,
|
|
190
|
+
ui_enabled: bool = True,
|
|
191
|
+
has_configmap: bool = False,
|
|
192
|
+
):
|
|
193
|
+
return await self._setup_server(
|
|
194
|
+
workflow_class=workflow_class,
|
|
195
|
+
ui_enabled=ui_enabled,
|
|
196
|
+
has_configmap=has_configmap,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
@observability(logger=logger, metrics=metrics, traces=traces)
|
|
200
|
+
async def _setup_server(
|
|
160
201
|
self,
|
|
161
202
|
workflow_class: Type[
|
|
162
203
|
BaseSQLMetadataExtractionWorkflow
|
|
163
204
|
] = BaseSQLMetadataExtractionWorkflow,
|
|
205
|
+
ui_enabled: bool = True,
|
|
164
206
|
has_configmap: bool = False,
|
|
165
207
|
) -> Any:
|
|
166
208
|
"""
|
|
@@ -168,6 +210,7 @@ class BaseSQLMetadataExtractionApplication(BaseApplication):
|
|
|
168
210
|
|
|
169
211
|
Args:
|
|
170
212
|
workflow_class (Type): Workflow class to register with the server. Defaults to BaseSQLMetadataExtractionWorkflow.
|
|
213
|
+
ui_enabled (bool): Whether to enable the UI. Defaults to True.
|
|
171
214
|
has_configmap (bool): Whether the application has a configmap. Defaults to False.
|
|
172
215
|
|
|
173
216
|
Returns:
|
|
@@ -180,6 +223,7 @@ class BaseSQLMetadataExtractionApplication(BaseApplication):
|
|
|
180
223
|
self.server = APIServer(
|
|
181
224
|
handler=self.handler_class(sql_client=self.client_class()),
|
|
182
225
|
workflow_client=self.workflow_client,
|
|
226
|
+
ui_enabled=ui_enabled,
|
|
183
227
|
has_configmap=has_configmap,
|
|
184
228
|
)
|
|
185
229
|
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import time
|
|
3
3
|
import uuid
|
|
4
4
|
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
from datetime import timedelta
|
|
5
6
|
from typing import Any, Dict, Optional, Sequence, Type
|
|
6
7
|
|
|
7
8
|
from temporalio import activity, workflow
|
|
@@ -18,6 +19,7 @@ from application_sdk.clients.workflow import WorkflowClient
|
|
|
18
19
|
from application_sdk.constants import (
|
|
19
20
|
APPLICATION_NAME,
|
|
20
21
|
DEPLOYMENT_NAME,
|
|
22
|
+
GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS,
|
|
21
23
|
IS_LOCKING_DISABLED,
|
|
22
24
|
MAX_CONCURRENT_ACTIVITIES,
|
|
23
25
|
WORKFLOW_HOST,
|
|
@@ -357,7 +359,7 @@ class TemporalWorkflowClient(WorkflowClient):
|
|
|
357
359
|
activity_executor: Optional[ThreadPoolExecutor] = None,
|
|
358
360
|
auto_start_token_refresh: bool = True,
|
|
359
361
|
) -> Worker:
|
|
360
|
-
"""Create a Temporal worker with automatic token refresh.
|
|
362
|
+
"""Create a Temporal worker with automatic token refresh and graceful shutdown.
|
|
361
363
|
|
|
362
364
|
Args:
|
|
363
365
|
activities (Sequence[CallableType]): Activity functions to register.
|
|
@@ -432,6 +434,9 @@ class TemporalWorkflowClient(WorkflowClient):
|
|
|
432
434
|
),
|
|
433
435
|
max_concurrent_activities=max_concurrent_activities,
|
|
434
436
|
activity_executor=activity_executor,
|
|
437
|
+
graceful_shutdown_timeout=timedelta(
|
|
438
|
+
seconds=GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS
|
|
439
|
+
),
|
|
435
440
|
interceptors=[
|
|
436
441
|
CorrelationContextInterceptor(),
|
|
437
442
|
EventInterceptor(),
|
application_sdk/constants.py
CHANGED
|
@@ -22,6 +22,7 @@ Note:
|
|
|
22
22
|
|
|
23
23
|
import os
|
|
24
24
|
from datetime import timedelta
|
|
25
|
+
from enum import Enum
|
|
25
26
|
|
|
26
27
|
from dotenv import load_dotenv
|
|
27
28
|
|
|
@@ -135,6 +136,14 @@ START_TO_CLOSE_TIMEOUT = timedelta(
|
|
|
135
136
|
) # 2 hours
|
|
136
137
|
)
|
|
137
138
|
|
|
139
|
+
#: Graceful shutdown timeout for workers
|
|
140
|
+
#: This is the maximum time the worker will wait for in-flight activities to complete
|
|
141
|
+
#: before forcing shutdown when receiving SIGTERM/SIGINT signals.
|
|
142
|
+
#: The worker will exit early if all activities complete before this timeout.
|
|
143
|
+
GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS = int(
|
|
144
|
+
os.getenv("ATLAN_GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS", 12 * 60 * 60) # 12 hours
|
|
145
|
+
)
|
|
146
|
+
|
|
138
147
|
# SQL Client Constants
|
|
139
148
|
#: Whether to use server-side cursors for SQL operations
|
|
140
149
|
USE_SERVER_SIDE_CURSOR = bool(os.getenv("ATLAN_SQL_USE_SERVER_SIDE_CURSOR", "true"))
|
|
@@ -279,6 +288,22 @@ ENABLE_MCP = os.getenv("ENABLE_MCP", "false").lower() == "true"
|
|
|
279
288
|
MCP_METADATA_KEY = "__atlan_application_sdk_mcp_metadata"
|
|
280
289
|
|
|
281
290
|
|
|
291
|
+
class ApplicationMode(str, Enum):
|
|
292
|
+
"""Application execution mode.
|
|
293
|
+
|
|
294
|
+
Determines which components of the application are started:
|
|
295
|
+
- LOCAL: Starts both the worker (daemon mode) and the server. Used for local development.
|
|
296
|
+
- WORKER: Starts only the worker (non-daemon mode). Used in production for worker pods.
|
|
297
|
+
- SERVER: Starts only the server. Used in production for API server pods.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
LOCAL = "LOCAL"
|
|
301
|
+
WORKER = "WORKER"
|
|
302
|
+
SERVER = "SERVER"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
APPLICATION_MODE = ApplicationMode(os.getenv("APPLICATION_MODE", "LOCAL").upper())
|
|
306
|
+
|
|
282
307
|
# Disable Analytics Configuration for DAFT
|
|
283
308
|
os.environ["DO_NOT_TRACK"] = "true"
|
|
284
309
|
os.environ["SCARF_NO_ANALYTICS"] = "true"
|
application_sdk/version.py
CHANGED
application_sdk/worker.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Worker module for managing Temporal workers.
|
|
2
2
|
|
|
3
3
|
This module provides the Worker class for managing Temporal workflow workers,
|
|
4
|
-
including their initialization, configuration, and execution.
|
|
4
|
+
including their initialization, configuration, and execution with graceful shutdown support.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
import signal
|
|
8
9
|
import sys
|
|
9
10
|
import threading
|
|
10
11
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -14,7 +15,11 @@ from temporalio.types import CallableType, ClassType
|
|
|
14
15
|
from temporalio.worker import Worker as TemporalWorker
|
|
15
16
|
|
|
16
17
|
from application_sdk.clients.workflow import WorkflowClient
|
|
17
|
-
from application_sdk.constants import
|
|
18
|
+
from application_sdk.constants import (
|
|
19
|
+
DEPLOYMENT_NAME,
|
|
20
|
+
GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS,
|
|
21
|
+
MAX_CONCURRENT_ACTIVITIES,
|
|
22
|
+
)
|
|
18
23
|
from application_sdk.interceptors.models import (
|
|
19
24
|
ApplicationEventNames,
|
|
20
25
|
Event,
|
|
@@ -37,13 +42,16 @@ if sys.platform not in ("win32", "cygwin"):
|
|
|
37
42
|
# uvloop is not available, use default asyncio
|
|
38
43
|
logger.warning("uvloop is not available, using default asyncio")
|
|
39
44
|
pass
|
|
45
|
+
elif sys.platform == "win32":
|
|
46
|
+
# Use WindowsSelectorEventLoopPolicy for Windows platform
|
|
47
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
40
48
|
|
|
41
49
|
|
|
42
50
|
class Worker:
|
|
43
51
|
"""Worker class for managing Temporal workflow workers.
|
|
44
52
|
|
|
45
53
|
This class handles the initialization and execution of Temporal workers,
|
|
46
|
-
including their activities, workflows, and
|
|
54
|
+
including their activities, workflows, module configurations, and graceful shutdown.
|
|
47
55
|
|
|
48
56
|
Attributes:
|
|
49
57
|
workflow_client: Client for interacting with Temporal.
|
|
@@ -53,6 +61,13 @@ class Worker:
|
|
|
53
61
|
passthrough_modules: List of module names to pass through.
|
|
54
62
|
max_concurrent_activities: Maximum number of concurrent activities.
|
|
55
63
|
|
|
64
|
+
Graceful Shutdown:
|
|
65
|
+
When SIGTERM or SIGINT is received:
|
|
66
|
+
1. Signal handlers trigger worker.shutdown()
|
|
67
|
+
2. Worker stops polling for new tasks
|
|
68
|
+
3. In-flight activities are allowed to complete within graceful_shutdown_timeout
|
|
69
|
+
4. Worker exits early if all activities complete, or at timeout
|
|
70
|
+
|
|
56
71
|
Note:
|
|
57
72
|
This class is designed to be thread-safe when running workers in daemon mode.
|
|
58
73
|
However, care should be taken when modifying worker attributes after initialization.
|
|
@@ -71,6 +86,78 @@ class Worker:
|
|
|
71
86
|
|
|
72
87
|
default_passthrough_modules = ["application_sdk", "pandas", "os", "app"]
|
|
73
88
|
|
|
89
|
+
def _setup_signal_handlers(self) -> None:
|
|
90
|
+
"""Set up SIGTERM and SIGINT handlers for graceful shutdown.
|
|
91
|
+
|
|
92
|
+
Signal handlers can only be registered from the main thread.
|
|
93
|
+
|
|
94
|
+
Platform Notes:
|
|
95
|
+
- Unix/Linux/macOS: Full signal handling support via asyncio event loop.
|
|
96
|
+
- Windows: Signal handling is not supported. Workers on Windows will not
|
|
97
|
+
respond to SIGTERM/SIGINT for graceful shutdown. On Windows, the worker
|
|
98
|
+
will continue running until the process is forcefully terminated.
|
|
99
|
+
"""
|
|
100
|
+
# Signal handlers only work on Unix-like systems
|
|
101
|
+
if sys.platform in ("win32", "cygwin"):
|
|
102
|
+
logger.warning(
|
|
103
|
+
"Signal handlers not supported on Windows. "
|
|
104
|
+
"Graceful shutdown via SIGTERM/SIGINT is not available. "
|
|
105
|
+
"For production deployments, use Unix-based systems."
|
|
106
|
+
)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Signal handlers can only be registered from the main thread
|
|
110
|
+
if threading.current_thread() is not threading.main_thread():
|
|
111
|
+
logger.debug(
|
|
112
|
+
"Skipping signal handler registration - not running in main thread"
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
loop = asyncio.get_running_loop()
|
|
117
|
+
|
|
118
|
+
def handle_signal(sig_name: str) -> None:
|
|
119
|
+
"""Handle shutdown signal by triggering worker.shutdown().
|
|
120
|
+
|
|
121
|
+
Uses a flag to prevent multiple shutdown tasks from being created
|
|
122
|
+
if multiple signals are received in quick succession.
|
|
123
|
+
"""
|
|
124
|
+
if self._shutdown_initiated:
|
|
125
|
+
logger.debug(f"Received {sig_name}, but shutdown already in progress")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
self._shutdown_initiated = True
|
|
129
|
+
logger.info(
|
|
130
|
+
f"Received {sig_name}, initiating graceful shutdown "
|
|
131
|
+
f"(timeout: {GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS}s)"
|
|
132
|
+
)
|
|
133
|
+
if self.workflow_worker:
|
|
134
|
+
asyncio.create_task(self._shutdown_worker())
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
loop.add_signal_handler(signal.SIGTERM, lambda: handle_signal("SIGTERM"))
|
|
138
|
+
loop.add_signal_handler(signal.SIGINT, lambda: handle_signal("SIGINT"))
|
|
139
|
+
logger.debug("Registered SIGTERM and SIGINT handlers")
|
|
140
|
+
except (ValueError, RuntimeError) as e:
|
|
141
|
+
logger.warning(f"Could not set up signal handlers: {e}")
|
|
142
|
+
|
|
143
|
+
async def _shutdown_worker(self) -> None:
|
|
144
|
+
"""Shutdown the worker gracefully.
|
|
145
|
+
|
|
146
|
+
Calls worker.shutdown() which:
|
|
147
|
+
1. Stops polling for new tasks
|
|
148
|
+
2. Waits for in-flight activities (up to graceful_shutdown_timeout)
|
|
149
|
+
3. Returns when done or timeout reached
|
|
150
|
+
"""
|
|
151
|
+
if not self.workflow_worker:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
logger.info("Stopping polling, waiting for in-flight activities...")
|
|
156
|
+
await self.workflow_worker.shutdown()
|
|
157
|
+
logger.info("Worker shutdown complete")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Error during shutdown: {e}")
|
|
160
|
+
|
|
74
161
|
def __init__(
|
|
75
162
|
self,
|
|
76
163
|
workflow_client: Optional[WorkflowClient] = None,
|
|
@@ -105,6 +192,7 @@ class Worker:
|
|
|
105
192
|
"""
|
|
106
193
|
self.workflow_client = workflow_client
|
|
107
194
|
self.workflow_worker: Optional[TemporalWorker] = None
|
|
195
|
+
self._shutdown_initiated = False
|
|
108
196
|
self.workflow_activities = workflow_activities
|
|
109
197
|
self.workflow_classes = workflow_classes
|
|
110
198
|
self.passthrough_modules = list(
|
|
@@ -153,9 +241,11 @@ class Worker:
|
|
|
153
241
|
RuntimeError: If worker creation fails.
|
|
154
242
|
ConnectionError: If connection to Temporal server fails.
|
|
155
243
|
|
|
156
|
-
|
|
157
|
-
When
|
|
158
|
-
|
|
244
|
+
Graceful Shutdown:
|
|
245
|
+
When SIGTERM/SIGINT is received:
|
|
246
|
+
1. Worker stops polling for new tasks
|
|
247
|
+
2. In-flight activities complete within graceful_shutdown_timeout
|
|
248
|
+
3. Worker exits early if activities finish, or at timeout
|
|
159
249
|
"""
|
|
160
250
|
if daemon:
|
|
161
251
|
worker_thread = threading.Thread(
|
|
@@ -184,11 +274,19 @@ class Worker:
|
|
|
184
274
|
max_concurrent_activities=self.max_concurrent_activities,
|
|
185
275
|
activity_executor=self.activity_executor,
|
|
186
276
|
)
|
|
277
|
+
self.workflow_worker = worker
|
|
187
278
|
|
|
188
279
|
logger.info(
|
|
189
280
|
f"Starting worker with task queue: {self.workflow_client.worker_task_queue}"
|
|
190
281
|
)
|
|
282
|
+
|
|
283
|
+
# Set up signal handlers and run worker
|
|
284
|
+
self._setup_signal_handlers()
|
|
285
|
+
|
|
191
286
|
await worker.run()
|
|
287
|
+
|
|
288
|
+
logger.info("Worker stopped")
|
|
289
|
+
|
|
192
290
|
except Exception as e:
|
|
193
291
|
logger.error(f"Error starting worker: {e}")
|
|
194
292
|
raise e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atlan-application-sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -93,8 +93,8 @@ poetry add atlan-application-sdk
|
|
|
93
93
|
|
|
94
94
|
## Getting Started
|
|
95
95
|
|
|
96
|
-
- Want to develop locally or run examples from this repository? Check out our [Getting Started Guide](docs/
|
|
97
|
-
- Detailed documentation for the application-sdk is available at [docs](https://github.com/atlanhq/application-sdk/blob/main/docs/
|
|
96
|
+
- Want to develop locally or run examples from this repository? Check out our [Getting Started Guide](docs/guides/getting-started.md) for a step-by-step walkthrough!
|
|
97
|
+
- Detailed documentation for the application-sdk is available at [docs](https://github.com/atlanhq/application-sdk/blob/main/docs/) folder.
|
|
98
98
|
|
|
99
99
|
## Contributing
|
|
100
100
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
application_sdk/__init__.py,sha256=2e2mvmLJ5dxmJGPELtb33xwP-j6JMdoIuqKycEn7hjg,151
|
|
2
|
-
application_sdk/constants.py,sha256=
|
|
3
|
-
application_sdk/version.py,sha256=
|
|
4
|
-
application_sdk/worker.py,sha256=
|
|
2
|
+
application_sdk/constants.py,sha256=_JprQls6AurPBHMG8TgGkLT7q4Lj8YxMAabYQx8Heks,12544
|
|
3
|
+
application_sdk/version.py,sha256=UmJhTVkb1lLHobuRG5ZPnQ2H_2oldVeDdAXt5Sm7DfQ,84
|
|
4
|
+
application_sdk/worker.py,sha256=5V_zxkx64moHSkMaZTNnbfstoygMBk7SAOSEy6AZZZM,11552
|
|
5
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
|
|
@@ -15,8 +15,8 @@ application_sdk/activities/metadata_extraction/rest.py,sha256=47DEQpj8HBSa-_TImW
|
|
|
15
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
|
-
application_sdk/application/__init__.py,sha256=
|
|
19
|
-
application_sdk/application/metadata_extraction/sql.py,sha256=
|
|
18
|
+
application_sdk/application/__init__.py,sha256=sTgyt4MBDvsQJ2yI0TxzB1Wp68WC-5fBkSExUgtW8LY,12559
|
|
19
|
+
application_sdk/application/metadata_extraction/sql.py,sha256=TmHTLdWLxqtSbVyzfNmnQNe6BUePVIlZxaiQHZXXbyI,9812
|
|
20
20
|
application_sdk/clients/__init__.py,sha256=C9T84J7V6ZumcoWJPAxdd3tqSmbyciaGBJn-CaCCny0,1341
|
|
21
21
|
application_sdk/clients/atlan.py,sha256=l6yV39fr1006SJFwkOTNDQlbSFlHCZQaUPfdUlzdVEg,5053
|
|
22
22
|
application_sdk/clients/atlan_auth.py,sha256=_MykgutI-Ill1t8ERgc1a7QrfaxnrtZjD48FAT-ER9M,8642
|
|
@@ -24,7 +24,7 @@ application_sdk/clients/base.py,sha256=TIn3pG89eXUc1XSYf4jk66m1vajWp0WxcCQOOltda
|
|
|
24
24
|
application_sdk/clients/models.py,sha256=iZOTyH6LO64kozdiUPCFCN0NgLhd_Gtv0lH7ZIPdo8w,1800
|
|
25
25
|
application_sdk/clients/redis.py,sha256=IfAD32vLp88BCvsDTaQtxFHxzHlEx4V7TK7h1HwDDBg,15917
|
|
26
26
|
application_sdk/clients/sql.py,sha256=J43FCxLW2YbnH2MlSm5hCTRFOMOEBtHFqi4ZTTul4JQ,26300
|
|
27
|
-
application_sdk/clients/temporal.py,sha256=
|
|
27
|
+
application_sdk/clients/temporal.py,sha256=7xxtzHrZdWjZ5pY1UPoRDY8U9IGpaGycANoWLY-r3io,20238
|
|
28
28
|
application_sdk/clients/utils.py,sha256=zLFOJbTr_6TOqnjfVFGY85OtIXZ4FQy_rquzjaydkbY,779
|
|
29
29
|
application_sdk/clients/workflow.py,sha256=6bSqmA3sNCk9oY68dOjBUDZ9DhNKQxPD75qqE0cfldc,6104
|
|
30
30
|
application_sdk/clients/.cursor/BUGBOT.md,sha256=7nEDUqWBEMI_uU6eK1jCSZGeXoQtLQcKwOrDn8AIDWo,10595
|
|
@@ -155,8 +155,8 @@ application_sdk/workflows/metadata_extraction/__init__.py,sha256=jHUe_ZBQ66jx8bg
|
|
|
155
155
|
application_sdk/workflows/metadata_extraction/sql.py,sha256=6ZaVt84n-8U2ZvR9GR7uIJKv5v8CuyQjhlnoRJvDszc,12435
|
|
156
156
|
application_sdk/workflows/query_extraction/__init__.py,sha256=n066_CX5RpJz6DIxGMkKS3eGSRg03ilaCtsqfJWQb7Q,117
|
|
157
157
|
application_sdk/workflows/query_extraction/sql.py,sha256=kT_JQkLCRZ44ZpaC4QvPL6DxnRIIVh8gYHLqRbMI-hA,4826
|
|
158
|
-
atlan_application_sdk-2.
|
|
159
|
-
atlan_application_sdk-2.
|
|
160
|
-
atlan_application_sdk-2.
|
|
161
|
-
atlan_application_sdk-2.
|
|
162
|
-
atlan_application_sdk-2.
|
|
158
|
+
atlan_application_sdk-2.3.1.dist-info/METADATA,sha256=R2fvoqWUrmDlnw0AQRINbuclCag6Ud2f08IsAUAQw4Y,6004
|
|
159
|
+
atlan_application_sdk-2.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
160
|
+
atlan_application_sdk-2.3.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
161
|
+
atlan_application_sdk-2.3.1.dist-info/licenses/NOTICE,sha256=A-XVVGt3KOYuuMmvSMIFkg534F1vHiCggEBp4Ez3wGk,1041
|
|
162
|
+
atlan_application_sdk-2.3.1.dist-info/RECORD,,
|
|
File without changes
|
{atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{atlan_application_sdk-2.2.0.dist-info → atlan_application_sdk-2.3.1.dist-info}/licenses/NOTICE
RENAMED
|
File without changes
|