salesforce-data-customcode 6.0.5.dev1__tar.gz → 6.0.6.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 (101) hide show
  1. salesforce_data_customcode-6.0.5.dev1/README.md → salesforce_data_customcode-6.0.6.dev1/PKG-INFO +81 -4
  2. salesforce_data_customcode-6.0.5.dev1/PKG-INFO → salesforce_data_customcode-6.0.6.dev1/README.md +55 -30
  3. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/pyproject.toml +2 -2
  4. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/__init__.py +15 -0
  5. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/client.py +116 -0
  6. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/config.yaml +3 -0
  7. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_predictions/__init__.py +8 -0
  8. salesforce_data_customcode-6.0.6.dev1/src/datacustomcode/einstein_predictions/errors.py +36 -0
  9. salesforce_data_customcode-6.0.6.dev1/src/datacustomcode/einstein_predictions/spark_base.py +72 -0
  10. salesforce_data_customcode-6.0.6.dev1/src/datacustomcode/einstein_predictions/spark_default.py +239 -0
  11. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_predictions_config.py +45 -1
  12. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/payload/entrypoint.py +17 -0
  13. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/LICENSE.txt +0 -0
  14. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/auth.py +0 -0
  15. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/cli.py +0 -0
  16. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/cmd.py +0 -0
  17. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/common_config.py +0 -0
  18. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/config.py +0 -0
  19. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/constants.py +0 -0
  20. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/credentials.py +0 -0
  21. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/deploy.py +0 -0
  22. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_platform_client.py +0 -0
  23. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_platform_config.py +0 -0
  24. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_predictions/base.py +0 -0
  25. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
  26. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/einstein_predictions/types.py +0 -0
  27. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/file/__init__.py +0 -0
  28. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/file/base.py +0 -0
  29. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/file/path/__init__.py +0 -0
  30. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/file/path/default.py +0 -0
  31. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/function/__init__.py +0 -0
  32. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/function/base.py +0 -0
  33. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/function/feature_types/__init__.py +0 -0
  34. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/function/feature_types/chunking.py +0 -0
  35. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/function/runtime.py +0 -0
  36. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/function_utils.py +0 -0
  37. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/__init__.py +0 -0
  38. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/base.py +0 -0
  39. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/reader/__init__.py +0 -0
  40. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/reader/base.py +0 -0
  41. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/reader/query_api.py +0 -0
  42. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/reader/sf_cli.py +0 -0
  43. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/reader/utils.py +0 -0
  44. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/writer/__init__.py +0 -0
  45. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/writer/base.py +0 -0
  46. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/writer/csv.py +0 -0
  47. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/io/writer/print.py +0 -0
  48. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/__init__.py +0 -0
  49. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/base.py +0 -0
  50. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/default.py +0 -0
  51. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/errors.py +0 -0
  52. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/spark_base.py +0 -0
  53. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/spark_default.py +0 -0
  54. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
  55. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
  56. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
  57. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
  58. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
  59. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/llm_gateway_config.py +0 -0
  60. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/mixin.py +0 -0
  61. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/py.typed +0 -0
  62. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/run.py +0 -0
  63. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/scan.py +0 -0
  64. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/spark/__init__.py +0 -0
  65. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/spark/base.py +0 -0
  66. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/spark/default.py +0 -0
  67. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/template.py +0 -0
  68. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/__init__.py +0 -0
  69. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +0 -0
  70. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
  71. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/README.md +0 -0
  72. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
  73. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
  74. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
  75. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
  76. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
  77. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
  78. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
  79. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
  80. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
  81. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
  82. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
  83. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/payload/config.json +0 -0
  84. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/payload/entrypoint.py +0 -0
  85. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/payload/utility.py +0 -0
  86. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
  87. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/function/requirements.txt +0 -0
  88. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +0 -0
  89. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/Dockerfile +0 -0
  90. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
  91. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/README.md +0 -0
  92. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/account.ipynb +0 -0
  93. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
  94. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
  95. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
  96. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
  97. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/payload/config.json +0 -0
  98. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
  99. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/templates/script/requirements.txt +0 -0
  100. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/token_provider.py +0 -0
  101. {salesforce_data_customcode-6.0.5.dev1 → salesforce_data_customcode-6.0.6.dev1}/src/datacustomcode/version.py +0 -0
