maxc-cli 0.1.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.
- maxc_cli/__init__.py +5 -0
- maxc_cli/__main__.py +6 -0
- maxc_cli/app.py +3406 -0
- maxc_cli/audit.py +18 -0
- maxc_cli/auth_providers.py +471 -0
- maxc_cli/backend/__init__.py +8 -0
- maxc_cli/backend/auth.py +144 -0
- maxc_cli/backend/data.py +87 -0
- maxc_cli/backend/job.py +304 -0
- maxc_cli/backend/meta.py +312 -0
- maxc_cli/backend/odps.py +130 -0
- maxc_cli/backend/query.py +148 -0
- maxc_cli/cache.py +662 -0
- maxc_cli/cli.py +1274 -0
- maxc_cli/config.py +406 -0
- maxc_cli/exceptions.py +99 -0
- maxc_cli/helpers.py +964 -0
- maxc_cli/models.py +533 -0
- maxc_cli/output.py +75 -0
- maxc_cli/store.py +123 -0
- maxc_cli/utils.py +136 -0
- maxc_cli-0.1.0.dist-info/METADATA +220 -0
- maxc_cli-0.1.0.dist-info/RECORD +26 -0
- maxc_cli-0.1.0.dist-info/WHEEL +5 -0
- maxc_cli-0.1.0.dist-info/entry_points.txt +2 -0
- maxc_cli-0.1.0.dist-info/top_level.txt +1 -0
maxc_cli/config.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from .exceptions import ValidationError
|
|
10
|
+
from .utils import deep_merge, resolve_path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TableColumn:
|
|
15
|
+
name: 'str'
|
|
16
|
+
type: 'str'
|
|
17
|
+
comment: 'str' = ""
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_mapping(cls, payload: 'dict[str, Any]') -> "TableColumn":
|
|
21
|
+
return cls(
|
|
22
|
+
name=str(payload["name"]),
|
|
23
|
+
type=str(payload.get("type", "string")),
|
|
24
|
+
comment=str(payload.get("comment", "")),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class TableDefinition:
|
|
30
|
+
name: 'str'
|
|
31
|
+
description: 'str'
|
|
32
|
+
columns: 'list[TableColumn]' = field(default_factory=list)
|
|
33
|
+
sample_rows: 'list[dict[str, Any]]' = field(default_factory=list)
|
|
34
|
+
partitions: 'list[str]' = field(default_factory=list)
|
|
35
|
+
upstream_tables: 'list[str]' = field(default_factory=list)
|
|
36
|
+
downstream_tables: 'list[str]' = field(default_factory=list)
|
|
37
|
+
partition_columns: 'list[TableColumn]' = field(default_factory=list)
|
|
38
|
+
owner: 'str | None' = None
|
|
39
|
+
created_at: 'str | None' = None
|
|
40
|
+
updated_at: 'str | None' = None
|
|
41
|
+
table_type: 'str | None' = None
|
|
42
|
+
size_bytes: 'int | None' = None
|
|
43
|
+
extra_metadata: 'dict[str, Any]' = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_mapping(cls, payload: 'dict[str, Any]') -> "TableDefinition":
|
|
47
|
+
return cls(
|
|
48
|
+
name=str(payload["name"]),
|
|
49
|
+
description=str(payload.get("description", "")),
|
|
50
|
+
columns=[TableColumn.from_mapping(item) for item in payload.get("columns", [])],
|
|
51
|
+
sample_rows=list(payload.get("sample_rows", [])),
|
|
52
|
+
partitions=[str(item) for item in payload.get("partitions", [])],
|
|
53
|
+
upstream_tables=[str(item) for item in payload.get("upstream_tables", [])],
|
|
54
|
+
downstream_tables=[str(item) for item in payload.get("downstream_tables", [])],
|
|
55
|
+
partition_columns=[
|
|
56
|
+
TableColumn.from_mapping(item)
|
|
57
|
+
for item in payload.get("partition_columns", [])
|
|
58
|
+
],
|
|
59
|
+
owner=str(payload["owner"]) if payload.get("owner") is not None else None,
|
|
60
|
+
created_at=(
|
|
61
|
+
str(payload["created_at"]) if payload.get("created_at") is not None else None
|
|
62
|
+
),
|
|
63
|
+
updated_at=(
|
|
64
|
+
str(payload["updated_at"]) if payload.get("updated_at") is not None else None
|
|
65
|
+
),
|
|
66
|
+
table_type=(
|
|
67
|
+
str(payload["table_type"]) if payload.get("table_type") is not None else None
|
|
68
|
+
),
|
|
69
|
+
size_bytes=(
|
|
70
|
+
int(payload["size_bytes"]) if payload.get("size_bytes") is not None else None
|
|
71
|
+
),
|
|
72
|
+
extra_metadata=dict(payload.get("extra_metadata", {})),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class AgentConfig:
|
|
78
|
+
auto_approve_cost_cu: 'float' = 10
|
|
79
|
+
safety_mode: 'str' = "strict"
|
|
80
|
+
audit_log: 'Path | None' = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# BackendConfig removed - only ODPS backend is supported
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class NcsAuthConfig:
|
|
88
|
+
account_type: 'str | None' = None
|
|
89
|
+
employee_id: 'str | None' = None
|
|
90
|
+
account_name: 'str | None' = None
|
|
91
|
+
app_name: 'str | None' = None
|
|
92
|
+
process_command: 'str | None' = None
|
|
93
|
+
process_timeout: 'int' = 20
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_mapping(cls, payload: 'dict[str, Any] | None') -> "NcsAuthConfig":
|
|
97
|
+
payload = payload or {}
|
|
98
|
+
return cls(
|
|
99
|
+
account_type=_optional_string(payload.get("account_type")),
|
|
100
|
+
employee_id=_optional_string(payload.get("employee_id")),
|
|
101
|
+
account_name=_optional_string(payload.get("account_name")),
|
|
102
|
+
app_name=_optional_string(payload.get("app_name")),
|
|
103
|
+
process_command=_optional_string(
|
|
104
|
+
payload.get("process_command") or payload.get("command")
|
|
105
|
+
),
|
|
106
|
+
process_timeout=int(payload.get("process_timeout", 20)),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def to_mapping(self) -> 'dict[str, Any]':
|
|
110
|
+
payload: 'dict[str, Any]' = {}
|
|
111
|
+
if self.account_type:
|
|
112
|
+
payload["account_type"] = self.account_type
|
|
113
|
+
if self.employee_id:
|
|
114
|
+
payload["employee_id"] = self.employee_id
|
|
115
|
+
if self.account_name:
|
|
116
|
+
payload["account_name"] = self.account_name
|
|
117
|
+
if self.app_name:
|
|
118
|
+
payload["app_name"] = self.app_name
|
|
119
|
+
if self.process_command:
|
|
120
|
+
payload["process_command"] = self.process_command
|
|
121
|
+
if payload:
|
|
122
|
+
payload["process_timeout"] = self.process_timeout
|
|
123
|
+
return payload
|
|
124
|
+
|
|
125
|
+
def is_configured(self) -> 'bool':
|
|
126
|
+
return bool(
|
|
127
|
+
self.process_command
|
|
128
|
+
or self.employee_id
|
|
129
|
+
or self.account_name
|
|
130
|
+
or self.app_name
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class AuthConfig:
|
|
136
|
+
provider: 'str | None' = None
|
|
137
|
+
access_id: 'str | None' = None
|
|
138
|
+
secret_access_key: 'str | None' = None
|
|
139
|
+
security_token: 'str | None' = None
|
|
140
|
+
token_expires_at: 'str | None' = None
|
|
141
|
+
project: 'str | None' = None
|
|
142
|
+
endpoint: 'str | None' = None
|
|
143
|
+
region_name: 'str | None' = None
|
|
144
|
+
tunnel_endpoint: 'str | None' = None
|
|
145
|
+
ncs: 'NcsAuthConfig' = field(default_factory=NcsAuthConfig)
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def from_mapping(cls, payload: 'dict[str, Any]') -> "AuthConfig":
|
|
149
|
+
return cls(
|
|
150
|
+
provider=_optional_string(payload.get("provider")),
|
|
151
|
+
access_id=_optional_string(
|
|
152
|
+
payload.get("access_id") or payload.get("access_key_id")
|
|
153
|
+
),
|
|
154
|
+
secret_access_key=_optional_string(
|
|
155
|
+
payload.get("secret_access_key") or payload.get("access_key_secret")
|
|
156
|
+
),
|
|
157
|
+
security_token=_optional_string(
|
|
158
|
+
payload.get("security_token") or payload.get("sts_token")
|
|
159
|
+
),
|
|
160
|
+
token_expires_at=_optional_string(payload.get("token_expires_at")),
|
|
161
|
+
project=_optional_string(payload.get("project")),
|
|
162
|
+
endpoint=_optional_string(payload.get("endpoint")),
|
|
163
|
+
region_name=_optional_string(
|
|
164
|
+
payload.get("region_name") or payload.get("region")
|
|
165
|
+
),
|
|
166
|
+
tunnel_endpoint=_optional_string(payload.get("tunnel_endpoint")),
|
|
167
|
+
ncs=NcsAuthConfig.from_mapping(payload.get("ncs") if isinstance(payload.get("ncs"), dict) else None),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def to_mapping(self) -> 'dict[str, Any]':
|
|
171
|
+
payload: 'dict[str, Any]' = {}
|
|
172
|
+
if self.provider:
|
|
173
|
+
payload["provider"] = self.provider
|
|
174
|
+
if self.access_id:
|
|
175
|
+
payload["access_id"] = self.access_id
|
|
176
|
+
if self.secret_access_key:
|
|
177
|
+
payload["secret_access_key"] = self.secret_access_key
|
|
178
|
+
if self.security_token:
|
|
179
|
+
payload["security_token"] = self.security_token
|
|
180
|
+
if self.token_expires_at:
|
|
181
|
+
payload["token_expires_at"] = self.token_expires_at
|
|
182
|
+
if self.project:
|
|
183
|
+
payload["project"] = self.project
|
|
184
|
+
if self.endpoint:
|
|
185
|
+
payload["endpoint"] = self.endpoint
|
|
186
|
+
if self.region_name:
|
|
187
|
+
payload["region_name"] = self.region_name
|
|
188
|
+
if self.tunnel_endpoint:
|
|
189
|
+
payload["tunnel_endpoint"] = self.tunnel_endpoint
|
|
190
|
+
if self.ncs.is_configured():
|
|
191
|
+
payload["ncs"] = self.ncs.to_mapping()
|
|
192
|
+
return payload
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class MaxCConfig:
|
|
197
|
+
default_project: 'str'
|
|
198
|
+
default_schema: 'str | None'
|
|
199
|
+
default_format: 'str'
|
|
200
|
+
default_region: 'str'
|
|
201
|
+
project_context: 'str'
|
|
202
|
+
allowed_operations: 'list[str]'
|
|
203
|
+
cost_threshold_cu: 'float'
|
|
204
|
+
sensitive_columns: 'list[str]'
|
|
205
|
+
agent: 'AgentConfig'
|
|
206
|
+
auth: 'AuthConfig'
|
|
207
|
+
state_dir: 'Path'
|
|
208
|
+
cache_dir: 'Path'
|
|
209
|
+
catalog: 'dict[str, TableDefinition]'
|
|
210
|
+
sources: 'list[Path]'
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _optional_string(value: 'Any') -> 'str | None':
|
|
214
|
+
if value is None:
|
|
215
|
+
return None
|
|
216
|
+
text = str(value).strip()
|
|
217
|
+
return text or None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _load_yaml_file(path: 'Path') -> 'dict[str, Any]':
|
|
221
|
+
if not path.exists() or path.is_dir():
|
|
222
|
+
return {}
|
|
223
|
+
try:
|
|
224
|
+
payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
225
|
+
except yaml.YAMLError as exc:
|
|
226
|
+
raise ValidationError(
|
|
227
|
+
f"Configuration file contains invalid YAML: {path}",
|
|
228
|
+
suggestion=f"Fix the syntax in {path}, or delete it and re-run `maxc auth login` to create a fresh config.",
|
|
229
|
+
) from exc
|
|
230
|
+
if not isinstance(payload, dict):
|
|
231
|
+
raise ValidationError(f"Invalid configuration file format: {path}")
|
|
232
|
+
return payload
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def default_global_config_path() -> 'Path':
|
|
236
|
+
return Path.home() / ".maxc" / "config.yaml"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def session_override_path() -> 'Path':
|
|
240
|
+
"""Path to session override file (highest priority for project/schema)."""
|
|
241
|
+
return Path.home() / ".maxc" / "session_override.yaml"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def load_config_mapping(path: 'Path') -> 'dict[str, Any]':
|
|
245
|
+
return _load_yaml_file(path)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def save_config_mapping(path: 'Path', payload: 'dict[str, Any]') -> 'None':
|
|
249
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
path.write_text(
|
|
251
|
+
yaml.safe_dump(payload, allow_unicode=True, sort_keys=False),
|
|
252
|
+
encoding="utf-8",
|
|
253
|
+
)
|
|
254
|
+
try:
|
|
255
|
+
path.chmod(0o600)
|
|
256
|
+
except OSError:
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def persist_login_config(
|
|
261
|
+
target_path: 'Path',
|
|
262
|
+
*,
|
|
263
|
+
auth: 'AuthConfig',
|
|
264
|
+
) -> 'dict[str, Any]':
|
|
265
|
+
payload = load_config_mapping(target_path) if target_path.exists() else {}
|
|
266
|
+
|
|
267
|
+
payload["auth"] = auth.to_mapping()
|
|
268
|
+
|
|
269
|
+
if auth.project:
|
|
270
|
+
payload["default_project"] = auth.project
|
|
271
|
+
if auth.region_name:
|
|
272
|
+
payload["default_region"] = auth.region_name
|
|
273
|
+
|
|
274
|
+
save_config_mapping(target_path, payload)
|
|
275
|
+
return payload
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def discover_config_files(cwd: 'Path', explicit_path: 'Path | None' = None) -> 'list[Path]':
|
|
279
|
+
if explicit_path is not None:
|
|
280
|
+
if not explicit_path.exists():
|
|
281
|
+
raise ValidationError(f"Configuration file does not exist: {explicit_path}")
|
|
282
|
+
return [explicit_path.resolve()]
|
|
283
|
+
|
|
284
|
+
candidates = [
|
|
285
|
+
Path.home() / ".maxc" / "config.yaml",
|
|
286
|
+
cwd / ".maxc" / "config.yaml",
|
|
287
|
+
cwd / ".maxc.yaml",
|
|
288
|
+
cwd / ".maxc",
|
|
289
|
+
]
|
|
290
|
+
paths: 'list[Path]' = []
|
|
291
|
+
for candidate in candidates:
|
|
292
|
+
if candidate.exists() and not candidate.is_dir():
|
|
293
|
+
paths.append(candidate.resolve())
|
|
294
|
+
return paths
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def load_config(cwd: 'Path', explicit_path: 'Path | None' = None) -> 'MaxCConfig':
|
|
298
|
+
sources = discover_config_files(cwd, explicit_path)
|
|
299
|
+
merged: 'dict[str, Any]' = {}
|
|
300
|
+
for source in sources:
|
|
301
|
+
merged = deep_merge(merged, _load_yaml_file(source))
|
|
302
|
+
|
|
303
|
+
# Load session override (highest priority for project/schema)
|
|
304
|
+
override = _load_yaml_file(session_override_path())
|
|
305
|
+
|
|
306
|
+
env_project = (
|
|
307
|
+
os.environ.get("MAXCOMPUTE_PROJECT")
|
|
308
|
+
or os.environ.get("ODPS_PROJECT")
|
|
309
|
+
or None
|
|
310
|
+
)
|
|
311
|
+
env_region = (
|
|
312
|
+
os.environ.get("MAXCOMPUTE_REGION")
|
|
313
|
+
or os.environ.get("ALIBABA_CLOUD_REGION")
|
|
314
|
+
or None
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
auth_payload = merged.get("auth", {}) or {}
|
|
318
|
+
if not isinstance(auth_payload, dict):
|
|
319
|
+
raise ValidationError("The `auth` configuration must be a mapping.")
|
|
320
|
+
auth = AuthConfig.from_mapping(auth_payload)
|
|
321
|
+
|
|
322
|
+
# Priority: session override > env var > config file > auth
|
|
323
|
+
# Exception: when auth.provider is explicitly configured, auth.project takes
|
|
324
|
+
# priority over env vars — env vars must not silently reroute to a different
|
|
325
|
+
# project when the user has committed to a specific auth configuration.
|
|
326
|
+
has_explicit_auth_provider = bool(auth.provider)
|
|
327
|
+
default_project_value = merged.get("default_project")
|
|
328
|
+
if override.get("project"):
|
|
329
|
+
default_project = str(override["project"])
|
|
330
|
+
elif env_project and not has_explicit_auth_provider:
|
|
331
|
+
default_project = env_project
|
|
332
|
+
elif default_project_value is not None:
|
|
333
|
+
default_project = str(default_project_value)
|
|
334
|
+
elif auth.project:
|
|
335
|
+
default_project = auth.project
|
|
336
|
+
elif env_project:
|
|
337
|
+
# Fallback: env var still used when no auth.project is available
|
|
338
|
+
default_project = env_project
|
|
339
|
+
else:
|
|
340
|
+
default_project = "demo_project"
|
|
341
|
+
|
|
342
|
+
# Priority: session override > config file
|
|
343
|
+
default_schema = _optional_string(override.get("schema")) or _optional_string(merged.get("default_schema"))
|
|
344
|
+
|
|
345
|
+
default_format = str(merged.get("default_format", "json"))
|
|
346
|
+
default_region_value = merged.get("default_region")
|
|
347
|
+
if env_region:
|
|
348
|
+
default_region = str(env_region)
|
|
349
|
+
elif auth.region_name:
|
|
350
|
+
default_region = auth.region_name
|
|
351
|
+
elif default_region_value is not None:
|
|
352
|
+
default_region = str(default_region_value)
|
|
353
|
+
else:
|
|
354
|
+
default_region = "local"
|
|
355
|
+
project_context = str(merged.get("project_context", "")).strip()
|
|
356
|
+
allowed_operations = [
|
|
357
|
+
str(item).upper() for item in merged.get("allowed_operations", ["SELECT"])
|
|
358
|
+
]
|
|
359
|
+
cost_threshold_cu = float(merged.get("cost_threshold_cu", 50))
|
|
360
|
+
sensitive_columns = [str(item) for item in merged.get("sensitive_columns", [])]
|
|
361
|
+
|
|
362
|
+
agent_payload = merged.get("agent", {}) or {}
|
|
363
|
+
if not isinstance(agent_payload, dict):
|
|
364
|
+
raise ValidationError("The `agent` configuration must be a mapping.")
|
|
365
|
+
state_dir = resolve_path(
|
|
366
|
+
merged.get("state_dir", "~/.maxc/state"),
|
|
367
|
+
base_dir=cwd,
|
|
368
|
+
)
|
|
369
|
+
cache_dir = resolve_path(
|
|
370
|
+
merged.get("cache_dir", "~/.maxc/cache"),
|
|
371
|
+
base_dir=cwd,
|
|
372
|
+
)
|
|
373
|
+
audit_log = resolve_path(
|
|
374
|
+
agent_payload.get("audit_log", str(state_dir / "audit.log")),
|
|
375
|
+
base_dir=cwd,
|
|
376
|
+
)
|
|
377
|
+
agent = AgentConfig(
|
|
378
|
+
auto_approve_cost_cu=float(agent_payload.get("auto_approve_cost_cu", 10)),
|
|
379
|
+
safety_mode=str(agent_payload.get("safety_mode", "strict")),
|
|
380
|
+
audit_log=audit_log,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
tables = {}
|
|
384
|
+
catalog_payload = merged.get("catalog", {}) or {}
|
|
385
|
+
table_items = catalog_payload.get("tables", []) if isinstance(catalog_payload, dict) else []
|
|
386
|
+
for item in table_items:
|
|
387
|
+
table = TableDefinition.from_mapping(item)
|
|
388
|
+
tables[table.name] = table
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
return MaxCConfig(
|
|
392
|
+
default_project=default_project,
|
|
393
|
+
default_schema=default_schema,
|
|
394
|
+
default_format=default_format,
|
|
395
|
+
default_region=default_region,
|
|
396
|
+
project_context=project_context,
|
|
397
|
+
allowed_operations=allowed_operations,
|
|
398
|
+
cost_threshold_cu=cost_threshold_cu,
|
|
399
|
+
sensitive_columns=sensitive_columns,
|
|
400
|
+
agent=agent,
|
|
401
|
+
auth=auth,
|
|
402
|
+
state_dir=state_dir,
|
|
403
|
+
cache_dir=cache_dir,
|
|
404
|
+
catalog=tables,
|
|
405
|
+
sources=sources,
|
|
406
|
+
)
|
maxc_cli/exceptions.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ErrorPayload:
|
|
8
|
+
code: 'str'
|
|
9
|
+
message: 'str'
|
|
10
|
+
suggestion: 'str | None'
|
|
11
|
+
recoverable: 'bool'
|
|
12
|
+
|
|
13
|
+
def to_dict(self) -> 'dict[str, Any]':
|
|
14
|
+
payload = {
|
|
15
|
+
"code": self.code,
|
|
16
|
+
"message": self.message,
|
|
17
|
+
"recoverable": self.recoverable,
|
|
18
|
+
}
|
|
19
|
+
if self.suggestion:
|
|
20
|
+
payload["suggestion"] = self.suggestion
|
|
21
|
+
return payload
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MaxCError(Exception):
|
|
25
|
+
exit_code = 1
|
|
26
|
+
error_code = "EXECUTION_FAILED"
|
|
27
|
+
recoverable = True
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: 'str',
|
|
32
|
+
*,
|
|
33
|
+
suggestion: 'str | None' = None,
|
|
34
|
+
recoverable: 'bool | None' = None,
|
|
35
|
+
) -> 'None':
|
|
36
|
+
super().__init__(message)
|
|
37
|
+
self.message = message
|
|
38
|
+
self.suggestion = suggestion
|
|
39
|
+
if recoverable is None:
|
|
40
|
+
self.recoverable = self.__class__.recoverable
|
|
41
|
+
else:
|
|
42
|
+
self.recoverable = recoverable
|
|
43
|
+
|
|
44
|
+
def to_payload(self) -> 'ErrorPayload':
|
|
45
|
+
return ErrorPayload(
|
|
46
|
+
code=self.error_code,
|
|
47
|
+
message=self.message,
|
|
48
|
+
suggestion=self.suggestion,
|
|
49
|
+
recoverable=self.recoverable,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PermissionDeniedError(MaxCError):
|
|
54
|
+
exit_code = 2
|
|
55
|
+
error_code = "PERMISSION_DENIED"
|
|
56
|
+
recoverable = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class QuotaExceededError(MaxCError):
|
|
60
|
+
exit_code = 3
|
|
61
|
+
error_code = "QUOTA_EXCEEDED"
|
|
62
|
+
recoverable = True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SqlError(MaxCError):
|
|
66
|
+
exit_code = 4
|
|
67
|
+
error_code = "SQL_ERROR"
|
|
68
|
+
recoverable = False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CostLimitExceededError(MaxCError):
|
|
72
|
+
exit_code = 5
|
|
73
|
+
error_code = "COST_LIMIT_EXCEEDED"
|
|
74
|
+
recoverable = False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class NotFoundError(MaxCError):
|
|
78
|
+
error_code = "NOT_FOUND"
|
|
79
|
+
recoverable = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ValidationError(MaxCError):
|
|
83
|
+
error_code = "VALIDATION_ERROR"
|
|
84
|
+
recoverable = False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class FeatureUnavailableError(MaxCError):
|
|
88
|
+
error_code = "FEATURE_UNAVAILABLE"
|
|
89
|
+
recoverable = False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class BackendConnectionError(MaxCError):
|
|
93
|
+
error_code = "BACKEND_CONNECTION_ERROR"
|
|
94
|
+
recoverable = True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class JobTimeoutError(MaxCError):
|
|
98
|
+
error_code = "JOB_TIMEOUT"
|
|
99
|
+
recoverable = True
|