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.
Files changed (78) hide show
  1. application_sdk/__init__.py +7 -0
  2. application_sdk/_build_info.py +62 -0
  3. application_sdk/_context_errors.py +65 -0
  4. application_sdk/_install.py +130 -0
  5. application_sdk/_logging.py +171 -0
  6. application_sdk/_runtime/__init__.py +29 -0
  7. application_sdk/_runtime/offload.py +1020 -0
  8. application_sdk/_runtime/progress.py +757 -0
  9. application_sdk/clients/__init__.py +63 -0
  10. application_sdk/clients/_interface.py +58 -0
  11. application_sdk/clients/_sql_failure_classifier.py +112 -0
  12. application_sdk/clients/base.py +565 -0
  13. application_sdk/clients/http_errors.py +28 -0
  14. application_sdk/clients/models.py +49 -0
  15. application_sdk/clients/sql.py +1218 -0
  16. application_sdk/clients/sql_errors.py +192 -0
  17. application_sdk/clients/sql_typecasters.py +431 -0
  18. application_sdk/clients/ssl_utils.py +247 -0
  19. application_sdk/common/__init__.py +53 -0
  20. application_sdk/common/_generated_tree.py +321 -0
  21. application_sdk/common/_listing.py +192 -0
  22. application_sdk/common/aws_utils.py +522 -0
  23. application_sdk/common/aws_utils_errors.py +88 -0
  24. application_sdk/common/build_identity.py +120 -0
  25. application_sdk/common/concurrency.py +45 -0
  26. application_sdk/common/errors.py +99 -0
  27. application_sdk/common/filter_matching.py +261 -0
  28. application_sdk/common/logger.py +11 -0
  29. application_sdk/common/models.py +31 -0
  30. application_sdk/common/sql_filters.py +719 -0
  31. application_sdk/common/sql_filters_errors.py +17 -0
  32. application_sdk/common/task_queue.py +431 -0
  33. application_sdk/common/types.py +51 -0
  34. application_sdk/common/utils.py +87 -0
  35. application_sdk/constants.py +1120 -0
  36. application_sdk/contracts/__init__.py +158 -0
  37. application_sdk/contracts/base.py +1178 -0
  38. application_sdk/contracts/events.py +251 -0
  39. application_sdk/contracts/types.py +801 -0
  40. application_sdk/contracts/types_errors.py +17 -0
  41. application_sdk/credentials/__init__.py +326 -0
  42. application_sdk/credentials/errors.py +226 -0
  43. application_sdk/credentials/extra.py +114 -0
  44. application_sdk/credentials/ingress.py +283 -0
  45. application_sdk/credentials/ref.py +376 -0
  46. application_sdk/credentials/spec.py +182 -0
  47. application_sdk/credentials/types.py +339 -0
  48. application_sdk/credentials/utils.py +257 -0
  49. application_sdk/errors/__init__.py +224 -0
  50. application_sdk/errors/base.py +504 -0
  51. application_sdk/errors/categories.py +118 -0
  52. application_sdk/errors/http.py +81 -0
  53. application_sdk/errors/leaves.py +526 -0
  54. application_sdk/errors/wire.py +124 -0
  55. application_sdk/handler/__init__.py +97 -0
  56. application_sdk/handler/_warmup.py +198 -0
  57. application_sdk/handler/asgi.py +252 -0
  58. application_sdk/handler/base.py +281 -0
  59. application_sdk/handler/context.py +212 -0
  60. application_sdk/handler/contracts.py +1166 -0
  61. application_sdk/handler/manifest.py +81 -0
  62. application_sdk/handler/metadata_routes.py +699 -0
  63. application_sdk/handler/object_routes.py +418 -0
  64. application_sdk/handler/request_contract.py +91 -0
  65. application_sdk/handler/routes.py +1118 -0
  66. application_sdk/handler/service_errors.py +37 -0
  67. application_sdk/server/__init__.py +15 -0
  68. application_sdk/server/fastapi/models.py +338 -0
  69. application_sdk/templates/__init__.py +52 -0
  70. application_sdk/templates/contracts/__init__.py +111 -0
  71. application_sdk/templates/contracts/base_metadata_extraction.py +32 -0
  72. application_sdk/templates/contracts/incremental_sql.py +563 -0
  73. application_sdk/templates/contracts/sql_metadata.py +824 -0
  74. application_sdk/templates/contracts/sql_query.py +105 -0
  75. application_sdk/version.py +40 -0
  76. atlan_application_sdk_api-3.51.0.dist-info/METADATA +230 -0
  77. atlan_application_sdk_api-3.51.0.dist-info/RECORD +78 -0
  78. atlan_application_sdk_api-3.51.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,7 @@