@@ -1,4 +1,29 @@
1
- # Data Cloud Custom Code SDK (BETA)
1
+ Metadata-Version: 2.4
2
+ Name: salesforce-data-customcode
3
+ Version: 6.0.6.dev1
4
+ Summary: Data Cloud Custom Code SDK
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE.txt
7
+ Requires-Python: >=3.10,<3.12
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Operating System :: Unix
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Dist: click (>=8.1.8,<9.0.0)
15
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
16
+ Requires-Dist: numpy
17
+ Requires-Dist: pandas
18
+ Requires-Dist: pydantic (==2.13.1)
19
+ Requires-Dist: pyspark (==3.5.1)
20
+ Requires-Dist: pyyaml (>=6.0,<7.0)
21
+ Requires-Dist: requests (==2.33.1)
22
+ Requires-Dist: salesforce-cdp-connector (>=1.0.19)
23
+ Requires-Dist: setuptools_scm (>=7.1.0,<8.0.0)
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Data Cloud Custom Code SDK
2
27
 
3
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).
4
29
 
@@ -11,7 +36,7 @@ Use of this project with Salesforce is subject to the [TERMS OF USE](./TERMS_OF_
11
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)
12
37
  - JDK 17
13
38
  - 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)
39
+ - A salesforce org with some DLOs or DMOs with data and this feature enabled
15
40
  - **One of the following** for authentication:
16
41
  - A Salesforce org already authenticated via the [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli)
17
42
  (simplest — no External Client App needed)
@@ -91,7 +116,7 @@ After modifying the `entrypoint.py` as needed, using any dependencies you add in
91
116
  ```zsh
92
117
  cd my_package
93
118
  datacustomcode scan ./payload/entrypoint.py
94
- 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
95
120
  ```
96
121
 
97
122
  > [!TIP]
@@ -320,6 +345,7 @@ Options:
320
345
  - `--description TEXT`: Description of the transformation job (default: "")
321
346
  - `--network TEXT`: docker network (default: "default")
322
347
  - `--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)
348
+ - `--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
349
  - `--function-invoke-opt TEXT`: Currently we support only `UnstructuredChunking` for functions.
324
350
 
325
351
 
@@ -372,6 +398,56 @@ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
372
398
  ```
373
399
 
374
400
 
401
+ ## Testing Einstein Predictions
402
+
403
+ You can use AI models configured in Einstein Studio to score your data while
404
+ transforming it. As with the LLM Gateway, there are two flavors: a one-shot
405
+ scalar call (`client.einstein_predict`) and a per-row column helper
406
+ (`einstein_predict_col`). Below is a sample code example:
407
+
408
+ ```
409
+ from datacustomcode.client import Client, einstein_predict_col
410
+ from datacustomcode.einstein_predictions.types import PredictionType
411
+
412
+
413
+ def main():
414
+ client = Client()
415
+ df = client.read_dlo("Input__dll")
416
+ # einstein_predict_col returns a struct
417
+ # {status, response, error_code, error_message} per row, so per-row
418
+ # failures don't abort the Spark job. `response` is the prediction
419
+ # payload as a JSON string. Pick the field you want with [].
420
+ df_scored = df.withColumn(
421
+ "prediction__c",
422
+ einstein_predict_col(
423
+ "my_regression_model", # An AI model in your org
424
+ PredictionType.REGRESSION,
425
+ {"square_feet": col("square_feet__c"), "beds": col("beds__c")},
426
+ )["response"],
427
+ )
428
+
429
+ dlo_name = "Output_dll"
430
+ client.write_to_dlo(dlo_name, df_scored, write_mode=WriteMode.APPEND)
431
+
432
+ # One-shot scalar prediction returns the response payload as a dict
433
+ prediction = client.einstein_predict(
434
+ "my_regression_model",
435
+ PredictionType.REGRESSION,
436
+ {"square_feet": 1800, "beds": 3},
437
+ )
438
+
439
+ if __name__ == "__main__":
440
+ main()
441
+ ```
442
+
443
+ Testing this code locally uses the same External Client App setup described in
444
+ [Testing LLM Gateway](#testing-llm-gateway). Once your `myorg` alias is set up,
445
+ run:
446
+ ```
447
+ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
448
+ ```
449
+
450
+
375
451
  ## Docker usage
376
452
 
377
453
  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 +559,7 @@ sf --version
483
559
  **Browser-based (recommended for developer orgs and sandboxes):**
484
560
  ```zsh
