clickzetta-studio-utils 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.
- clickzetta_studio_utils/__init__.py +5 -0
- clickzetta_studio_utils/_utils.py +11 -0
- clickzetta_studio_utils/dbutils.py +358 -0
- clickzetta_studio_utils-0.1.0.dist-info/METADATA +144 -0
- clickzetta_studio_utils-0.1.0.dist-info/RECORD +7 -0
- clickzetta_studio_utils-0.1.0.dist-info/WHEEL +4 -0
- clickzetta_studio_utils-0.1.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from clickzetta.connector.v0.connection import Connection
|
|
2
|
+
from clickzetta.connector.v0._fsutil import FsUtil
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class _Utils:
|
|
6
|
+
def __init__(self, connection: Connection):
|
|
7
|
+
self._fs = FsUtil(connection)
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def fs(self) -> FsUtil:
|
|
11
|
+
return self._fs
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
_DEFAULT_TIMEOUT_SECONDS = 3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WidgetNotFoundError(KeyError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaskValueNotFoundError(KeyError):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class DbutilsRuntimeConfig:
|
|
27
|
+
runtime: dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
widget_values: dict[str, str] = field(default_factory=dict)
|
|
29
|
+
dbutils_api: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
task_values: dict[str, Any] = field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_mapping(cls, config: dict[str, Any] | None) -> "DbutilsRuntimeConfig":
|
|
34
|
+
if isinstance(config, list):
|
|
35
|
+
config = {"connections": config}
|
|
36
|
+
config = config or {}
|
|
37
|
+
return cls(
|
|
38
|
+
runtime=dict(config.get("runtime") or {}),
|
|
39
|
+
widget_values=_string_map(config.get("widgetValues") or {}),
|
|
40
|
+
dbutils_api=dict(config.get("dbutilsApi") or {}),
|
|
41
|
+
task_values=dict(config.get("taskValues") or {}),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _HttpClient:
|
|
46
|
+
def __init__(self, base_url: str | None, timeout_seconds: int = _DEFAULT_TIMEOUT_SECONDS):
|
|
47
|
+
self._base_url = (base_url or "").rstrip("/")
|
|
48
|
+
self._timeout_seconds = timeout_seconds
|
|
49
|
+
|
|
50
|
+
def post_json(self, path: str, payload: dict[str, Any]) -> Any:
|
|
51
|
+
if not self._base_url:
|
|
52
|
+
LOGGER.debug("Skip dbutils HTTP call because dbutilsApi.baseUrl is not configured")
|
|
53
|
+
return None
|
|
54
|
+
url = f"{self._base_url}{path}"
|
|
55
|
+
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
56
|
+
request = urllib.request.Request(
|
|
57
|
+
url,
|
|
58
|
+
data=data,
|
|
59
|
+
headers={"Content-Type": "application/json"},
|
|
60
|
+
method="POST",
|
|
61
|
+
)
|
|
62
|
+
with urllib.request.urlopen(request, timeout=self._timeout_seconds) as response:
|
|
63
|
+
body = response.read()
|
|
64
|
+
if not body:
|
|
65
|
+
return None
|
|
66
|
+
return json.loads(body.decode("utf-8"))
|
|
67
|
+
|
|
68
|
+
def post_widget_event(self, payload: dict[str, Any]) -> None:
|
|
69
|
+
if not self._base_url:
|
|
70
|
+
LOGGER.debug("Skip widget event because dbutilsApi.baseUrl is not configured")
|
|
71
|
+
return
|
|
72
|
+
try:
|
|
73
|
+
self.post_json("/v1/dbutils/widgets/events", payload)
|
|
74
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
|
75
|
+
LOGGER.warning("Failed to write dbutils widget event: %s", exc)
|
|
76
|
+
|
|
77
|
+
def set_task_value(self, payload: dict[str, Any]) -> None:
|
|
78
|
+
if not self._base_url:
|
|
79
|
+
LOGGER.debug("Skip task value set because dbutilsApi.baseUrl is not configured")
|
|
80
|
+
return
|
|
81
|
+
try:
|
|
82
|
+
self.post_json("/v1/dbutils/task-values/set", payload)
|
|
83
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
|
84
|
+
LOGGER.warning("Failed to write dbutils task value: %s", exc)
|
|
85
|
+
|
|
86
|
+
def get_task_value(self, payload: dict[str, Any]) -> Any:
|
|
87
|
+
if not self._base_url:
|
|
88
|
+
return None
|
|
89
|
+
return self.post_json("/v1/dbutils/task-values/get", payload)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Widgets:
|
|
93
|
+
def __init__(self, runtime_config: DbutilsRuntimeConfig):
|
|
94
|
+
self._runtime = runtime_config.runtime
|
|
95
|
+
self._values = dict(runtime_config.widget_values)
|
|
96
|
+
self._client = _HttpClient(runtime_config.dbutils_api.get("baseUrl"))
|
|
97
|
+
self._event_order = 0
|
|
98
|
+
|
|
99
|
+
def text(self, name: str, defaultValue: str = "", label: str | None = None) -> None:
|
|
100
|
+
self._register("text", name, defaultValue, None, label)
|
|
101
|
+
|
|
102
|
+
def dropdown(
|
|
103
|
+
self,
|
|
104
|
+
name: str,
|
|
105
|
+
defaultValue: str,
|
|
106
|
+
choices: list[str] | tuple[str, ...],
|
|
107
|
+
label: str | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
self._register("dropdown", name, defaultValue, choices, label)
|
|
110
|
+
|
|
111
|
+
def combobox(
|
|
112
|
+
self,
|
|
113
|
+
name: str,
|
|
114
|
+
defaultValue: str,
|
|
115
|
+
choices: list[str] | tuple[str, ...],
|
|
116
|
+
label: str | None = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
self._register("combobox", name, defaultValue, choices, label)
|
|
119
|
+
|
|
120
|
+
def multiselect(
|
|
121
|
+
self,
|
|
122
|
+
name: str,
|
|
123
|
+
defaultValue: str,
|
|
124
|
+
choices: list[str] | tuple[str, ...],
|
|
125
|
+
label: str | None = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
self._register("multiselect", name, defaultValue, choices, label)
|
|
128
|
+
|
|
129
|
+
def get(self, name: str) -> str:
|
|
130
|
+
if name not in self._values:
|
|
131
|
+
raise WidgetNotFoundError(f"Widget not found: {name}")
|
|
132
|
+
return self._values[name]
|
|
133
|
+
|
|
134
|
+
def getAll(self) -> dict[str, str]:
|
|
135
|
+
return dict(self._values)
|
|
136
|
+
|
|
137
|
+
def remove(self, name: str) -> None:
|
|
138
|
+
self._values.pop(name, None)
|
|
139
|
+
self._emit(
|
|
140
|
+
{
|
|
141
|
+
"eventType": "REMOVE",
|
|
142
|
+
"widgetKey": name,
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def removeAll(self) -> None:
|
|
147
|
+
self._values.clear()
|
|
148
|
+
self._emit({"eventType": "REMOVE_ALL"})
|
|
149
|
+
|
|
150
|
+
def _register(
|
|
151
|
+
self,
|
|
152
|
+
widget_type: str,
|
|
153
|
+
name: str,
|
|
154
|
+
default_value: Any,
|
|
155
|
+
choices: list[str] | tuple[str, ...] | None,
|
|
156
|
+
label: str | None,
|
|
157
|
+
) -> None:
|
|
158
|
+
key = _require_name(name)
|
|
159
|
+
default_str = _to_widget_value(default_value)
|
|
160
|
+
if key not in self._values:
|
|
161
|
+
self._values[key] = default_str
|
|
162
|
+
self._emit(
|
|
163
|
+
{
|
|
164
|
+
"eventType": "CREATE",
|
|
165
|
+
"widgetKey": key,
|
|
166
|
+
"widgetType": widget_type,
|
|
167
|
+
"label": label,
|
|
168
|
+
"defaultValue": default_str,
|
|
169
|
+
"choices": [_to_widget_value(choice) for choice in choices] if choices is not None else [],
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _emit(self, payload: dict[str, Any]) -> None:
|
|
174
|
+
self._event_order += 1
|
|
175
|
+
event = {
|
|
176
|
+
**self._runtime_fields(),
|
|
177
|
+
**payload,
|
|
178
|
+
"eventOrder": self._event_order,
|
|
179
|
+
"timestamp": int(time.time() * 1000),
|
|
180
|
+
}
|
|
181
|
+
self._client.post_widget_event(event)
|
|
182
|
+
|
|
183
|
+
def _runtime_fields(self) -> dict[str, Any]:
|
|
184
|
+
return {
|
|
185
|
+
"tenantId": self._runtime.get("tenantId"),
|
|
186
|
+
"projectId": self._runtime.get("projectId"),
|
|
187
|
+
"scheduleTaskId": self._runtime.get("scheduleTaskId"),
|
|
188
|
+
"scheduleInstanceId": self._runtime.get("scheduleInstanceId"),
|
|
189
|
+
"executeLogId": self._runtime.get("executeLogId"),
|
|
190
|
+
"paragraphId": self._runtime.get("paragraphId"),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
def _update_runtime(self, values: dict[str, Any]) -> None:
|
|
194
|
+
self._runtime.update({key: value for key, value in values.items() if value is not None})
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class Dbutils:
|
|
198
|
+
def __init__(self, runtime_config: DbutilsRuntimeConfig | dict[str, Any] | None = None):
|
|
199
|
+
if not isinstance(runtime_config, DbutilsRuntimeConfig):
|
|
200
|
+
runtime_config = DbutilsRuntimeConfig.from_mapping(runtime_config)
|
|
201
|
+
self.widgets = Widgets(runtime_config)
|
|
202
|
+
self.jobs = Jobs(runtime_config)
|
|
203
|
+
|
|
204
|
+
def set_runtime_context(self, **values: Any) -> None:
|
|
205
|
+
self.widgets._update_runtime(values)
|
|
206
|
+
self.jobs.taskValues._update_runtime(values)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Jobs:
|
|
210
|
+
def __init__(self, runtime_config: DbutilsRuntimeConfig):
|
|
211
|
+
self.taskValues = TaskValues(runtime_config)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TaskValues:
|
|
215
|
+
_NO_DEFAULT = object()
|
|
216
|
+
|
|
217
|
+
def __init__(self, runtime_config: DbutilsRuntimeConfig):
|
|
218
|
+
self._runtime = runtime_config.runtime
|
|
219
|
+
self._client = _HttpClient(runtime_config.dbutils_api.get("baseUrl"))
|
|
220
|
+
task_values_config = runtime_config.task_values
|
|
221
|
+
self._current_producer = dict(task_values_config.get("currentProducer") or {})
|
|
222
|
+
self._producers = dict(task_values_config.get("producers") or {})
|
|
223
|
+
self._local_values: dict[str, Any] = {}
|
|
224
|
+
|
|
225
|
+
def set(self, key: str | None = None, value: Any = None, **kwargs: Any) -> None:
|
|
226
|
+
if key is None:
|
|
227
|
+
key = kwargs.get("key")
|
|
228
|
+
if "value" in kwargs:
|
|
229
|
+
value = kwargs["value"]
|
|
230
|
+
value_key = _require_name(key)
|
|
231
|
+
value_json, value_type = _serialize_task_value(value)
|
|
232
|
+
self._local_values[value_key] = value
|
|
233
|
+
payload = {
|
|
234
|
+
**self._runtime_fields(),
|
|
235
|
+
"producerInstanceType": self._current_producer.get("producerInstanceType"),
|
|
236
|
+
"producerInstanceId": self._current_producer.get("producerInstanceId"),
|
|
237
|
+
"key": value_key,
|
|
238
|
+
"valueJson": value_json,
|
|
239
|
+
"valueType": value_type,
|
|
240
|
+
}
|
|
241
|
+
self._client.set_task_value(payload)
|
|
242
|
+
|
|
243
|
+
def get(
|
|
244
|
+
self,
|
|
245
|
+
taskKey: str | None = None,
|
|
246
|
+
key: str | None = None,
|
|
247
|
+
default: Any = _NO_DEFAULT,
|
|
248
|
+
**kwargs: Any,
|
|
249
|
+
) -> Any:
|
|
250
|
+
if taskKey is None:
|
|
251
|
+
taskKey = kwargs.get("taskKey")
|
|
252
|
+
if key is None:
|
|
253
|
+
key = kwargs.get("key")
|
|
254
|
+
if key is None and taskKey is not None:
|
|
255
|
+
key = taskKey
|
|
256
|
+
taskKey = None
|
|
257
|
+
value_key = _require_name(key)
|
|
258
|
+
if taskKey is None or taskKey == self._current_producer.get("taskKey"):
|
|
259
|
+
return self._get_local(value_key, default)
|
|
260
|
+
producer = self._producers.get(str(taskKey))
|
|
261
|
+
if not producer:
|
|
262
|
+
return self._missing(value_key, default)
|
|
263
|
+
payload = {
|
|
264
|
+
"tenantId": self._runtime.get("tenantId"),
|
|
265
|
+
"producerInstanceType": producer.get("producerInstanceType"),
|
|
266
|
+
"producerInstanceId": producer.get("producerInstanceId"),
|
|
267
|
+
"key": value_key,
|
|
268
|
+
}
|
|
269
|
+
try:
|
|
270
|
+
response = self._client.get_task_value(payload)
|
|
271
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
|
272
|
+
LOGGER.warning("Failed to read dbutils task value: %s", exc)
|
|
273
|
+
return self._missing(value_key, default)
|
|
274
|
+
data = _extract_common_result_data(response)
|
|
275
|
+
if not data:
|
|
276
|
+
return self._missing(value_key, default)
|
|
277
|
+
if isinstance(data, dict) and "valueJson" in data:
|
|
278
|
+
return json.loads(data["valueJson"])
|
|
279
|
+
return self._missing(value_key, default)
|
|
280
|
+
|
|
281
|
+
def _get_local(self, key: str, default: Any) -> Any:
|
|
282
|
+
if key in self._local_values:
|
|
283
|
+
return self._local_values[key]
|
|
284
|
+
return self._missing(key, default)
|
|
285
|
+
|
|
286
|
+
def _missing(self, key: str, default: Any) -> Any:
|
|
287
|
+
if default is not self._NO_DEFAULT:
|
|
288
|
+
return default
|
|
289
|
+
raise TaskValueNotFoundError(f"Task value not found: {key}")
|
|
290
|
+
|
|
291
|
+
def _runtime_fields(self) -> dict[str, Any]:
|
|
292
|
+
return {
|
|
293
|
+
"tenantId": self._runtime.get("tenantId"),
|
|
294
|
+
"projectId": self._runtime.get("projectId"),
|
|
295
|
+
"scheduleTaskId": self._runtime.get("scheduleTaskId"),
|
|
296
|
+
"scheduleInstanceId": self._runtime.get("scheduleInstanceId"),
|
|
297
|
+
"executeLogId": self._runtime.get("executeLogId"),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
def _update_runtime(self, values: dict[str, Any]) -> None:
|
|
301
|
+
self._runtime.update({key: value for key, value in values.items() if value is not None})
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def init_dbutils(config: dict[str, Any] | None = None, *, bind_builtins: bool = True) -> Dbutils:
|
|
305
|
+
existing = getattr(builtins, "dbutils", None)
|
|
306
|
+
if config is None and isinstance(existing, Dbutils):
|
|
307
|
+
if bind_builtins:
|
|
308
|
+
builtins.czutils = existing
|
|
309
|
+
return existing
|
|
310
|
+
runtime_config = DbutilsRuntimeConfig.from_mapping(config or {})
|
|
311
|
+
dbutils = Dbutils(runtime_config)
|
|
312
|
+
if bind_builtins:
|
|
313
|
+
builtins.dbutils = dbutils
|
|
314
|
+
builtins.czutils = dbutils
|
|
315
|
+
return dbutils
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _require_name(name: str) -> str:
|
|
319
|
+
if name is None or str(name).strip() == "":
|
|
320
|
+
raise ValueError("Widget name is required")
|
|
321
|
+
return str(name)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _to_widget_value(value: Any) -> str:
|
|
325
|
+
if value is None:
|
|
326
|
+
return ""
|
|
327
|
+
return str(value)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _string_map(values: dict[str, Any]) -> dict[str, str]:
|
|
331
|
+
return {str(key): _to_widget_value(value) for key, value in values.items()}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _serialize_task_value(value: Any) -> tuple[str, str]:
|
|
335
|
+
value_json = json.dumps(value, ensure_ascii=False)
|
|
336
|
+
if len(value_json.encode("utf-8")) > 48 * 1024:
|
|
337
|
+
raise ValueError("Task value JSON size must be <= 48 KiB")
|
|
338
|
+
if value is None:
|
|
339
|
+
value_type = "NULL"
|
|
340
|
+
elif isinstance(value, bool):
|
|
341
|
+
value_type = "BOOLEAN"
|
|
342
|
+
elif isinstance(value, (int, float)):
|
|
343
|
+
value_type = "NUMBER"
|
|
344
|
+
elif isinstance(value, str):
|
|
345
|
+
value_type = "STRING"
|
|
346
|
+
else:
|
|
347
|
+
value_type = "JSON"
|
|
348
|
+
return value_json, value_type
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _extract_common_result_data(response: Any) -> Any:
|
|
352
|
+
if response is None:
|
|
353
|
+
return None
|
|
354
|
+
if not isinstance(response, dict):
|
|
355
|
+
return response
|
|
356
|
+
if "data" in response:
|
|
357
|
+
return response.get("data")
|
|
358
|
+
return response
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: clickzetta-studio-utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: clickzetta-studio-utils
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: clickzetta-connector>=1.0.28
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# clickzetta-studio-utils
|
|
10
|
+
|
|
11
|
+
`clickzetta-studio-utils` is a Python utility package built on top of `clickzetta-connector`. It currently provides a lightweight wrapper around ClickZetta's `FsUtil`, making it convenient to work with Volume files from Studio or scripts.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
This project requires Python `>=3.10`.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install clickzetta-studio-utils
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For local development, use `uv` to install dependencies:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv sync --dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Basic Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from clickzetta.connector.v0.connection import connect
|
|
31
|
+
from clickzetta_studio_utils._utils import _Utils
|
|
32
|
+
|
|
33
|
+
conn = connect(
|
|
34
|
+
username="<USERNAME>",
|
|
35
|
+
password="<PASSWORD>",
|
|
36
|
+
service="<SERVICE>",
|
|
37
|
+
instance="<INSTANCE>",
|
|
38
|
+
workspace="<WORKSPACE>",
|
|
39
|
+
schema="<SCHEMA>",
|
|
40
|
+
vcluster="<VCLUSTER>",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
dbutils = _Utils(conn)
|
|
45
|
+
|
|
46
|
+
dbutils.fs.put("volume://<VOLUME>/data/1.txt", "hello clickzetta")
|
|
47
|
+
print(dbutils.fs.ls("volume://<VOLUME>/data/"))
|
|
48
|
+
print(dbutils.fs.head("volume://<VOLUME>/data/1.txt"))
|
|
49
|
+
dbutils.fs.rm("volume://<VOLUME>/data/1.txt")
|
|
50
|
+
finally:
|
|
51
|
+
conn.close()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`dbutils.fs` returns a `clickzetta.connector.v0._fsutil.FsUtil` instance, so you can continue using the filesystem capabilities provided by `clickzetta-connector`.
|
|
55
|
+
|
|
56
|
+
## Notebook `dbutils.widgets`
|
|
57
|
+
|
|
58
|
+
Notebook runtimes can initialize a Databricks-like `dbutils.widgets` object from scheduler-provided runtime config.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from clickzetta_studio_utils import init_dbutils
|
|
62
|
+
|
|
63
|
+
dbutils = init_dbutils(
|
|
64
|
+
{
|
|
65
|
+
"runtime": {
|
|
66
|
+
"tenantId": 1,
|
|
67
|
+
"projectId": 2,
|
|
68
|
+
"scheduleTaskId": 3,
|
|
69
|
+
"scheduleInstanceId": 4,
|
|
70
|
+
"executeLogId": 5,
|
|
71
|
+
},
|
|
72
|
+
"dbutilsApi": {
|
|
73
|
+
"baseUrl": "http://scheduler-worker:8080",
|
|
74
|
+
},
|
|
75
|
+
"widgetValues": {
|
|
76
|
+
"biz_date": "2026-07-27",
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
dbutils.widgets.text("biz_date", "2026-07-20", "Business Date")
|
|
82
|
+
print(dbutils.widgets.get("biz_date")) # keeps the runtime value: 2026-07-27
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Supported APIs:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
dbutils.widgets.text(name, defaultValue="", label=None)
|
|
89
|
+
dbutils.widgets.dropdown(name, defaultValue, choices, label=None)
|
|
90
|
+
dbutils.widgets.combobox(name, defaultValue, choices, label=None)
|
|
91
|
+
dbutils.widgets.multiselect(name, defaultValue, choices, label=None)
|
|
92
|
+
dbutils.widgets.get(name)
|
|
93
|
+
dbutils.widgets.getAll()
|
|
94
|
+
dbutils.widgets.remove(name)
|
|
95
|
+
dbutils.widgets.removeAll()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Widget declaration and removal events are posted to `POST /v1/dbutils/widgets/events` when `dbutilsApi.baseUrl` is configured. Event write failures are logged as warnings and do not change local widget behavior.
|
|
99
|
+
|
|
100
|
+
## Notebook `dbutils.jobs.taskValues`
|
|
101
|
+
|
|
102
|
+
Notebook runtimes can use `dbutils.jobs.taskValues` to persist values for downstream task instances and to share values across cells in the current Python kernel.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
dbutils.jobs.taskValues.set("row_count", 100)
|
|
106
|
+
|
|
107
|
+
print(dbutils.jobs.taskValues.get("row_count"))
|
|
108
|
+
print(dbutils.jobs.taskValues.get(taskKey="extract_orders", key="row_count", default=0))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`set()` writes local in-memory state first, then posts serialized JSON to `POST /v1/dbutils/task-values/set` when `dbutilsApi.baseUrl` is configured. `get(taskKey=...)` reads upstream values through `POST /v1/dbutils/task-values/get` using scheduler-provided producer mappings.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
Common commands:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Check code style and lint issues
|
|
119
|
+
uv run ruff check .
|
|
120
|
+
|
|
121
|
+
# Format Python files
|
|
122
|
+
uv run ruff format .
|
|
123
|
+
|
|
124
|
+
# Run tests
|
|
125
|
+
uv run pytest
|
|
126
|
+
|
|
127
|
+
# Build source and wheel distributions
|
|
128
|
+
uv build
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Testing Notes
|
|
132
|
+
|
|
133
|
+
The test suite includes integration tests that connect to a real ClickZetta service and operate on temporary Volumes. Before running them, make sure that:
|
|
134
|
+
|
|
135
|
+
- Connection parameters are valid, and the account has permission to create and delete Volumes and read/write files.
|
|
136
|
+
- Personal, production, or long-lived credentials are not committed. Prefer environment variables or local untracked configuration when credentials are needed.
|
|
137
|
+
- You run targeted tests for the code you changed first, then run the full test suite when appropriate.
|
|
138
|
+
|
|
139
|
+
## Project Information
|
|
140
|
+
|
|
141
|
+
- Package name: `clickzetta-studio-utils`
|
|
142
|
+
- Import module: `clickzetta_studio_utils`
|
|
143
|
+
- Runtime dependency: `clickzetta-connector>=1.0.25`
|
|
144
|
+
- Build backend: `pdm-backend`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
clickzetta_studio_utils-0.1.0.dist-info/METADATA,sha256=MTZ2-X1YLNCIA-OpO0i7Nr9JU_f2Lo-UEOmo7tS2WvM,4473
|
|
2
|
+
clickzetta_studio_utils-0.1.0.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90
|
|
3
|
+
clickzetta_studio_utils-0.1.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
|
4
|
+
clickzetta_studio_utils/__init__.py,sha256=qCqPbw5lrellgRQRKI6FLmUmmXJPPdpDxl167ZIdXSQ,237
|
|
5
|
+
clickzetta_studio_utils/_utils.py,sha256=UF_MyHPDuOX2kf0wV1s-67DkNvroIaLF541bA8WpYks,278
|
|
6
|
+
clickzetta_studio_utils/dbutils.py,sha256=JPX7m5wYbUS790F83lWQgiG8V-VvzHz7_S-MfrRb3Ao,12682
|
|
7
|
+
clickzetta_studio_utils-0.1.0.dist-info/RECORD,,
|