elementary-runtime 0.1.0rc1__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.
- elementary_runtime/__init__.py +5 -0
- elementary_runtime/common/__init__.py +0 -0
- elementary_runtime/common/validators.py +27 -0
- elementary_runtime/config/__init__.py +40 -0
- elementary_runtime/config/base.py +19 -0
- elementary_runtime/config/connections.py +55 -0
- elementary_runtime/config/env.py +66 -0
- elementary_runtime/config/errors.py +20 -0
- elementary_runtime/config/runtime.py +60 -0
- elementary_runtime/config/secrets.py +77 -0
- elementary_runtime/config/sections.py +98 -0
- elementary_runtime/coordinator/__init__.py +0 -0
- elementary_runtime/execution/__init__.py +0 -0
- elementary_runtime/execution/redaction/__init__.py +0 -0
- elementary_runtime/execution/sql/__init__.py +0 -0
- elementary_runtime/execution/worker/__init__.py +0 -0
- elementary_runtime/protocol/__init__.py +162 -0
- elementary_runtime/protocol/base.py +82 -0
- elementary_runtime/protocol/enums.py +89 -0
- elementary_runtime/protocol/errors.py +28 -0
- elementary_runtime/protocol/http.py +15 -0
- elementary_runtime/protocol/instances.py +51 -0
- elementary_runtime/protocol/limits.py +18 -0
- elementary_runtime/protocol/tasks/__init__.py +80 -0
- elementary_runtime/protocol/tasks/custom_sql_test.py +35 -0
- elementary_runtime/protocol/tasks/envelope.py +20 -0
- elementary_runtime/protocol/tasks/heartbeat.py +34 -0
- elementary_runtime/protocol/tasks/poll.py +20 -0
- elementary_runtime/protocol/tasks/registry.py +35 -0
- elementary_runtime/protocol/tasks/results.py +30 -0
- elementary_runtime/protocol/tasks/run_sql.py +29 -0
- elementary_runtime/protocol/tasks/sql_query.py +102 -0
- elementary_runtime/protocol/telemetry.py +99 -0
- elementary_runtime/py.typed +0 -0
- elementary_runtime/warehouses/__init__.py +0 -0
- elementary_runtime-0.1.0rc1.dist-info/METADATA +95 -0
- elementary_runtime-0.1.0rc1.dist-info/RECORD +38 -0
- elementary_runtime-0.1.0rc1.dist-info/WHEEL +4 -0
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from collections.abc import Callable, Hashable, Sequence
|
|
2
|
+
from typing import TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
H = TypeVar("H", bound=Hashable)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def require_unique(items: Sequence[H]) -> list[H]:
|
|
9
|
+
seen: set[H] = set()
|
|
10
|
+
for item in items:
|
|
11
|
+
if item in seen:
|
|
12
|
+
raise ValueError("values must be unique")
|
|
13
|
+
seen.add(item)
|
|
14
|
+
return list(items)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def require_unique_attr(attr: str) -> Callable[[Sequence[T]], list[T]]:
|
|
18
|
+
def validator(items: Sequence[T]) -> list[T]:
|
|
19
|
+
seen: set[object] = set()
|
|
20
|
+
for item in items:
|
|
21
|
+
value = getattr(item, attr)
|
|
22
|
+
if value in seen:
|
|
23
|
+
raise ValueError(f"duplicate {attr} {value!r}")
|
|
24
|
+
seen.add(value)
|
|
25
|
+
return list(items)
|
|
26
|
+
|
|
27
|
+
return validator
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from elementary_runtime.config.base import ConfigModel
|
|
2
|
+
from elementary_runtime.config.connections import (
|
|
3
|
+
SnowflakeConnectionConfig,
|
|
4
|
+
WarehouseConnection,
|
|
5
|
+
WarehouseConnectionConfig,
|
|
6
|
+
WarehouseType,
|
|
7
|
+
)
|
|
8
|
+
from elementary_runtime.config.errors import (
|
|
9
|
+
ConfigError,
|
|
10
|
+
InvalidPlaceholderError,
|
|
11
|
+
MissingEnvironmentVariableError,
|
|
12
|
+
)
|
|
13
|
+
from elementary_runtime.config.runtime import RuntimeConfig
|
|
14
|
+
from elementary_runtime.config.secrets import FileBacked, FileBackedSecret
|
|
15
|
+
from elementary_runtime.config.sections import (
|
|
16
|
+
DEFAULT_API_URL,
|
|
17
|
+
CloudConfig,
|
|
18
|
+
ConcurrencyConfig,
|
|
19
|
+
InstanceConfig,
|
|
20
|
+
LimitsConfig,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"DEFAULT_API_URL",
|
|
25
|
+
"CloudConfig",
|
|
26
|
+
"ConcurrencyConfig",
|
|
27
|
+
"ConfigError",
|
|
28
|
+
"ConfigModel",
|
|
29
|
+
"FileBacked",
|
|
30
|
+
"FileBackedSecret",
|
|
31
|
+
"InstanceConfig",
|
|
32
|
+
"InvalidPlaceholderError",
|
|
33
|
+
"LimitsConfig",
|
|
34
|
+
"MissingEnvironmentVariableError",
|
|
35
|
+
"RuntimeConfig",
|
|
36
|
+
"SnowflakeConnectionConfig",
|
|
37
|
+
"WarehouseConnection",
|
|
38
|
+
"WarehouseConnectionConfig",
|
|
39
|
+
"WarehouseType",
|
|
40
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
4
|
+
|
|
5
|
+
from elementary_runtime.config.secrets import resolve_file_backed
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConfigModel(BaseModel):
|
|
9
|
+
model_config = ConfigDict(
|
|
10
|
+
extra="forbid",
|
|
11
|
+
frozen=True,
|
|
12
|
+
allow_inf_nan=False,
|
|
13
|
+
validate_default=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
@model_validator(mode="before")
|
|
17
|
+
@classmethod
|
|
18
|
+
def _resolve_file_backed(cls, data: Any) -> Any:
|
|
19
|
+
return resolve_file_backed(cls, data)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Annotated, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, model_validator
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
from elementary_runtime.config.base import ConfigModel
|
|
8
|
+
from elementary_runtime.config.secrets import FileBackedSecret
|
|
9
|
+
from elementary_runtime.protocol import IdStr, NonEmptyStr
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WarehouseType(str, Enum):
|
|
13
|
+
SNOWFLAKE = "snowflake"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WarehouseConnectionConfig(ConfigModel):
|
|
17
|
+
warehouse_connection_id: Annotated[
|
|
18
|
+
IdStr, Field(description="Elementary Cloud connection id")
|
|
19
|
+
]
|
|
20
|
+
type: WarehouseType
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SnowflakeConnectionConfig(WarehouseConnectionConfig):
|
|
24
|
+
type: Literal[WarehouseType.SNOWFLAKE] = WarehouseType.SNOWFLAKE
|
|
25
|
+
account: NonEmptyStr
|
|
26
|
+
user: NonEmptyStr
|
|
27
|
+
password: FileBackedSecret | None = None
|
|
28
|
+
private_key: Annotated[
|
|
29
|
+
FileBackedSecret | None, Field(description="key-pair auth")
|
|
30
|
+
] = None
|
|
31
|
+
private_key_passphrase: FileBackedSecret | None = None
|
|
32
|
+
role: NonEmptyStr | None = None
|
|
33
|
+
warehouse: Annotated[
|
|
34
|
+
NonEmptyStr | None, Field(description="Snowflake compute warehouse")
|
|
35
|
+
] = None
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="after")
|
|
38
|
+
def _password_or_private_key(self) -> Self:
|
|
39
|
+
if self.password is None and self.private_key is None:
|
|
40
|
+
raise ValueError("password or private_key is required")
|
|
41
|
+
if self.password is not None and self.private_key is not None:
|
|
42
|
+
raise ValueError("password and private_key cannot both be set")
|
|
43
|
+
if self.private_key is not None and self.private_key_passphrase is None:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"private_key_passphrase is required when private_key is set"
|
|
46
|
+
)
|
|
47
|
+
if self.password is not None and self.private_key_passphrase is not None:
|
|
48
|
+
raise ValueError("private_key_passphrase cannot be set with password")
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
WarehouseConnection = Annotated[
|
|
53
|
+
SnowflakeConnectionConfig,
|
|
54
|
+
Field(discriminator="type"),
|
|
55
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from expandvars import (
|
|
5
|
+
ExpandvarsException,
|
|
6
|
+
ParameterNullOrNotSet,
|
|
7
|
+
UnboundVariable,
|
|
8
|
+
expand,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from elementary_runtime.config.errors import (
|
|
12
|
+
InvalidPlaceholderError,
|
|
13
|
+
MissingEnvironmentVariableError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# expandvars honors this as a process-wide escape hatch: when set, missing
|
|
17
|
+
# required variables are replaced with its value instead of raising. Strip it
|
|
18
|
+
# so a customer environment cannot silently weaken nounset.
|
|
19
|
+
_RECOVER_NULL = "EXPANDVARS_RECOVER_NULL"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def expand_env(value: Any, environ: dict[str, str] | None = None) -> Any:
|
|
23
|
+
env = dict(os.environ if environ is None else environ)
|
|
24
|
+
env.pop(_RECOVER_NULL, None)
|
|
25
|
+
return _expand(value, env)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _expand(value: Any, environ: dict[str, str]) -> Any:
|
|
29
|
+
if isinstance(value, str):
|
|
30
|
+
return _expand_string(value, environ)
|
|
31
|
+
if isinstance(value, list):
|
|
32
|
+
return [_expand(item, environ) for item in value]
|
|
33
|
+
if isinstance(value, dict):
|
|
34
|
+
return {key: _expand(item, environ) for key, item in value.items()}
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _exc_message(exc: BaseException) -> str:
|
|
39
|
+
if exc.args and isinstance(exc.args[0], str):
|
|
40
|
+
return exc.args[0]
|
|
41
|
+
return str(exc)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _placeholder_name(exc: BaseException) -> tuple[str, str | None]:
|
|
45
|
+
name, sep, detail = _exc_message(exc).partition(": ")
|
|
46
|
+
if not sep or detail in {"unbound variable", "parameter null or not set"}:
|
|
47
|
+
return name, None
|
|
48
|
+
return name, detail
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _expand_string(value: str, environ: dict[str, str]) -> str:
|
|
52
|
+
try:
|
|
53
|
+
expanded = expand(
|
|
54
|
+
value,
|
|
55
|
+
nounset=True,
|
|
56
|
+
environ=environ,
|
|
57
|
+
surrounded_vars_only=True,
|
|
58
|
+
)
|
|
59
|
+
except (UnboundVariable, ParameterNullOrNotSet) as exc:
|
|
60
|
+
name, hint = _placeholder_name(exc)
|
|
61
|
+
raise MissingEnvironmentVariableError(name, hint) from exc
|
|
62
|
+
except ExpandvarsException as exc:
|
|
63
|
+
raise InvalidPlaceholderError(value, _exc_message(exc)) from exc
|
|
64
|
+
if not isinstance(expanded, str):
|
|
65
|
+
raise InvalidPlaceholderError(value, "expansion must produce a string")
|
|
66
|
+
return expanded
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class ConfigError(Exception):
|
|
2
|
+
def __init__(self, message: str) -> None:
|
|
3
|
+
super().__init__(message)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MissingEnvironmentVariableError(ConfigError):
|
|
7
|
+
def __init__(self, name: str, hint: str | None = None) -> None:
|
|
8
|
+
self.name = name
|
|
9
|
+
self.hint = hint
|
|
10
|
+
if hint is None:
|
|
11
|
+
super().__init__(f"missing environment variable {name}")
|
|
12
|
+
else:
|
|
13
|
+
super().__init__(f"missing environment variable {name}: {hint}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InvalidPlaceholderError(ConfigError):
|
|
17
|
+
def __init__(self, value: str, reason: str) -> None:
|
|
18
|
+
self.value = value
|
|
19
|
+
self.reason = reason
|
|
20
|
+
super().__init__(f"invalid placeholder in {value!r}: {reason}")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Any, Literal
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from pydantic import AfterValidator, Field, ValidationError
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
from elementary_runtime.common.validators import require_unique_attr
|
|
10
|
+
from elementary_runtime.config.base import ConfigModel
|
|
11
|
+
from elementary_runtime.config.connections import WarehouseConnection
|
|
12
|
+
from elementary_runtime.config.env import expand_env
|
|
13
|
+
from elementary_runtime.config.errors import ConfigError
|
|
14
|
+
from elementary_runtime.config.sections import (
|
|
15
|
+
CloudConfig,
|
|
16
|
+
ConcurrencyConfig,
|
|
17
|
+
InstanceConfig,
|
|
18
|
+
LimitsConfig,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RuntimeConfig(ConfigModel):
|
|
23
|
+
version: Literal[1]
|
|
24
|
+
cloud: CloudConfig
|
|
25
|
+
instance: InstanceConfig = Field(default_factory=InstanceConfig)
|
|
26
|
+
limits: LimitsConfig = Field(default_factory=LimitsConfig)
|
|
27
|
+
concurrency: ConcurrencyConfig = Field(default_factory=ConcurrencyConfig)
|
|
28
|
+
connections: Annotated[
|
|
29
|
+
list[WarehouseConnection],
|
|
30
|
+
Field(min_length=1),
|
|
31
|
+
AfterValidator(require_unique_attr("warehouse_connection_id")),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_dict(cls, data: Mapping[str, Any]) -> Self:
|
|
36
|
+
try:
|
|
37
|
+
return cls.model_validate(data)
|
|
38
|
+
except ValidationError as exc:
|
|
39
|
+
raise ConfigError(str(exc)) from exc
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_yaml(cls, path: str | Path) -> Self:
|
|
43
|
+
config_path = Path(path)
|
|
44
|
+
try:
|
|
45
|
+
raw = config_path.read_text(encoding="utf-8")
|
|
46
|
+
except OSError:
|
|
47
|
+
raise ConfigError(
|
|
48
|
+
f"unable to read configuration file {config_path}"
|
|
49
|
+
) from None
|
|
50
|
+
try:
|
|
51
|
+
loaded = yaml.safe_load(raw)
|
|
52
|
+
except yaml.YAMLError as exc:
|
|
53
|
+
raise ConfigError(
|
|
54
|
+
f"invalid YAML in configuration file {config_path}: {exc}"
|
|
55
|
+
) from exc
|
|
56
|
+
if loaded is None:
|
|
57
|
+
loaded = {}
|
|
58
|
+
if not isinstance(loaded, dict):
|
|
59
|
+
raise ConfigError("configuration root must be a mapping")
|
|
60
|
+
return cls.from_dict(expand_env(loaded))
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from types import UnionType
|
|
5
|
+
from typing import Annotated, Any, Union, get_args, get_origin
|
|
6
|
+
|
|
7
|
+
from pydantic import AfterValidator, BaseModel, SecretStr
|
|
8
|
+
|
|
9
|
+
FILE_SUFFIX = "_file"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class FileBacked:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _require_non_empty_secret(value: SecretStr) -> SecretStr:
|
|
18
|
+
if not value.get_secret_value():
|
|
19
|
+
raise ValueError("value must not be empty")
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
FileBackedSecret = Annotated[
|
|
24
|
+
SecretStr,
|
|
25
|
+
FileBacked(),
|
|
26
|
+
AfterValidator(_require_non_empty_secret),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read_secret_file(path: object) -> str:
|
|
31
|
+
if not isinstance(path, str) or not path:
|
|
32
|
+
raise ValueError("secret file path must be a non-empty string")
|
|
33
|
+
try:
|
|
34
|
+
return Path(path).read_text(encoding="utf-8").strip()
|
|
35
|
+
except OSError:
|
|
36
|
+
raise ValueError(f"unable to read secret file {path}") from None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _has_file_backed(annotation: object) -> bool:
|
|
40
|
+
origin = get_origin(annotation)
|
|
41
|
+
if origin is Annotated:
|
|
42
|
+
args = get_args(annotation)
|
|
43
|
+
return any(isinstance(arg, FileBacked) for arg in args[1:]) or _has_file_backed(
|
|
44
|
+
args[0]
|
|
45
|
+
)
|
|
46
|
+
if origin in {Union, UnionType}:
|
|
47
|
+
return any(_has_file_backed(arg) for arg in get_args(annotation))
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@cache
|
|
52
|
+
def file_backed_field_names(cls: type[BaseModel]) -> tuple[str, ...]:
|
|
53
|
+
return tuple(
|
|
54
|
+
name
|
|
55
|
+
for name, field in cls.model_fields.items()
|
|
56
|
+
if any(isinstance(marker, FileBacked) for marker in field.metadata)
|
|
57
|
+
or _has_file_backed(field.annotation)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def resolve_file_backed(cls: type[BaseModel], data: Any) -> Any:
|
|
62
|
+
if not isinstance(data, dict):
|
|
63
|
+
return data
|
|
64
|
+
names = file_backed_field_names(cls)
|
|
65
|
+
if not names:
|
|
66
|
+
return data
|
|
67
|
+
resolved = dict(data)
|
|
68
|
+
for name in names:
|
|
69
|
+
file_key = f"{name}{FILE_SUFFIX}"
|
|
70
|
+
has_value = name in resolved
|
|
71
|
+
has_file = file_key in resolved
|
|
72
|
+
if has_value and has_file:
|
|
73
|
+
raise ValueError(f"{name} and {file_key} cannot both be set")
|
|
74
|
+
if not has_file:
|
|
75
|
+
continue
|
|
76
|
+
resolved[name] = read_secret_file(resolved.pop(file_key))
|
|
77
|
+
return resolved
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, HttpUrl, TypeAdapter, model_validator
|
|
4
|
+
from typing_extensions import Self
|
|
5
|
+
|
|
6
|
+
from elementary_runtime.config.base import ConfigModel
|
|
7
|
+
from elementary_runtime.config.secrets import FileBackedSecret
|
|
8
|
+
from elementary_runtime.protocol import DisplayName, InstanceIdPrefix
|
|
9
|
+
from elementary_runtime.protocol.limits import (
|
|
10
|
+
MAX_POLL_TASKS,
|
|
11
|
+
MAX_QUERY_TIMEOUT_SECONDS,
|
|
12
|
+
MAX_SAMPLE_ROW_LIMIT,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
DEFAULT_API_URL = "https://app.elementary-data.com/runtime/v1"
|
|
16
|
+
_DEFAULT_API_URL = TypeAdapter(HttpUrl).validate_python(DEFAULT_API_URL)
|
|
17
|
+
DEFAULT_MAX_QUERY_TIMEOUT_SECONDS = 300
|
|
18
|
+
DEFAULT_MAX_RESULT_BYTES = 10_485_760
|
|
19
|
+
DEFAULT_MAX_SAMPLE_ROWS_RETURNED = 1000
|
|
20
|
+
DEFAULT_MAX_CONCURRENT_TASKS = 8
|
|
21
|
+
DEFAULT_POLL_INTERVAL_SECONDS = 10
|
|
22
|
+
DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 30
|
|
23
|
+
|
|
24
|
+
MAX_QUERY_TIMEOUT_SECONDS_LIMIT = MAX_QUERY_TIMEOUT_SECONDS
|
|
25
|
+
MAX_RESULT_BYTES_LIMIT = 1_073_741_824
|
|
26
|
+
MAX_SAMPLE_ROWS_RETURNED_LIMIT = MAX_SAMPLE_ROW_LIMIT
|
|
27
|
+
MAX_CONCURRENT_TASKS_LIMIT = 256
|
|
28
|
+
POLL_INTERVAL_SECONDS_LIMIT = 3_600
|
|
29
|
+
HEARTBEAT_INTERVAL_SECONDS_LIMIT = 3_600
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CloudConfig(ConfigModel):
|
|
33
|
+
api_url: Annotated[HttpUrl, Field(description="Elementary Cloud runtime API")] = (
|
|
34
|
+
_DEFAULT_API_URL
|
|
35
|
+
)
|
|
36
|
+
token: FileBackedSecret
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InstanceConfig(ConfigModel):
|
|
40
|
+
id_prefix: Annotated[
|
|
41
|
+
InstanceIdPrefix | None,
|
|
42
|
+
Field(description="prepended to the generated instance id"),
|
|
43
|
+
] = None
|
|
44
|
+
display_name: DisplayName | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class LimitsConfig(ConfigModel):
|
|
48
|
+
max_query_timeout_seconds: Annotated[
|
|
49
|
+
int, Field(ge=1, le=MAX_QUERY_TIMEOUT_SECONDS_LIMIT)
|
|
50
|
+
] = DEFAULT_MAX_QUERY_TIMEOUT_SECONDS
|
|
51
|
+
max_result_bytes: Annotated[
|
|
52
|
+
int,
|
|
53
|
+
Field(
|
|
54
|
+
ge=1,
|
|
55
|
+
le=MAX_RESULT_BYTES_LIMIT,
|
|
56
|
+
description="encoded result payload sent to Cloud",
|
|
57
|
+
),
|
|
58
|
+
] = DEFAULT_MAX_RESULT_BYTES
|
|
59
|
+
max_sample_rows_returned: Annotated[
|
|
60
|
+
int,
|
|
61
|
+
Field(
|
|
62
|
+
ge=0,
|
|
63
|
+
le=MAX_SAMPLE_ROWS_RETURNED_LIMIT,
|
|
64
|
+
description="rows included in the result sample",
|
|
65
|
+
),
|
|
66
|
+
] = DEFAULT_MAX_SAMPLE_ROWS_RETURNED
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ConcurrencyConfig(ConfigModel):
|
|
70
|
+
max_concurrent_tasks: Annotated[int, Field(ge=1, le=MAX_CONCURRENT_TASKS_LIMIT)] = (
|
|
71
|
+
DEFAULT_MAX_CONCURRENT_TASKS
|
|
72
|
+
)
|
|
73
|
+
prefetch_count: Annotated[
|
|
74
|
+
int, Field(ge=1, le=MAX_POLL_TASKS, description="tasks requested per poll")
|
|
75
|
+
] = DEFAULT_MAX_CONCURRENT_TASKS
|
|
76
|
+
poll_interval_seconds: Annotated[
|
|
77
|
+
int, Field(ge=1, le=POLL_INTERVAL_SECONDS_LIMIT)
|
|
78
|
+
] = DEFAULT_POLL_INTERVAL_SECONDS
|
|
79
|
+
heartbeat_interval_seconds: Annotated[
|
|
80
|
+
int, Field(ge=1, le=HEARTBEAT_INTERVAL_SECONDS_LIMIT)
|
|
81
|
+
] = DEFAULT_HEARTBEAT_INTERVAL_SECONDS
|
|
82
|
+
|
|
83
|
+
@model_validator(mode="before")
|
|
84
|
+
@classmethod
|
|
85
|
+
def _default_prefetch_count(cls, data: object) -> object:
|
|
86
|
+
if not isinstance(data, dict):
|
|
87
|
+
return data
|
|
88
|
+
data = dict(data)
|
|
89
|
+
if data.get("prefetch_count") is None:
|
|
90
|
+
max_tasks = data.get("max_concurrent_tasks", DEFAULT_MAX_CONCURRENT_TASKS)
|
|
91
|
+
data["prefetch_count"] = max_tasks
|
|
92
|
+
return data
|
|
93
|
+
|
|
94
|
+
@model_validator(mode="after")
|
|
95
|
+
def _prefetch_within_capacity(self) -> Self:
|
|
96
|
+
if self.prefetch_count > self.max_concurrent_tasks:
|
|
97
|
+
raise ValueError("prefetch_count cannot exceed max_concurrent_tasks")
|
|
98
|
+
return self
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from elementary_runtime.protocol.base import (
|
|
2
|
+
DisplayName,
|
|
3
|
+
ErrorMessage,
|
|
4
|
+
IdStr,
|
|
5
|
+
InstanceId,
|
|
6
|
+
InstanceIdPrefix,
|
|
7
|
+
NonEmptyStr,
|
|
8
|
+
ProtocolEnum,
|
|
9
|
+
ProtocolModel,
|
|
10
|
+
UtcDatetime,
|
|
11
|
+
)
|
|
12
|
+
from elementary_runtime.protocol.enums import (
|
|
13
|
+
ConnectionErrorCode,
|
|
14
|
+
ConnectionHealth,
|
|
15
|
+
ErrorCode,
|
|
16
|
+
ExceptionComponent,
|
|
17
|
+
ExceptionOperation,
|
|
18
|
+
HeartbeatStatus,
|
|
19
|
+
RuntimeStopReason,
|
|
20
|
+
SubmittedResultStatus,
|
|
21
|
+
TaskExecutionOutcome,
|
|
22
|
+
TaskType,
|
|
23
|
+
TelemetryEventType,
|
|
24
|
+
TestSourceType,
|
|
25
|
+
)
|
|
26
|
+
from elementary_runtime.protocol.errors import (
|
|
27
|
+
ErrorBody,
|
|
28
|
+
ErrorDetails,
|
|
29
|
+
ErrorResponse,
|
|
30
|
+
TaskErrorDetail,
|
|
31
|
+
)
|
|
32
|
+
from elementary_runtime.protocol.http import Headers, Paths
|
|
33
|
+
from elementary_runtime.protocol.instances import (
|
|
34
|
+
ConnectionReport,
|
|
35
|
+
ObservedWarehouseIdentity,
|
|
36
|
+
RegisterInstanceRequest,
|
|
37
|
+
)
|
|
38
|
+
from elementary_runtime.protocol.tasks import (
|
|
39
|
+
TASK_MODELS,
|
|
40
|
+
CloudTestSource,
|
|
41
|
+
CustomSqlTestResult,
|
|
42
|
+
CustomSqlTestResultPayload,
|
|
43
|
+
CustomSqlTestTask,
|
|
44
|
+
CustomSqlTestTaskPayload,
|
|
45
|
+
HeartbeatTasksRequest,
|
|
46
|
+
HeartbeatTasksResponse,
|
|
47
|
+
PollTasksRequest,
|
|
48
|
+
PollTasksResponse,
|
|
49
|
+
ResultSample,
|
|
50
|
+
RunSqlResult,
|
|
51
|
+
RunSqlResultPayload,
|
|
52
|
+
RunSqlTask,
|
|
53
|
+
RunSqlTaskPayload,
|
|
54
|
+
SampleColumnType,
|
|
55
|
+
SampleHeader,
|
|
56
|
+
SqlExecutionStatus,
|
|
57
|
+
SqlQueryResultPayload,
|
|
58
|
+
SqlQueryTaskPayload,
|
|
59
|
+
SubmitResultsRequest,
|
|
60
|
+
SubmitResultsResponse,
|
|
61
|
+
SubmittedResult,
|
|
62
|
+
Task,
|
|
63
|
+
TaskEnvelope,
|
|
64
|
+
TaskHeartbeat,
|
|
65
|
+
TaskHeartbeatStatus,
|
|
66
|
+
TaskModels,
|
|
67
|
+
TaskResult,
|
|
68
|
+
TaskResultEnvelope,
|
|
69
|
+
WarehouseTaskEnvelope,
|
|
70
|
+
)
|
|
71
|
+
from elementary_runtime.protocol.telemetry import (
|
|
72
|
+
ExceptionFrame,
|
|
73
|
+
RuntimeExceptionEvent,
|
|
74
|
+
RuntimeExceptionFields,
|
|
75
|
+
RuntimeStartedEvent,
|
|
76
|
+
RuntimeStartedFields,
|
|
77
|
+
RuntimeStoppedEvent,
|
|
78
|
+
RuntimeStoppedFields,
|
|
79
|
+
TaskExecutionSummaryEvent,
|
|
80
|
+
TaskExecutionSummaryFields,
|
|
81
|
+
TelemetryEvent,
|
|
82
|
+
TelemetryEventBase,
|
|
83
|
+
TelemetryRequest,
|
|
84
|
+
TelemetryResponse,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"TASK_MODELS",
|
|
89
|
+
"CloudTestSource",
|
|
90
|
+
"ConnectionErrorCode",
|
|
91
|
+
"ConnectionHealth",
|
|
92
|
+
"ConnectionReport",
|
|
93
|
+
"CustomSqlTestResult",
|
|
94
|
+
"CustomSqlTestResultPayload",
|
|
95
|
+
"CustomSqlTestTask",
|
|
96
|
+
"CustomSqlTestTaskPayload",
|
|
97
|
+
"DisplayName",
|
|
98
|
+
"ErrorBody",
|
|
99
|
+
"ErrorCode",
|
|
100
|
+
"ErrorDetails",
|
|
101
|
+
"ErrorMessage",
|
|
102
|
+
"ErrorResponse",
|
|
103
|
+
"ExceptionComponent",
|
|
104
|
+
"ExceptionFrame",
|
|
105
|
+
"ExceptionOperation",
|
|
106
|
+
"Headers",
|
|
107
|
+
"HeartbeatStatus",
|
|
108
|
+
"HeartbeatTasksRequest",
|
|
109
|
+
"HeartbeatTasksResponse",
|
|
110
|
+
"IdStr",
|
|
111
|
+
"InstanceId",
|
|
112
|
+
"InstanceIdPrefix",
|
|
113
|
+
"NonEmptyStr",
|
|
114
|
+
"ObservedWarehouseIdentity",
|
|
115
|
+
"Paths",
|
|
116
|
+
"PollTasksRequest",
|
|
117
|
+
"PollTasksResponse",
|
|
118
|
+
"ProtocolEnum",
|
|
119
|
+
"ProtocolModel",
|
|
120
|
+
"RegisterInstanceRequest",
|
|
121
|
+
"ResultSample",
|
|
122
|
+
"RunSqlResult",
|
|
123
|
+
"RunSqlResultPayload",
|
|
124
|
+
"RunSqlTask",
|
|
125
|
+
"RunSqlTaskPayload",
|
|
126
|
+
"RuntimeExceptionEvent",
|
|
127
|
+
"RuntimeExceptionFields",
|
|
128
|
+
"RuntimeStartedEvent",
|
|
129
|
+
"RuntimeStartedFields",
|
|
130
|
+
"RuntimeStopReason",
|
|
131
|
+
"RuntimeStoppedEvent",
|
|
132
|
+
"RuntimeStoppedFields",
|
|
133
|
+
"SampleColumnType",
|
|
134
|
+
"SampleHeader",
|
|
135
|
+
"SqlExecutionStatus",
|
|
136
|
+
"SqlQueryResultPayload",
|
|
137
|
+
"SqlQueryTaskPayload",
|
|
138
|
+
"SubmitResultsRequest",
|
|
139
|
+
"SubmitResultsResponse",
|
|
140
|
+
"SubmittedResult",
|
|
141
|
+
"SubmittedResultStatus",
|
|
142
|
+
"Task",
|
|
143
|
+
"TaskEnvelope",
|
|
144
|
+
"TaskErrorDetail",
|
|
145
|
+
"TaskExecutionOutcome",
|
|
146
|
+
"TaskExecutionSummaryEvent",
|
|
147
|
+
"TaskExecutionSummaryFields",
|
|
148
|
+
"TaskHeartbeat",
|
|
149
|
+
"TaskHeartbeatStatus",
|
|
150
|
+
"TaskModels",
|
|
151
|
+
"TaskResult",
|
|
152
|
+
"TaskResultEnvelope",
|
|
153
|
+
"TaskType",
|
|
154
|
+
"TelemetryEvent",
|
|
155
|
+
"TelemetryEventBase",
|
|
156
|
+
"TelemetryEventType",
|
|
157
|
+
"TelemetryRequest",
|
|
158
|
+
"TelemetryResponse",
|
|
159
|
+
"TestSourceType",
|
|
160
|
+
"UtcDatetime",
|
|
161
|
+
"WarehouseTaskEnvelope",
|
|
162
|
+
]
|