485
561
  # Production / Developer Edition
486
- sf org login web --alias myorg
562
+ sf org login web --alias myorg --instance-url <your-instance-url>
487
563
 
488
564
  # Sandbox
489
565
  sf org login web --alias mysandbox --instance-url https://test.salesforce.com
@@ -513,3 +589,4 @@ If you're using OAuth Tokens authentication, the initial configure will retrieve
513
589
 
514
590
  - [Troubleshooting](./docs/troubleshooting.md)
515
591
  - [For Contributors](./FOR_CONTRIBUTORS.md)
592
+
@@ -1,29 +1,4 @@
1
- Metadata-Version: 2.4
2
- Name: salesforce-data-customcode
3
- Version: 6.0.5.dev1
4
- Summary: Data Cloud Custom Code SDK
5
- License-Expression: Apache-2.0
6
- License-File: LICENSE.txt
7
- Requires-Python: >=3.10,<3.12
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Operating System :: Unix
10
- Classifier: Programming Language :: Python
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Requires-Dist: click (>=8.1.8,<9.0.0)
15
- Requires-Dist: loguru (>=0.7.3,<0.8.0)
16
- Requires-Dist: numpy
17
- Requires-Dist: pandas
18
- Requires-Dist: pydantic (==2.13.1)
19
- Requires-Dist: pyspark (==3.5.1)
20
- Requires-Dist: pyyaml (>=6.0,<7.0)
21
- Requires-Dist: requests (==2.33.1)
22
- Requires-Dist: salesforce-cdp-connector (>=1.0.19)
23
- Requires-Dist: setuptools_scm (>=7.1.0,<8.0.0)
24
- Description-Content-Type: text/markdown
25
-
26
- # Data Cloud Custom Code SDK (BETA)
1
+ # Data Cloud Custom Code SDK
27
2
 
28
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).
29
4
 
@@ -36,7 +11,7 @@ Use of this project with Salesforce is subject to the [TERMS OF USE](./TERMS_OF_
36
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)
37
12
  - JDK 17
38
13
  - 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)
14
+ - A salesforce org with some DLOs or DMOs with data and this feature enabled
40
15
  - **One of the following** for authentication:
41
16
  - A Salesforce org already authenticated via the [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli)
42
17
  (simplest — no External Client App needed)
@@ -116,7 +91,7 @@ After modifying the `entrypoint.py` as needed, using any dependencies you add in
116
91
  ```zsh
117
92
  cd my_package
118
93
  datacustomcode scan ./payload/entrypoint.py
119
- 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
120
95
  ```
121
96
 
122
97
  > [!TIP]
@@ -345,6 +320,7 @@ Options:
345
320
  - `--description TEXT`: Description of the transformation job (default: "")
346
321
  - `--network TEXT`: docker network (default: "default")
347
322
  - `--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)
323
+ - `--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
324
  - `--function-invoke-opt TEXT`: Currently we support only `UnstructuredChunking` for functions.
349
325
 
350
326
 
