salesforce-data-customcode 6.0.5.dev1__tar.gz → 6.0.6.dev2__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.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/PKG-INFO +96 -8
  2. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/README.md +94 -6
  3. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/pyproject.toml +2 -2
  4. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/__init__.py +20 -0
  5. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/client.py +325 -98
  6. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/config.py +5 -0
  7. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/config.yaml +3 -0
  8. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_predictions/__init__.py +8 -0
  9. salesforce_data_customcode-6.0.6.dev2/src/datacustomcode/einstein_predictions/errors.py +36 -0
  10. salesforce_data_customcode-6.0.6.dev2/src/datacustomcode/einstein_predictions/spark_base.py +72 -0
  11. salesforce_data_customcode-6.0.6.dev2/src/datacustomcode/einstein_predictions/spark_default.py +239 -0
  12. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_predictions_config.py +45 -1
  13. salesforce_data_customcode-6.0.6.dev2/src/datacustomcode/io/reader/base.py +85 -0
  14. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/writer/base.py +33 -0
  15. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/run.py +11 -0
  16. salesforce_data_customcode-6.0.6.dev2/src/datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py +49 -0
  17. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/payload/entrypoint.py +17 -0
  18. salesforce_data_customcode-6.0.5.dev1/src/datacustomcode/io/reader/base.py +0 -43
  19. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/LICENSE.txt +0 -0
  20. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/auth.py +0 -0
  21. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/cli.py +0 -0
  22. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/cmd.py +0 -0
  23. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/common_config.py +0 -0
  24. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/constants.py +0 -0
  25. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/credentials.py +0 -0
  26. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/deploy.py +0 -0
  27. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_platform_client.py +0 -0
  28. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_platform_config.py +0 -0
  29. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_predictions/base.py +0 -0
  30. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
  31. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/einstein_predictions/types.py +0 -0
  32. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/file/__init__.py +0 -0
  33. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/file/base.py +0 -0
  34. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/file/path/__init__.py +0 -0
  35. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/file/path/default.py +0 -0
  36. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/function/__init__.py +0 -0
  37. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/function/base.py +0 -0
  38. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/function/feature_types/__init__.py +0 -0
  39. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/function/feature_types/chunking.py +0 -0
  40. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/function/runtime.py +0 -0
  41. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/function_utils.py +0 -0
  42. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/__init__.py +0 -0
  43. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/base.py +0 -0
  44. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/reader/__init__.py +0 -0
  45. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/reader/query_api.py +0 -0
  46. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/reader/sf_cli.py +0 -0
  47. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/reader/utils.py +0 -0
  48. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/writer/__init__.py +0 -0
  49. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/writer/csv.py +0 -0
  50. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/io/writer/print.py +0 -0
  51. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/__init__.py +0 -0
  52. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/base.py +0 -0
  53. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/default.py +0 -0
  54. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/errors.py +0 -0
  55. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/spark_base.py +0 -0
  56. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/spark_default.py +0 -0
  57. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
  58. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
  59. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
  60. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
  61. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
  62. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/llm_gateway_config.py +0 -0
  63. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/mixin.py +0 -0
  64. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/py.typed +0 -0
  65. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/scan.py +0 -0
  66. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/spark/__init__.py +0 -0
  67. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/spark/base.py +0 -0
  68. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/spark/default.py +0 -0
  69. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/template.py +0 -0
  70. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/__init__.py +0 -0
  71. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +0 -0
  72. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
  73. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/README.md +0 -0
  74. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
  75. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
  76. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
  77. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
  78. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
  79. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
  80. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
  81. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
  82. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
  83. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
  84. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
  85. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/payload/config.json +0 -0
  86. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/payload/entrypoint.py +0 -0
  87. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/payload/utility.py +0 -0
  88. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
  89. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/function/requirements.txt +0 -0
  90. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +0 -0
  91. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/Dockerfile +0 -0
  92. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
  93. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/README.md +0 -0
  94. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/account.ipynb +0 -0
  95. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
  96. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
  97. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
  98. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
  99. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/payload/config.json +0 -0
  100. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
  101. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/templates/script/requirements.txt +0 -0
  102. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/token_provider.py +0 -0
  103. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev2}/src/datacustomcode/version.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: salesforce-data-customcode
3
- Version: 6.0.5.dev1
3
+ Version: 6.0.6.dev2
4
4
  Summary: Data Cloud Custom Code SDK
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE.txt
7
7
  Requires-Python: >=3.10,<3.12
