salesforce-data-customcode 6.0.8.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.
Files changed (103) hide show
  1. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/PKG-INFO +40 -3
  2. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/README.md +39 -2
  3. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/pyproject.toml +1 -1
  4. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/__init__.py +5 -0
  5. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/cli.py +29 -4
  6. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/client.py +211 -100
  7. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/config.py +5 -0
  8. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/constants.py +8 -0
  9. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/deploy.py +55 -16
  10. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/spark_default.py +88 -27
  11. salesforce_data_customcode-6.1.0.dev1/src/datacustomcode/io/reader/base.py +85 -0
  12. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/base.py +33 -0
  13. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/run.py +11 -0
  14. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/scan.py +164 -29
  15. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/template.py +13 -1
  16. salesforce_data_customcode-6.1.0.dev1/src/datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py +49 -0
  17. salesforce_data_customcode-6.0.8.dev1/src/datacustomcode/io/reader/base.py +0 -43
  18. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/LICENSE.txt +0 -0
  19. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/auth.py +0 -0
  20. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/cmd.py +0 -0
  21. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/common_config.py +0 -0
  22. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/config.yaml +0 -0
  23. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/credentials.py +0 -0
  24. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_platform_client.py +0 -0
  25. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_platform_config.py +0 -0
  26. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/__init__.py +0 -0
  27. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/base.py +0 -0
  28. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/errors.py +0 -0
  29. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
  30. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/spark_base.py +0 -0
  31. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions/types.py +0 -0
  32. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/einstein_predictions_config.py +0 -0
  33. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/__init__.py +0 -0
  34. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/base.py +0 -0
  35. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/path/__init__.py +0 -0
  36. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/file/path/default.py +0 -0
  37. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/__init__.py +0 -0
  38. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/base.py +0 -0
  39. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/feature_types/__init__.py +0 -0
  40. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/feature_types/chunking.py +0 -0
  41. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function/runtime.py +0 -0
  42. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/function_utils.py +0 -0
  43. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/__init__.py +0 -0
  44. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/base.py +0 -0
  45. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/__init__.py +0 -0
  46. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/query_api.py +0 -0
  47. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/sf_cli.py +0 -0
  48. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/reader/utils.py +0 -0
  49. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/__init__.py +0 -0
  50. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/csv.py +0 -0
  51. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/io/writer/print.py +0 -0
  52. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/__init__.py +0 -0
  53. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/base.py +0 -0
  54. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/default.py +0 -0
  55. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/errors.py +0 -0
  56. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/spark_base.py +0 -0
  57. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/spark_default.py +0 -0
  58. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
  59. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
  60. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
  61. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
  62. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
  63. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/llm_gateway_config.py +0 -0
  64. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/mixin.py +0 -0
  65. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/py.typed +0 -0
  66. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/spark/__init__.py +0 -0
  67. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/spark/base.py +0 -0
  68. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/spark/default.py +0 -0
  69. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/__init__.py +0 -0
  70. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +0 -0
  71. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
  72. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/README.md +0 -0
  73. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
  74. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
  75. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
  76. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
  77. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
  78. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
  79. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
  80. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
  81. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
  82. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
  83. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
  84. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/payload/config.json +0 -0
  85. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/payload/entrypoint.py +0 -0
  86. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/payload/utility.py +0 -0
  87. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
  88. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/function/requirements.txt +0 -0
  89. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +0 -0
  90. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/Dockerfile +0 -0
  91. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
  92. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/README.md +0 -0
  93. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/account.ipynb +0 -0
  94. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
  95. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
  96. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
  97. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
  98. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/payload/config.json +0 -0
  99. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/payload/entrypoint.py +0 -0
  100. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
  101. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/templates/script/requirements.txt +0 -0
  102. {salesforce_data_customcode-6.0.8.dev1 → salesforce_data_customcode-6.1.0.dev1}/src/datacustomcode/token_provider.py +0 -0
  103. {salesforce_data_customcode-6.0.8.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.8.dev1
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 wraps data access layers.
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 wraps data access layers.
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:
@@ -18,7 +18,7 @@ license = "Apache-2.0"
18
18
  name = "salesforce-data-customcode"
19
19
  readme = "README.md"
20
20
  requires-python = ">=3.10,<3.12"
21
- version = "6.0.8.dev1"
21
+ version = "6.1.0.dev1"
22
22
 
23
23
  [tool.black]
24
24
  exclude = '''
@@ -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
- default="SearchIndexChunking",
287
- help="Feature where this function will be used (only applicable for function).",
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(entrypoint_path, code_type)
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 Column, DataFrame as PySparkDataFrame
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 = Client()._get_spark_llm_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 = Client()._get_spark_einstein_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
- class Client:
209
- """Entrypoint for accessing DataCloud objects.
249
+ _ClientT = TypeVar("_ClientT", bound="_BaseClient")
250
+
251
+
252
+ class _BaseClient:
253
+ """Shared machinery for the Data Cloud client singletons.
210
254
 
211
- This is the object used to access Data Cloud DLOs and DMOs. Accessing DLOs/DMOs
212
- are tracked and will throw an exception if they are mixed. In other words, you
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
- You can provide custom readers and writers to the client for advanced use
219
- cases, but this is not recommended for testing as they may result in unexpected
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: ClassVar[Optional[Client]] = None
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
- ) -> Client:
298
+ ) -> _ClientT:
258
299
 
259
300
  if cls._instance is None:
260
- cls._instance = super().__new__(cls)
261
- cls._instance._spark_llm_gateway = spark_llm_gateway
262
- cls._instance._spark_einstein_predictions = spark_einstein_predictions
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 config.spark_config is None:
268
- raise ValueError(
269
- "Spark config is required when reader/writer is not provided"
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
- provider = DefaultSparkSessionProvider()
279
-
280
- spark = provider.get_session(config.spark_config)
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
- cls._instance._reader = reader_init
304
- cls._instance._writer = writer_init
305
- cls._instance._file = DefaultFindFilePath()
306
- cls._instance._data_layer_history = {
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
- elif (reader is not None or writer is not None) and cls._instance is not None:
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",