@@ -397,6 +373,56 @@ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
397
373
  ```
398
374
 
399
375
 
376
+ ## Testing Einstein Predictions
377
+
378
+ You can use AI models configured in Einstein Studio to score your data while
379
+ transforming it. As with the LLM Gateway, there are two flavors: a one-shot
380
+ scalar call (`client.einstein_predict`) and a per-row column helper
381
+ (`einstein_predict_col`). Below is a sample code example:
382
+
383
+ ```
384
+ from datacustomcode.client import Client, einstein_predict_col
385
+ from datacustomcode.einstein_predictions.types import PredictionType
386
+
387
+
388
+ def main():
389
+ client = Client()
390
+ df = client.read_dlo("Input__dll")
391
+ # einstein_predict_col returns a struct
392
+ # {status, response, error_code, error_message} per row, so per-row
393
+ # failures don't abort the Spark job. `response` is the prediction
394
+ # payload as a JSON string. Pick the field you want with [].
395
+ df_scored = df.withColumn(
396
+ "prediction__c",
397
+ einstein_predict_col(
398
+ "my_regression_model", # An AI model in your org
399
+ PredictionType.REGRESSION,
400
+ {"square_feet": col("square_feet__c"), "beds": col("beds__c")},
401
+ )["response"],
402
+ )
403
+
404
+ dlo_name = "Output_dll"
405
+ client.write_to_dlo(dlo_name, df_scored, write_mode=WriteMode.APPEND)
406
+
407
+ # One-shot scalar prediction returns the response payload as a dict
408
+ prediction = client.einstein_predict(
409
+ "my_regression_model",
410
+ PredictionType.REGRESSION,
411
+ {"square_feet": 1800, "beds": 3},
412
+ )
413
+
414
+ if __name__ == "__main__":
415
+ main()
416
+ ```
417
+
418
+ Testing this code locally uses the same External Client App setup described in
419
+ [Testing LLM Gateway](#testing-llm-gateway). Once your `myorg` alias is set up,
420
+ run:
421
+ ```
422
+ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
423
+ ```
424
+
425
+
400
426
  ## Docker usage
401
427
 
402
428
  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 +534,7 @@ sf --version
508
534
  **Browser-based (recommended for developer orgs and sandboxes):**
509
535
  ```zsh
510
536
  # Production / Developer Edition
511
- sf org login web --alias myorg
537
+ sf org login web --alias myorg --instance-url <your-instance-url>
512
538
 
513
539
  # Sandbox
514
540
  sf org login web --alias mysandbox --instance-url https://test.salesforce.com
@@ -538,4 +564,3 @@ If you're using OAuth Tokens authentication, the initial configure will retrieve
538
564
 
539
565
  - [Troubleshooting](./docs/troubleshooting.md)
540
566
  - [For Contributors](./FOR_CONTRIBUTORS.md)
541
-
@@ -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.dev1"
22
22
 
23
23
  [tool.black]
