salesforce-data-customcode 6.0.7.dev1__tar.gz → 6.1.0.dev1__tar.gz
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.
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/PKG-INFO +40 -3
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/README.md +39 -2
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/pyproject.toml +1 -1
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/__init__.py +5 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/cli.py +29 -4
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/client.py +211 -100
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/config.py +5 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/constants.py +8 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/deploy.py +55 -16
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/spark_default.py +88 -27
- salesforce_data_customcode-6.1.0.dev1/src/datacustomcode/io/reader/base.py +85 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/base.py +33 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/run.py +11 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/scan.py +164 -29
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/template.py +13 -1
- salesforce_data_customcode-6.1.0.dev1/src/datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py +49 -0
- salesforce_data_customcode-6.0.7.dev1/src/datacustomcode/io/reader/base.py +0 -43
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/LICENSE.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/auth.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/cmd.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/common_config.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/config.yaml +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/credentials.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_platform_client.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_platform_config.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/errors.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/spark_base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/types.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions_config.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/path/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/path/default.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/feature_types/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/feature_types/chunking.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/runtime.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function_utils.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/query_api.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/sf_cli.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/utils.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/csv.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/print.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/default.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/errors.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/spark_base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/spark_default.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway_config.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/mixin.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/py.typed +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/spark/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/spark/base.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/spark/default.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/__init__.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/README.md +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/payload/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/payload/utility.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/Dockerfile +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/README.md +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/account.ipynb +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/payload/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/token_provider.py +0 -0
- {salesforce_data_customcode-6.0.7.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: salesforce-data-customcode
|
|
3
|
-
Version: 6.0.
|
|
3
|
+
Version: 6.1.0.dev1
|
|
4
4
|
Summary: Data Cloud Custom Code SDK
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE.txt
|
|
@@ -170,15 +170,22 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing
|
|
|
170
170
|
|
|
171
171
|
## API
|
|
172
172
|
|
|
173
|
-
Your entry point script will define logic using the `Client` object which
|
|
173
|
+
Your entry point script will define logic using the `Client` object (for batch transforms) or the `StreamingClient` object (for streaming delta transforms), which wrap the data access layers. Both are singletons; a single transform should use one or the other, not both.
|
|
174
174
|
|
|
175
|
-
You should only need the following methods:
|
|
175
|
+
For a batch transform, use `Client`. You should only need the following methods:
|
|
176
176
|
* `find_file_path(file_name)` – Resolve a bundled file (placed under `payload/files/`) to a `pathlib.Path` that exists. Works the same locally and inside Data Cloud — see [Bundled file resolution](#bundled-file-resolution) below for the full lookup order. Raises `FileNotFoundError` if the file isn't found.
|
|
177
177
|
* `read_dlo(name)` – Read from a Data Lake Object by name
|
|
178
178
|
* `read_dmo(name)` – Read from a Data Model Object by name
|
|
179
179
|
* `write_to_dlo(name, spark_dataframe, write_mode)` – Write to a Data Model Object by name with a Spark dataframe
|
|
180
180
|
* `write_to_dmo(name, spark_dataframe, write_mode)` – Write to a Data Lake Object by name with a Spark dataframe
|
|
181
181
|
|
|
182
|
+
For a streaming (delta) transform, use `StreamingClient`, which exposes the streaming counterparts:
|
|
183
|
+
* `read_dlo_deltas()` – Read the streaming change feed (deltas) of a Data Lake Object as a streaming DataFrame.
|
|
184
|
+
* `read_dmo_deltas()` – Read the streaming change feed (deltas) of a Data Model Object as a streaming DataFrame.
|
|
185
|
+
* `write_dlo_deltas(name, spark_dataframe)` – Write a streaming DataFrame of deltas to a Data Lake Object; returns the started `StreamingQuery`
|
|
186
|
+
|
|
187
|
+
`find_file_path`, `llm_gateway_generate_text`, and `einstein_predict` are available on both clients.
|
|
188
|
+
|
|
182
189
|
For example:
|
|
183
190
|
```python
|
|
184
191
|
from datacustomcode import Client
|
|
@@ -194,6 +201,36 @@ client.write_to_dlo('output_DLO')
|
|
|
194
201
|
> [!WARNING]
|
|
195
202
|
> Currently we only support reading from DMOs and writing to DMOs or reading from DLOs and writing to DLOs, but they cannot mix.
|
|
196
203
|
|
|
204
|
+
### Streaming (delta) transforms
|
|
205
|
+
|
|
206
|
+
Streaming BYOC transforms process a Data Lake Object's Change Data Feed continuously instead of reading a bounded snapshot. Use a `StreamingClient` and its `*_deltas` methods in place of the batch `Client` read/write methods:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from pyspark.sql.functions import col, upper
|
|
210
|
+
|
|
211
|
+
from datacustomcode import StreamingClient
|
|
212
|
+
|
|
213
|
+
client = StreamingClient()
|
|
214
|
+
|
|
215
|
+
# read_dlo_deltas returns a *streaming* DataFrame over the change feed.
|
|
216
|
+
# The runtime resolves the single streaming source, so no name is passed.
|
|
217
|
+
deltas = client.read_dlo_deltas()
|
|
218
|
+
|
|
219
|
+
# Ordinary PySpark transform.
|
|
220
|
+
transformed = deltas.withColumn("description__c", upper(col("description__c")))
|
|
221
|
+
|
|
222
|
+
# write_dlo_deltas starts a streaming query and returns the StreamingQuery.
|
|
223
|
+
# The runtime owns the trigger and checkpoint location; you
|
|
224
|
+
# choose only the target table.
|
|
225
|
+
query = client.write_dlo_deltas("Output__dll", transformed)
|
|
226
|
+
query.awaitTermination()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Notes:
|
|
230
|
+
|
|
231
|
+
- These methods only run inside the Data Cloud streaming (`DELTA_SYNC`) runtime. Locally (`datacustomcode run`) they raise `NotImplementedError`, since there is no change feed to stream.
|
|
232
|
+
- A complete runnable entry point is provided in [`examples/streaming_deltas/entrypoint.py`](src/datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py).
|
|
233
|
+
|
|
197
234
|
### Bundled file resolution
|
|
198
235
|
|
|
199
236
|
Place bundled files (CSVs, prompt files, etc.) under `payload/files/`. The same `client.find_file_path("data.csv")` call resolves consistently across all three runtimes:
|
|
@@ -145,15 +145,22 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing
|
|
|
145
145
|
|
|
146
146
|
## API
|
|
147
147
|
|
|
148
|
-
Your entry point script will define logic using the `Client` object which
|
|
148
|
+
Your entry point script will define logic using the `Client` object (for batch transforms) or the `StreamingClient` object (for streaming delta transforms), which wrap the data access layers. Both are singletons; a single transform should use one or the other, not both.
|
|
149
149
|
|
|
150
|
-
You should only need the following methods:
|
|
150
|
+
For a batch transform, use `Client`. You should only need the following methods:
|
|
151
151
|
* `find_file_path(file_name)` – Resolve a bundled file (placed under `payload/files/`) to a `pathlib.Path` that exists. Works the same locally and inside Data Cloud — see [Bundled file resolution](#bundled-file-resolution) below for the full lookup order. Raises `FileNotFoundError` if the file isn't found.
|
|
152
152
|
* `read_dlo(name)` – Read from a Data Lake Object by name
|
|
153
153
|
* `read_dmo(name)` – Read from a Data Model Object by name
|
|
154
154
|
* `write_to_dlo(name, spark_dataframe, write_mode)` – Write to a Data Model Object by name with a Spark dataframe
|
|
155
155
|
* `write_to_dmo(name, spark_dataframe, write_mode)` – Write to a Data Lake Object by name with a Spark dataframe
|
|
156
156
|
|
|
157
|
+
For a streaming (delta) transform, use `StreamingClient`, which exposes the streaming counterparts:
|
|
158
|
+
* `read_dlo_deltas()` – Read the streaming change feed (deltas) of a Data Lake Object as a streaming DataFrame.
|
|
159
|
+
* `read_dmo_deltas()` – Read the streaming change feed (deltas) of a Data Model Object as a streaming DataFrame.
|
|
160
|
+
* `write_dlo_deltas(name, spark_dataframe)` – Write a streaming DataFrame of deltas to a Data Lake Object; returns the started `StreamingQuery`
|
|
161
|
+
|
|
162
|
+
`find_file_path`, `llm_gateway_generate_text`, and `einstein_predict` are available on both clients.
|
|
163
|
+
|
|
157
164
|
For example:
|
|
158
165
|
```python
|
|
159
166
|
from datacustomcode import Client
|
|
@@ -169,6 +176,36 @@ client.write_to_dlo('output_DLO')
|
|
|
169
176
|
> [!WARNING]
|
|
170
177
|
> Currently we only support reading from DMOs and writing to DMOs or reading from DLOs and writing to DLOs, but they cannot mix.
|
|
171
178
|
|
|
179
|
+
### Streaming (delta) transforms
|
|
180
|
+
|
|
181
|
+
Streaming BYOC transforms process a Data Lake Object's Change Data Feed continuously instead of reading a bounded snapshot. Use a `StreamingClient` and its `*_deltas` methods in place of the batch `Client` read/write methods:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from pyspark.sql.functions import col, upper
|
|
185
|
+
|
|
186
|
+
from datacustomcode import StreamingClient
|
|
187
|
+
|
|
188
|
+
client = StreamingClient()
|
|
189
|
+
|
|
190
|
+
# read_dlo_deltas returns a *streaming* DataFrame over the change feed.
|
|
191
|
+
# The runtime resolves the single streaming source, so no name is passed.
|
|
192
|
+
deltas = client.read_dlo_deltas()
|
|
193
|
+
|
|
194
|
+
# Ordinary PySpark transform.
|
|
195
|
+
transformed = deltas.withColumn("description__c", upper(col("description__c")))
|
|
196
|
+
|
|
197
|
+
# write_dlo_deltas starts a streaming query and returns the StreamingQuery.
|
|
198
|
+
# The runtime owns the trigger and checkpoint location; you
|
|
199
|
+
# choose only the target table.
|
|
200
|
+
query = client.write_dlo_deltas("Output__dll", transformed)
|
|
201
|
+
query.awaitTermination()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Notes:
|
|
205
|
+
|
|
206
|
+
- These methods only run inside the Data Cloud streaming (`DELTA_SYNC`) runtime. Locally (`datacustomcode run`) they raise `NotImplementedError`, since there is no change feed to stream.
|
|
207
|
+
- A complete runnable entry point is provided in [`examples/streaming_deltas/entrypoint.py`](src/datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py).
|
|
208
|
+
|
|
172
209
|
### Bundled file resolution
|
|
173
210
|
|
|
174
211
|
Place bundled files (CSVs, prompt files, etc.) under `payload/files/`. The same `client.find_file_path("data.csv")` call resolves consistently across all three runtimes:
|
|
@@ -23,6 +23,7 @@ __all__ = [
|
|
|
23
23
|
"QueryAPIDataCloudReader",
|
|
24
24
|
"SparkEinsteinPredictions",
|
|
25
25
|
"SparkLLMGateway",
|
|
26
|
+
"StreamingClient",
|
|
26
27
|
"einstein_predict_col",
|
|
27
28
|
"llm_gateway_generate_text_col",
|
|
28
29
|
]
|
|
@@ -34,6 +35,10 @@ def __getattr__(name: str):
|
|
|
34
35
|
from datacustomcode.client import Client
|
|
35
36
|
|
|
36
37
|
return Client
|
|
38
|
+
elif name == "StreamingClient":
|
|
39
|
+
from datacustomcode.client import StreamingClient
|
|
40
|
+
|
|
41
|
+
return StreamingClient
|
|
37
42
|
elif name == "AuthType":
|
|
38
43
|
from datacustomcode.credentials import AuthType
|
|
39
44
|
|
|
@@ -283,10 +283,19 @@ def deploy(
|
|
|
283
283
|
)
|
|
284
284
|
@click.option(
|
|
285
285
|
"--use-in-feature",
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
"-u",
|
|
287
|
+
default=None,
|
|
288
|
+
help=(
|
|
289
|
+
"Invoke option for this package. For scripts: 'BatchTransform' "
|
|
290
|
+
"(default) or 'StreamingTransform'. For functions: 'SearchIndexChunking'."
|
|
291
|
+
),
|
|
288
292
|
)
|
|
289
293
|
def init(directory: str, code_type: str, use_in_feature: Optional[str]):
|
|
294
|
+
from datacustomcode.constants import (
|
|
295
|
+
SCRIPT_USE_IN_FEATURE_BATCH,
|
|
296
|
+
SCRIPT_USE_IN_FEATURE_OPTIONS,
|
|
297
|
+
SCRIPT_USE_IN_FEATURE_STREAMING,
|
|
298
|
+
)
|
|
290
299
|
from datacustomcode.scan import (
|
|
291
300
|
dc_config_json_from_file,
|
|
292
301
|
update_config,
|
|
@@ -294,9 +303,23 @@ def init(directory: str, code_type: str, use_in_feature: Optional[str]):
|
|
|
294
303
|
)
|
|
295
304
|
from datacustomcode.template import copy_function_template, copy_script_template
|
|
296
305
|
|
|
306
|
+
streaming = False
|
|
307
|
+
if code_type == "script":
|
|
308
|
+
use_in_feature = use_in_feature or SCRIPT_USE_IN_FEATURE_BATCH
|
|
309
|
+
if use_in_feature not in SCRIPT_USE_IN_FEATURE_OPTIONS:
|
|
310
|
+
click.secho(
|
|
311
|
+
f"Error: Invalid --use-in-feature '{use_in_feature}' for a "
|
|
312
|
+
f"script. Valid options: {', '.join(SCRIPT_USE_IN_FEATURE_OPTIONS)}.",
|
|
313
|
+
fg="red",
|
|
314
|
+
)
|
|
315
|
+
raise click.Abort()
|
|
316
|
+
streaming = use_in_feature == SCRIPT_USE_IN_FEATURE_STREAMING
|
|
317
|
+
else:
|
|
318
|
+
use_in_feature = use_in_feature or "SearchIndexChunking"
|
|
319
|
+
|
|
297
320
|
click.echo("Copying template to " + click.style(directory, fg="blue", bold=True))
|
|
298
321
|
if code_type == "script":
|
|
299
|
-
copy_script_template(directory)
|
|
322
|
+
copy_script_template(directory, streaming=streaming)
|
|
300
323
|
elif code_type == "function":
|
|
301
324
|
copy_function_template(directory, use_in_feature)
|
|
302
325
|
entrypoint_path = os.path.join(directory, PAYLOAD_DIR, ENTRYPOINT_FILE)
|
|
@@ -306,7 +329,9 @@ def init(directory: str, code_type: str, use_in_feature: Optional[str]):
|
|
|
306
329
|
sdk_config = {"type": code_type}
|
|
307
330
|
write_sdk_config(directory, sdk_config)
|
|
308
331
|
|
|
309
|
-
config_json = dc_config_json_from_file(
|
|
332
|
+
config_json = dc_config_json_from_file(
|
|
333
|
+
entrypoint_path, code_type, streaming=streaming
|
|
334
|
+
)
|
|
310
335
|
with open(config_location, "w") as f:
|
|
311
336
|
json.dump(config_json, f, indent=2)
|
|
312
337
|
|
|
@@ -21,7 +21,9 @@ from typing import (
|
|
|
21
21
|
ClassVar,
|
|
22
22
|
Dict,
|
|
23
23
|
Optional,
|
|
24
|
+
TypeVar,
|
|
24
25
|
Union,
|
|
26
|
+
cast,
|
|
25
27
|
)
|
|
26
28
|
|
|
27
29
|
from datacustomcode.config import config
|
|
@@ -34,7 +36,12 @@ from datacustomcode.spark.default import DefaultSparkSessionProvider
|
|
|
34
36
|
if TYPE_CHECKING:
|
|
35
37
|
from pathlib import Path
|
|
36
38
|
|
|
37
|
-
from pyspark.sql import
|
|
39
|
+
from pyspark.sql import (
|
|
40
|
+
Column,
|
|
41
|
+
DataFrame as PySparkDataFrame,
|
|
42
|
+
SparkSession,
|
|
43
|
+
)
|
|
44
|
+
from pyspark.sql.streaming import StreamingQuery
|
|
38
45
|
|
|
39
46
|
from datacustomcode.einstein_predictions.spark_base import SparkEinsteinPredictions
|
|
40
47
|
from datacustomcode.einstein_predictions.types import PredictionType
|
|
@@ -44,6 +51,40 @@ if TYPE_CHECKING:
|
|
|
44
51
|
from datacustomcode.spark.base import BaseSparkSessionProvider
|
|
45
52
|
|
|
46
53
|
|
|
54
|
+
def _streaming_source_name() -> str:
|
|
55
|
+
"""Return the streaming transform's read-source name.
|
|
56
|
+
|
|
57
|
+
Resolved from ``config.streaming_source``, which ``run_entrypoint``
|
|
58
|
+
populates from config.json's ``streamingSource`` field.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
RuntimeError: If no ``streaming_source`` has been configured (e.g. the
|
|
62
|
+
transform's config.json has no ``streamingSource`` field).
|
|
63
|
+
"""
|
|
64
|
+
source = config.streaming_source
|
|
65
|
+
if not source:
|
|
66
|
+
raise RuntimeError(
|
|
67
|
+
"No streaming source configured. A streaming transform must declare "
|
|
68
|
+
"its read source in config.json under 'streamingSource'."
|
|
69
|
+
)
|
|
70
|
+
return source
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _active_client() -> "_BaseClient":
|
|
74
|
+
"""Return the client backing the module-level Spark column helpers.
|
|
75
|
+
|
|
76
|
+
Prefers an already-initialized singleton so a streaming job reuses its
|
|
77
|
+
:class:`StreamingClient` (and a batch job its :class:`Client`) rather than
|
|
78
|
+
forcing an unrelated client into existence. Falls back to building the
|
|
79
|
+
batch :class:`Client` when neither has been created yet.
|
|
80
|
+
"""
|
|
81
|
+
if Client._instance is not None:
|
|
82
|
+
return Client._instance
|
|
83
|
+
if StreamingClient._instance is not None:
|
|
84
|
+
return StreamingClient._instance
|
|
85
|
+
return Client()
|
|
86
|
+
|
|
87
|
+
|
|
47
88
|
def _build_spark_llm_gateway() -> "SparkLLMGateway":
|
|
48
89
|
"""Instantiate the SDK-configured :class:`SparkLLMGateway`.
|
|
49
90
|
|
|
@@ -99,7 +140,7 @@ def llm_gateway_generate_text_col(
|
|
|
99
140
|
the generated text; on failure, ``status == "ERROR"`` and the
|
|
100
141
|
``error_*`` fields carry diagnostic detail.
|
|
101
142
|
"""
|
|
102
|
-
gateway =
|
|
143
|
+
gateway = _active_client()._get_spark_llm_gateway()
|
|
103
144
|
return gateway.llm_gateway_generate_text_col(template, values, model_id=model_id)
|
|
104
145
|
|
|
105
146
|
|
|
@@ -161,7 +202,7 @@ def einstein_predict_col(
|
|
|
161
202
|
the JSON-serialized prediction payload; on failure, ``status ==
|
|
162
203
|
"ERROR"`` and the ``error_*`` fields carry diagnostic detail.
|
|
163
204
|
"""
|
|
164
|
-
predictions =
|
|
205
|
+
predictions = _active_client()._get_spark_einstein_predictions()
|
|
165
206
|
return predictions.einstein_predict_col(
|
|
166
207
|
model_api_name, prediction_type, features, settings=settings
|
|
167
208
|
)
|
|
@@ -205,39 +246,39 @@ class DataCloudAccessLayerException(Exception):
|
|
|
205
246
|
return msg
|
|
206
247
|
|
|
207
248
|
|
|
208
|
-
|
|
209
|
-
|
|
249
|
+
_ClientT = TypeVar("_ClientT", bound="_BaseClient")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class _BaseClient:
|
|
253
|
+
"""Shared machinery for the Data Cloud client singletons.
|
|
210
254
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
can read from DLOs and write to DLOs, read from DMOs and write to DMOs, but you
|
|
214
|
-
cannot read from DLOs and write to DMOs or read from DMOs and write to DLOs.
|
|
215
|
-
Furthermore you cannot mix during merging tables. This class is a singleton to
|
|
216
|
-
prevent accidental mixing of DLOs and DMOs.
|
|
255
|
+
Holds the wiring common to :class:`Client` (batch) and
|
|
256
|
+
:class:`StreamingClient`
|
|
217
257
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
behavior once deployed to Data Cloud. By default, the client intercepts all
|
|
221
|
-
read/write operations and mocks access to Data Cloud. For example, during
|
|
222
|
-
writing, we print to the console instead of writing to Data Cloud.
|
|
258
|
+
This base class is not meant to be instantiated directly; use
|
|
259
|
+
:class:`Client` or :class:`StreamingClient`.
|
|
223
260
|
|
|
224
261
|
Args:
|
|
225
|
-
finder: Find a file path
|
|
226
262
|
reader: A custom reader to use for reading Data Cloud objects.
|
|
227
263
|
writer: A custom writer to use for writing Data Cloud objects.
|
|
264
|
+
spark_provider: Optional custom :class:`BaseSparkSessionProvider`.
|
|
228
265
|
spark_llm_gateway: Optional custom :class:`SparkLLMGateway`.
|
|
229
266
|
spark_einstein_predictions: Optional custom
|
|
230
267
|
:class:`SparkEinsteinPredictions`.
|
|
231
|
-
|
|
232
|
-
Example:
|
|
233
|
-
>>> client = Client()
|
|
234
|
-
>>> file_path = client.find_file_path("data.csv")
|
|
235
|
-
>>> dlo = client.read_dlo("my_dlo")
|
|
236
|
-
>>> client.write_to_dmo("my_dmo", dlo)
|
|
237
|
-
>>> answer = client.llm_gateway_generate_text("Generate a greeting message")
|
|
238
268
|
"""
|
|
239
269
|
|
|
240
|
-
_instance:
|
|
270
|
+
# Each concrete subclass gets its own ``_instance`` slot: reads fall through
|
|
271
|
+
# to this base default of ``None``, but ``cls._instance = ...`` in __new__
|
|
272
|
+
# always writes to the subclass, so ``Client`` and ``StreamingClient`` never
|
|
273
|
+
# share an instance.
|
|
274
|
+
_instance: ClassVar[Optional[_BaseClient]] = None
|
|
275
|
+
# Process-wide Spark session shared across BOTH client types. Unlike
|
|
276
|
+
# ``_instance``, this is written via ``_BaseClient._shared_spark`` (never
|
|
277
|
+
# ``cls._shared_spark``), so the slot lives on the base class and a
|
|
278
|
+
# ``Client`` and a ``StreamingClient`` in the same process reuse one session
|
|
279
|
+
# — and therefore one underlying connection — instead of opening two
|
|
280
|
+
# containing differing state
|
|
281
|
+
_shared_spark: ClassVar[Optional[SparkSession]] = None
|
|
241
282
|
_reader: BaseDataCloudReader
|
|
242
283
|
_writer: BaseDataCloudWriter
|
|
243
284
|
_file: DefaultFindFilePath
|
|
@@ -247,37 +288,44 @@ class Client:
|
|
|
247
288
|
_code_type: str
|
|
248
289
|
|
|
249
290
|
def __new__(
|
|
250
|
-
cls,
|
|
291
|
+
cls: type[_ClientT],
|
|
251
292
|
reader: Optional[BaseDataCloudReader] = None,
|
|
252
293
|
writer: Optional[BaseDataCloudWriter] = None,
|
|
253
294
|
spark_provider: Optional[BaseSparkSessionProvider] = None,
|
|
254
295
|
spark_llm_gateway: Optional[SparkLLMGateway] = None,
|
|
255
296
|
spark_einstein_predictions: Optional[SparkEinsteinPredictions] = None,
|
|
256
297
|
code_type: str = "script",
|
|
257
|
-
) ->
|
|
298
|
+
) -> _ClientT:
|
|
258
299
|
|
|
259
300
|
if cls._instance is None:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
301
|
+
instance = super().__new__(cls)
|
|
302
|
+
instance._spark_llm_gateway = spark_llm_gateway
|
|
303
|
+
instance._spark_einstein_predictions = spark_einstein_predictions
|
|
263
304
|
# Initialize Readers and Writers from config
|
|
264
305
|
# and/or provided reader and writer
|
|
265
306
|
if reader is None or writer is None:
|
|
266
|
-
# We need a spark because we will initialize readers and writers
|
|
267
|
-
if
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
provider: BaseSparkSessionProvider
|
|
273
|
-
if spark_provider is not None:
|
|
274
|
-
provider = spark_provider
|
|
275
|
-
elif config.spark_provider_config is not None:
|
|
276
|
-
provider = config.spark_provider_config.to_object()
|
|
307
|
+
# We need a spark because we will initialize readers and writers.
|
|
308
|
+
# Reuse the process-wide session if one client already built it,
|
|
309
|
+
# so a Client and a StreamingClient share a single connection.
|
|
310
|
+
if _BaseClient._shared_spark is not None:
|
|
311
|
+
spark = _BaseClient._shared_spark
|
|
277
312
|
else:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
if config.spark_config is None:
|
|
314
|
+
raise ValueError(
|
|
315
|
+
"Spark config is required when reader/writer is not "
|
|
316
|
+
"provided"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
provider: BaseSparkSessionProvider
|
|
320
|
+
if spark_provider is not None:
|
|
321
|
+
provider = spark_provider
|
|
322
|
+
elif config.spark_provider_config is not None:
|
|
323
|
+
provider = config.spark_provider_config.to_object()
|
|
324
|
+
else:
|
|
325
|
+
provider = DefaultSparkSessionProvider()
|
|
326
|
+
|
|
327
|
+
spark = provider.get_session(config.spark_config)
|
|
328
|
+
_BaseClient._shared_spark = spark
|
|
281
329
|
|
|
282
330
|
if config.reader_config is None and reader is None:
|
|
283
331
|
raise ValueError(
|
|
@@ -300,66 +348,17 @@ class Client:
|
|
|
300
348
|
else:
|
|
301
349
|
writer_init = writer
|
|
302
350
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
351
|
+
instance._reader = reader_init
|
|
352
|
+
instance._writer = writer_init
|
|
353
|
+
instance._file = DefaultFindFilePath()
|
|
354
|
+
instance._data_layer_history = {
|
|
307
355
|
DataCloudObjectType.DLO: set(),
|
|
308
356
|
DataCloudObjectType.DMO: set(),
|
|
309
357
|
}
|
|
310
|
-
|
|
358
|
+
cls._instance = instance
|
|
359
|
+
elif reader is not None or writer is not None:
|
|
311
360
|
raise ValueError("Cannot set reader or writer after client is initialized")
|
|
312
|
-
return cls._instance
|
|
313
|
-
|
|
314
|
-
def read_dlo(self, name: str) -> PySparkDataFrame:
|
|
315
|
-
"""Read a DLO from Data Cloud.
|
|
316
|
-
|
|
317
|
-
Args:
|
|
318
|
-
name: The name of the DLO to read.
|
|
319
|
-
|
|
320
|
-
Returns:
|
|
321
|
-
A PySpark DataFrame containing the DLO data.
|
|
322
|
-
"""
|
|
323
|
-
self._record_dlo_access(name)
|
|
324
|
-
return self._reader.read_dlo(name) # type: ignore[no-any-return]
|
|
325
|
-
|
|
326
|
-
def read_dmo(self, name: str) -> PySparkDataFrame:
|
|
327
|
-
"""Read a DMO from Data Cloud.
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
name: The name of the DMO to read.
|
|
331
|
-
|
|
332
|
-
Returns:
|
|
333
|
-
A PySpark DataFrame containing the DMO data.
|
|
334
|
-
"""
|
|
335
|
-
self._record_dmo_access(name)
|
|
336
|
-
return self._reader.read_dmo(name) # type: ignore[no-any-return]
|
|
337
|
-
|
|
338
|
-
def write_to_dlo(
|
|
339
|
-
self, name: str, dataframe: PySparkDataFrame, write_mode: WriteMode, **kwargs
|
|
340
|
-
) -> None:
|
|
341
|
-
"""Write a PySpark DataFrame to a DLO in Data Cloud.
|
|
342
|
-
|
|
343
|
-
Args:
|
|
344
|
-
name: The name of the DLO to write to.
|
|
345
|
-
dataframe: The PySpark DataFrame to write.
|
|
346
|
-
write_mode: The write mode to use for writing to the DLO.
|
|
347
|
-
"""
|
|
348
|
-
self._validate_data_layer_history_does_not_contain(DataCloudObjectType.DMO)
|
|
349
|
-
return self._writer.write_to_dlo(name, dataframe, write_mode, **kwargs) # type: ignore[no-any-return]
|
|
350
|
-
|
|
351
|
-
def write_to_dmo(
|
|
352
|
-
self, name: str, dataframe: PySparkDataFrame, write_mode: WriteMode, **kwargs
|
|
353
|
-
) -> None:
|
|
354
|
-
"""Write a PySpark DataFrame to a DMO in Data Cloud.
|
|
355
|
-
|
|
356
|
-
Args:
|
|
357
|
-
name: The name of the DMO to write to.
|
|
358
|
-
dataframe: The PySpark DataFrame to write.
|
|
359
|
-
write_mode: The write mode to use for writing to the DMO.
|
|
360
|
-
"""
|
|
361
|
-
self._validate_data_layer_history_does_not_contain(DataCloudObjectType.DLO)
|
|
362
|
-
return self._writer.write_to_dmo(name, dataframe, write_mode, **kwargs) # type: ignore[no-any-return]
|
|
361
|
+
return cast(_ClientT, cls._instance)
|
|
363
362
|
|
|
364
363
|
def find_file_path(self, file_name: str) -> Path:
|
|
365
364
|
"""Resolve a bundled file shipped in the package to an absolute path.
|
|
@@ -487,3 +486,115 @@ class Client:
|
|
|
487
486
|
|
|
488
487
|
def _record_dmo_access(self, name: str) -> None:
|
|
489
488
|
self._data_layer_history[DataCloudObjectType.DMO].add(name)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class Client(_BaseClient):
|
|
492
|
+
"""Entrypoint for batch access to Data Cloud objects.
|
|
493
|
+
|
|
494
|
+
This is the object used to read and write bounded snapshots of Data Cloud
|
|
495
|
+
DLOs and DMOs.
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
_instance: ClassVar[Optional[Client]] = None
|
|
499
|
+
|
|
500
|
+
def read_dlo(self, name: str) -> PySparkDataFrame:
|
|
501
|
+
"""Read a DLO from Data Cloud.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
name: The name of the DLO to read.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
A PySpark DataFrame containing the DLO data.
|
|
508
|
+
"""
|
|
509
|
+
self._record_dlo_access(name)
|
|
510
|
+
return self._reader.read_dlo(name) # type: ignore[no-any-return]
|
|
511
|
+
|
|
512
|
+
def read_dmo(self, name: str) -> PySparkDataFrame:
|
|
513
|
+
"""Read a DMO from Data Cloud.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
name: The name of the DMO to read.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
A PySpark DataFrame containing the DMO data.
|
|
520
|
+
"""
|
|
521
|
+
self._record_dmo_access(name)
|
|
522
|
+
return self._reader.read_dmo(name) # type: ignore[no-any-return]
|
|
523
|
+
|
|
524
|
+
def write_to_dlo(
|
|
525
|
+
self, name: str, dataframe: PySparkDataFrame, write_mode: WriteMode, **kwargs
|
|
526
|
+
) -> None:
|
|
527
|
+
"""Write a PySpark DataFrame to a DLO in Data Cloud.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
name: The name of the DLO to write to.
|
|
531
|
+
dataframe: The PySpark DataFrame to write.
|
|
532
|
+
write_mode: The write mode to use for writing to the DLO.
|
|
533
|
+
"""
|
|
534
|
+
self._validate_data_layer_history_does_not_contain(DataCloudObjectType.DMO)
|
|
535
|
+
return self._writer.write_to_dlo(name, dataframe, write_mode, **kwargs) # type: ignore[no-any-return]
|
|
536
|
+
|
|
537
|
+
def write_to_dmo(
|
|
538
|
+
self, name: str, dataframe: PySparkDataFrame, write_mode: WriteMode, **kwargs
|
|
539
|
+
) -> None:
|
|
540
|
+
"""Write a PySpark DataFrame to a DMO in Data Cloud.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
name: The name of the DMO to write to.
|
|
544
|
+
dataframe: The PySpark DataFrame to write.
|
|
545
|
+
write_mode: The write mode to use for writing to the DMO.
|
|
546
|
+
"""
|
|
547
|
+
self._validate_data_layer_history_does_not_contain(DataCloudObjectType.DLO)
|
|
548
|
+
return self._writer.write_to_dmo(name, dataframe, write_mode, **kwargs) # type: ignore[no-any-return]
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class StreamingClient(_BaseClient):
|
|
552
|
+
"""Entrypoint for streaming (``DELTA_SYNC``) access to Data Cloud objects.
|
|
553
|
+
|
|
554
|
+
This is the streaming counterpart to :class:`Client`. Instead of reading and
|
|
555
|
+
writing bounded snapshots, it reads a DLO/DMO change feed as a streaming
|
|
556
|
+
DataFrame and writes the transformed stream back via a ``StreamingQuery``.
|
|
557
|
+
"""
|
|
558
|
+
|
|
559
|
+
_instance: ClassVar[Optional[StreamingClient]] = None
|
|
560
|
+
|
|
561
|
+
def read_dlo_deltas(self) -> PySparkDataFrame:
|
|
562
|
+
"""Read the streaming change feed (deltas) for a DLO from Data Cloud.
|
|
563
|
+
|
|
564
|
+
For use in a streaming (``DELTA_SYNC``) BYOC transform. Returns a
|
|
565
|
+
streaming DataFrame whose rows carry the change-feed metadata columns
|
|
566
|
+
(``_record_type``, ``_commit_*``) alongside the source columns.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
A streaming PySpark DataFrame over the DLO change feed.
|
|
570
|
+
"""
|
|
571
|
+
self._record_dlo_access(_streaming_source_name())
|
|
572
|
+
return self._reader.read_dlo_deltas() # type: ignore[no-any-return]
|
|
573
|
+
|
|
574
|
+
def read_dmo_deltas(self) -> PySparkDataFrame:
|
|
575
|
+
"""Read the streaming change feed (deltas) for a DMO from Data Cloud.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
A streaming PySpark DataFrame over the DMO change feed.
|
|
579
|
+
"""
|
|
580
|
+
self._record_dmo_access(_streaming_source_name())
|
|
581
|
+
return self._reader.read_dmo_deltas() # type: ignore[no-any-return]
|
|
582
|
+
|
|
583
|
+
def write_dlo_deltas(
|
|
584
|
+
self, name: str, dataframe: PySparkDataFrame, **kwargs
|
|
585
|
+
) -> StreamingQuery:
|
|
586
|
+
"""Write a streaming DataFrame of deltas to a DLO in Data Cloud.
|
|
587
|
+
|
|
588
|
+
Starts a streaming query that writes each micro-batch to the
|
|
589
|
+
target DLO and returns the ``StreamingQuery`` handle; the caller
|
|
590
|
+
typically calls ``query.awaitTermination()``.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
name: The name of the DLO to write to.
|
|
594
|
+
dataframe: The streaming PySpark DataFrame to write.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
The started ``StreamingQuery``.
|
|
598
|
+
"""
|
|
599
|
+
self._validate_data_layer_history_does_not_contain(DataCloudObjectType.DMO)
|
|
600
|
+
return self._writer.write_dlo_deltas(name, dataframe, **kwargs) # type: ignore[no-any-return]
|
|
@@ -89,6 +89,9 @@ class ClientConfig(BaseConfig):
|
|
|
89
89
|
spark_provider_config: Union[
|
|
90
90
|
SparkProviderConfig[BaseSparkSessionProvider], None
|
|
91
91
|
] = None
|
|
92
|
+
# Source object name for a streaming (DELTA_SYNC) transform, populated by
|
|
93
|
+
# ``run_entrypoint`` from config.json's ``streamingSource`` field
|
|
94
|
+
streaming_source: Union[str, None] = None
|
|
92
95
|
|
|
93
96
|
def update(self, other: ClientConfig) -> ClientConfig:
|
|
94
97
|
"""Merge this ClientConfig with another, respecting force flags.
|
|
@@ -116,6 +119,8 @@ class ClientConfig(BaseConfig):
|
|
|
116
119
|
self.spark_provider_config = merge(
|
|
117
120
|
self.spark_provider_config, other.spark_provider_config
|
|
118
121
|
)
|
|
122
|
+
if other.streaming_source is not None:
|
|
123
|
+
self.streaming_source = other.streaming_source
|
|
119
124
|
return self
|
|
120
125
|
|
|
121
126
|
|
|
@@ -38,6 +38,14 @@ USE_IN_FEATURE_MAPPING_FOR_CONNECT_API = {
|
|
|
38
38
|
"SearchIndexChunking": "UnstructuredChunking",
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
# Script (data transform) invoke options
|
|
42
|
+
SCRIPT_USE_IN_FEATURE_BATCH = "BatchTransform"
|
|
43
|
+
SCRIPT_USE_IN_FEATURE_STREAMING = "StreamingTransform"
|
|
44
|
+
SCRIPT_USE_IN_FEATURE_OPTIONS = [
|
|
45
|
+
SCRIPT_USE_IN_FEATURE_BATCH,
|
|
46
|
+
SCRIPT_USE_IN_FEATURE_STREAMING,
|
|
47
|
+
]
|
|
48
|
+
|
|
41
49
|
# Pydantic request/response type names to feature names
|
|
42
50
|
REQUEST_TYPE_TO_FEATURE = {
|
|
43
51
|
"SearchIndexChunkingV1Request": "SearchIndexChunking",
|