atlan-application-sdk-api 3.51.0__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/__init__.py +7 -0
- application_sdk/_build_info.py +62 -0
- application_sdk/_context_errors.py +65 -0
- application_sdk/_install.py +130 -0
- application_sdk/_logging.py +171 -0
- application_sdk/_runtime/__init__.py +29 -0
- application_sdk/_runtime/offload.py +1020 -0
- application_sdk/_runtime/progress.py +757 -0
- application_sdk/clients/__init__.py +63 -0
- application_sdk/clients/_interface.py +58 -0
- application_sdk/clients/_sql_failure_classifier.py +112 -0
- application_sdk/clients/base.py +565 -0
- application_sdk/clients/http_errors.py +28 -0
- application_sdk/clients/models.py +49 -0
- application_sdk/clients/sql.py +1218 -0
- application_sdk/clients/sql_errors.py +192 -0
- application_sdk/clients/sql_typecasters.py +431 -0
- application_sdk/clients/ssl_utils.py +247 -0
- application_sdk/common/__init__.py +53 -0
- application_sdk/common/_generated_tree.py +321 -0
- application_sdk/common/_listing.py +192 -0
- application_sdk/common/aws_utils.py +522 -0
- application_sdk/common/aws_utils_errors.py +88 -0
- application_sdk/common/build_identity.py +120 -0
- application_sdk/common/concurrency.py +45 -0
- application_sdk/common/errors.py +99 -0
- application_sdk/common/filter_matching.py +261 -0
- application_sdk/common/logger.py +11 -0
- application_sdk/common/models.py +31 -0
- application_sdk/common/sql_filters.py +719 -0
- application_sdk/common/sql_filters_errors.py +17 -0
- application_sdk/common/task_queue.py +431 -0
- application_sdk/common/types.py +51 -0
- application_sdk/common/utils.py +87 -0
- application_sdk/constants.py +1120 -0
- application_sdk/contracts/__init__.py +158 -0
- application_sdk/contracts/base.py +1178 -0
- application_sdk/contracts/events.py +251 -0
- application_sdk/contracts/types.py +801 -0
- application_sdk/contracts/types_errors.py +17 -0
- application_sdk/credentials/__init__.py +326 -0
- application_sdk/credentials/errors.py +226 -0
- application_sdk/credentials/extra.py +114 -0
- application_sdk/credentials/ingress.py +283 -0
- application_sdk/credentials/ref.py +376 -0
- application_sdk/credentials/spec.py +182 -0
- application_sdk/credentials/types.py +339 -0
- application_sdk/credentials/utils.py +257 -0
- application_sdk/errors/__init__.py +224 -0
- application_sdk/errors/base.py +504 -0
- application_sdk/errors/categories.py +118 -0
- application_sdk/errors/http.py +81 -0
- application_sdk/errors/leaves.py +526 -0
- application_sdk/errors/wire.py +124 -0
- application_sdk/handler/__init__.py +97 -0
- application_sdk/handler/_warmup.py +198 -0
- application_sdk/handler/asgi.py +252 -0
- application_sdk/handler/base.py +281 -0
- application_sdk/handler/context.py +212 -0
- application_sdk/handler/contracts.py +1166 -0
- application_sdk/handler/manifest.py +81 -0
- application_sdk/handler/metadata_routes.py +699 -0
- application_sdk/handler/object_routes.py +418 -0
- application_sdk/handler/request_contract.py +91 -0
- application_sdk/handler/routes.py +1118 -0
- application_sdk/handler/service_errors.py +37 -0
- application_sdk/server/__init__.py +15 -0
- application_sdk/server/fastapi/models.py +338 -0
- application_sdk/templates/__init__.py +52 -0
- application_sdk/templates/contracts/__init__.py +111 -0
- application_sdk/templates/contracts/base_metadata_extraction.py +32 -0
- application_sdk/templates/contracts/incremental_sql.py +563 -0
- application_sdk/templates/contracts/sql_metadata.py +824 -0
- application_sdk/templates/contracts/sql_query.py +105 -0
- application_sdk/version.py +40 -0
- atlan_application_sdk_api-3.51.0.dist-info/METADATA +230 -0
- atlan_application_sdk_api-3.51.0.dist-info/RECORD +78 -0
- atlan_application_sdk_api-3.51.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Base client interfaces for Atlan applications.
|
|
2
|
+
|
|
3
|
+
Public API::
|
|
4
|
+
|
|
5
|
+
from application_sdk.clients import (
|
|
6
|
+
ClientInterface, BaseClient,
|
|
7
|
+
BaseSQLClient, AsyncBaseSQLClient, DatabaseConfig,
|
|
8
|
+
RedisClient, RedisClientAsync,
|
|
9
|
+
AzureClient, AzureAuthProvider,
|
|
10
|
+
get_ssl_context, create_ssl_context_with_custom_certs,
|
|
11
|
+
)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
from application_sdk.clients._interface import ClientInterface as ClientInterface
|
|
18
|
+
from application_sdk.clients.base import BaseClient as BaseClient
|
|
19
|
+
from application_sdk.clients.models import DatabaseConfig as DatabaseConfig
|
|
20
|
+
from application_sdk.clients.sql import AsyncBaseSQLClient as AsyncBaseSQLClient
|
|
21
|
+
from application_sdk.clients.sql import BaseSQLClient as BaseSQLClient
|
|
22
|
+
from application_sdk.clients.ssl_utils import (
|
|
23
|
+
create_ssl_context_with_custom_certs as create_ssl_context_with_custom_certs,
|
|
24
|
+
)
|
|
25
|
+
from application_sdk.clients.ssl_utils import get_ssl_context as get_ssl_context
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from application_sdk.clients.azure.auth import AzureAuthProvider
|
|
29
|
+
from application_sdk.clients.azure.client import AzureClient
|
|
30
|
+
from application_sdk.clients.redis import RedisClient, RedisClientAsync
|
|
31
|
+
|
|
32
|
+
# Symbols backed by optional extras ([azure] and [distributed_lock]) — lazy-loaded
|
|
33
|
+
# so that `from application_sdk.clients import BaseSQLClient` never pulls azure-* or redis.
|
|
34
|
+
_LAZY: dict[str, tuple[str, str]] = {
|
|
35
|
+
"AzureAuthProvider": ("application_sdk.clients.azure.auth", "AzureAuthProvider"),
|
|
36
|
+
"AzureClient": ("application_sdk.clients.azure.client", "AzureClient"),
|
|
37
|
+
"RedisClient": ("application_sdk.clients.redis", "RedisClient"),
|
|
38
|
+
"RedisClientAsync": ("application_sdk.clients.redis", "RedisClientAsync"),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"AsyncBaseSQLClient",
|
|
43
|
+
"AzureAuthProvider",
|
|
44
|
+
"AzureClient",
|
|
45
|
+
"BaseClient",
|
|
46
|
+
"BaseSQLClient",
|
|
47
|
+
"ClientInterface",
|
|
48
|
+
"DatabaseConfig",
|
|
49
|
+
"RedisClient",
|
|
50
|
+
"RedisClientAsync",
|
|
51
|
+
"create_ssl_context_with_custom_certs",
|
|
52
|
+
"get_ssl_context",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def __getattr__(name: str):
|
|
57
|
+
target = _LAZY.get(name)
|
|
58
|
+
if target is None:
|
|
59
|
+
raise AttributeError(
|
|
60
|
+
f"module 'application_sdk.clients' has no attribute {name!r}"
|
|
61
|
+
)
|
|
62
|
+
mod_name, attr = target
|
|
63
|
+
return getattr(importlib.import_module(mod_name), attr)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from contextlib import AbstractAsyncContextManager
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
from application_sdk.clients.sql_errors import AbstractClientLoadError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClientInterface(AbstractAsyncContextManager["ClientInterface"], ABC):
|
|
10
|
+
"""Base interface class for implementing client connections.
|
|
11
|
+
|
|
12
|
+
This abstract class defines the required methods that any client implementation
|
|
13
|
+
must provide for establishing and managing connections to data sources.
|
|
14
|
+
|
|
15
|
+
A client is an async context manager: ``async with`` calls ``close()`` on
|
|
16
|
+
exit, including when the block raises. Prefer it over calling ``close()``
|
|
17
|
+
by hand::
|
|
18
|
+
|
|
19
|
+
async with MyClient() as client:
|
|
20
|
+
await client.load(credentials=credentials)
|
|
21
|
+
...
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def load(self, *args: Any, **kwargs: Any) -> None:
|
|
26
|
+
"""Establish the client connection.
|
|
27
|
+
|
|
28
|
+
This method should handle the initialization and connection setup
|
|
29
|
+
for the specific client implementation.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
AbstractClientLoadError: If the subclass does not implement this method.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
raise AbstractClientLoadError()
|
|
36
|
+
|
|
37
|
+
async def close(self, *args: Any, **kwargs: Any) -> None:
|
|
38
|
+
"""Close the client connection.
|
|
39
|
+
|
|
40
|
+
This method should properly terminate the connection and clean up
|
|
41
|
+
any resources used by the client. By default, it does nothing.
|
|
42
|
+
Subclasses should override this method if cleanup is needed, and
|
|
43
|
+
call ``super().close()`` so the base class releases its resources.
|
|
44
|
+
"""
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
async def __aenter__(self) -> Self:
|
|
48
|
+
"""Return the client. ``load()`` is not called; it needs credentials."""
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
async def __aexit__(
|
|
52
|
+
self,
|
|
53
|
+
exc_type: type[BaseException] | None,
|
|
54
|
+
exc_val: BaseException | None,
|
|
55
|
+
exc_tb: TracebackType | None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Close the client, whether or not the block raised."""
|
|
58
|
+
await self.close()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Classify a failed SQL connect by what actually went wrong.
|
|
2
|
+
|
|
3
|
+
One reader for every SQL connect surface: ``BaseSQLClient.load`` /
|
|
4
|
+
``AsyncBaseSQLClient.load`` and ``SqlApp.prime_sql_auth`` all ask the same
|
|
5
|
+
question of a driver exception — was it the credentials, the clock, or the
|
|
6
|
+
network path — and answer it here. Each caller builds its own typed leaf from
|
|
7
|
+
the verdict, so the wording can differ per surface while the decision cannot.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SqlFailureKind(enum.Enum):
|
|
16
|
+
"""What a SQL connect failure was, as far as the driver error can tell."""
|
|
17
|
+
|
|
18
|
+
AUTH = "auth"
|
|
19
|
+
TIMEOUT = "timeout"
|
|
20
|
+
SOURCE_UNAVAILABLE = "source_unavailable"
|
|
21
|
+
UNCLASSIFIED = "unclassified"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def root_cause(exc: BaseException) -> BaseException:
|
|
25
|
+
"""Walk ``__cause__`` to the innermost exception.
|
|
26
|
+
|
|
27
|
+
The SDK's SQL client rewraps driver exceptions, and SQLAlchemy rewraps the
|
|
28
|
+
DBAPI error, so the outermost exception describes a wrapper and the
|
|
29
|
+
innermost describes what actually happened. Cycle-guarded — a hand-built
|
|
30
|
+
``__cause__`` loop would otherwise hang the worker.
|
|
31
|
+
"""
|
|
32
|
+
seen = {id(exc)}
|
|
33
|
+
while exc.__cause__ is not None and id(exc.__cause__) not in seen:
|
|
34
|
+
exc = exc.__cause__
|
|
35
|
+
seen.add(id(exc))
|
|
36
|
+
return exc
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ODBC renders SQLSTATE HYT00 as "Login timeout expired" — no "timeout" in the
|
|
40
|
+
# class name and no "timed out" in the message, so a generic substring check
|
|
41
|
+
# misses it. Match the driver phrasings explicitly.
|
|
42
|
+
_TIMEOUT_MSG_HINTS = ("timed out", "login timeout", "timeout expired", "hyt00")
|
|
43
|
+
|
|
44
|
+
# Driver error_type for MySQL ``Access denied`` is ``OperationalError`` — that
|
|
45
|
+
# class is overloaded across both auth and network failures, so message content
|
|
46
|
+
# has to be the tiebreaker.
|
|
47
|
+
_AUTH_CLASS_HINTS = ("authentication", "accessdenied", "authfail")
|
|
48
|
+
_AUTH_MSG_HINTS = (
|
|
49
|
+
"access denied",
|
|
50
|
+
"1045",
|
|
51
|
+
"authentication failed",
|
|
52
|
+
"authentication error",
|
|
53
|
+
"login failed",
|
|
54
|
+
"password authentication failed",
|
|
55
|
+
"invalid credentials",
|
|
56
|
+
# Thrift SASL handshake (impyla, PyHive): the server refused the sign-in.
|
|
57
|
+
# Its transport error class says nothing about auth, so only the message can.
|
|
58
|
+
# "TSocket read 0 bytes" and "Bad status: 3" are left out: impalad sends
|
|
59
|
+
# them on a rejected login, but other Thrift servers send them for a dropped
|
|
60
|
+
# connection or a mechanism mismatch, so they are the connector's call.
|
|
61
|
+
# "Could not start SASL" is left out too: the client raises it before any
|
|
62
|
+
# credential reaches the server, e.g. when no local SASL mechanism exists.
|
|
63
|
+
"authentication failure",
|
|
64
|
+
"bad sasl result",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Network / DNS / TLS / connection-refused / generic OperationalError without
|
|
68
|
+
# an auth-keyword message.
|
|
69
|
+
_NETWORK_CLASS_HINTS = (
|
|
70
|
+
"connectionerror",
|
|
71
|
+
"connectionrefused",
|
|
72
|
+
"gaierror",
|
|
73
|
+
"sslerror",
|
|
74
|
+
# ``ssl.SSLCertVerificationError`` — its name does not contain "sslerror".
|
|
75
|
+
"sslcertverificationerror",
|
|
76
|
+
"oserror",
|
|
77
|
+
"operationalerror",
|
|
78
|
+
"interfaceerror",
|
|
79
|
+
# Match wrapped SDK error classnames surfaced as error_type.
|
|
80
|
+
# "dependencyunavailable" is retained for legacy wrappers that predate the
|
|
81
|
+
# SourceUnavailableError migration.
|
|
82
|
+
"sourceunavailable",
|
|
83
|
+
"dependencyunavailable",
|
|
84
|
+
"socketerror",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def classify_sql_failure(error_type: str, error_message: str) -> SqlFailureKind:
|
|
89
|
+
"""Map a driver exception's class name and message to a failure kind.
|
|
90
|
+
|
|
91
|
+
Checked in order — timeout, then auth, then network — because the classes
|
|
92
|
+
overlap: a login timeout is an ``OperationalError`` and so is a rejected
|
|
93
|
+
password, and the message is what tells them apart.
|
|
94
|
+
"""
|
|
95
|
+
type_lower = error_type.lower()
|
|
96
|
+
msg_lower = error_message.lower()
|
|
97
|
+
|
|
98
|
+
if "timeout" in type_lower or any(h in msg_lower for h in _TIMEOUT_MSG_HINTS):
|
|
99
|
+
return SqlFailureKind.TIMEOUT
|
|
100
|
+
if any(h in type_lower for h in _AUTH_CLASS_HINTS) or any(
|
|
101
|
+
h in msg_lower for h in _AUTH_MSG_HINTS
|
|
102
|
+
):
|
|
103
|
+
return SqlFailureKind.AUTH
|
|
104
|
+
if any(h in type_lower for h in _NETWORK_CLASS_HINTS):
|
|
105
|
+
return SqlFailureKind.SOURCE_UNAVAILABLE
|
|
106
|
+
return SqlFailureKind.UNCLASSIFIED
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def classify_sql_exception(exc: BaseException) -> SqlFailureKind:
|
|
110
|
+
"""Classify a live exception by its root cause's class name and message."""
|
|
111
|
+
root = root_cause(exc)
|
|
112
|
+
return classify_sql_failure(type(root).__name__, str(root))
|