24
24
  exclude = '''
@@ -17,10 +17,13 @@ __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
+ "einstein_predict_col",
24
27
  "llm_gateway_generate_text_col",
25
28
  ]
26
29
 
@@ -59,4 +62,16 @@ def __getattr__(name: str):
59
62
  from datacustomcode.client import llm_gateway_generate_text_col
60
63
 
61
64
  return llm_gateway_generate_text_col
65
+ elif name == "SparkEinsteinPredictions":
66
+ from datacustomcode.einstein_predictions import SparkEinsteinPredictions
67
+
68
+ return SparkEinsteinPredictions
69
+ elif name == "DefaultSparkEinsteinPredictions":
70
+ from datacustomcode.einstein_predictions import DefaultSparkEinsteinPredictions
71
+
72
+ return DefaultSparkEinsteinPredictions
73
+ elif name == "einstein_predict_col":
74
+ from datacustomcode.client import einstein_predict_col
75
+
76
+ return einstein_predict_col
62
77
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
  from enum import Enum
18
18
  from typing import (
19
19
  TYPE_CHECKING,
20
+ Any,
20
21
  ClassVar,
21
22
  Dict,
22
23
  Optional,
@@ -24,6 +25,7 @@ from typing import (
24
25
  )
25
26
 
26
27
  from datacustomcode.config import config
28
+ from datacustomcode.einstein_predictions_config import spark_einstein_predictions_config
27
29
  from datacustomcode.file.path.default import DefaultFindFilePath
28
30
  from datacustomcode.io.reader.base import BaseDataCloudReader
29
31
  from datacustomcode.llm_gateway_config import spark_llm_gateway_config
@@ -34,6 +36,8 @@ if TYPE_CHECKING:
34
36
 
35
37
  from pyspark.sql import Column, DataFrame as PySparkDataFrame
36
38
 
39
+ from datacustomcode.einstein_predictions.spark_base import SparkEinsteinPredictions
40
+ from datacustomcode.einstein_predictions.types import PredictionType
37
41
  from datacustomcode.io.reader.base import BaseDataCloudReader
38
42
  from datacustomcode.io.writer.base import BaseDataCloudWriter, WriteMode
39
43
  from datacustomcode.llm_gateway.spark_base import SparkLLMGateway
@@ -99,6 +103,70 @@ def llm_gateway_generate_text_col(
99
103
  return gateway.llm_gateway_generate_text_col(template, values, model_id=model_id)
100
104
 
101
105
 
106
+ def _build_spark_einstein_predictions() -> "SparkEinsteinPredictions":
107
+ """Instantiate the SDK-configured :class:`SparkEinsteinPredictions`.
108
+
109
+ Raises:
110
+ RuntimeError: If no ``spark_einstein_predictions_config`` has been loaded.
111
+ """
112
+ cfg = spark_einstein_predictions_config.spark_einstein_predictions_config
113
+ if cfg is None:
114
+ raise RuntimeError(
115
+ "spark_einstein_predictions_config is not configured. Add a "
116
+ "'spark_einstein_predictions_config' section to config.yaml."
117
+ )
118
+ return cfg.to_object()
119
+
120
+
121
+ def einstein_predict_col(
122
+ model_api_name: str,
123
+ prediction_type: "PredictionType",
124
+ features: Dict[str, "Column"],
125
+ settings: Optional[Dict[str, Any]] = None,
126
+ ) -> "Column":
127
+ """Build a Spark Column that runs an Einstein prediction per row.
128
+
129
+ The returned Column yields a struct ``{status, response, error_code,
130
+ error_message}`` for each row. Use ``[...]`` (or ``getField``) to pick the
131
+ field you want, e.g. ``einstein_predict_col(...)["response"]``. ``response``
132
+ holds the prediction response payload as a JSON string. Per-row failures
133
+ populate ``status`` / ``error_code`` / ``error_message`` so a single bad row
134
+ does not abort the whole Spark job.
135
+
136
+ Example:
137
+
138
+ >>> from datacustomcode.einstein_predictions.types import PredictionType
139
+ >>> result = einstein_predict_col(
140
+ ... "my_regression_model",
141
+ ... PredictionType.REGRESSION,
142
+ ... {"square_feet": col("square_feet__c"), "beds": col("beds__c")},
143
+ ... )
144
+ >>> df.withColumn("prediction__c", result["response"])
145
+ >>> # …or keep the struct around and inspect failures:
146
+ >>> df.withColumn("pred", result).select(
147
+ ... "pred.status", "pred.response", "pred.error_message"
148
+ ... )
149
+
150
+ Args:
151
+ model_api_name: API name of the Einstein model to invoke.
152
+ prediction_type: The :class:`PredictionType` of the model.
153
+ features: A mapping from model feature column name to a Spark ``Column``
154
+ supplying that feature's per-row value.
155
+ settings: Optional prediction settings forwarded to the model.
156
+
157
+ Returns:
158
+ A Spark ``Column`` of ``StructType`` with fields ``status``,
159
+ ``response``, ``error_code``, and ``error_message`` (all nullable
160
+ strings). On success, ``status == "SUCCESS"`` and ``response`` holds
161
+ the JSON-serialized prediction payload; on failure, ``status ==
162
+ "ERROR"`` and the ``error_*`` fields carry diagnostic detail.
163
+ """
164
+ predictions = Client()._get_spark_einstein_predictions()
165
+ return predictions.einstein_predict_col(
166
+ model_api_name, prediction_type, features, settings=settings
167
+ )
168
+
169
+
102
170
  class DataCloudObjectType(Enum):
103
171
  DLO = "dlo"
104
172
  DMO = "dmo"
@@ -158,6 +226,8 @@ class Client:
158
226
  reader: A custom reader to use for reading Data Cloud objects.
159
227
  writer: A custom writer to use for writing Data Cloud objects.
160
228
  spark_llm_gateway: Optional custom :class:`SparkLLMGateway`.
229
+ spark_einstein_predictions: Optional custom
230
+ :class:`SparkEinsteinPredictions`.
161
231
 
162
232
  Example:
163
233
  >>> client = Client()
@@ -172,6 +242,7 @@ class Client:
172
242
  _writer: BaseDataCloudWriter
173
243
  _file: DefaultFindFilePath
174
244
  _spark_llm_gateway: Optional[SparkLLMGateway]
245
+ _spark_einstein_predictions: Optional[SparkEinsteinPredictions]
175
246
  _data_layer_history: dict[DataCloudObjectType, set[str]]
176
247
  _code_type: str
177
248
 
@@ -181,12 +252,14 @@ class Client:
181
252
  writer: Optional[BaseDataCloudWriter] = None,
182
253
  spark_provider: Optional[BaseSparkSessionProvider] = None,
183
254
  spark_llm_gateway: Optional[SparkLLMGateway] = None,
255
+ spark_einstein_predictions: Optional[SparkEinsteinPredictions] = None,
184
256
  code_type: str = "script",
185
257
  ) -> Client:
186
258
 
187
259
  if cls._instance is None:
188
260
  cls._instance = super().__new__(cls)
189
261
  cls._instance._spark_llm_gateway = spark_llm_gateway
262
+ cls._instance._spark_einstein_predictions = spark_einstein_predictions
190
263
  # Initialize Readers and Writers from config
191
264
  # and/or provided reader and writer
192
265
  if reader is None or writer is None:
@@ -358,6 +431,49 @@ class Client:
358
431
  self._spark_llm_gateway = _build_spark_llm_gateway()
359
432
  return self._spark_llm_gateway
360
433
 
434
+ def einstein_predict(
435
+ self,
436
+ model_api_name: str,
437
+ prediction_type: "PredictionType",
438
+ features: Dict[str, Any],
439
+ settings: Optional[Dict[str, Any]] = None,
440
+ ) -> Dict[str, Any]:
441
+ """Issue a one-shot Einstein prediction. This is the scalar counterpart
442
+ to :func:`einstein_predict_col`: it runs **once** — not per row. Use the
443
+ column helper method instead when you want to fan a prediction out
444
+ across every row of a DataFrame.
445
+
446
+ Example:
447
+
448
+ >>> from datacustomcode.einstein_predictions.types import PredictionType
449
+ >>> response = Client().einstein_predict(
450
+ ... "my_regression_model",
451
+ ... PredictionType.REGRESSION,
452
+ ... {"square_feet": 1800, "beds": 3},
453
+ ... )
454
+
455
+ Args:
456
+ model_api_name: API name of the Einstein model to invoke.
457
+ prediction_type: The :class:`PredictionType` of the model.
458
+ features: A mapping from model feature column name to a single
459
+ scalar value (``str`` / ``float`` / ``bool``).
460
+ settings: Optional prediction settings forwarded to the model.
461
+
462
+ Returns:
463
+ The prediction response payload as a plain Python ``dict``.
464
+
465
+ Raises:
466
+ EinsteinPredictionsCallError: If the prediction call fails.
467
+ """
468
+ return self._get_spark_einstein_predictions().einstein_predict(
469
+ model_api_name, prediction_type, features, settings=settings
470
+ )
471
+
472
+ def _get_spark_einstein_predictions(self) -> SparkEinsteinPredictions:
473
+ if self._spark_einstein_predictions is None:
474
+ self._spark_einstein_predictions = _build_spark_einstein_predictions()
475
+ return self._spark_einstein_predictions
476
+
361
477
  def _validate_data_layer_history_does_not_contain(
362
478
  self, data_cloud_object_type: DataCloudObjectType
363
479
  ) -> None:
@@ -24,6 +24,9 @@ einstein_predictions_config:
24
24
  options:
25
25
  credentials_profile: default
26
26
 
27
+ spark_einstein_predictions_config:
28
+ type_config_name: DefaultSparkEinsteinPredictions
29
+
27
30
  llm_gateway_config:
28
31
  type_config_name: DefaultLLMGateway
29
32
  options:
@@ -14,9 +14,17 @@
14
14
  # limitations under the License.
15
15
 
16
16
  from datacustomcode.einstein_predictions.base import EinsteinPredictions
17
+ from datacustomcode.einstein_predictions.errors import EinsteinPredictionsCallError
17
18
  from datacustomcode.einstein_predictions.impl.default import DefaultEinsteinPredictions
19
+ from datacustomcode.einstein_predictions.spark_base import SparkEinsteinPredictions
20
+ from datacustomcode.einstein_predictions.spark_default import (
21
+ DefaultSparkEinsteinPredictions,
22
+ )
18
23
 
19
24
  __all__ = [
20
25
  "DefaultEinsteinPredictions",
26
+ "DefaultSparkEinsteinPredictions",
21
27
  "EinsteinPredictions",
28
+ "EinsteinPredictionsCallError",
29
+ "SparkEinsteinPredictions",
22
30
  ]
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2025, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Exceptions raised by Einstein Predictions implementations."""
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Optional
20
+
21
+
22
+ class EinsteinPredictionsCallError(RuntimeError):
23
+ """Raised when an Einstein Predictions call returns an error."""
24
+
25
+ def __init__(
26
+ self,
27
+ message: str,
28
+ *,
29
+ status: Optional[object] = None,
30
+ error_code: Optional[str] = None,
31
+ error_message: Optional[str] = None,
32
+ ) -> None:
33
+ super().__init__(message)
34
+ self.status = status
35
+ self.error_code = error_code
36
+ self.error_message = error_message
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2025, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+ from typing import (
19
+ TYPE_CHECKING,
20
+ Any,
21
+ Dict,
22
+ Optional,
23
+ )
24
+
25
+ from datacustomcode.mixin import UserExtendableNamedConfigMixin
26
+
27
+ if TYPE_CHECKING:
28
+ from pyspark.sql import Column
29
+
30
+ from datacustomcode.einstein_predictions.types import PredictionType
31
+
32
+
33
+ class SparkEinsteinPredictions(ABC, UserExtendableNamedConfigMixin):
34
+ CONFIG_NAME: str
35
+
36
+ def __init__(self, **kwargs: Any) -> None:
37
+ pass
38
+
39
+ @abstractmethod
40
+ def einstein_predict(
41
+ self,
42
+ model_api_name: str,
43
+ prediction_type: PredictionType,
44
+ features: Dict[str, Any],
45
+ settings: Optional[Dict[str, Any]] = None,
46
+ ) -> Dict[str, Any]:
47
+ """Issue a one-shot Einstein prediction and return the response data.
48
+
49
+ ``features`` maps each model feature column name to a single scalar
50
+ value (``str``/``float``/``bool``). The value is wrapped into a
51
+ single-element prediction column of the appropriate type.
52
+ """
53
+
54
+ @abstractmethod
55
+ def einstein_predict_col(
56
+ self,
57
+ model_api_name: str,
58
+ prediction_type: PredictionType,
59
+ features: Dict[str, "Column"],
60
+ settings: Optional[Dict[str, Any]] = None,
61
+ ) -> "Column":
62
+ """Build a Spark ``Column`` that invokes Einstein predict per row and
63
+ yields a struct ``{status, response, error_code, error_message}``.
64
+
65
+ ``features`` maps each model feature column name to a Spark ``Column``
66
+ supplying that feature's per-row value. Select an individual field,
67
+ e.g. ``einstein_predict_col(...)["response"]``. ``response`` holds the
68
+ prediction response payload as a JSON string. Returning a struct means
69
+ a single failing row leaves the rest of the DataFrame intact — callers
70
+ can inspect ``status`` / ``error_code`` per row instead of having the
71
+ Spark job abort.
72
+ """