atlan-application-sdk 0.1.1rc62__py3-none-any.whl → 0.1.1rc64__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/clients/atlan_auth.py +13 -17
- application_sdk/clients/temporal.py +4 -13
- application_sdk/constants.py +20 -7
- application_sdk/events/models.py +2 -0
- application_sdk/services/secretstore.py +183 -51
- application_sdk/version.py +1 -1
- application_sdk/worker.py +2 -1
- {atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/METADATA +1 -1
- {atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/RECORD +12 -12
- {atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/WHEEL +0 -0
- {atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/licenses/LICENSE +0 -0
- {atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
"""OAuth2 token manager with automatic secret store discovery."""
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Dict, Optional
|
|
5
5
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
|
|
8
8
|
from application_sdk.common.error_codes import ClientError
|
|
9
9
|
from application_sdk.constants import (
|
|
10
10
|
APPLICATION_NAME,
|
|
11
|
+
AUTH_ENABLED,
|
|
12
|
+
AUTH_URL,
|
|
11
13
|
WORKFLOW_AUTH_CLIENT_ID_KEY,
|
|
12
14
|
WORKFLOW_AUTH_CLIENT_SECRET_KEY,
|
|
13
|
-
WORKFLOW_AUTH_ENABLED,
|
|
14
|
-
WORKFLOW_AUTH_URL_KEY,
|
|
15
15
|
)
|
|
16
16
|
from application_sdk.observability.logger_adaptor import get_logger
|
|
17
17
|
from application_sdk.services.secretstore import SecretStore
|
|
@@ -39,9 +39,8 @@ class AtlanAuthClient:
|
|
|
39
39
|
(environment variables, AWS Secrets Manager, Azure Key Vault, etc.)
|
|
40
40
|
"""
|
|
41
41
|
self.application_name = APPLICATION_NAME
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.auth_url: Optional[str] = None
|
|
42
|
+
self.auth_enabled: bool = AUTH_ENABLED
|
|
43
|
+
self.auth_url: Optional[str] = AUTH_URL
|
|
45
44
|
|
|
46
45
|
# Secret store credentials (cached after first fetch)
|
|
47
46
|
self.credentials: Optional[Dict[str, str]] = None
|
|
@@ -175,18 +174,17 @@ class AtlanAuthClient:
|
|
|
175
174
|
|
|
176
175
|
async def _extract_auth_credentials(self) -> Optional[Dict[str, str]]:
|
|
177
176
|
"""Fetch app credentials from secret store - auth-specific logic"""
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
177
|
+
client_id = SecretStore.get_deployment_secret(WORKFLOW_AUTH_CLIENT_ID_KEY)
|
|
178
|
+
client_secret = SecretStore.get_deployment_secret(
|
|
179
|
+
WORKFLOW_AUTH_CLIENT_SECRET_KEY
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if client_id and client_secret:
|
|
182
183
|
credentials = {
|
|
183
|
-
"client_id":
|
|
184
|
-
"client_secret":
|
|
184
|
+
"client_id": client_id,
|
|
185
|
+
"client_secret": client_secret,
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
if WORKFLOW_AUTH_URL_KEY in self.auth_config:
|
|
188
|
-
self.auth_url = self.auth_config[WORKFLOW_AUTH_URL_KEY]
|
|
189
|
-
|
|
190
188
|
return credentials
|
|
191
189
|
return None
|
|
192
190
|
|
|
@@ -199,10 +197,8 @@ class AtlanAuthClient:
|
|
|
199
197
|
"""
|
|
200
198
|
# we are doing this to force a fetch of the credentials from secret store
|
|
201
199
|
self.credentials = None
|
|
202
|
-
self.auth_url = None
|
|
203
200
|
self._access_token = None
|
|
204
201
|
self._token_expiry = 0
|
|
205
|
-
self.auth_config = {}
|
|
206
202
|
|
|
207
203
|
def calculate_refresh_interval(self) -> int:
|
|
208
204
|
"""Calculate the optimal token refresh interval based on token expiry.
|
|
@@ -18,14 +18,13 @@ from application_sdk.clients.workflow import WorkflowClient
|
|
|
18
18
|
from application_sdk.constants import (
|
|
19
19
|
APPLICATION_NAME,
|
|
20
20
|
DEPLOYMENT_NAME,
|
|
21
|
-
DEPLOYMENT_NAME_KEY,
|
|
22
21
|
IS_LOCKING_DISABLED,
|
|
23
22
|
MAX_CONCURRENT_ACTIVITIES,
|
|
24
23
|
WORKFLOW_HOST,
|
|
25
24
|
WORKFLOW_MAX_TIMEOUT_HOURS,
|
|
26
25
|
WORKFLOW_NAMESPACE,
|
|
27
26
|
WORKFLOW_PORT,
|
|
28
|
-
|
|
27
|
+
WORKFLOW_TLS_ENABLED,
|
|
29
28
|
)
|
|
30
29
|
from application_sdk.events.models import (
|
|
31
30
|
ApplicationEventNames,
|
|
@@ -97,7 +96,6 @@ class TemporalWorkflowClient(WorkflowClient):
|
|
|
97
96
|
self.port = port if port else WORKFLOW_PORT
|
|
98
97
|
self.namespace = namespace if namespace else WORKFLOW_NAMESPACE
|
|
99
98
|
|
|
100
|
-
self.deployment_config: Dict[str, Any] = SecretStore.get_deployment_secret()
|
|
101
99
|
self.worker_task_queue = self.get_worker_task_queue()
|
|
102
100
|
self.auth_manager = AtlanAuthClient()
|
|
103
101
|
|
|
@@ -118,12 +116,8 @@ class TemporalWorkflowClient(WorkflowClient):
|
|
|
118
116
|
Returns:
|
|
119
117
|
str: The task queue name in format "app_name-deployment_name".
|
|
120
118
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
if deployment_name:
|
|
126
|
-
return f"atlan-{self.application_name}-{deployment_name}"
|
|
119
|
+
if DEPLOYMENT_NAME:
|
|
120
|
+
return f"atlan-{self.application_name}-{DEPLOYMENT_NAME}"
|
|
127
121
|
else:
|
|
128
122
|
return self.application_name
|
|
129
123
|
|
|
@@ -228,12 +222,9 @@ class TemporalWorkflowClient(WorkflowClient):
|
|
|
228
222
|
connection_options: Dict[str, Any] = {
|
|
229
223
|
"target_host": self.get_connection_string(),
|
|
230
224
|
"namespace": self.namespace,
|
|
231
|
-
"tls":
|
|
225
|
+
"tls": WORKFLOW_TLS_ENABLED,
|
|
232
226
|
}
|
|
233
227
|
|
|
234
|
-
connection_options["tls"] = self.deployment_config.get(
|
|
235
|
-
WORKFLOW_TLS_ENABLED_KEY, False
|
|
236
|
-
)
|
|
237
228
|
self.worker_task_queue = self.get_worker_task_queue()
|
|
238
229
|
|
|
239
230
|
if self.auth_manager.auth_enabled:
|
application_sdk/constants.py
CHANGED
|
@@ -105,16 +105,23 @@ MAX_CONCURRENT_ACTIVITIES = int(os.getenv("ATLAN_MAX_CONCURRENT_ACTIVITIES", "5"
|
|
|
105
105
|
DEPLOYMENT_SECRET_PATH = os.getenv(
|
|
106
106
|
"ATLAN_DEPLOYMENT_SECRET_PATH", "ATLAN_DEPLOYMENT_SECRETS"
|
|
107
107
|
)
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
AUTH_ENABLED = os.getenv("ATLAN_AUTH_ENABLED", "false").lower() == "true"
|
|
109
|
+
#: OAuth2 authentication URL for workflow services
|
|
110
|
+
AUTH_URL = os.getenv("ATLAN_AUTH_URL")
|
|
111
|
+
#: Whether to enable TLS for Temporal workflow connections
|
|
112
|
+
WORKFLOW_TLS_ENABLED = (
|
|
113
|
+
os.getenv("ATLAN_WORKFLOW_TLS_ENABLED", "false").lower() == "true"
|
|
110
114
|
)
|
|
111
115
|
|
|
112
116
|
# Deployment Secret Store Key Names
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
#: Key name for OAuth2 client ID in deployment secrets (can be overridden via ATLAN_AUTH_CLIENT_ID_KEY)
|
|
118
|
+
WORKFLOW_AUTH_CLIENT_ID_KEY = os.getenv(
|
|
119
|
+
"ATLAN_AUTH_CLIENT_ID_KEY", "ATLAN_AUTH_CLIENT_ID"
|
|
120
|
+
)
|
|
121
|
+
#: Key name for OAuth2 client secret in deployment secrets (can be overridden via ATLAN_AUTH_CLIENT_SECRET_KEY)
|
|
122
|
+
WORKFLOW_AUTH_CLIENT_SECRET_KEY = os.getenv(
|
|
123
|
+
"ATLAN_AUTH_CLIENT_SECRET_KEY", "ATLAN_AUTH_CLIENT_SECRET"
|
|
124
|
+
)
|
|
118
125
|
|
|
119
126
|
# Workflow Constants
|
|
120
127
|
#: Timeout duration for activity heartbeats
|
|
@@ -270,3 +277,9 @@ LOCK_RETRY_INTERVAL_SECONDS = int(os.getenv("LOCK_RETRY_INTERVAL_SECONDS", "60")
|
|
|
270
277
|
#: with the application.
|
|
271
278
|
ENABLE_MCP = os.getenv("ENABLE_MCP", "false").lower() == "true"
|
|
272
279
|
MCP_METADATA_KEY = "__atlan_application_sdk_mcp_metadata"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# Disable Analytics Configuration for DAFT
|
|
283
|
+
os.environ["DO_NOT_TRACK"] = "true"
|
|
284
|
+
os.environ["SCARF_NO_ANALYTICS"] = "true"
|
|
285
|
+
os.environ["DAFT_ANALYTICS_ENABLED"] = "0"
|
application_sdk/events/models.py
CHANGED
|
@@ -155,6 +155,7 @@ class WorkerStartEventData(BaseModel):
|
|
|
155
155
|
|
|
156
156
|
Attributes:
|
|
157
157
|
application_name: Name of the application the worker belongs to.
|
|
158
|
+
deployment_name: Name of the deployment the worker belongs to.
|
|
158
159
|
task_queue: Task queue name for the worker.
|
|
159
160
|
namespace: Temporal namespace for the worker.
|
|
160
161
|
host: Host address of the Temporal server.
|
|
@@ -167,6 +168,7 @@ class WorkerStartEventData(BaseModel):
|
|
|
167
168
|
|
|
168
169
|
version: str = WORKER_START_EVENT_VERSION
|
|
169
170
|
application_name: str
|
|
171
|
+
deployment_name: str
|
|
170
172
|
task_queue: str
|
|
171
173
|
namespace: str
|
|
172
174
|
host: str
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
"""Unified secret store service for the application.
|
|
1
|
+
"""Unified secret store service for the application.
|
|
2
|
+
|
|
3
|
+
Logic summary:
|
|
4
|
+
|
|
5
|
+
1. Fetch credential config from state store.
|
|
6
|
+
|
|
7
|
+
2. Determine mode: Multi-key if (credentialSource == 'direct' OR secret-path is defined), Single-key otherwise.
|
|
8
|
+
|
|
9
|
+
3. Fetch secrets accordingly: Multi-key uses secret_path if credentialSource == "agent" else credential_guid, Single-key fetches each field individually.
|
|
10
|
+
|
|
11
|
+
4. Merge & resolve secrets.
|
|
12
|
+
"""
|
|
2
13
|
|
|
3
14
|
import collections.abc
|
|
4
15
|
import copy
|
|
5
16
|
import json
|
|
6
17
|
import uuid
|
|
18
|
+
from enum import Enum
|
|
7
19
|
from typing import Any, Dict
|
|
8
20
|
|
|
9
21
|
from dapr.clients import DaprClient
|
|
@@ -23,8 +35,22 @@ from application_sdk.services.statestore import StateStore, StateType
|
|
|
23
35
|
logger = get_logger(__name__)
|
|
24
36
|
|
|
25
37
|
|
|
38
|
+
class CredentialSource(Enum):
|
|
39
|
+
"""Enumeration of credential source types."""
|
|
40
|
+
|
|
41
|
+
DIRECT = "direct"
|
|
42
|
+
AGENT = "agent"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SecretMode(Enum):
|
|
46
|
+
"""Enumeration of secret retrieval modes."""
|
|
47
|
+
|
|
48
|
+
MULTI_KEY = "multi-key"
|
|
49
|
+
SINGLE_KEY = "single-key"
|
|
50
|
+
|
|
51
|
+
|
|
26
52
|
class SecretStore:
|
|
27
|
-
"""Unified secret store service for handling secret management."""
|
|
53
|
+
"""Unified secret store service for handling secret management across providers."""
|
|
28
54
|
|
|
29
55
|
@classmethod
|
|
30
56
|
async def get_credentials(cls, credential_guid: str) -> Dict[str, Any]:
|
|
@@ -33,6 +59,8 @@ class SecretStore:
|
|
|
33
59
|
This method retrieves credential configuration from the state store and resolves
|
|
34
60
|
any secret references by fetching actual values from the secret store.
|
|
35
61
|
|
|
62
|
+
Supports Multi-key mode (direct / has secret-path) and Single-key mode (no secret-path, non-direct).
|
|
63
|
+
|
|
36
64
|
Args:
|
|
37
65
|
credential_guid (str): The unique GUID of the credential configuration to resolve.
|
|
38
66
|
|
|
@@ -48,7 +76,6 @@ class SecretStore:
|
|
|
48
76
|
>>> creds = await SecretStore.get_credentials("db-cred-abc123")
|
|
49
77
|
>>> print(f"Connecting to {creds['host']}:{creds['port']}")
|
|
50
78
|
>>> # Password is automatically resolved from secret store
|
|
51
|
-
|
|
52
79
|
>>> # Handle resolution errors
|
|
53
80
|
>>> try:
|
|
54
81
|
... creds = await SecretStore.get_credentials("invalid-guid")
|
|
@@ -62,13 +89,44 @@ class SecretStore:
|
|
|
62
89
|
credential_guid, StateType.CREDENTIALS
|
|
63
90
|
)
|
|
64
91
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
92
|
+
credential_source_str = credential_config.get(
|
|
93
|
+
"credentialSource", CredentialSource.DIRECT.value
|
|
94
|
+
)
|
|
95
|
+
try:
|
|
96
|
+
credential_source = CredentialSource(credential_source_str)
|
|
97
|
+
except ValueError:
|
|
98
|
+
credential_source = CredentialSource.DIRECT
|
|
99
|
+
secret_path = credential_config.get("secret-path")
|
|
100
|
+
|
|
101
|
+
secret_data: Dict[str, Any] = {}
|
|
68
102
|
|
|
69
|
-
#
|
|
70
|
-
credential_source
|
|
71
|
-
|
|
103
|
+
# Decide mode
|
|
104
|
+
if credential_source == CredentialSource.DIRECT or secret_path:
|
|
105
|
+
mode = SecretMode.MULTI_KEY
|
|
106
|
+
else:
|
|
107
|
+
mode = SecretMode.SINGLE_KEY
|
|
108
|
+
|
|
109
|
+
# Multi-key secret fetch (direct or has secret-path)
|
|
110
|
+
if mode == SecretMode.MULTI_KEY:
|
|
111
|
+
key_to_fetch = (
|
|
112
|
+
secret_path
|
|
113
|
+
if credential_source == CredentialSource.AGENT
|
|
114
|
+
else credential_guid
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
logger.debug(f"Fetching multi-key secret from '{key_to_fetch}'")
|
|
118
|
+
secret_data = cls.get_secret(secret_key=key_to_fetch)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.warning(
|
|
121
|
+
f"Failed to fetch secret bundle '{key_to_fetch}': {e}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Single-key mode → per-field secret lookup
|
|
125
|
+
else:
|
|
126
|
+
secret_data = cls._fetch_single_key_secrets(credential_config)
|
|
127
|
+
|
|
128
|
+
# Merge or resolve references
|
|
129
|
+
if credential_source == CredentialSource.DIRECT:
|
|
72
130
|
credential_config.update(secret_data)
|
|
73
131
|
return credential_config
|
|
74
132
|
else:
|
|
@@ -78,12 +136,59 @@ class SecretStore:
|
|
|
78
136
|
# Run async operations directly
|
|
79
137
|
return await _get_credentials_async(credential_guid)
|
|
80
138
|
except Exception as e:
|
|
81
|
-
logger.error(f"Error resolving credentials: {str(e)}")
|
|
139
|
+
logger.error(f"Error resolving credentials for {credential_guid}: {str(e)}")
|
|
82
140
|
raise CommonError(
|
|
83
141
|
CommonError.CREDENTIALS_RESOLUTION_ERROR,
|
|
84
142
|
f"Failed to resolve credentials: {str(e)}",
|
|
85
143
|
)
|
|
86
144
|
|
|
145
|
+
# Secret resolution helpers
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def _fetch_single_key_secrets(
|
|
149
|
+
cls, credential_config: Dict[str, Any]
|
|
150
|
+
) -> Dict[str, Any]:
|
|
151
|
+
"""Fetch secrets in single-key mode by looking up each field individually.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
credential_config: The credential configuration dictionary
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Dictionary containing collected secret values
|
|
158
|
+
"""
|
|
159
|
+
logger.debug("Single-key mode: fetching secrets per field")
|
|
160
|
+
collected = {}
|
|
161
|
+
for field, value in credential_config.items():
|
|
162
|
+
if isinstance(value, str):
|
|
163
|
+
try:
|
|
164
|
+
single_secret = cls.get_secret(value)
|
|
165
|
+
if single_secret:
|
|
166
|
+
for k, v in single_secret.items():
|
|
167
|
+
# Only filter out None and empty strings, not all falsy values.
|
|
168
|
+
# This preserves valid secret values like False, 0, 0.0 which are
|
|
169
|
+
# legitimate secret values that should not be excluded.
|
|
170
|
+
if v is None or v == "":
|
|
171
|
+
continue
|
|
172
|
+
collected[k] = v
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.debug(f"Skipping '{field}' → '{value}' ({e})")
|
|
175
|
+
elif field == "extra" and isinstance(value, dict):
|
|
176
|
+
# Recursively process string values in the extra dictionary
|
|
177
|
+
for extra_key, extra_value in value.items():
|
|
178
|
+
if isinstance(extra_value, str):
|
|
179
|
+
try:
|
|
180
|
+
single_secret = cls.get_secret(extra_value)
|
|
181
|
+
if single_secret:
|
|
182
|
+
for k, v in single_secret.items():
|
|
183
|
+
if v is None or v == "":
|
|
184
|
+
continue
|
|
185
|
+
collected[k] = v
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.debug(
|
|
188
|
+
f"Skipping 'extra.{extra_key}' → '{extra_value}' ({e})"
|
|
189
|
+
)
|
|
190
|
+
return collected
|
|
191
|
+
|
|
87
192
|
@classmethod
|
|
88
193
|
def resolve_credentials(
|
|
89
194
|
cls, credential_config: Dict[str, Any], secret_data: Dict[str, Any]
|
|
@@ -106,7 +211,6 @@ class SecretStore:
|
|
|
106
211
|
>>> secrets = {"db_password_key": "actual_secret_password"}
|
|
107
212
|
>>> resolved = SecretStore.resolve_credentials(config, secrets)
|
|
108
213
|
>>> print(resolved) # {"host": "db.example.com", "password": "actual_secret_password"}
|
|
109
|
-
|
|
110
214
|
>>> # Resolution with nested 'extra' fields
|
|
111
215
|
>>> config = {
|
|
112
216
|
... "host": "db.example.com",
|
|
@@ -125,51 +229,65 @@ class SecretStore:
|
|
|
125
229
|
# Apply the same substitution to the 'extra' dictionary if it exists
|
|
126
230
|
if "extra" in credentials and isinstance(credentials["extra"], dict):
|
|
127
231
|
for key, value in list(credentials["extra"].items()):
|
|
128
|
-
if isinstance(value, str):
|
|
129
|
-
|
|
130
|
-
credentials["extra"][key] = secret_data[value]
|
|
131
|
-
elif value in secret_data.get("extra", {}):
|
|
132
|
-
credentials["extra"][key] = secret_data["extra"][value]
|
|
232
|
+
if isinstance(value, str) and value in secret_data:
|
|
233
|
+
credentials["extra"][key] = secret_data[value]
|
|
133
234
|
|
|
134
235
|
return credentials
|
|
135
236
|
|
|
136
237
|
@classmethod
|
|
137
|
-
def get_deployment_secret(cls) ->
|
|
138
|
-
"""Get deployment configuration
|
|
238
|
+
def get_deployment_secret(cls, key: str) -> Any:
|
|
239
|
+
"""Get a specific key from deployment configuration in the deployment secret store.
|
|
139
240
|
|
|
140
241
|
Validates that the deployment secret store component is registered
|
|
141
242
|
before attempting to fetch secrets to prevent errors. This method
|
|
142
|
-
|
|
243
|
+
fetches only the specified key from the deployment secret, rather than
|
|
244
|
+
the entire secret dictionary.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
key (str): The key to fetch from the deployment secret.
|
|
143
248
|
|
|
144
249
|
Returns:
|
|
145
|
-
|
|
146
|
-
|
|
250
|
+
Any: The value for the specified key, or None if the key is not found
|
|
251
|
+
or the component is unavailable.
|
|
147
252
|
|
|
148
253
|
Examples:
|
|
149
|
-
>>> # Get deployment configuration
|
|
150
|
-
>>>
|
|
151
|
-
>>> if
|
|
152
|
-
... print(f"
|
|
153
|
-
|
|
154
|
-
>>>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
>>> # Use in application initialization
|
|
158
|
-
>>> deployment_config = SecretStore.get_deployment_secret()
|
|
159
|
-
>>> if deployment_config.get('debug_mode'):
|
|
160
|
-
... logging.getLogger().setLevel(logging.DEBUG)
|
|
254
|
+
>>> # Get a specific deployment configuration value
|
|
255
|
+
>>> auth_url = SecretStore.get_deployment_secret("ATLAN_AUTH_CLIENT_ID")
|
|
256
|
+
>>> if auth_url:
|
|
257
|
+
... print(f"Auth URL: {auth_url}")
|
|
258
|
+
>>> # Get deployment name
|
|
259
|
+
>>> deployment_name = SecretStore.get_deployment_secret("deployment_name")
|
|
260
|
+
>>> if deployment_name:
|
|
261
|
+
... print(f"Deployment: {deployment_name}")
|
|
161
262
|
"""
|
|
162
263
|
if not is_component_registered(DEPLOYMENT_SECRET_STORE_NAME):
|
|
163
264
|
logger.warning(
|
|
164
|
-
f"Deployment secret store component '{DEPLOYMENT_SECRET_STORE_NAME}'
|
|
265
|
+
f"Deployment secret store component '{DEPLOYMENT_SECRET_STORE_NAME}' not registered."
|
|
165
266
|
)
|
|
166
|
-
return
|
|
267
|
+
return None
|
|
167
268
|
|
|
168
269
|
try:
|
|
169
|
-
|
|
270
|
+
secret_data = cls.get_secret(
|
|
271
|
+
DEPLOYMENT_SECRET_PATH, DEPLOYMENT_SECRET_STORE_NAME
|
|
272
|
+
)
|
|
273
|
+
if isinstance(secret_data, dict) and key in secret_data:
|
|
274
|
+
return secret_data[key]
|
|
275
|
+
|
|
276
|
+
logger.debug(f"Multi-key not found, checking single-key secret for '{key}'")
|
|
277
|
+
single_secret_data = cls.get_secret(key, DEPLOYMENT_SECRET_STORE_NAME)
|
|
278
|
+
if isinstance(single_secret_data, dict):
|
|
279
|
+
# Handle both {key:value} and {"value": "..."} cases
|
|
280
|
+
if key in single_secret_data:
|
|
281
|
+
return single_secret_data[key]
|
|
282
|
+
elif len(single_secret_data) == 1:
|
|
283
|
+
# extract single value
|
|
284
|
+
return list(single_secret_data.values())[0]
|
|
285
|
+
|
|
286
|
+
return None
|
|
287
|
+
|
|
170
288
|
except Exception as e:
|
|
171
|
-
logger.error(f"Failed to fetch deployment config: {e}")
|
|
172
|
-
return
|
|
289
|
+
logger.error(f"Failed to fetch deployment config key '{key}': {e}")
|
|
290
|
+
return None
|
|
173
291
|
|
|
174
292
|
@classmethod
|
|
175
293
|
def get_secret(
|
|
@@ -182,7 +300,7 @@ class SecretStore:
|
|
|
182
300
|
|
|
183
301
|
Args:
|
|
184
302
|
secret_key (str): Key of the secret to fetch from the secret store.
|
|
185
|
-
component_name (str
|
|
303
|
+
component_name (str): Name of the Dapr component to fetch from.
|
|
186
304
|
Defaults to SECRET_STORE_NAME.
|
|
187
305
|
|
|
188
306
|
Returns:
|
|
@@ -199,7 +317,6 @@ class SecretStore:
|
|
|
199
317
|
>>> # Get database credentials
|
|
200
318
|
>>> db_secret = SecretStore.get_secret("database-credentials")
|
|
201
319
|
>>> print(f"Host: {db_secret.get('host')}")
|
|
202
|
-
|
|
203
320
|
>>> # Get from specific component
|
|
204
321
|
>>> api_secret = SecretStore.get_secret(
|
|
205
322
|
... "api-keys",
|
|
@@ -217,7 +334,7 @@ class SecretStore:
|
|
|
217
334
|
return cls._process_secret_data(dapr_secret_object.secret)
|
|
218
335
|
except Exception as e:
|
|
219
336
|
logger.error(
|
|
220
|
-
f"Failed to fetch secret using component {component_name}: {str(e)}"
|
|
337
|
+
f"Failed to fetch secret using component '{component_name}': {str(e)}"
|
|
221
338
|
)
|
|
222
339
|
raise
|
|
223
340
|
|
|
@@ -235,17 +352,34 @@ class SecretStore:
|
|
|
235
352
|
if isinstance(secret_data, collections.abc.Mapping):
|
|
236
353
|
secret_data = dict(secret_data)
|
|
237
354
|
|
|
238
|
-
#
|
|
239
|
-
if len(secret_data) == 1
|
|
355
|
+
# Handle single-key secrets gracefully
|
|
356
|
+
if len(secret_data) == 1:
|
|
357
|
+
k, v = next(iter(secret_data.items()))
|
|
358
|
+
return cls._handle_single_key_secret(k, v)
|
|
359
|
+
|
|
360
|
+
return secret_data
|
|
361
|
+
|
|
362
|
+
# Utility helpers
|
|
363
|
+
|
|
364
|
+
@classmethod
|
|
365
|
+
def _handle_single_key_secret(cls, key: str, value: Any) -> Dict[str, Any]:
|
|
366
|
+
"""Handle single-key secret by attempting to parse JSON value.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
key: The secret key.
|
|
370
|
+
value: The secret value (may be a JSON string).
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Dictionary with parsed JSON if value is valid JSON dict, otherwise {key: value}.
|
|
374
|
+
"""
|
|
375
|
+
if isinstance(value, str):
|
|
240
376
|
try:
|
|
241
|
-
parsed = json.loads(
|
|
377
|
+
parsed = json.loads(value)
|
|
242
378
|
if isinstance(parsed, dict):
|
|
243
|
-
|
|
244
|
-
except Exception
|
|
245
|
-
logger.error(f"Failed to parse secret data: {e}")
|
|
379
|
+
return parsed
|
|
380
|
+
except Exception:
|
|
246
381
|
pass
|
|
247
|
-
|
|
248
|
-
return secret_data
|
|
382
|
+
return {key: value}
|
|
249
383
|
|
|
250
384
|
@classmethod
|
|
251
385
|
def apply_secret_values(
|
|
@@ -275,7 +409,6 @@ class SecretStore:
|
|
|
275
409
|
... "db_password": "secure_db_password"
|
|
276
410
|
... }
|
|
277
411
|
>>> resolved = SecretStore.apply_secret_values(source, secrets)
|
|
278
|
-
|
|
279
412
|
>>> # With nested extra fields
|
|
280
413
|
>>> source = {
|
|
281
414
|
... "host": "api.example.com",
|
|
@@ -328,7 +461,6 @@ class SecretStore:
|
|
|
328
461
|
... }
|
|
329
462
|
>>> guid = await SecretStore.save_secret(config)
|
|
330
463
|
>>> print(f"Stored credentials with GUID: {guid}")
|
|
331
|
-
|
|
332
464
|
>>> # Later retrieve these credentials
|
|
333
465
|
>>> retrieved = await SecretStore.get_credentials(guid)
|
|
334
466
|
"""
|
application_sdk/version.py
CHANGED
application_sdk/worker.py
CHANGED
|
@@ -14,7 +14,7 @@ from temporalio.types import CallableType, ClassType
|
|
|
14
14
|
from temporalio.worker import Worker as TemporalWorker
|
|
15
15
|
|
|
16
16
|
from application_sdk.clients.workflow import WorkflowClient
|
|
17
|
-
from application_sdk.constants import MAX_CONCURRENT_ACTIVITIES
|
|
17
|
+
from application_sdk.constants import DEPLOYMENT_NAME, MAX_CONCURRENT_ACTIVITIES
|
|
18
18
|
from application_sdk.events.models import (
|
|
19
19
|
ApplicationEventNames,
|
|
20
20
|
Event,
|
|
@@ -122,6 +122,7 @@ class Worker:
|
|
|
122
122
|
if self.workflow_client:
|
|
123
123
|
self._worker_creation_event_data = WorkerStartEventData(
|
|
124
124
|
application_name=self.workflow_client.application_name,
|
|
125
|
+
deployment_name=DEPLOYMENT_NAME,
|
|
125
126
|
task_queue=self.workflow_client.worker_task_queue,
|
|
126
127
|
namespace=self.workflow_client.namespace,
|
|
127
128
|
host=self.workflow_client.host,
|
{atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atlan-application-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1rc64
|
|
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
|
{atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/RECORD
RENAMED
|
@@ -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=S3I_WUGFbmAPH5GTtoTKD5rxILGevkZ219zhctLQles,11568
|
|
3
|
+
application_sdk/version.py,sha256=Q9wvKTw8AbBOhwCGADxsnpaSLWW9daKc9sh6yO50mMo,88
|
|
4
|
+
application_sdk/worker.py,sha256=D3-wtfGv1DLFKi1YSaE3jTcX66eC00N6RwtBu9RkgNc,7555
|
|
5
5
|
application_sdk/activities/__init__.py,sha256=L5WXkTwOwGtjWAlXrUJRCKGwyIyp3z8fBv8BZVCRFQI,11175
|
|
6
6
|
application_sdk/activities/lock_management.py,sha256=6Wdf3jMKitoarHQP91PIJOoGFz4aaOLS_40c7n1yAOA,3902
|
|
7
7
|
application_sdk/activities/.cursor/BUGBOT.md,sha256=FNykX5aMkdOhzgpiGqstOnSp9JN63iR2XP3onU4AGh8,15843
|
|
@@ -18,12 +18,12 @@ application_sdk/application/__init__.py,sha256=hb5zBc4zi-10av8Ivbovhb0CEAwNgr3eF
|
|
|
18
18
|
application_sdk/application/metadata_extraction/sql.py,sha256=rOd06Wodr4GyzupCYxVSCsNcuNar1rJM66ej9vocNHw,8138
|
|
19
19
|
application_sdk/clients/__init__.py,sha256=C9T84J7V6ZumcoWJPAxdd3tqSmbyciaGBJn-CaCCny0,1341
|
|
20
20
|
application_sdk/clients/atlan.py,sha256=l6yV39fr1006SJFwkOTNDQlbSFlHCZQaUPfdUlzdVEg,5053
|
|
21
|
-
application_sdk/clients/atlan_auth.py,sha256=
|
|
21
|
+
application_sdk/clients/atlan_auth.py,sha256=_MykgutI-Ill1t8ERgc1a7QrfaxnrtZjD48FAT-ER9M,8642
|
|
22
22
|
application_sdk/clients/base.py,sha256=TIn3pG89eXUc1XSYf4jk66m1vajWp0WxcCQOOltdazA,14021
|
|
23
23
|
application_sdk/clients/models.py,sha256=iZOTyH6LO64kozdiUPCFCN0NgLhd_Gtv0lH7ZIPdo8w,1800
|
|
24
24
|
application_sdk/clients/redis.py,sha256=IfAD32vLp88BCvsDTaQtxFHxzHlEx4V7TK7h1HwDDBg,15917
|
|
25
25
|
application_sdk/clients/sql.py,sha256=lXeVu_dute30IaWWK5gHBhjEs2dXp_e0XkOMsbOsq64,19589
|
|
26
|
-
application_sdk/clients/temporal.py,sha256=
|
|
26
|
+
application_sdk/clients/temporal.py,sha256=ekmgQhOOfPTz7Mi9n85H_p2jS6DF5inxbMGLljTXQyA,19869
|
|
27
27
|
application_sdk/clients/utils.py,sha256=zLFOJbTr_6TOqnjfVFGY85OtIXZ4FQy_rquzjaydkbY,779
|
|
28
28
|
application_sdk/clients/workflow.py,sha256=6bSqmA3sNCk9oY68dOjBUDZ9DhNKQxPD75qqE0cfldc,6104
|
|
29
29
|
application_sdk/clients/.cursor/BUGBOT.md,sha256=7nEDUqWBEMI_uU6eK1jCSZGeXoQtLQcKwOrDn8AIDWo,10595
|
|
@@ -55,7 +55,7 @@ application_sdk/docgen/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
55
55
|
application_sdk/docgen/parsers/directory.py,sha256=8Kk2sjb-0l2wLO_njdlcuHjv5akoNgmf-FmaDSaE4WM,7751
|
|
56
56
|
application_sdk/docgen/parsers/manifest.py,sha256=3NP-dBTpHAUQa477usMIDaKSb_9xfLE8G3RX0T1Bq2s,3318
|
|
57
57
|
application_sdk/events/__init__.py,sha256=OcbVWDF4ZKRTJXK9UaFVtYEwu-3DHE77S-Sn6jNafUs,204
|
|
58
|
-
application_sdk/events/models.py,sha256=
|
|
58
|
+
application_sdk/events/models.py,sha256=kEzJKvb-G1M7aKrLPgAmsukJXLXeh8hIJKwEkOiaY28,6115
|
|
59
59
|
application_sdk/handlers/__init__.py,sha256=3Wf7jCVFR2nYOyHZEc9jj8BQUnHCylFqoezp70J2Df0,1329
|
|
60
60
|
application_sdk/handlers/base.py,sha256=ieWFbv8Gm7vfrrpS-mdMSm-mHGuQY02qiAVX2qPdj3w,2467
|
|
61
61
|
application_sdk/handlers/sql.py,sha256=6A_9xCtkXyNY5gPhImbftzrdPIEWIeTTqjyIewVESHA,17815
|
|
@@ -98,7 +98,7 @@ application_sdk/services/__init__.py,sha256=H-5HZEPdr53MUfAggyHqHhRXDRLZFZsxvJgW
|
|
|
98
98
|
application_sdk/services/atlan_storage.py,sha256=TKzXxu0yXeUcmZehwp8PcnQTC4A9w9RlZ0Fl-Xp1bLE,8509
|
|
99
99
|
application_sdk/services/eventstore.py,sha256=X03JzodKByXh8w8nOl658rnnZfMFTj0IkmiLVbd6IN8,6729
|
|
100
100
|
application_sdk/services/objectstore.py,sha256=7TZSQAoba1-2Eb-C8v9ULhba6-Ss9ym4ICjrISr8CAs,17203
|
|
101
|
-
application_sdk/services/secretstore.py,sha256=
|
|
101
|
+
application_sdk/services/secretstore.py,sha256=Jd2gYyBcF31x8Hs8d5J93SWBXBdt6ULGvSk-Gfxb8Dw,19072
|
|
102
102
|
application_sdk/services/statestore.py,sha256=3-afiM3Vsoe1XDYRokdGTB5I5CwOKyieuX5RwIZf77o,9413
|
|
103
103
|
application_sdk/test_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
104
|
application_sdk/test_utils/workflow_monitoring.py,sha256=gqq6CsT62GrMt2GqtNSb1iD_-t4MBffQvpO0BXboZek,3490
|
|
@@ -157,8 +157,8 @@ application_sdk/workflows/metadata_extraction/__init__.py,sha256=jHUe_ZBQ66jx8bg
|
|
|
157
157
|
application_sdk/workflows/metadata_extraction/sql.py,sha256=6ZaVt84n-8U2ZvR9GR7uIJKv5v8CuyQjhlnoRJvDszc,12435
|
|
158
158
|
application_sdk/workflows/query_extraction/__init__.py,sha256=n066_CX5RpJz6DIxGMkKS3eGSRg03ilaCtsqfJWQb7Q,117
|
|
159
159
|
application_sdk/workflows/query_extraction/sql.py,sha256=kT_JQkLCRZ44ZpaC4QvPL6DxnRIIVh8gYHLqRbMI-hA,4826
|
|
160
|
-
atlan_application_sdk-0.1.
|
|
161
|
-
atlan_application_sdk-0.1.
|
|
162
|
-
atlan_application_sdk-0.1.
|
|
163
|
-
atlan_application_sdk-0.1.
|
|
164
|
-
atlan_application_sdk-0.1.
|
|
160
|
+
atlan_application_sdk-0.1.1rc64.dist-info/METADATA,sha256=EwFzqHyE-Kr85w44hkHi_91zSJBFrSq3GV9BMnJJ_VM,5730
|
|
161
|
+
atlan_application_sdk-0.1.1rc64.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
162
|
+
atlan_application_sdk-0.1.1rc64.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
163
|
+
atlan_application_sdk-0.1.1rc64.dist-info/licenses/NOTICE,sha256=A-XVVGt3KOYuuMmvSMIFkg534F1vHiCggEBp4Ez3wGk,1041
|
|
164
|
+
atlan_application_sdk-0.1.1rc64.dist-info/RECORD,,
|
{atlan_application_sdk-0.1.1rc62.dist-info → atlan_application_sdk-0.1.1rc64.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|