8
- Classifier: Development Status :: 4 - Beta
8
+ Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Operating System :: Unix
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
@@ -23,7 +23,7 @@ Requires-Dist: salesforce-cdp-connector (>=1.0.19)
23
23
  Requires-Dist: setuptools_scm (>=7.1.0,<8.0.0)
24
24
  Description-Content-Type: text/markdown
25
25
 
26
- # Data Cloud Custom Code SDK (BETA)
26
+ # Data Cloud Custom Code SDK
27
27
 
28
28
  This package provides a development kit for creating custom data transformations in [Data Cloud](https://www.salesforce.com/data/). It allows you to write your own data processing logic in Python while leveraging Data Cloud's infrastructure for data access and running data transformations, mapping execution into Data Cloud data structures like [Data Model Objects](https://help.salesforce.com/s/articleView?id=data.c360_a_data_model_objects.htm&type=5) and [Data Lake Objects](https://help.salesforce.com/s/articleView?id=sf.c360_a_data_lake_objects.htm&language=en_US&type=5).
29
29
 
@@ -36,7 +36,7 @@ Use of this project with Salesforce is subject to the [TERMS OF USE](./TERMS_OF_
36
36
  - **Python 3.11 only** (currently supported version - if your system version is different, we recommend using [pyenv](https://github.com/pyenv/pyenv) to configure 3.11)
37
37
  - JDK 17
38
38
  - Docker support like [Docker Desktop](https://docs.docker.com/desktop/)
39
- - A salesforce org with some DLOs or DMOs with data and this feature enabled (it is not GA)
39
+ - A salesforce org with some DLOs or DMOs with data and this feature enabled
40
40
  - **One of the following** for authentication:
41
41
  - A Salesforce org already authenticated via the [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli)
42
42
  (simplest — no External Client App needed)
@@ -116,7 +116,7 @@ After modifying the `entrypoint.py` as needed, using any dependencies you add in
116
116
  ```zsh
117
117
  cd my_package
118
118
  datacustomcode scan ./payload/entrypoint.py
119
- datacustomcode deploy --path ./payload --name my_custom_script --cpu-size CPU_L
119
+ datacustomcode deploy --path ./payload --name my_custom_script --cpu-size CPU_L --sf-cli-org myorg
120
120
  ```
121
121
 
122
122
  > [!TIP]
@@ -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:
@@ -345,6 +382,7 @@ Options:
345
382
  - `--description TEXT`: Description of the transformation job (default: "")
346
383
  - `--network TEXT`: docker network (default: "default")
347
384
  - `--cpu-size TEXT`: CPU size for the deployment (default: `CPU_2XL`). Available options: CPU_L(Large), CPU_XL(Extra Large), CPU_2XL(2X Large), CPU_4XL(4X Large)
385
+ - `--sf-cli-org TEXT`: Salesforce CLI org alias or username (e.g. `myorg`). Fetches credentials via `sf org display` — no `datacustomcode configure` step needed. Takes precedence over `--profile` if both are supplied.
348
386
  - `--function-invoke-opt TEXT`: Currently we support only `UnstructuredChunking` for functions.
349
387
 
350
388
 
@@ -397,6 +435,56 @@ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
397
435
  ```
398
436
 
399
437
 
438
+ ## Testing Einstein Predictions
439
+
440
+ You can use AI models configured in Einstein Studio to score your data while
441
+ transforming it. As with the LLM Gateway, there are two flavors: a one-shot
442
+ scalar call (`client.einstein_predict`) and a per-row column helper
443
+ (`einstein_predict_col`). Below is a sample code example:
444
+
445
+ ```
446
+ from datacustomcode.client import Client, einstein_predict_col
447
+ from datacustomcode.einstein_predictions.types import PredictionType
448
+
449
+
450
+ def main():
451
+ client = Client()
452
+ df = client.read_dlo("Input__dll")
453
+ # einstein_predict_col returns a struct
454
+ # {status, response, error_code, error_message} per row, so per-row
455
+ # failures don't abort the Spark job. `response` is the prediction
456
+ # payload as a JSON string. Pick the field you want with [].
457
+ df_scored = df.withColumn(
458
+ "prediction__c",
459
+ einstein_predict_col(
460
+ "my_regression_model", # An AI model in your org
461
+ PredictionType.REGRESSION,
462
+ {"square_feet": col("square_feet__c"), "beds": col("beds__c")},
463
+ )["response"],
464
+ )
465
+
466
+ dlo_name = "Output_dll"
467
+ client.write_to_dlo(dlo_name, df_scored, write_mode=WriteMode.APPEND)
468
+
469
+ # One-shot scalar prediction returns the response payload as a dict
470
+ prediction = client.einstein_predict(
471
+ "my_regression_model",
472
+ PredictionType.REGRESSION,
473
+ {"square_feet": 1800, "beds": 3},
474
+ )
475
+
476
+ if __name__ == "__main__":
477
+ main()
478
+ ```
479
+
480
+ Testing this code locally uses the same External Client App setup described in
481
+ [Testing LLM Gateway](#testing-llm-gateway). Once your `myorg` alias is set up,
482
+ run:
483
+ ```
484
+ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
485
+ ```
486
+
487
+
400
488
  ## Docker usage
401
489
 
402
490
  The SDK provides Docker-based development options that allow you to test your code in an environment that closely resembles Data Cloud's execution environment.
@@ -508,7 +596,7 @@ sf --version
508
596
  **Browser-based (recommended for developer orgs and sandboxes):**
509
597
  ```zsh
510
598
  # Production / Developer Edition
511
- sf org login web --alias myorg
599
+ sf org login web --alias myorg --instance-url <your-instance-url>
512
600
 
513
601
  # Sandbox
514
602
  sf org login web --alias mysandbox --instance-url https://test.salesforce.com
@@ -1,4 +1,4 @@
1
- # Data Cloud Custom Code SDK (BETA)
1
+ # Data Cloud Custom Code SDK
2
2
 
3
3
  This package provides a development kit for creating custom data transformations in [Data Cloud](https://www.salesforce.com/data/). It allows you to write your own data processing logic in Python while leveraging Data Cloud's infrastructure for data access and running data transformations, mapping execution into Data Cloud data structures like [Data Model Objects](https://help.salesforce.com/s/articleView?id=data.c360_a_data_model_objects.htm&type=5) and [Data Lake Objects](https://help.salesforce.com/s/articleView?id=sf.c360_a_data_lake_objects.htm&language=en_US&type=5).
4
4
 
@@ -11,7 +11,7 @@ Use of this project with Salesforce is subject to the [TERMS OF USE](./TERMS_OF_
11
11
  - **Python 3.11 only** (currently supported version - if your system version is different, we recommend using [pyenv](https://github.com/pyenv/pyenv) to configure 3.11)
12
12
  - JDK 17
13
13
  - Docker support like [Docker Desktop](https://docs.docker.com/desktop/)
14
- - A salesforce org with some DLOs or DMOs with data and this feature enabled (it is not GA)
14
+ - A salesforce org with some DLOs or DMOs with data and this feature enabled
15
15
  - **One of the following** for authentication:
16
16
  - A Salesforce org already authenticated via the [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli)
17
17
  (simplest — no External Client App needed)
@@ -91,7 +91,7 @@ After modifying the `entrypoint.py` as needed, using any dependencies you add in
91
91
  ```zsh
92
92
  cd my_package
93
93
  datacustomcode scan ./payload/entrypoint.py
94
- datacustomcode deploy --path ./payload --name my_custom_script --cpu-size CPU_L
94
+ datacustomcode deploy --path ./payload --name my_custom_script --cpu-size CPU_L --sf-cli-org myorg
95
95
  ```
96
96
 
97
97
  > [!TIP]
@@ -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:
@@ -320,6 +357,7 @@ Options:
320
357
  - `--description TEXT`: Description of the transformation job (default: "")
321
358
  - `--network TEXT`: docker network (default: "default")
322
359
  - `--cpu-size TEXT`: CPU size for the deployment (default: `CPU_2XL`). Available options: CPU_L(Large), CPU_XL(Extra Large), CPU_2XL(2X Large), CPU_4XL(4X Large)
360
+ - `--sf-cli-org TEXT`: Salesforce CLI org alias or username (e.g. `myorg`). Fetches credentials via `sf org display` — no `datacustomcode configure` step needed. Takes precedence over `--profile` if both are supplied.
323
361
  - `--function-invoke-opt TEXT`: Currently we support only `UnstructuredChunking` for functions.
324
362
 
325
363
 
@@ -372,6 +410,56 @@ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
372
410
  ```
373
411
 
374
412
 
413
+ ## Testing Einstein Predictions
414
+
415
+ You can use AI models configured in Einstein Studio to score your data while
416
+ transforming it. As with the LLM Gateway, there are two flavors: a one-shot
417
+ scalar call (`client.einstein_predict`) and a per-row column helper
418
+ (`einstein_predict_col`). Below is a sample code example:
419
+
420
+ ```
421
+ from datacustomcode.client import Client, einstein_predict_col
422
+ from datacustomcode.einstein_predictions.types import PredictionType
423
+
424
+
425
+ def main():
426
+ client = Client()
427
+ df = client.read_dlo("Input__dll")
428
+ # einstein_predict_col returns a struct
429
+ # {status, response, error_code, error_message} per row, so per-row
430
+ # failures don't abort the Spark job. `response` is the prediction
431
+ # payload as a JSON string. Pick the field you want with [].
432
+ df_scored = df.withColumn(
433
+ "prediction__c",
434
+ einstein_predict_col(
435
+ "my_regression_model", # An AI model in your org
436
+ PredictionType.REGRESSION,
437
+ {"square_feet": col("square_feet__c"), "beds": col("beds__c")},
438
+ )["response"],
439
+ )
440
+
441
+ dlo_name = "Output_dll"
442
+ client.write_to_dlo(dlo_name, df_scored, write_mode=WriteMode.APPEND)
443
+
444
+ # One-shot scalar prediction returns the response payload as a dict
445
+ prediction = client.einstein_predict(
446
+ "my_regression_model",
447
+ PredictionType.REGRESSION,
448
+ {"square_feet": 1800, "beds": 3},
449
+ )
450
+
451
+ if __name__ == "__main__":
452
+ main()
453
+ ```
454
+
455
+ Testing this code locally uses the same External Client App setup described in
456
+ [Testing LLM Gateway](#testing-llm-gateway). Once your `myorg` alias is set up,
457
+ run:
458
+ ```
459
+ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
460
+ ```
461
+
462
+
375
463
  ## Docker usage
376
464
 
377
465
  The SDK provides Docker-based development options that allow you to test your code in an environment that closely resembles Data Cloud's execution environment.
@@ -483,7 +571,7 @@ sf --version
483
571
  **Browser-based (recommended for developer orgs and sandboxes):**
484
572
  ```zsh
485
573
  # Production / Developer Edition
486
- sf org login web --alias myorg
574
+ sf org login web --alias myorg --instance-url <your-instance-url>
487
575
 
488
576
  # Sandbox
489
577
  sf org login web --alias mysandbox --instance-url https://test.salesforce.com
@@ -5,7 +5,7 @@ requires = ["poetry-core>=2.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
5
5
  [project]
6
6
  authors = []
7
7
  classifiers = [
8
- "Development Status :: 4 - Beta",
8
+ "Development Status :: 5 - Production/Stable",
9
9
  "Operating System :: Unix",
10
10
  "Programming Language :: Python",
11
11
  "Programming Language :: Python :: 3",
@@ -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.5.dev1"
21
+ version = "6.0.6.dev2"
22
22
 
23
23
  [tool.black]
24
24
  exclude = '''
@@ -17,10 +17,14 @@ __all__ = [
17
17
  "AuthType",
18
18
  "Client",
19
19
  "Credentials",
20
+ "DefaultSparkEinsteinPredictions",
20
21
  "DefaultSparkLLMGateway",
21
22
  "PrintDataCloudWriter",
22
23
  "QueryAPIDataCloudReader",
24
+ "SparkEinsteinPredictions",
23
25
  "SparkLLMGateway",
26
+ "StreamingClient",
27
+ "einstein_predict_col",
24
28
  "llm_gateway_generate_text_col",
25
29
  ]
26
30
 
@@ -31,6 +35,10 @@ def __getattr__(name: str):
31
35
  from datacustomcode.client import Client
32
36
 
33
37
  return Client
38
+ elif name == "StreamingClient":
39
+ from datacustomcode.client import StreamingClient
40
+
41
+ return StreamingClient
34
42
  elif name == "AuthType":
35
43
  from datacustomcode.credentials import AuthType
36
44
 
@@ -59,4 +67,16 @@ def __getattr__(name: str):
59
67
  from datacustomcode.client import llm_gateway_generate_text_col
60
68
 
61
69
  return llm_gateway_generate_text_col
70
+ elif name == "SparkEinsteinPredictions":
71
+ from datacustomcode.einstein_predictions import SparkEinsteinPredictions
72
+
73
+ return SparkEinsteinPredictions
74
+ elif name == "DefaultSparkEinsteinPredictions":
75
+ from datacustomcode.einstein_predictions import DefaultSparkEinsteinPredictions
76
+
77
+ return DefaultSparkEinsteinPredictions
78
+ elif name == "einstein_predict_col":
79
+ from datacustomcode.client import einstein_predict_col
80
+
81
+ return einstein_predict_col
62
82
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")