1
+ """
2
+ Application SDK for developing applications on the Atlan Platform.
3
+ """
4
+
5
+ from application_sdk.version import __version__
6
+
7
+ __all__ = ["__version__"]
@@ -0,0 +1,62 @@
1
+ """The build-identity file reader, with no dependencies beyond the stdlib.
2
+
3
+ :mod:`application_sdk.constants` exposes it as ``load_build_info``, as before.
4
+ It lives here so :mod:`application_sdk.common.build_identity` can read the file
5
+ without importing ``constants``, whose module-level imports the consolidated
6
+ API host's core install does not carry.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+
14
+ __all__ = ["load_build_info"]
15
+
16
+
17
+ def load_build_info(path: str | None = None) -> dict[str, str]:
18
+ """Read the build-identity file the reusable CI bakes into the image.
19
+
20
+ ``build-and-publish-app.yaml`` writes ``app/atlan_build.json`` into the
21
+ build context and the template Dockerfile's ``COPY app/ app/`` carries it
22
+ into the image. Keys: ``app_version`` (the exact string Global Marketplace
23
+ stores as ``versions.version``), ``commit_sha``, ``build_id`` (the immutable
24
+ image tag, the same value ``ATLAN_BUILD_ID`` carries on the e2e path),
25
+ ``image``, ``built_at``.
26
+
27
+ Missing or malformed file → ``{}``; callers fall back to the env vars.
28
+ ``ATLAN_BUILD_INFO_PATH`` overrides the lookup for non-template layouts.
29
+
30
+ Public because :mod:`application_sdk.app.build_identity` reads the same file
31
+ through it. A second parser there would be a second place for the key names
32
+ to drift from what CI writes.
33
+ """
34
+ if path:
35
+ candidates = [path]
36
+ else:
37
+ override = os.getenv("ATLAN_BUILD_INFO_PATH", "")
38
+ generated_dir = os.environ.get("ATLAN_CONTRACT_GENERATED_DIR", "app/generated")
39
+ candidates = (
40
+ [override]
41
+ if override
42
+ else [
43
+ "app/atlan_build.json",
44
+ os.path.join(
45
+ os.path.dirname(generated_dir.rstrip("/")) or ".",
46
+ "atlan_build.json",
47
+ ),
48
+ ]
49
+ )
50
+ for candidate in candidates:
51
+ try:
52
+ with open(candidate, encoding="utf-8") as fh:
53
+ data = json.load(fh)
54
+ except (OSError, ValueError):
55
+ continue
56
+ if isinstance(data, dict):
57
+ return {
58
+ str(k): str(v)
59
+ for k, v in data.items()
60
+ if isinstance(v, (str, int, float)) and not isinstance(v, bool)
61
+ }
62
+ return {}
@@ -0,0 +1,65 @@
1
+ """Context errors raised by code that ships in the api distribution.
2
+
3
+ Defined here, not in ``application_sdk.app``, so the handler surface can raise
4
+ them on an api-only install. ``application_sdk.app.base`` and
5
+ ``application_sdk.app.base_errors`` re-export them under their usual names.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import ClassVar
12
+
13
+ from application_sdk.errors import APP_CONTEXT_ERROR, ErrorCode
14
+ from application_sdk.errors.leaves import InternalError as _InternalError
15
+ from application_sdk.errors.leaves import PreconditionError
16
+
17
+
18
+ @dataclass(kw_only=True)
19
+ class ObjectStoreNotConfiguredError(PreconditionError):
20
+ """Object store required by a @task but not configured in the deployment."""
21
+
22
+ code: ClassVar[str] = "PRECONDITION_OBJECT_STORE_NOT_CONFIGURED"
23
+ message: str = (
24
+ "No object store configured. "
25
+ "Ensure the deployment has a storage binding or APP_STORAGE_ROOT set."
26
+ )
27
+ resource: str | None = "object_store"
28
+ expected_state: str | None = "configured"
29
+
30
+
31
+ @dataclass(kw_only=True)
32
+ class SecretStoreNotConfiguredError(PreconditionError):
33
+ """Secret store required by get_secret / resolve_credential but not configured."""
34
+
35
+ code: ClassVar[str] = "PRECONDITION_SECRET_STORE_NOT_CONFIGURED"
36
+ message: str = "No secret store configured"
37
+ resource: str | None = "secret_store"
38
+ expected_state: str | None = "configured"
39
+
40
+
41
+ class AppContextError(_InternalError):
42
+ """Raised when App or task context is accessed outside of valid execution scope.
43
+
44
+ This is a programming error — it indicates that context-dependent methods
45
+ (e.g. ``self.context``, ``self.heartbeat()``) were called outside of a
46
+ workflow run or @task execution.
47
+ """
48
+
49
+ DEFAULT_ERROR_CODE: ClassVar[ErrorCode] = APP_CONTEXT_ERROR
50
+ code: ClassVar[str] = "INTERNAL_APP_CONTEXT"
51
+
52
+ def __init__(self, message: str, *, error_code: ErrorCode | None = None) -> None:
53
+ _InternalError.__init__(self, message=message)
54
+ self._legacy_error_code = error_code
55
+
56
+ @property
57
+ def error_code(self) -> ErrorCode:
58
+ return (
59
+ self._legacy_error_code
60
+ if self._legacy_error_code is not None
61
+ else self.DEFAULT_ERROR_CODE
62
+ )
63
+
64
+ def __str__(self) -> str:
65
+ return f"[{self.error_code.code}] {self.message}"
@@ -0,0 +1,130 @@
1
+ """Which distribution is installed: the full SDK, or only the api subset.
2
+
3
+ ``atlan-application-sdk-api`` ships a listed subset of ``application_sdk/`` (see
4
+ ``packages/api/api-files.txt``) for the consolidated API host. A few listed
5
+ files reach worker-only code lazily (the object store, Temporal, the worker's
6
+ infrastructure). Each such import is wrapped so that, on an api-only install,
7
+ it takes a stated fallback instead of raising at call time — and so that, with
8
+ the full SDK installed, a missing module is still a real error and still raises.
9
+ :func:`api_only_install` is the one test that tells the two apart.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import functools
15
+ import importlib
16
+ import importlib.util
17
+ from collections.abc import Collection, Mapping, MutableMapping
18
+ from types import ModuleType
19
+ from typing import Any
20
+
21
+ #: A module the full SDK always ships and the api distribution never does.
22
+ _WORKER_SENTINEL = "application_sdk.main"
23
+
24
+ #: The modules the :func:`worker_only_missing` guards import, none of them shipped
25
+ #: by the api distribution: the object store (``contracts.types``,
26
+ #: ``credentials.utils``, ``common.utils``, ``observability.observability``), the
27
+ #: worker's infrastructure (``handler.context``, ``observability.observability``),
28
+ #: the SDK's structured logger (``_logging``) and Temporal
29
+ #: (``observability.utils``). A guard falls back only when one of these (or a
30
+ #: submodule of one) is what is missing; anything else is a real error. A new
31
+ #: guard over another worker-only module adds that module here.
32
+ _WORKER_ONLY_PREFIXES: tuple[str, ...] = (
33
+ "application_sdk.infrastructure",
34
+ "application_sdk.observability",
35
+ "application_sdk.storage",
36
+ "temporalio",
37
+ )
38
+
39
+
40
+ @functools.cache
41
+ def api_only_install() -> bool:
42
+ """True when only ``atlan-application-sdk-api`` is installed (the API host)."""
43
+ return importlib.util.find_spec(_WORKER_SENTINEL) is None
44
+
45
+
46
+ def worker_only_missing(exc: ModuleNotFoundError) -> bool:
47
+ """True when ``exc`` is an api-only install lacking a worker-only module.
48
+
49
+ Use it in ``except ModuleNotFoundError as exc: if not worker_only_missing(exc):
50
+ raise``. With the full SDK installed it is always False, so the error
51
+ propagates exactly as it did before the split. On an api-only install it is
52
+ True only when the missing module is one of :data:`_WORKER_ONLY_PREFIXES`;
53
+ a missing api file or third-party dependency still raises.
54
+ """
55
+ if not api_only_install():
56
+ return False
57
+ missing = exc.name or ""
58
+ return any(
59
+ missing == prefix or missing.startswith(prefix + ".")
60
+ for prefix in _WORKER_ONLY_PREFIXES
61
+ )
62
+
63
+
64
+ def import_submodule(package: str, name: str) -> ModuleType | None:
65
+ """``package.name`` when it names an existing submodule, else ``None``.
66
+
67
+ A package whose ``__init__`` imports lazily (a ``__getattr__`` over a
68
+ ``_LAZY`` map) no longer has its submodules as attributes until something
69
+ imports them. :func:`lazy_getattr` calls this for the submodules the package
70
+ used to import eagerly, so ``import pkg; pkg.sub`` keeps working for them.
71
+ """
72
+ if not name.isidentifier() or name.startswith("__"):
73
+ return None
74
+ full = f"{package}.{name}"
75
+ try:
76
+ spec = importlib.util.find_spec(full)
77
+ except (ImportError, ValueError):
78
+ return None
79
+ return importlib.import_module(full) if spec is not None else None
80
+
81
+
82
+ def _names_missing_module(exc: ModuleNotFoundError, module: str) -> bool:
83
+ """Whether ``exc`` is *module* itself (or a parent package) being absent."""
84
+ missing = exc.name or ""
85
+ return bool(missing) and (module == missing or module.startswith(f"{missing}."))
86
+
87
+
88
+ def lazy_getattr(
89
+ module_name: str,
90
+ name: str,
91
+ lazy_map: Mapping[str, tuple[str, str]],
92
+ globals_: MutableMapping[str, Any],
93
+ submodules: Collection[str] = (),
94
+ ) -> Any:
95
+ """The PEP 562 ``__getattr__`` body of a package that imports lazily.
96
+
97
+ *lazy_map* maps a public name to ``(module, attribute)``; the first access
98
+ imports it and caches it in the package's *globals_*. *submodules* are the
99
+ submodules the package imported eagerly before it went lazy, so
100
+ ``import pkg; pkg.sub`` keeps working for those and only those: any other
101
+ submodule is an attribute only once something imports it, as with any
102
+ package (so ``hasattr(pkg, "sub")`` does not import it).
103
+ ``from pkg import sub`` needs no entry; the import system loads it.
104
+
105
+ A name whose module is not installed (on an api-only install the worker's
106
+ modules are absent) raises :class:`AttributeError` chained to the import
107
+ error, so ``hasattr`` and ``getattr(pkg, name, default)`` answer normally.
108
+ A module that is installed but fails to import raises its own error
109
+ unchanged: that is a real bug, and ``from pkg import name`` would otherwise
110
+ report only "cannot import name".
111
+ """
112
+ target = lazy_map.get(name)
113
+ if target is not None:
114
+ try:
115
+ module = importlib.import_module(target[0])
116
+ except ModuleNotFoundError as exc:
117
+ if not _names_missing_module(exc, target[0]):
118
+ raise
119
+ raise AttributeError(
120
+ f"module {module_name!r} has no attribute {name!r}: "
121
+ f"{target[0]} is not installed"
122
+ ) from exc
123
+ value = getattr(module, target[1])
124
+ globals_[name] = value
125
+ return value
126
+ if name in submodules:
127
+ submodule = import_submodule(module_name, name)
128
+ if submodule is not None:
129
+ return submodule
130
+ raise AttributeError(f"module {module_name!r} has no attribute {name!r}")
@@ -0,0 +1,171 @@
1
+ """Logger lookup for code that ships in the api distribution.
2
+
3
+ The handler surface (errors, handler, contracts, credential specs) ships in
4
+ ``atlan-application-sdk-api``, which ``atlan-application-sdk`` depends on and
5
+ which the consolidated API host installs without the worker SDK. Those files log
6
+ through this helper. With the full SDK installed it returns the SDK's structured
7
+ logger (``observability.logger_adaptor.get_logger``), exactly as before; with
8
+ only the api distribution installed ``application_sdk.observability`` does not
9
+ exist, and it falls back to the standard library.
10
+
11
+ On that api-only path the stdlib ``application_sdk`` logger would otherwise have
12
+ no handler, so its INFO lines (route lifecycle, preflight verdicts) would be
13
+ dropped by the root logger's default WARNING level. The first fallback lookup
14
+ therefore gives it one minimal handler — INFO and above, to stderr — and sets
15
+ its level to INFO, but only when neither it nor the root logger has a handler
16
+ yet (a host that configured logging first is left alone). If the root logger
17
+ gains a handler later, the SDK's handler stands down and records propagate there
18
+ instead, so no line is written twice; the ``application_sdk`` logger stays at
19
+ INFO, so the SDK's INFO lines still reach those handlers unless the host sets
20
+ that logger's level itself. The full SDK install never takes this path: worker
21
+ logging is unchanged.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import functools
27
+ import logging
28
+ from collections.abc import Mapping
29
+ from typing import Protocol
30
+
31
+ from application_sdk._install import worker_only_missing
32
+
33
+ _ExcInfo = bool | BaseException | None
34
+
35
+
36
+ class SdkLogger(Protocol):
37
+ """The logging calls both loggers :func:`get_logger` returns accept.
38
+
39
+ Satisfied by the SDK's ``AtlanLoggerAdapter`` (full install) and by
40
+ :class:`logging.Logger` (api-only install), so code here may use only what
41
+ both take: ``%``-style arguments and the stdlib keywords.
42
+ """
43
+
44
+ def debug(
45
+ self,
46
+ msg: str,
47
+ /,
48
+ *args: object,
49
+ exc_info: _ExcInfo = ...,
50
+ stack_info: bool = ...,
51
+ stacklevel: int = ...,
52
+ extra: Mapping[str, object] | None = ...,
53
+ ) -> None: ...
54
+
55
+ def info(
56
+ self,
57
+ msg: str,
58
+ /,
59
+ *args: object,
60
+ exc_info: _ExcInfo = ...,
61
+ stack_info: bool = ...,
62
+ stacklevel: int = ...,
63
+ extra: Mapping[str, object] | None = ...,
64
+ ) -> None: ...
65
+
66
+ def warning(
67
+ self,
68
+ msg: str,
69
+ /,
70
+ *args: object,
71
+ exc_info: _ExcInfo = ...,
72
+ stack_info: bool = ...,
73
+ stacklevel: int = ...,
74
+ extra: Mapping[str, object] | None = ...,
75
+ ) -> None: ...
76
+
77
+ def error(
78
+ self,
79
+ msg: str,
80
+ /,
81
+ *args: object,
82
+ exc_info: _ExcInfo = ...,
83
+ stack_info: bool = ...,
84
+ stacklevel: int = ...,
85
+ extra: Mapping[str, object] | None = ...,
86
+ ) -> None: ...
87
+
88
+ def exception(
89
+ self,
90
+ msg: str,
91
+ /,
92
+ *args: object,
93
+ exc_info: _ExcInfo = ...,
94
+ stack_info: bool = ...,
95
+ stacklevel: int = ...,
96
+ extra: Mapping[str, object] | None = ...,
97
+ ) -> None: ...
98
+
99
+ def critical(
100
+ self,
101
+ msg: str,
102
+ /,
103
+ *args: object,
104
+ exc_info: _ExcInfo = ...,
105
+ stack_info: bool = ...,
106
+ stacklevel: int = ...,
107
+ extra: Mapping[str, object] | None = ...,
108
+ ) -> None: ...
109
+
110
+ def log(
111
+ self,
112
+ level: int,
113
+ msg: str,
114
+ /,
115
+ *args: object,
116
+ exc_info: _ExcInfo = ...,
117
+ stack_info: bool = ...,
118
+ stacklevel: int = ...,
119
+ extra: Mapping[str, object] | None = ...,
120
+ ) -> None: ...
121
+
122
+
123
+ def get_logger(name: str | None = None) -> SdkLogger:
124
+ """The SDK logger for ``name``, or a stdlib logger on an api-only install.
125
+
126
+ Same signature as :func:`application_sdk.observability.logger_adaptor.get_logger`,
127
+ which the modules using this helper re-exported before.
128
+ """
129
+ try:
130
+ from application_sdk.observability.logger_adaptor import ( # noqa: PLC0415 — absent on an api-only install
131
+ get_logger as _sdk_get_logger,
132
+ )
133
+ except ModuleNotFoundError as exc:
134
+ if not worker_only_missing(exc):
135
+ raise
136
+ _configure_api_only_logging()
137
+ return logging.getLogger(name or __name__)
138
+ return _sdk_get_logger(name)
139
+
140
+
141
+ #: The stdlib logger every api-only ``get_logger(__name__)`` in the SDK sits under.
142
+ _SDK_LOGGER_NAME = "application_sdk"
143
+
144
+
145
+ class _ApiOnlyHandler(logging.StreamHandler):
146
+ """stderr, unless the root logger has since been given a handler of its own."""
147
+
148
+ def emit(self, record: logging.LogRecord) -> None:
149
+ if logging.getLogger().handlers:
150
+ return # the record propagates to the host's own root handlers
151
+ super().emit(record)
152
+
153
+
154
+ @functools.cache
155
+ def _configure_api_only_logging() -> None:
156
+ """Give the ``application_sdk`` stdlib logger a minimal INFO handler, once.
157
+
158
+ Only on the api-only path, and only when neither that logger nor the root
159
+ logger has a handler: an existing logging setup is never touched.
160
+ """
161
+ sdk_logger = logging.getLogger(_SDK_LOGGER_NAME)
162
+ if sdk_logger.handlers or logging.getLogger().handlers:
163
+ return
164
+ handler = _ApiOnlyHandler() # stderr
165
+ handler.setLevel(logging.INFO)
166
+ handler.setFormatter(
167
+ logging.Formatter("%(asctime)s %(levelname)s %(name)s - %(message)s")
168
+ )
169
+ sdk_logger.addHandler(handler)
170
+ if sdk_logger.level == logging.NOTSET:
171
+ sdk_logger.setLevel(logging.INFO)
@@ -0,0 +1,29 @@
1
+ """Dependency-neutral runtime substrate — the SDK's bottom layer (ADR-0019).
2
+
3
+ Everything in this package may be imported **at module scope from anywhere in
4
+ the SDK**, including from ``storage/``. That is the package's entire purpose:
5
+ the offload pool and the progress seam are needed by every layer, so they cannot
6
+ live in a layer.
7
+
8
+ **The rule for modules in here.** A ``_runtime`` module may import from the
9
+ standard library, from third-party packages, from
10
+ ``application_sdk.observability.logger_adaptor``, and from its siblings in this
11
+ package (``offload.py`` reads ``progress.py``, which is what makes the offload
12
+ auto-holds work). Nothing else in ``application_sdk``: never
13
+ ``application_sdk.contracts``, ``application_sdk.credentials``,
14
+ ``application_sdk.storage``, ``application_sdk.execution`` or
15
+ ``application_sdk.app`` — each of those pulls in ``storage.ops`` transitively,
16
+ which is exactly the cycle this package exists to remove.
17
+ ``tests/unit/runtime/test_layering.py`` enforces this in a subprocess, so
18
+ breaking the rule fails CI rather than surfacing as an ``ImportError`` at some
19
+ unrelated call site months later.
20
+
21
+ The boundary is *upward*, not lateral — a sibling import stays inside the bottom
22
+ layer and cannot reach a layer above it, so it cannot re-create the cycle.
23
+
24
+ **Private on purpose.** The app-facing paths are unchanged and stay where the
25
+ docs point them: ``application_sdk.execution.heartbeat`` for the offload
26
+ primitives and ``application_sdk.execution.progress`` for the progress seam,
27
+ both thin re-exports over this package. SDK-internal code imports from here
28
+ directly; apps should not need to know this package exists.
29
+ """